AmendHub

Download

jcs

/

detritus

/

util.c

 

(View History)

jcs   util: Add strcaseidx Latest amendment: 67 on 2024-12-27

1 /*
2 * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
3 * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
4 * Copyright (c) 1990, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /* strcasecmp and strncasecmp:
21 *
22 * Copyright (c) 1987, 1993
23 * The Regents of the University of California. All rights reserved.
24 *
25 * Redistribution and use in source and binary forms, with or without
26 * modification, are permitted provided that the following conditions
27 * are met:
28 * 1. Redistributions of source code must retain the above copyright
29 * notice, this list of conditions and the following disclaimer.
30 * 2. Redistributions in binary form must reproduce the above copyright
31 * notice, this list of conditions and the following disclaimer in the
32 * documentation and/or other materials provided with the distribution.
33 * 3. Neither the name of the University nor the names of its contributors
34 * may be used to endorse or promote products derived from this software
35 * without specific prior written permission.
36 *
37 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
38 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
41 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
42 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
43 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
44 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
45 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
46 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 */
49
50 /* THINK C Project must have a header of "#include <MacHeaders>" */
51
52 #include <GestaltEqu.h>
53 #include <Folders.h>
54 #include <Script.h>
55 #include <SetUpA4.h>
56 #include <Traps.h>
57 #include <stdarg.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include "util.h"
62
63 #define ERROR_STRING_SIZE 1024
64 static char err_str[ERROR_STRING_SIZE];
65
66 /* basic DITL with an ok button (1), static text area (2), and icon (3) */
67 #define ALERT_DITL_OK 1
68 #define ALERT_DITL_ICON 3
69 static const char alert_ditl[] = {
70 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E,
71 0x00, 0xFA, 0x00, 0x64, 0x01, 0x34, 0x04, 0x02,
72 0x4F, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D,
73 0x00, 0x4E, 0x00, 0x41, 0x01, 0x36, 0x08, 0x02,
74 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D,
75 0x00, 0x17, 0x00, 0x2D, 0x00, 0x37, 0xA0, 0x02,
76 0x00, 0x01
77 };
78 static Handle alert_ditl_h = NULL;
79
80 /* ICN# to show for APPICON_ALERT */
81 #define APPICON_ICN_ID 128
82
83 /* DITL with a Yes button (1), No button (2), text (3), and icon (4) */
84 static const char ask_ditl[] = {
85 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
86 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x03,
87 0x59, 0x65, 0x73, 0x21, 0x00, 0x00, 0x00, 0x00,
88 0x00, 0x46, 0x00, 0xA0, 0x00, 0x5A, 0x00, 0xDA,
89 0x04, 0x02, 0x4E, 0x6F, 0x00, 0x00, 0x00, 0x00,
90 0x00, 0x0A, 0x00, 0x32, 0x00, 0x41, 0x01, 0x22,
91 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00,
92 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A,
93 0xA0, 0x02, 0x00, 0x01
94 };
95 static Handle ask_ditl_h = NULL;
96
97 /* DITL with just a text view */
98 static const char progress_ditl[] = {
99 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
100 0x00, 0x1E, 0x00, 0x32, 0x01, 0x3B, 0x08, 0x02,
101 0x5E, 0x30
102 };
103 static Handle progress_ditl_h = NULL;
104 static DialogPtr progress_dialog = NULL;
105
106 static TEHandle track_control_te = NULL;
107
108 /*
109 * strcasecmp: This array is designed for mapping upper and lower case
110 * letter together for a case independent comparison. The mappings are
111 * based upon ascii character sequences.
112 */
113 static const unsigned char strcasecmp_charmap[] = {
114 '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
115 '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
116 '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
117 '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
118 '\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
119 '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
120 '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
121 '\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
122 '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
123 '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
124 '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
125 '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
126 '\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
127 '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
128 '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
129 '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
130 '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
131 '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
132 '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
133 '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
134 '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
135 '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
136 '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
137 '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
138 '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
139 '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
140 '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
141 '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
142 '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
143 '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
144 '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
145 '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
146 };
147
148 enum {
149 STOP_ALERT,
150 CAUTION_ALERT,
151 NOTE_ALERT,
152 APPICON_ALERT
153 };
154
155 void vwarn(short alert_func, const char *format, va_list ap);
156
157 /*
158 * Util helper needed to be called at program startup, to pre-allocate
159 * some things that we can't do during errors.
160 */
161
162 void
163 util_init(void)
164 {
165 alert_ditl_h = xNewHandle(sizeof(alert_ditl));
166 HLock(alert_ditl_h);
167 memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
168 HUnlock(alert_ditl_h);
169
170 xorshift32();
171 }
172
173 /*
174 * Memory functions
175 */
176
177 #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
178
179 #ifdef MALLOC_DEBUG
180 /*
181 * malloc tracking, storing a stack trace of each xmalloc call and its
182 * size, which can be dumped with xalloc_print(). xfree also gains the
183 * ability to detect junk frees.
184 *
185 * Requires project options 'Always generate stack frames',
186 * 'Macsbug Names', and 'Long format' to be enabled.
187 */
188 #if !__option(force_frame) || !__option(long_macsbug_names)
189 #error
190 #endif
191
192 #define XALLOC_SYMBOL_SIZE 32
193 #define XALLOC_NSYMBOLS 64
194 #define XALLOC_MAX_STACK_DEPTH 10
195 static char *symbols = NULL;
196 struct xalloc {
197 char *stack[XALLOC_MAX_STACK_DEPTH];
198 unsigned long addr;
199 unsigned long size;
200 };
201 #define XALLOC_NXALLOCS 512
202 static struct xalloc *xallocs = NULL;
203 #endif
204
205 void *
206 xmalloc(size_t size)
207 {
208 void *ptr;
209 #ifdef MALLOC_DEBUG
210 static char cursym[XALLOC_SYMBOL_SIZE], *tsym, *sym;
211 struct xalloc *xa;
212 unsigned long _a6, ret;
213 unsigned char *code;
214 size_t n, cn, j;
215 short nframe, tries;
216 #endif
217
218 if (size == 0)
219 panic("xmalloc: zero size");
220
221 ptr = NewPtr(size);
222 if (ptr == NULL)
223 return NULL;
224
225 #ifdef MALLOC_DEBUG
226 asm {
227 move.l a6,_a6
228 };
229
230 if (symbols == NULL) {
231 symbols = NewPtr(n = XALLOC_SYMBOL_SIZE * XALLOC_NSYMBOLS);
232 if (symbols == NULL)
233 panic("NewPtr failed for xalloc symbols");
234 memset(symbols, 0, n);
235 }
236 if (xallocs == NULL) {
237 xallocs = (struct xalloc *)NewPtr(n = (XALLOC_NXALLOCS *
238 sizeof(struct xalloc)));
239 if (xallocs == NULL)
240 panic("NewPtr failed for xallocs");
241 memset(xallocs, 0, n);
242 }
243
244 xa = NULL;
245 for (n = 0; n < XALLOC_NXALLOCS; n++) {
246 if (xallocs[n].addr == 0) {
247 xa = &xallocs[n];
248 break;
249 }
250 }
251 if (xa == NULL)
252 panic("out of xalloc space");
253
254 /*
255 * A6 register points to previous stack A6. Above A6 is our return
256 * address. Walk the code after the return address looking for an RTS
257 * instruction, then walk 3 bytes after that and we should have that
258 * function's symbol. Then jump to the previous A6 that our A6 points
259 * to and do it again to find the previous function symbol, repeating
260 * until we hit "main" or "uthread_begin".
261 */
262 for (nframe = 0; nframe < XALLOC_MAX_STACK_DEPTH; nframe++) {
263 ret = *(unsigned long *)(_a6 + 4);
264 code = (unsigned char *)ret;
265
266 cursym[0] = '\0';
267 for (tries = 0; tries < 1024; code += 2, tries++) {
268 if (code[0] == 0x4e && code[1] == 0x75) { /* RTS */
269 code += 3;
270 strlcpy(cursym, (char *)code, sizeof(cursym));
271 break;
272 }
273 }
274
275 if (!cursym[0])
276 break;
277
278 sym = NULL;
279 for (n = 0; n < XALLOC_NSYMBOLS; n++) {
280 tsym = symbols + (XALLOC_SYMBOL_SIZE * n);
281
282 if (tsym[0] == '\0')
283 break;
284 if (strcmp(tsym, cursym) == 0) {
285 sym = tsym;
286 break;
287 }
288 }
289 if (sym == NULL) {
290 for (n = 0; n < XALLOC_NSYMBOLS; n++) {
291 tsym = symbols + (XALLOC_SYMBOL_SIZE * n);
292 if (tsym[0] != '\0')
293 continue;
294
295 strlcpy(tsym, cursym, XALLOC_SYMBOL_SIZE);
296 sym = tsym;
297 break;
298 }
299 }
300 if (sym == NULL)
301 break;
302
303 xa->stack[nframe] = sym;
304
305 if (strcmp(sym, "main") == 0 || strcmp(sym, "uthread_begin") == 0)
306 break;
307
308 /* walk back up the chain */
309 _a6 = *(unsigned long *)_a6;
310 }
311
312 xa->addr = (unsigned long)ptr;
313 xa->size = size;
314 #endif
315
316 return ptr;
317 }
318
319 void
320 xfree(void *ptrptr)
321 {
322 unsigned long *addr = (unsigned long *)ptrptr;
323 void *ptr;
324 #ifdef MALLOC_DEBUG
325 size_t n;
326 #endif
327
328 if (ptrptr == NULL)
329 panic("xfree(NULL)");
330
331 ptr = (void *)*addr;
332 if (ptr == NULL)
333 panic("xfree(&NULL) likely a double-free");
334
335 #ifdef MALLOC_DEBUG
336 for (n = 0; n < XALLOC_NXALLOCS; n++) {
337 if (xallocs[n].addr == (unsigned long)ptr) {
338 memset(&xallocs[n], 0, sizeof(struct xalloc));
339 break;
340 }
341 }
342 if (n == XALLOC_NXALLOCS)
343 panic("xfree() without xalloc, possibly a junk free",
344 (unsigned long)ptr);
345 #endif
346
347 DisposePtr(ptr);
348 *addr = 0L;
349 }
350
351 void *
352 xmalloczero(size_t size)
353 {
354 void *ptr;
355
356 ptr = xmalloc(size);
357 if (ptr != NULL)
358 memset(ptr, 0, size);
359
360 return ptr;
361 }
362
363 void *
364 xcalloc(size_t nmemb, size_t size)
365 {
366 if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
367 nmemb > 0 && SIZE_MAX / nmemb < size)
368 panic("xcalloc(%lu, %lu) overflow", nmemb, size);
369
370 return xmalloczero(nmemb * size);
371 }
372
373 void *
374 xrealloc(void *src, size_t size)
375 {
376 void *ptr, *tsrc;
377
378 ptr = xmalloc(size);
379 if (ptr != NULL && src != NULL) {
380 memcpy(ptr, src, size);
381 tsrc = src;
382 xfree(&tsrc);
383 }
384
385 return ptr;
386 }
387
388 void *
389 xreallocarray(void *optr, size_t nmemb, size_t size)
390 {
391 if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
392 nmemb > 0 && SIZE_MAX / nmemb < size)
393 panic("xreallocarray(%lu, %lu) failed", nmemb, size);
394
395 return xrealloc(optr, size * nmemb);
396 }
397
398 char *
399 xstrdup(const char *str)
400 {
401 char *cp;
402 size_t len;
403
404 len = strlen(str);
405 cp = xmalloc(len + 1);
406 if (cp != NULL)
407 strlcpy(cp, str, len + 1);
408
409 return cp;
410 }
411
412 char *
413 xstrndup(const char *str, size_t maxlen)
414 {
415 char *copy;
416 const char *cp;
417 size_t len;
418
419 /* strnlen */
420 for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--)
421 ;
422
423 len = (size_t)(cp - str);
424 copy = xmalloc(len + 1);
425 if (copy != NULL) {
426 (void)memcpy(copy, str, len);
427 copy[len] = '\0';
428 }
429
430 return copy;
431 }
432
433 #ifdef MALLOC_DEBUG
434 void
435 xalloc_print(size_t (*printer)(const char *, ...))
436 {
437 ssize_t n, j, len;
438 size_t total = 0, nallocs = 0;
439 char chain[128];
440
441 for (n = 0; n < XALLOC_NXALLOCS; n++) {
442 if (xallocs[n].addr == 0)
443 continue;
444
445 total += xallocs[n].size;
446 nallocs++;
447
448 chain[0] = '\0';
449 len = 0;
450 for (j = XALLOC_MAX_STACK_DEPTH - 1; j >= 0; j--) {
451 if (xallocs[n].stack[j] == 0)
452 continue;
453
454 if (chain[0] != '\0') {
455 if (len >= sizeof(chain) - 2)
456 break;
457 chain[len++] = ' ';
458 chain[len++] = ':';
459 chain[len++] = ' ';
460 chain[len++] = '\0';
461 }
462
463 len = strlcat(chain, xallocs[n].stack[j], sizeof(chain));
464 if (len >= sizeof(chain))
465 break;
466 }
467
468 printer("%ld @ %s", xallocs[n].size, chain);
469 }
470
471 printer("%ld bytes in %ld allocations in use", total, nallocs);
472 }
473 #endif
474
475 /*
476 * String functions
477 */
478
479 short
480 getline(char *str, size_t len, char **ret)
481 {
482 short i;
483
484 for (i = 0; i < len; i++) {
485 if (str[i] == '\r' || i == len - 1) {
486 if (*ret == NULL)
487 *ret = xmalloc(i + 1);
488 memcpy(*ret, str, i + 1);
489 (*ret)[i] = '\0';
490 return i + 1;
491 }
492 }
493
494 return 0;
495 }
496
497 const char *
498 ordinal(unsigned short n)
499 {
500 switch (n % 100) {
501 case 11:
502 case 12:
503 case 13:
504 return "th";
505 default:
506 switch (n % 10) {
507 case 1:
508 return "st";
509 case 2:
510 return "nd";
511 case 3:
512 return "rd";
513 default:
514 return "th";
515 }
516 }
517 }
518
519 size_t
520 rtrim(char *str, char *chars)
521 {
522 size_t len, rlen, n, j;
523
524 rlen = len = strlen(str);
525
526 for (n = len; n > 0; n--) {
527 for (j = 0; chars[j] != '\0'; j++) {
528 if (str[n - 1] == chars[j]) {
529 rlen--;
530 str[n - 1] = '\0';
531 goto next_in_str;
532 }
533 }
534
535 break;
536 next_in_str:
537 continue;
538 }
539
540 return rlen;
541 }
542
543 long
544 strpos_quoted(char *str, char c)
545 {
546 long pos;
547 unsigned char quot = 0;
548
549 for (pos = 0; str[pos] != '\0'; pos++) {
550 if (quot) {
551 if (str[pos] == '\\') {
552 pos++;
553 continue;
554 }
555 if (str[pos] == quot)
556 quot = 0;
557 continue;
558 } else {
559 if (str[pos] == '"') {
560 quot = str[pos];
561 continue;
562 }
563 if (str[pos] == c)
564 return pos;
565 }
566 }
567
568 return -1;
569 }
570
571 size_t
572 hexify(unsigned char *input, size_t len, unsigned char *output)
573 {
574 static const char hexchars[] = "0123456789abcdef";
575 size_t n, retlen = 0;
576
577 for (n = 0; n < len; n++) {
578 *output++ = hexchars[input[n] >> 4];
579 *output++ = hexchars[input[n] & 0xf];
580 retlen += 2;
581 }
582
583 return retlen;
584 }
585
586 size_t
587 unhexify(unsigned char *input, size_t len, unsigned char *output)
588 {
589 size_t n, retlen = 0;
590 uint8_t c;
591
592 for (n = 0; n < len; n++, input++) {
593 if (*input >= '0' && *input <= '9')
594 c = *input - '0';
595 else if (*input >= 'a' && *input <= 'f')
596 c = *input - 'a' + 10;
597 else if (*input >= 'A' && *input <= 'F')
598 c = *input - 'A' + 10;
599 else
600 continue;
601
602 if (n % 2 == 0)
603 *output = c << 4;
604 else {
605 *output |= c;
606 retlen++;
607 output++;
608 }
609 }
610
611 return retlen;
612 }
613
614 char *
615 OSTypeToString(OSType type)
616 {
617 static char ostype_s[5];
618
619 ostype_s[0] = (unsigned char)((type >> 24) & 0xff);
620 ostype_s[1] = (unsigned char)((type >> 16) & 0xff);
621 ostype_s[2] = (unsigned char)((type >> 8) & 0xff);
622 ostype_s[3] = (unsigned char)(type & 0xff);
623 ostype_s[4] = 0;
624
625 return ostype_s;
626 }
627
628 short
629 strcasecmp(const char *s1, const char *s2)
630 {
631 const unsigned char *cm = strcasecmp_charmap;
632 const unsigned char *us1 = (const unsigned char *)s1;
633 const unsigned char *us2 = (const unsigned char *)s2;
634
635 while (cm[*us1] == cm[*us2++])
636 if (*us1++ == '\0')
637 return (0);
638 return (cm[*us1] - cm[*--us2]);
639 }
640
641 short
642 strncasecmp(const char *s1, const char *s2, size_t n)
643 {
644 if (n != 0) {
645 const unsigned char *cm = strcasecmp_charmap;
646 const unsigned char *us1 = (const unsigned char *)s1;
647 const unsigned char *us2 = (const unsigned char *)s2;
648
649 do {
650 if (cm[*us1] != cm[*us2++])
651 return (cm[*us1] - cm[*--us2]);
652 if (*us1++ == '\0')
653 break;
654 } while (--n != 0);
655 }
656 return (0);
657 }
658
659 char *
660 strcasestr(const char *s, const char *find)
661 {
662 const unsigned char *cm = strcasecmp_charmap;
663 const unsigned char *us = (const unsigned char *)s;
664 const unsigned char *ufind = (const unsigned char *)find;
665 const unsigned char *tus, *tufind;
666
667 if (find == NULL)
668 return NULL;
669 if (find[0] == '\0')
670 return (char *)s;
671
672 while (*us) {
673 if (cm[*us] == cm[*ufind]) {
674 tus = us;
675 tufind = ufind;
676
677 while (*tufind) {
678 if (*tus == '\0')
679 goto advance_us;
680 if (cm[*tus] != cm[*tufind])
681 goto advance_us;
682 tus++;
683 tufind++;
684 }
685
686 return (char *)us;
687 }
688
689 advance_us:
690 us++;
691 }
692
693 return NULL;
694 }
695
696 long
697 strcaseidx(const char *s, const char **list)
698 {
699 const unsigned char *cm = strcasecmp_charmap;
700 const unsigned char *us = (const unsigned char *)s;
701 long sn, ln, n;
702
703 /*
704 * Entries in list must be sorted.
705 *
706 * For first char in s (sn=0), walk list as ln until we get a partial
707 * match. Once matching fails, advance ln and make sure that next
708 * list entry matches all characters up to sn. If not, there are no
709 * possible matches in this sorted list. If everything does match up
710 * to sn, keep advancing sn until we reach its terminator.
711 */
712 for (sn = 0, ln = 0; ; sn++) {
713 while (cm[list[ln][sn]] != cm[us[sn]]) {
714 if (list[++ln] == NULL)
715 return -1;
716
717 /* make sure each char in next list entry matches up to now */
718 for (n = 0; n < sn; n++) {
719 if (cm[us[n]] != cm[list[ln][n]])
720 return -1;
721 }
722 }
723
724 if (us[sn] == '\0')
725 return ln;
726 }
727
728 return -1;
729 }
730
731 /*
732 * BSD warn(3) functions
733 */
734
735 void
736 vwarn(short alert_func, const char *format, va_list ap)
737 {
738 Rect bounds;
739 short hit, itype;
740 WindowPtr win, dialog;
741 Handle ihandle, okhandle;
742 Rect irect;
743
744 progress(NULL);
745 GetPort(&win);
746
747 vsnprintf(err_str, ERROR_STRING_SIZE, format, ap);
748 ParamText(CtoPstr(err_str), "\p", "\p", "\p");
749
750 center_in_screen(320, 110, false, &bounds);
751 dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
752 (WindowPtr)-1L, false, 0, alert_ditl_h);
753
754 GetDItem(dialog, ALERT_DITL_ICON, &itype, &ihandle, &irect);
755 switch (alert_func) {
756 case CAUTION_ALERT:
757 ihandle = GetIcon(cautionIcon);
758 break;
759 case NOTE_ALERT:
760 ihandle = GetIcon(noteIcon);
761 break;
762 case APPICON_ALERT:
763 ihandle = GetResource('ICN#', APPICON_ICN_ID);
764 if (ihandle)
765 break;
766 default:
767 ihandle = GetIcon(stopIcon);
768 }
769 SetDItem(dialog, ALERT_DITL_ICON, itype, ihandle, &irect);
770
771 ShowWindow(dialog);
772
773 for (;;) {
774 ModalDialog(ModalDialogFilter, &hit);
775 if (hit == ok)
776 break;
777 }
778 ReleaseResource(ihandle);
779 DisposDialog(dialog);
780
781 /*
782 * Pre-allocate for the next one while we have memory. We can't use
783 * xNewHandle because we might already be alerting about an OOM
784 * condition.
785 */
786 DisposHandle(alert_ditl_h);
787 alert_ditl_h = NewHandle(sizeof(alert_ditl));
788 if (alert_ditl_h == NULL)
789 ExitToShell();
790 HLock(alert_ditl_h);
791 memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
792 HUnlock(alert_ditl_h);
793
794 SetPort(win);
795 }
796
797 void
798 panic(const char *format, ...)
799 {
800 va_list ap;
801
802 va_start(ap, format);
803 vwarn(STOP_ALERT, format, ap);
804 va_end(ap);
805
806 Debugger();
807 ExitToShell();
808 }
809
810 void
811 warn(const char *format, ...)
812 {
813 va_list ap;
814
815 va_start(ap, format);
816 vwarn(CAUTION_ALERT, format, ap);
817 va_end(ap);
818 }
819
820 void
821 note(const char *format, ...)
822 {
823 va_list ap;
824
825 va_start(ap, format);
826 vwarn(NOTE_ALERT, format, ap);
827 va_end(ap);
828 }
829
830 void
831 appicon_note(const char *format, ...)
832 {
833 va_list ap;
834
835 va_start(ap, format);
836 vwarn(APPICON_ALERT, format, ap);
837 va_end(ap);
838 }
839
840 short
841 ask(const char *format, ...)
842 {
843 Rect bounds;
844 WindowPtr win, dialog;
845 va_list ap;
846 short hit;
847
848 GetPort(&win);
849
850 va_start(ap, format);
851 vsnprintf(err_str, ERROR_STRING_SIZE, format, ap);
852 va_end(ap);
853
854 ParamText(CtoPstr(err_str), "\p", "\p", "\p");
855
856 ask_ditl_h = xNewHandle(sizeof(ask_ditl));
857 HLock(ask_ditl_h);
858 memcpy(*ask_ditl_h, ask_ditl, sizeof(ask_ditl));
859 HUnlock(ask_ditl_h);
860
861 center_in_screen(300, 100, false, &bounds);
862 dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
863 (WindowPtr)-1L, false, 0, ask_ditl_h);
864
865 ShowWindow(dialog);
866 for (;;) {
867 ModalDialog(ModalDialogFilter, &hit);
868 if (hit == 1 || hit == 2)
869 break;
870 }
871 DisposDialog(dialog);
872 DisposHandle(ask_ditl_h);
873
874 SetPort(win);
875
876 return (hit == 1);
877 }
878
879 void
880 about(char *program_name)
881 {
882 VersRecHndl vers;
883 char vers_s[255];
884 char short_vers[255];
885 short vlen, n;
886
887 if ((vers = (VersRecHndl)GetResource('vers', 1))) {
888 /*
889 * vers "long version string" is a pascal string after the
890 * short version pascal string
891 */
892 HLock(vers);
893 vlen = (*vers)->shortVersion[0];
894 memcpy(short_vers, (*vers)->shortVersion + vlen + 1,
895 sizeof((*vers)->shortVersion) - vlen - 1);
896 PtoCstr(short_vers);
897 snprintf(vers_s, sizeof(vers_s), "%s %s", program_name,
898 short_vers);
899 for (n = 0; n < sizeof(vers_s); n++) {
900 if (vers_s[n] == '©') {
901 vers_s[n - 1] = '\r';
902 break;
903 }
904 }
905 ReleaseResource(vers);
906 appicon_note("%s", vers_s);
907 } else
908 warn("Can't find version number!");
909 }
910
911 void
912 progress(char *format, ...)
913 {
914 static Str255 progress_s;
915 Cursor c;
916 CursHandle ch;
917 Handle thandle;
918 va_list argptr;
919 Rect bounds;
920 Rect trect;
921 short ttype;
922 bool new = false;
923
924 if (format == NULL) {
925 if (progress_dialog != NULL) {
926 DisposDialog(progress_dialog);
927 DisposHandle(progress_ditl_h);
928 progress_dialog = NULL;
929 SetCursor(&arrow);
930 }
931 return;
932 }
933
934 va_start(argptr, format);
935 vsnprintf((char *)progress_s, 256, format, argptr);
936 va_end(argptr);
937 CtoPstr(progress_s);
938
939 if (progress_dialog == NULL) {
940 progress_ditl_h = xNewHandle(sizeof(progress_ditl));
941 HLock(progress_ditl_h);
942 memcpy(*progress_ditl_h, progress_ditl, sizeof(progress_ditl));
943 HUnlock(progress_ditl_h);
944
945 center_in_screen(330, 60, false, &bounds);
946 progress_dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
947 (WindowPtr)-1L, false, 0, progress_ditl_h);
948 new = true;
949 }
950
951 GetDItem(progress_dialog, 1, &ttype, &thandle, &trect);
952 SetIText(thandle, progress_s);
953
954 if (new) {
955 ch = GetCursor(watchCursor);
956 c = **ch;
957 SetCursor(&c);
958 ShowWindow(progress_dialog);
959 DrawDialog(progress_dialog);
960 }
961 }
962
963 void
964 window_rect(WindowPtr win, Rect *ret)
965 {
966 *ret = (*(((WindowPeek)win)->strucRgn))->rgnBBox;
967 ret->left += 1;
968 ret->right -= 2;
969 ret->top -= 1;
970 ret->bottom -= 1;
971 }
972
973 void
974 center_in_screen(short width, short height, bool titlebar, Rect *b)
975 {
976 b->left = ((screenBits.bounds.right - screenBits.bounds.left) / 2) -
977 (width / 2);
978 b->top = ((screenBits.bounds.bottom - screenBits.bounds.top) / 2) -
979 (height / 2) + (titlebar ? GetMBarHeight() : 0);
980 b->right = b->left + width;
981 b->bottom = b->top + height;
982 }
983
984 Point
985 centered_sfget_dialog(void)
986 {
987 Point p;
988 Rect r;
989
990 center_in_screen(364, 216, false, &r);
991 p.h = r.left;
992 p.v = r.top;
993
994 return p;
995 }
996
997 Point
998 centered_sfput_dialog(void)
999 {
1000 Point p;
1001 Rect r;
1002
1003 center_in_screen(320, 200, false, &r);
1004 p.h = r.left;
1005 p.v = r.top;
1006
1007 return p;
1008 }
1009
1010 void
1011 outline_button(ControlHandle button)
1012 {
1013 PenState pen;
1014 Rect rect;
1015
1016 SetPort((*button)->contrlOwner);
1017 GetPenState(&pen);
1018 PenNormal();
1019
1020 rect = (*button)->contrlRect;
1021 InsetRect(&rect, -4, -4);
1022 PenSize(3, 3);
1023 FrameRoundRect(&rect, (rect.bottom - rect.top) / 2,
1024 (rect.bottom - rect.top) / 2);
1025 SetPenState(&pen);
1026 }
1027
1028 /*
1029 * General Mac-specific non-GUI functions
1030 */
1031
1032 static unsigned long _xorshift_state = 0;
1033 unsigned long
1034 xorshift32(void)
1035 {
1036 unsigned long x = _xorshift_state;
1037 if (x == 0)
1038 x = Ticks;
1039 x ^= x << 13;
1040 x ^= x >> 17;
1041 x ^= x << 5;
1042 return _xorshift_state = x;
1043 }
1044
1045
1046 /*
1047 * Error checking wrappers for Mac toolkit functions
1048 */
1049
1050 Handle
1051 xNewHandle(size_t size)
1052 {
1053 Handle h;
1054
1055 if (size == 0)
1056 panic("Zero xNewHandle size");
1057
1058 h = NewHandle(size);
1059 if (h == NULL)
1060 panic("Failed to NewHandle(%lu)", size);
1061
1062 return h;
1063 }
1064
1065 Handle
1066 xGetResource(ResType type, short id)
1067 {
1068 Handle h;
1069
1070 h = GetResource(type, id);
1071 if (h == NULL)
1072 panic("Failed to find resource %d", id);
1073
1074 return h;
1075 }
1076
1077 StringHandle
1078 xGetString(short id)
1079 {
1080 StringHandle h;
1081
1082 h = GetString(id);
1083 if (h == NULL)
1084 panic("Failed to find STR resource %d", id);
1085
1086 return h;
1087 }
1088
1089 char *
1090 xGetStringAsChar(short id)
1091 {
1092 StringHandle h;
1093 char *out;
1094 size_t l;
1095
1096 h = xGetString(id);
1097 HLock(h);
1098 l = (*h)[0];
1099 out = xmalloc(l + 1);
1100 memcpy((void *)out, (void *)(*h + 1), l);
1101 out[l] = '\0';
1102 ReleaseResource(h);
1103
1104 return out;
1105 }
1106
1107 long
1108 xGetStringAsLong(short id)
1109 {
1110 char *c;
1111 long r;
1112
1113 c = xGetStringAsChar(id);
1114 r = atol(c);
1115 xfree(&c);
1116 return r;
1117 }
1118
1119 void
1120 xSetHandleSize(Handle h, Size s)
1121 {
1122 SetHandleSize(h, s);
1123 if (MemError())
1124 panic("Failed to SetHandleSize to %ld", s);
1125 }
1126
1127 /*
1128 * Filesystem utilities
1129 */
1130
1131 short
1132 getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file)
1133 {
1134 WDPBRec wdir;
1135 HVolumeParam wvol;
1136 DirInfo wcinfo;
1137 char *name = NULL;
1138 size_t retlen = 0, len;
1139 char *tmpret = NULL, *tmp = NULL;
1140 char *lastcolon;
1141
1142 if (strchr((char *)fileName + 1, ':') != NULL) {
1143 /* already a full path */
1144 memcpy(*ret, fileName, 256);
1145 if (!include_file) {
1146 PtoCstr(*ret);
1147 lastcolon = strrchr((char *)*ret, ':');
1148 lastcolon[0] = '\0';
1149 CtoPstr(*ret);
1150 }
1151 return 0;
1152 }
1153
1154 name = xmalloc(FILENAME_MAX);
1155
1156 wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum;
1157 wdir.ioWDIndex = 0;
1158 wdir.ioWDProcID = 0;
1159 wdir.ioNamePtr = (StringPtr)name;
1160 if (PBGetWDInfo(&wdir, 0) != noErr) {
1161 warn("Failed looking up directory");
1162 xfree(&name);
1163 return 1;
1164 }
1165
1166 wvol.ioNamePtr = (StringPtr)name;
1167 wvol.ioVRefNum = vRefNum;
1168 wvol.ioVolIndex = 0;
1169
1170 if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) {
1171 warn("Failed getting volume info");
1172 xfree(&name);
1173 return 1;
1174 }
1175
1176 if (wvol.ioVSigWord != 0x4244) {
1177 warn("Unknown filesystem type 0x%x", wvol.ioVSigWord);
1178 xfree(&name);
1179 return 1;
1180 }
1181
1182 wcinfo.ioVRefNum = vRefNum;
1183 wcinfo.ioNamePtr = (StringPtr)name;
1184 wcinfo.ioFDirIndex = -1;
1185 wcinfo.ioDrParID = wdir.ioWDDirID;
1186 wcinfo.ioDrDirID = wdir.ioWDDirID;
1187
1188 tmp = xmalloc(FILENAME_MAX);
1189 tmpret = xmalloc(FILENAME_MAX);
1190
1191 /* go backwards, prepending each folder's parent */
1192 while (wcinfo.ioDrParID != 1) {
1193 wcinfo.ioDrDirID = wcinfo.ioDrParID; /* .. */
1194
1195 if (PBGetCatInfo((CInfoPBPtr)&wcinfo, 0) != noErr)
1196 break;
1197
1198 len = name[0];
1199 PtoCstr(name);
1200 if (retlen == 0) {
1201 retlen = len;
1202 strlcpy(tmpret, (char *)name, FILENAME_MAX);
1203 } else {
1204 strlcpy(tmp, tmpret, FILENAME_MAX);
1205 snprintf(tmpret, FILENAME_MAX, "%s:%s", name, tmp);
1206 }
1207 }
1208
1209 if (include_file) {
1210 /* append the original path */
1211 memcpy(name, fileName, FILENAME_MAX);
1212 PtoCstr(name);
1213 if (retlen == 0)
1214 strlcpy(tmpret, name, FILENAME_MAX);
1215 else {
1216 strlcat(tmpret, ":", FILENAME_MAX);
1217 strlcat(tmpret, name, FILENAME_MAX);
1218 }
1219 } else if (retlen == 0) {
1220 (*ret)[0] = 0;
1221 xfree(&tmp);
1222 xfree(&tmpret);
1223 xfree(&name);
1224 return 0;
1225 }
1226
1227 CtoPstr(tmpret);
1228 memcpy(*ret, tmpret, FILENAME_MAX);
1229 xfree(&tmp);
1230 xfree(&tmpret);
1231 xfree(&name);
1232
1233 return 0;
1234 }
1235
1236 short
1237 stat(char *path, struct stat *sb)
1238 {
1239 char *ppath;
1240 short ret;
1241
1242 ppath = xstrdup(path);
1243 CtoPstr(ppath);
1244 ret = FStat((unsigned char *)ppath, sb);
1245 xfree(&ppath);
1246
1247 return ret;
1248 }
1249
1250 short
1251 FStat(Str255 path, struct stat *sb)
1252 {
1253 CInfoPBRec catblock;
1254 short ret;
1255
1256 memset(&catblock, 0, sizeof(catblock));
1257 catblock.hFileInfo.ioNamePtr = path;
1258 ret = PBGetCatInfo(&catblock, 0);
1259 if (ret != noErr)
1260 return -1;
1261
1262 /* data fork logical length + resource fork logical length */
1263 sb->st_size = catblock.hFileInfo.ioFlLgLen +
1264 catblock.hFileInfo.ioFlRLgLen;
1265 sb->st_ctime = catblock.hFileInfo.ioFlCrDat;
1266 sb->st_mtime = catblock.hFileInfo.ioFlMdDat;
1267 sb->st_flags = catblock.hFileInfo.ioFlAttrib;
1268
1269 return 0;
1270 }
1271
1272 bool
1273 FIsDir(Str255 path)
1274 {
1275 struct stat st;
1276 short ret;
1277
1278 if (FStat(path, &st) != 0)
1279 return false;
1280
1281 /* bit 4 is set in ioFlAttrib if the item is a directory */
1282 if (st.st_flags & (1 << 4))
1283 return true;
1284
1285 return false;
1286 }
1287
1288 OSErr
1289 copy_file(Str255 source, Str255 dest, bool overwrite)
1290 {
1291 FInfo fi;
1292 IOParam pb;
1293 short error, source_ref, dest_ref;
1294
1295 /* copy data fork */
1296
1297 error = GetFInfo(source, 0, &fi);
1298 if (error)
1299 return error;
1300
1301 error = Create(dest, 0, fi.fdCreator, fi.fdType);
1302 if (error == dupFNErr && overwrite) {
1303 error = FSDelete(dest, 0);
1304 if (error)
1305 return error;
1306 error = Create(dest, 0, fi.fdCreator, fi.fdType);
1307 }
1308 if (error)
1309 return error;
1310
1311 memset(&pb, 0, sizeof(pb));
1312 pb.ioNamePtr = source;
1313 pb.ioPermssn = fsRdPerm;
1314 error = PBOpen(&pb, false);
1315 if (error)
1316 return error;
1317 source_ref = pb.ioRefNum;
1318
1319 error = FSOpen(dest, 0, &dest_ref);
1320 if (error) {
1321 FSClose(source_ref);
1322 return error;
1323 }
1324
1325 error = copy_file_contents(source_ref, dest_ref);
1326
1327 FSClose(source_ref);
1328 FSClose(dest_ref);
1329
1330 if (error)
1331 return error;
1332
1333 /*
1334 * Copy resource fork, open source as shared read/write in case it's
1335 * an open resource file.
1336 */
1337 source_ref = OpenRFPerm(source, 0, fsRdWrShPerm);
1338
1339 if (source_ref == -1 && ResError() == eofErr) {
1340 /* no resource fork */
1341 FSClose(source_ref);
1342 FSClose(dest_ref);
1343 return 0;
1344 }
1345
1346 if (source_ref == -1)
1347 return ResError();
1348
1349 CreateResFile(dest);
1350 if (ResError()) {
1351 FSClose(source_ref);
1352 return ResError();
1353 }
1354 error = OpenRF(dest, 0, &dest_ref);
1355 if (error) {
1356 FSClose(source_ref);
1357 return error;
1358 }
1359
1360 error = copy_file_contents(source_ref, dest_ref);
1361
1362 FSClose(source_ref);
1363 FSClose(dest_ref);
1364
1365 return error;
1366 }
1367
1368 OSErr
1369 copy_file_contents(short source_ref, short dest_ref)
1370 {
1371 char *buf;
1372 short error;
1373 long source_size, count;
1374
1375 GetEOF(source_ref, &source_size);
1376 error = SetFPos(source_ref, fsFromStart, 0);
1377 if (error)
1378 return error;
1379 error = SetEOF(dest_ref, source_size);
1380 if (error)
1381 return error;
1382 error = SetFPos(dest_ref, fsFromStart, 0);
1383 if (error)
1384 return error;
1385
1386 buf = xmalloc(1024);
1387 if (buf == NULL)
1388 return -1;
1389
1390 while (source_size > 0) {
1391 count = 1024;
1392 if (count > source_size)
1393 count = source_size;
1394 error = FSRead(source_ref, &count, buf);
1395 if (error && error != eofErr)
1396 break;
1397 source_size -= count;
1398 error = FSWrite(dest_ref, &count, buf);
1399 if (error && error != eofErr)
1400 break;
1401 }
1402
1403 xfree(&buf);
1404
1405 if (error && error != eofErr)
1406 return error;
1407
1408 return 0;
1409 }
1410
1411 /* read a \r-terminated line or the final non-line bytes of an open file */
1412 OSErr
1413 FSReadLine(short frefnum, char *buf, size_t buflen)
1414 {
1415 char tbuf;
1416 size_t pos, fsize, rlen = 1, total_read = 0;
1417 short error;
1418
1419 GetFPos(frefnum, &pos);
1420 GetEOF(frefnum, &fsize);
1421
1422 for (; pos <= fsize; pos++) {
1423 if (total_read > buflen)
1424 return -1;
1425
1426 error = FSRead(frefnum, &rlen, &tbuf);
1427 if (error)
1428 return -1;
1429
1430 if (tbuf == '\r')
1431 return total_read;
1432
1433 buf[total_read++] = tbuf;
1434 }
1435
1436 /* nothing found until the end of the file */
1437 return total_read;
1438 }
1439
1440 TrapType
1441 GetTrapType(unsigned long theTrap)
1442 {
1443 if (BitAnd(theTrap, 0x0800) > 0)
1444 return ToolTrap;
1445
1446 return OSTrap;
1447 }
1448
1449 bool
1450 TrapAvailable(unsigned long trap)
1451 {
1452 TrapType trapType = ToolTrap;
1453 unsigned long numToolBoxTraps;
1454
1455 if (NGetTrapAddress(_InitGraf, ToolTrap) ==
1456 NGetTrapAddress(0xAA6E, ToolTrap))
1457 numToolBoxTraps = 0x200;
1458 else
1459 numToolBoxTraps = 0x400;
1460
1461 trapType = GetTrapType(trap);
1462 if (trapType == ToolTrap) {
1463 trap = BitAnd(trap, 0x07FF);
1464 if (trap >= numToolBoxTraps)
1465 trap = _Unimplemented;
1466 }
1467
1468 return (NGetTrapAddress(trap, trapType) !=
1469 NGetTrapAddress(_Unimplemented, ToolTrap));
1470 }
1471
1472 void
1473 GetSystemFolder(short *vRefNumP, long *dirIDP)
1474 {
1475 SysEnvRec info;
1476 long wdProcID;
1477
1478 SysEnvirons(1, &info);
1479 if (GetWDInfo(info.sysVRefNum, vRefNumP, dirIDP, &wdProcID) != noErr) {
1480 *vRefNumP = 0;
1481 *dirIDP = 0;
1482 }
1483 }
1484
1485 void
1486 GetSystemSubfolder(OSType folder, bool create, short *vRefNumP, long *dirIDP)
1487 {
1488 bool hasFolderMgr = false;
1489 long feature;
1490
1491 if (TrapAvailable(_GestaltDispatch) &&
1492 Gestalt(gestaltFindFolderAttr, &feature) == noErr)
1493 hasFolderMgr = true;
1494
1495 if (!hasFolderMgr) {
1496 GetSystemFolder(vRefNumP, dirIDP);
1497 return;
1498 }
1499
1500 if (FindFolder(kOnSystemDisk, folder, create, vRefNumP,
1501 dirIDP) != noErr) {
1502 *vRefNumP = 0;
1503 *dirIDP = 0;
1504 }
1505 }
1506
1507 /*
1508 * Gestalt functions
1509 */
1510 char *
1511 gestalt_machine_type(void)
1512 {
1513 long resp;
1514
1515 if (Gestalt(gestaltMachineType, &resp) != 0)
1516 return NULL;
1517
1518 switch (resp) {
1519 case gestaltClassic:
1520 return "128K";
1521 case gestaltMacXL:
1522 return "XL";
1523 case gestaltMac512KE:
1524 return "512Ke";
1525 case gestaltMacPlus:
1526 return "Plus";
1527 case gestaltMacSE:
1528 return "SE";
1529 case gestaltMacII:
1530 return "II";
1531 case gestaltMacIIx:
1532 return "IIx";
1533 case gestaltMacIIcx:
1534 return "IIcx";
1535 case gestaltMacSE030:
1536 return "SE/30";
1537 case gestaltPortable:
1538 return "Portable";
1539 case gestaltMacIIci:
1540 return "IIci";
1541 case gestaltMacIIfx:
1542 return "IIfx";
1543 case gestaltMacClassic:
1544 return "Classic";
1545 case gestaltMacIIsi:
1546 return "IIsi";
1547 case gestaltMacLC:
1548 return "LC";
1549
1550 case 20:
1551 return "Quadra 900";
1552 case 21:
1553 return "PowerBook 170";
1554 case 22:
1555 return "Quadra 700";
1556 case 23:
1557 return "Classic II";
1558 case 24:
1559 return "PowerBook 100";
1560 case 25:
1561 return "PowerBook 140";
1562 case 26:
1563 return "Quadra 950";
1564 case 27:
1565 return "LC III";
1566 case 29:
1567 return "PowerBook Duo 210";
1568 case 30:
1569 return "Centris 650";
1570 case 32:
1571 return "PowerBook Duo 230";
1572 case 33:
1573 return "PowerBook 180";
1574 case 34:
1575 return "PowerBook 160";
1576 case 35:
1577 return "Quadra 800";
1578 case 36:
1579 return "Quadra 650";
1580 case 37:
1581 return "LC II";
1582 case 38:
1583 return "PowerBook Duo 250";
1584 case 44:
1585 return "IIvi";
1586 case 45:
1587 return "Performa 600";
1588 case 48:
1589 return "IIvx";
1590 case 49:
1591 return "Color Classic";
1592 case 50:
1593 return "PowerBook 165c";
1594 case 52:
1595 return "Centris 610";
1596 case 53:
1597 return "Quadra 610";
1598 case 54:
1599 return "PowerBook 145";
1600 case 56:
1601 return "LC 520";
1602 case 71:
1603 return "PowerBook 180c";
1604 case 84:
1605 return "PowerBook 165";
1606 case 102:
1607 return "PowerBook Duo 280";
1608 case 103:
1609 return "PowerBook Duo 280c";
1610 case 115:
1611 return "PowerBook 150";
1612 case 122:
1613 return "PowerBook 190";
1614 }
1615
1616 return NULL;
1617 }
1618
1619 char *
1620 get_version(bool long_version)
1621 {
1622 static char vers_s[256] = { 0 };
1623 char *v;
1624 VersRecHndl vers;
1625 short len;
1626
1627 vers = (VersRecHndl)GetResource('vers', 1);
1628 if (!vers)
1629 return "?.?";
1630
1631 HLock(vers);
1632 v = (char *)&(*vers)->shortVersion;
1633 len = v[0];
1634 if (long_version) {
1635 v += len + 1;
1636 len = v[0];
1637 }
1638 memcpy(vers_s, v, len + 1);
1639 ReleaseResource(vers);
1640
1641 PtoCstr(vers_s);
1642 return vers_s;
1643 }
1644
1645 bool
1646 screen_saver_running(void)
1647 {
1648 short error;
1649 long resp;
1650
1651 error = Gestalt('SAVR', &resp); /* gestaltScreenSaverAttr */
1652 if (error == 0 && (resp & (1 << 1))) /* gestaltSaverAsleep */
1653 return true;
1654
1655 return false;
1656 }
1657
1658 /*
1659 * General Mac-specific GUI functions
1660 */
1661
1662 short
1663 FontHeight(short font_id, short size)
1664 {
1665 FMInput f_in;
1666 FMOutput *f_out;
1667
1668 if (font_id == -1)
1669 font_id = thePort->txFont;
1670 if (size == -1)
1671 size = thePort->txSize;
1672
1673 f_in.family = font_id;
1674 f_in.size = size;
1675 f_in.face = 0;
1676 f_in.needBits = false;
1677 f_in.device = 0;
1678 f_in.numer.h = f_in.numer.v = 1;
1679 f_in.denom.h = f_in.denom.v = 1;
1680
1681 f_out = FMSwapFont(&f_in);
1682
1683 return (((f_out->leading + f_out->ascent + f_out->descent) *
1684 f_out->numer.v) / f_out->denom.v);
1685 }
1686
1687 void
1688 DrawGrowIconOnly(WindowPtr win)
1689 {
1690 Rect r;
1691 RgnHandle tmp;
1692
1693 GetClip(tmp = NewRgn());
1694 r = win->portRect;
1695 r.top = r.bottom - SCROLLBAR_WIDTH;
1696 r.left = r.right - SCROLLBAR_WIDTH + 1;
1697 ClipRect(&r);
1698 DrawGrowIcon(win);
1699 SetClip(tmp);
1700 DisposeRgn(tmp);
1701 }
1702
1703 /* assumes a fixed-width style */
1704 short
1705 TEGetWidth(short off, TEHandle te)
1706 {
1707 TextStyle style;
1708 short fheight, fwidth, ascent, old_font, old_size;
1709
1710 TEGetStyle(off, &style, &fheight, &ascent, te);
1711
1712 old_font = thePort->txFace;
1713 old_size = thePort->txSize;
1714 thePort->txFace = style.tsFont;
1715 thePort->txSize = style.tsSize;
1716 fwidth = CharWidth('m');
1717 thePort->txFace = old_font;
1718 thePort->txSize = old_size;
1719
1720 return fwidth;
1721 }
1722
1723 #define ceildiv(a,b) ((a / b) + (!!(a % b)))
1724
1725 void
1726 UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te,
1727 bool reset)
1728 {
1729 size_t vlines, telines;
1730 TERec *ter;
1731 RgnHandle rgn;
1732 short fheight, fwidth, max, val, per_line, horiz, max_chars, n;
1733
1734 HLock(control);
1735 HLock(te);
1736 ter = *te;
1737
1738 horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) <
1739 ((*control)->contrlRect.right - (*control)->contrlRect.left));
1740
1741 if (horiz) {
1742 fwidth = TEGetWidth(0, te);
1743 per_line = ((ter->viewRect.right - ter->viewRect.left) /
1744 fwidth) - 1;
1745 for (max_chars = 0, n = 1; n < ter->nLines; n++) {
1746 if (ter->lineStarts[n] - ter->lineStarts[n - 1] > max_chars)
1747 max_chars = ter->lineStarts[n] - ter->lineStarts[n - 1];
1748 }
1749
1750 if (max_chars <= per_line)
1751 /* don't enable the scrollbar */
1752 max = 1;
1753 else
1754 max = max_chars - per_line + 1;
1755
1756 if (reset) {
1757 val = 1;
1758 TESetSelect(0, 0, te);
1759 } else {
1760 val = (ter->viewRect.left - ter->destRect.left);
1761
1762 if (val < 0)
1763 val = 1;
1764 else {
1765 val = ceildiv(val, fwidth) + 1;
1766 if (val > max)
1767 max = val;
1768 }
1769 }
1770 } else {
1771 fheight = TEGetHeight(0, 0, te);
1772 vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight;
1773 telines = ter->nLines;
1774 /* telines is inaccurate if the last line doesn't have any chars */
1775 //if (telines >= vlines)
1776 // telines++;
1777 max = telines - vlines + 1;
1778 if (max < 1)
1779 max = 1;
1780
1781 if (reset) {
1782 val = 1;
1783 TESetSelect(0, 0, te);
1784 } else {
1785 val = (ter->viewRect.top - ter->destRect.top);
1786 if (val < 0)
1787 val = 1;
1788 else {
1789 val = ceildiv(val, fheight) + 1;
1790 if (val > max)
1791 max = val;
1792 }
1793 }
1794 }
1795
1796 /*
1797 * Avoid SetCtlMax because it will redraw and then SetCtlValue will
1798 * redraw again, which can cause a jump if we're trying to keep the
1799 * scrollbar position in the same place (like at the bottom).
1800 */
1801 (*control)->contrlMax = max;
1802 SetCtlValue(control, val);
1803
1804 rgn = NewRgn();
1805 RectRgn(rgn, &(*control)->contrlRect);
1806 UpdtControl(win, rgn);
1807 CloseRgn(rgn);
1808
1809 HUnlock(te);
1810 HUnlock(control);
1811 }
1812
1813 void
1814 SetTrackControlTE(TEHandle te)
1815 {
1816 track_control_te = te;
1817 }
1818
1819 pascal void
1820 TrackMouseDownInControl(ControlHandle control, short part)
1821 {
1822 TERec *ter;
1823 short page, val, adj, fheight, fwidth, horiz, per_line;
1824
1825 if (track_control_te == NULL)
1826 panic("TrackMouseDownInControl without SetTrackControlTE");
1827
1828 HLock(track_control_te);
1829 ter = *track_control_te;
1830
1831 horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) <
1832 ((*control)->contrlRect.right - (*control)->contrlRect.left));
1833
1834 if (horiz) {
1835 fwidth = TEGetWidth(0, track_control_te);
1836 per_line = ((ter->viewRect.right - ter->viewRect.left) /
1837 fwidth) - 1;
1838 page = ceildiv(GetCtlMax(control) + per_line,
1839 ((per_line * 75) / 100)) + 1;
1840 } else {
1841 /* keep 1 line of context between pages */
1842 fheight = TEGetHeight(0, 0, track_control_te);
1843 page = ((ter->viewRect.bottom - ter->viewRect.top) / fheight) - 1;
1844 }
1845
1846 adj = 0;
1847 switch (part) {
1848 case inUpButton:
1849 adj = -1;
1850 break;
1851 case inPageUp:
1852 adj = -page;
1853 break;
1854 case inDownButton:
1855 adj = 1;
1856 break;
1857 case inPageDown:
1858 adj = page;
1859 break;
1860 }
1861
1862 val = GetCtlValue(control);
1863 if (val + adj < GetCtlMin(control))
1864 adj = -(val - GetCtlMin(control));
1865 if (val + adj > GetCtlMax(control))
1866 adj = (GetCtlMax(control) - val);
1867 if (adj == 0)
1868 return;
1869
1870 if (horiz)
1871 TEScroll(-adj * fwidth, 0, track_control_te);
1872 else
1873 TEScroll(0, -adj * fheight, track_control_te);
1874
1875 SetCtlValue(control, val + adj);
1876
1877 HUnlock(track_control_te);
1878 }
1879
1880 pascal bool
1881 ModalDialogFilter(DialogPtr dlg, EventRecord *event, short *hit)
1882 {
1883 WindowPtr event_win;
1884 short event_in, itype;
1885 char key;
1886 ControlHandle ihandle;
1887 Rect irect;
1888
1889 switch (event->what) {
1890 case nullEvent:
1891 /*
1892 * TODO: support passing this through to a function the caller
1893 * can specify to do something while we're staying modal
1894 */
1895 break;
1896 case updateEvt:
1897 GetDItem(dlg, ok, &itype, &ihandle, &irect);
1898 outline_button(ihandle);
1899 break;
1900 case mouseDown:
1901 event_in = FindWindow(event->where, &event_win);
1902
1903 switch (event_in) {
1904 case inGoAway:
1905 if (TrackGoAway(dlg, event->where)) {
1906 *hit = -1;
1907 return true;
1908 }
1909 }
1910 break;
1911 case keyDown:
1912 key = event->message & charCodeMask;
1913
1914 if (event->modifiers & cmdKey) {
1915 switch (key) {
1916 case 'x':
1917 ZeroScrap();
1918 DlgCut(dlg);
1919 break;
1920 case 'c':
1921 ZeroScrap();
1922 DlgCopy(dlg);
1923 break;
1924 case 'v':
1925 if (TEFromScrap() == noErr)
1926 DlgPaste(dlg);
1927 break;
1928 }
1929 event->what = nullEvent;
1930 return false;
1931 } else if (key == 13 || key == 3) {
1932 /* OK button */
1933 *hit = OK;
1934 return true;
1935 }
1936
1937 break;
1938 }
1939
1940 return false;
1941 }
1942
1943 static short _password_dialog_ditl_id = -1;
1944 static char *_password_dialog_storage = NULL;
1945 static size_t _password_dialog_storage_len = 0;
1946
1947 void
1948 PasswordDialogFieldFilterSetup(short ditl_id, char *storage, size_t len)
1949 {
1950 _password_dialog_ditl_id = ditl_id;
1951 _password_dialog_storage = storage;
1952 _password_dialog_storage_len = len;
1953
1954 memset(_password_dialog_storage, 0, len);
1955 }
1956
1957 pascal bool
1958 PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit)
1959 {
1960 DialogPeek dlgp;
1961 short sel_start, sel_end;
1962 char key;
1963
1964 dlgp = (DialogPeek)dlg;
1965 if (dlgp->editField == _password_dialog_ditl_id - 1) {
1966 sel_start = (*(dlgp->textH))->selStart;
1967 sel_end = (*(dlgp->textH))->selEnd;
1968
1969 switch (event->what) {
1970 case keyDown:
1971 case autoKey:
1972 key = event->message & charCodeMask;
1973 if (event->modifiers & cmdKey) {
1974 /* TODO: implement DlgPaste for cmd+v? */
1975 event->what = nullEvent;
1976 return false;
1977 }
1978
1979 if (key == 8) {
1980 /* backspace */
1981 if (sel_start == sel_end && sel_start > 0)
1982 memmove(_password_dialog_storage + sel_start - 1,
1983 _password_dialog_storage + sel_start,
1984 _password_dialog_storage_len - sel_start - 1);
1985 else if (sel_start != sel_end)
1986 memmove(_password_dialog_storage + sel_start,
1987 _password_dialog_storage + sel_end,
1988 _password_dialog_storage_len - sel_end - 1);
1989 } else if (sel_start >= _password_dialog_storage_len) {
1990 event->what = nullEvent;
1991 return false;
1992 } else if (key >= ' ' && key <= '~') {
1993 if (sel_start != sel_end)
1994 /* delete selection before making space for new char */
1995 memmove(_password_dialog_storage + sel_start,
1996 _password_dialog_storage + sel_end,
1997 _password_dialog_storage_len - sel_end - 1);
1998 memmove(_password_dialog_storage + sel_start + 1,
1999 _password_dialog_storage + sel_start,
2000 _password_dialog_storage_len - sel_start - 1);
2001 _password_dialog_storage[sel_start] = key;
2002 event->message = '•';
2003 }
2004 _password_dialog_storage[(*(dlgp->textH))->teLength + 1] = '\0';
2005 sel_start = 0;
2006 break;
2007 }
2008 }
2009
2010 return ModalDialogFilter(dlg, event, hit);
2011 }
2012
2013 void
2014 PasswordDialogFieldFinish(void)
2015 {
2016 _password_dialog_ditl_id = -1;
2017 _password_dialog_storage = NULL;
2018 _password_dialog_storage_len = 0;
2019 }
2020
2021 /* (*(some_te))->caretHook = NullCaretHook; */
2022 pascal void
2023 NullCaretHook(void)
2024 {
2025 asm {
2026 move.l (a7)+,d0
2027 rts
2028 }
2029 }
2030
2031 static bool menu_bar_hidden = false;
2032 static short old_mbar_height;
2033 static Rect mbar_rect;
2034 static RgnHandle mbar_save_region;
2035
2036 void
2037 HideMenuBar(void)
2038 {
2039 RgnHandle mbar_region;
2040 WindowPeek win;
2041
2042 old_mbar_height = GetMBarHeight();
2043 SetRect(&mbar_rect, screenBits.bounds.left, screenBits.bounds.top,
2044 screenBits.bounds.right, screenBits.bounds.top + old_mbar_height);
2045
2046 mbar_save_region = NewRgn();
2047 mbar_region = NewRgn();
2048
2049 MBarHeight = 0;
2050 CopyRgn(GetGrayRgn(), mbar_save_region);
2051 RectRgn(mbar_region, &mbar_rect);
2052 UnionRgn(GetGrayRgn(), mbar_region, GetGrayRgn());
2053
2054 win = (WindowPeek)FrontWindow();
2055 PaintOne(win, mbar_region);
2056 PaintBehind(win, mbar_region);
2057 CalcVis(win);
2058 CalcVisBehind(win, mbar_region);
2059 DisposeRgn(mbar_region);
2060
2061 menu_bar_hidden = true;
2062 }
2063
2064 void
2065 RestoreHiddenMenuBar(void)
2066 {
2067 WindowPeek win;
2068
2069 if (!menu_bar_hidden)
2070 return;
2071
2072 CopyRgn(mbar_save_region, GetGrayRgn());
2073 MBarHeight = old_mbar_height;
2074
2075 RectRgn(mbar_save_region, &mbar_rect);
2076 win = (WindowPeek)FrontWindow();
2077 CalcVis(win);
2078 CalcVisBehind(win, mbar_save_region);
2079 DisposeRgn(mbar_save_region);
2080
2081 menu_bar_hidden = false;
2082
2083 HiliteMenu(0);
2084 DrawMenuBar();
2085 }
2086
2087 bool
2088 CommandPeriodPressed(void)
2089 {
2090 EventRecord event;
2091
2092 if (!GetNextEvent(keyDownMask | autoKeyMask, &event))
2093 return false;
2094
2095 if ((event.modifiers & cmdKey) &&
2096 ((event.message & charCodeMask) == '.'))
2097 return true;
2098
2099 return false;
2100 }
2101
2102 /* C Extensions */
2103
2104 /*
2105 * Appends src to string dst of size dsize (unlike strncat, dsize is the
2106 * full size of dst, not space left). At most dsize-1 characters
2107 * will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
2108 * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
2109 * If retval >= dsize, truncation occurred.
2110 */
2111 size_t
2112 strlcat(char *dst, const char *src, size_t dsize)
2113 {
2114 const char *odst = dst;
2115 const char *osrc = src;
2116 size_t n = dsize;
2117 size_t dlen;
2118
2119 /* Find the end of dst and adjust bytes left but don't go past end. */
2120 while (n-- != 0 && *dst != '\0')
2121 dst++;
2122 dlen = dst - odst;
2123 n = dsize - dlen;
2124
2125 if (n-- == 0)
2126 return(dlen + strlen(src));
2127 while (*src != '\0') {
2128 if (n != 0) {
2129 *dst++ = *src;
2130 n--;
2131 }
2132 src++;
2133 }
2134 *dst = '\0';
2135
2136 return(dlen + (src - osrc)); /* count does not include NUL */
2137 }
2138
2139 /*
2140 * Copy string src to buffer dst of size dsize. At most dsize-1
2141 * chars will be copied. Always NUL terminates (unless dsize == 0).
2142 * Returns strlen(src); if retval >= dsize, truncation occurred.
2143 */
2144 size_t
2145 strlcpy(char *dst, const char *src, size_t dsize)
2146 {
2147 const char *osrc = src;
2148 size_t nleft = dsize;
2149
2150 /* Copy as many bytes as will fit. */
2151 if (nleft != 0) {
2152 while (--nleft != 0) {
2153 if ((*dst++ = *src++) == '\0')
2154 break;
2155 }
2156 }
2157
2158 /* Not enough room in dst, add NUL and traverse rest of src. */
2159 if (nleft == 0) {
2160 if (dsize != 0)
2161 *dst = '\0'; /* NUL-terminate dst */
2162 while (*src++)
2163 ;
2164 }
2165
2166 return(src - osrc - 1); /* count does not include NUL */
2167 }
2168
2169 char *
2170 strndup(const char *str, size_t maxlen)
2171 {
2172 char *copy;
2173 const char *cp;
2174 size_t len;
2175
2176 /* strnlen */
2177 for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--)
2178 ;
2179
2180 len = (size_t)(cp - str);
2181 copy = malloc(len + 1);
2182 if (copy != NULL) {
2183 (void)memcpy(copy, str, len);
2184 copy[len] = '\0';
2185 }
2186
2187 return copy;
2188 }
2189
2190 /*
2191 * Get next token from string *stringp, where tokens are possibly-empty
2192 * strings separated by characters from delim.
2193 *
2194 * Writes NULs into the string at *stringp to end tokens.
2195 * delim need not remain constant from call to call.
2196 * On return, *stringp points past the last NUL written (if there might
2197 * be further tokens), or is NULL (if there are definitely no more tokens).
2198 *
2199 * If *stringp is NULL, strsep returns NULL.
2200 */
2201 char *
2202 strsep(char **stringp, const char *delim)
2203 {
2204 char *s;
2205 const char *spanp;
2206 int c, sc;
2207 char *tok;
2208
2209 if ((s = *stringp) == NULL)
2210 return (NULL);
2211 for (tok = s;;) {
2212 c = *s++;
2213 spanp = delim;
2214 do {
2215 if ((sc = *spanp++) == c) {
2216 if (c == 0)
2217 s = NULL;
2218 else
2219 s[-1] = 0;
2220 *stringp = s;
2221 return (tok);
2222 }
2223 } while (sc != 0);
2224 }
2225 return (NULL);
2226 }
2227
2228 static struct printf_format {
2229 unsigned short leftJustify : 1;
2230 unsigned short forceSign : 1;
2231 unsigned short altForm : 1;
2232 unsigned short zeroPad : 1;
2233 unsigned short havePrecision : 1;
2234 unsigned short hSize : 1;
2235 unsigned short lSize : 1;
2236 unsigned short LSize : 1;
2237 char sign;
2238 char exponent;
2239 short fieldWidth;
2240 short precision;
2241 } printf_default_format;
2242
2243 short
2244 snprintf(char *s, size_t size, const char *fmt, ...)
2245 {
2246 return vsnprintf(s, size, fmt, __va(fmt));
2247 }
2248
2249 short
2250 vsnprintf(char *str, size_t size, const char *fmt, void *arg)
2251 {
2252 register short c, i, si, nwritten;
2253 register unsigned long n;
2254 long double x;
2255 register char *s;
2256 #define VSNPRINTF_BUFLEN 512
2257 static char buf[VSNPRINTF_BUFLEN], *digits, *t;
2258 struct printf_format F;
2259
2260 nwritten = 0;
2261
2262 if (size == 0)
2263 return 0;
2264
2265 for (c = *fmt; c; c = *++fmt) {
2266 if (c != '%')
2267 goto copy1;
2268
2269 F = printf_default_format;
2270
2271 /* decode flags */
2272 for (;;) {
2273 c = *++fmt;
2274 if (c == '-')
2275 F.leftJustify = true;
2276 else if (c == '+')
2277 F.forceSign = true;
2278 else if (c == ' ')
2279 F.sign = ' ';
2280 else if (c == '#')
2281 F.altForm = true;
2282 else if (c == '0')
2283 F.zeroPad = true;
2284 else
2285 break;
2286 }
2287
2288 /* decode field width */
2289 if (c == '*') {
2290 if ((F.fieldWidth = va_arg(arg, short)) < 0) {
2291 F.leftJustify = true;
2292 F.fieldWidth = -F.fieldWidth;
2293 }
2294 c = *++fmt;
2295 } else {
2296 for (; c >= '0' && c <= '9'; c = *++fmt)
2297 F.fieldWidth = (10 * F.fieldWidth) + (c - '0');
2298 }
2299
2300 /* decode precision */
2301 if (c == '.') {
2302 if ((c = *++fmt) == '*') {
2303 F.precision = va_arg(arg, short);
2304 c = *++fmt;
2305 } else {
2306 for (; c >= '0' && c <= '9'; c = *++fmt)
2307 F.precision = (10 * F.precision) + (c - '0');
2308 }
2309 if (F.precision >= 0)
2310 F.havePrecision = true;
2311 }
2312
2313 /* perform appropriate conversion */
2314 s = &buf[VSNPRINTF_BUFLEN];
2315 if (F.leftJustify)
2316 F.zeroPad = false;
2317 conv:
2318 switch (c) {
2319 /* 'h' size modifier */
2320 case 'h':
2321 F.hSize = true;
2322 c = *++fmt;
2323 goto conv;
2324
2325 /* 'l' size modifier */
2326 case 'l':
2327 F.lSize = true;
2328 c = *++fmt;
2329 goto conv;
2330
2331 /* 'L' size modifier */
2332 case 'L':
2333 F.LSize = true;
2334 c = *++fmt;
2335 goto conv;
2336
2337 /* decimal (signed) */
2338 case 'd':
2339 case 'i':
2340 if (F.lSize)
2341 n = va_arg(arg, long);
2342 else
2343 n = va_arg(arg, short);
2344 if (F.hSize)
2345 n = (short)n;
2346 if ((long)n < 0) {
2347 n = -n;
2348 F.sign = '-';
2349 } else if (F.forceSign)
2350 F.sign = '+';
2351 goto decimal;
2352
2353 /* decimal (unsigned) */
2354 case 'u':
2355 if (F.lSize)
2356 n = va_arg(arg, unsigned long);
2357 else
2358 n = va_arg(arg, unsigned short);
2359 if (F.hSize)
2360 n = (unsigned short)n;
2361 F.sign = 0;
2362 goto decimal;
2363
2364 /* decimal (common code) */
2365 decimal:
2366 if (!F.havePrecision) {
2367 if (F.zeroPad) {
2368 F.precision = F.fieldWidth;
2369 if (F.sign)
2370 --F.precision;
2371 }
2372 if (F.precision < 1)
2373 F.precision = 1;
2374 }
2375 for (i = 0; n; n /= 10, i++)
2376 *--s = n % 10 + '0';
2377 for (; i < F.precision; i++)
2378 *--s = '0';
2379 if (F.sign) {
2380 *--s = F.sign;
2381 i++;
2382 }
2383 break;
2384
2385 /* octal (unsigned) */
2386 case 'o':
2387 if (F.lSize)
2388 n = va_arg(arg, unsigned long);
2389 else
2390 n = va_arg(arg, unsigned short);
2391 if (F.hSize)
2392 n = (unsigned short)n;
2393 if (!F.havePrecision) {
2394 if (F.zeroPad)
2395 F.precision = F.fieldWidth;
2396 if (F.precision < 1)
2397 F.precision = 1;
2398 }
2399 for (i = 0; n; n /= 8, i++)
2400 *--s = n % 8 + '0';
2401 if (F.altForm && i && *s != '0') {
2402 *--s = '0';
2403 i++;
2404 }
2405 for (; i < F.precision; i++)
2406 *--s = '0';
2407 break;
2408
2409 /* hexadecimal (unsigned) */
2410 case 'p':
2411 F.havePrecision = F.lSize = true;
2412 F.precision = 8;
2413 /* ... */
2414 case 'X':
2415 digits = "0123456789ABCDEF";
2416 goto hexadecimal;
2417 case 'x':
2418 digits = "0123456789abcdef";
2419 /* ... */
2420 hexadecimal:
2421 if (F.lSize)
2422 n = va_arg(arg, unsigned long);
2423 else
2424 n = va_arg(arg, unsigned short);
2425 if (F.hSize)
2426 n = (unsigned short)n;
2427 if (!F.havePrecision) {
2428 if (F.zeroPad) {
2429 F.precision = F.fieldWidth;
2430 if (F.altForm)
2431 F.precision -= 2;
2432 }
2433 if (F.precision < 1)
2434 F.precision = 1;
2435 }
2436 for (i = 0; n; n /= 16, i++)
2437 *--s = digits[n % 16];
2438 for (; i < F.precision; i++)
2439 *--s = '0';
2440 if (F.altForm) {
2441 *--s = c;
2442 *--s = '0';
2443 i += 2;
2444 }
2445 break;
2446
2447 /* character */
2448 case 'c':
2449 *--s = va_arg(arg, short);
2450 i = 1;
2451 break;
2452
2453 /* string */
2454 case 's':
2455 s = va_arg(arg, char *);
2456 if (F.altForm) {
2457 i = (unsigned char)*s++;
2458 if (F.havePrecision && i > F.precision)
2459 i = F.precision;
2460 } else {
2461 if (!F.havePrecision)
2462 i = strlen(s);
2463 else if (t = memchr(s, '\0', F.precision))
2464 i = t - s;
2465 else
2466 i = F.precision;
2467 }
2468 break;
2469
2470 /* store # bytes written so far */
2471 case 'n':
2472 s = va_arg(arg, void *);
2473 if (F.lSize)
2474 *(long *)s = nwritten;
2475 else
2476 *(short *)s = nwritten;
2477 continue;
2478
2479 /* unknown conversion, abort */
2480 default:
2481 if (c != '%')
2482 goto done;
2483 copy1:
2484 if (size) {
2485 *str++ = c;
2486 size--;
2487 }
2488 ++nwritten;
2489 continue;
2490 }
2491
2492 /* pad on the left */
2493 if (i < F.fieldWidth && !F.leftJustify) {
2494 do {
2495 if (size) {
2496 *str++ = ' ';
2497 size--;
2498 }
2499 ++nwritten;
2500 } while (i < --F.fieldWidth);
2501 }
2502
2503 /* write the converted result */
2504 si = i > size ? size : i;
2505 if (si) {
2506 memcpy(str, s, si);
2507 str += si;
2508 size -= si;
2509 }
2510 nwritten += i;
2511
2512 /* pad on the right */
2513 for (; i < F.fieldWidth; i++) {
2514 if (size) {
2515 *str++ = ' ';
2516 size--;
2517 }
2518 ++nwritten;
2519 }
2520 }
2521
2522 /* all done! */
2523 if (size)
2524 *str = '\0';
2525 else
2526 str[-1] = '\0';
2527
2528 done:
2529 return nwritten;
2530 }