/* * Copyright (c) 2020-2022 joshua stein * Copyright (c) 1998, 2015 Todd C. Miller * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* THINK C Project must have a header of "#include " */ #include #include #include #include #include #include #include #include "util.h" /* ALRT resources */ #define ASK_ALERT_ID 130 #define ERROR_STRING_SIZE 1024 static char err_str[ERROR_STRING_SIZE]; /* basic DITL with an ok button (1), text area (2), and icon (3) */ #define ALERT_DITL_OK 1 #define ALERT_DITL_ICON 3 static const char alert_ditl[] = { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x00, 0xFA, 0x00, 0x64, 0x01, 0x34, 0x04, 0x02, 0x4F, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x4E, 0x00, 0x41, 0x01, 0x36, 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x17, 0x00, 0x2D, 0x00, 0x37, 0xA0, 0x02, 0x00, 0x01 }; static Handle alert_ditl_h = NULL; /* ICN# to show for APPICON_ALERT */ #define APPICON_ICN_ID 128 /* DITL with a Yes button (1), No button (2), text (3), and icon (4) */ static const char ask_ditl[] = { 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x03, 0x59, 0x65, 0x73, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0xA0, 0x00, 0x5A, 0x00, 0xDA, 0x04, 0x02, 0x4E, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x32, 0x00, 0x41, 0x01, 0x22, 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, 0xA0, 0x02, 0x00, 0x01 }; static Handle ask_ditl_h = NULL; /* DITL with just a text view */ static const char progress_ditl[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1E, 0x00, 0x32, 0x01, 0x3B, 0x08, 0x02, 0x5E, 0x30 }; static Handle progress_ditl_h = NULL; static DialogPtr progress_dialog = NULL; static TEHandle track_control_te = NULL; enum { STOP_ALERT, CAUTION_ALERT, NOTE_ALERT, APPICON_ALERT }; /* * Define to audit each malloc and free and verify that a pointer isn't * double-freed. The list of outstanding allocations can be checked by * looking through malloc_map. */ //#define MALLOC_DEBUG #ifdef MALLOC_DEBUG /* * List of allocations, updated at xmalloc() and xfree(). If an address * passed to xfree() isn't in the list, it indicates a double-free. */ #define MALLOC_MAP_CHUNK_SIZE 1024 struct malloc_map_e { unsigned long addr; unsigned long size; char note[MALLOC_NOTE_SIZE]; } *malloc_map = NULL; unsigned long malloc_map_size = 0; static bool malloc_map_compact = false; #endif void vwarn(short alert_func, const char *format, va_list ap); /* * Util helper needed to be called at program startup, to pre-allocate * some things that we can't do during errors. */ void util_init(void) { alert_ditl_h = xNewHandle(sizeof(alert_ditl)); HLock(alert_ditl_h); memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl)); HUnlock(alert_ditl_h); #ifdef MALLOC_DEBUG malloc_map_size = MALLOC_MAP_CHUNK_SIZE; malloc_map = (struct malloc_map_e *)NewPtr(malloc_map_size * sizeof(struct malloc_map_e)); if (malloc_map == NULL) panic("NewPtr(%lu) failed", MALLOC_MAP_CHUNK_SIZE); memset(malloc_map, 0, malloc_map_size); #endif } /* * Memory functions */ #define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) void * xmalloc(size_t size, char *note) { void *ptr; #ifdef MALLOC_DEBUG struct malloc_map_e *new_malloc_map; unsigned short n, j; #endif if (size == 0) panic("xmalloc: zero size"); ptr = NewPtr(size); if (ptr == NULL) panic("xmalloc(%lu) failed", size); #ifdef MALLOC_DEBUG if (malloc_map_compact) { for (n = 0; n < malloc_map_size; n++) { if (malloc_map[n].addr != 0) continue; for (j = n + 1; j < malloc_map_size; j++) { if (malloc_map[j].addr == 0) continue; malloc_map[n] = malloc_map[j]; memset(&malloc_map[j], 0, sizeof(struct malloc_map_e)); break; } } malloc_map_compact = false; } for (n = 0; n <= malloc_map_size; n++) { if (n == malloc_map_size) { malloc_map_size += MALLOC_MAP_CHUNK_SIZE; warn("xmalloc(%lu): out of malloc map entries, maybe a " "memory leak, resizing to %ld", size, malloc_map_size); new_malloc_map = (struct malloc_map_e *)NewPtr( malloc_map_size * sizeof(struct malloc_map_e)); if (new_malloc_map == NULL) panic("out of memory resizing malloc map"); memcpy(new_malloc_map, malloc_map, (malloc_map_size - MALLOC_MAP_CHUNK_SIZE) * sizeof(struct malloc_map_e)); DisposePtr(malloc_map); malloc_map = new_malloc_map; } if (malloc_map[n].addr == 0) { malloc_map[n].addr = (unsigned long)ptr; malloc_map[n].size = size; strlcpy(malloc_map[n].note, note, sizeof(malloc_map[n].note)); break; } n = n; } #endif return ptr; } void xfree(void *ptrptr) { unsigned long *addr = (unsigned long *)ptrptr; void *ptr = (void *)*addr; #ifdef MALLOC_DEBUG unsigned long n; for (n = 0; n <= malloc_map_size; n++) { if (n == malloc_map_size) panic("xfree(0x%lx): can't find in alloc map, likely " "double free()", *addr); if (malloc_map[n].addr == *addr) { malloc_map[n].addr = 0; malloc_map[n].size = 0; malloc_map[n].note[0] = '\0'; break; } } #endif DisposePtr(ptr); *addr = 0L; } void * xmalloczero(size_t size, char *note) { void *ptr; ptr = xmalloc(size, note); memset(ptr, 0, size); return ptr; } void * xcalloc(size_t nmemb, size_t size, char *note) { void *ptr; if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) panic("xcalloc(%lu, %lu) overflow", nmemb, size); ptr = xmalloczero(nmemb * size, note); if (ptr == NULL) panic("xcalloc(%lu, %lu) failed", nmemb, size); return ptr; } void * xrealloc(void *src, size_t size) { void *ptr, *tsrc; #ifdef MALLOC_DEBUG unsigned long n; #endif char note[MALLOC_NOTE_SIZE] = "realloc from null"; #ifdef MALLOC_DEBUG if (src != NULL) { for (n = 0; n <= malloc_map_size; n++) { if (n == malloc_map_size) { panic("xrealloc(%lu): can't find in alloc map, likely " "double free()", (unsigned long)src); return NULL; } if (malloc_map[n].addr == (unsigned long)src) { strlcpy(note, malloc_map[n].note, sizeof(note)); break; } } } #endif ptr = xmalloc(size, note); if (src != NULL) { memcpy(ptr, src, size); tsrc = src; xfree(&tsrc); } return ptr; } void * xreallocarray(void *optr, size_t nmemb, size_t size) { if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) panic("xreallocarray(%lu, %lu) failed", nmemb, size); return xrealloc(optr, size * nmemb); } char * xstrdup(const char *str, char *note) { char *cp; size_t len; len = strlen(str); cp = xmalloc(len + 1, note); strlcpy(cp, str, len + 1); return cp; } char * xstrndup(const char *str, size_t maxlen, char *note) { char *copy; const char *cp; size_t len; /* strnlen */ for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--) ; len = (size_t)(cp - str); copy = xmalloc(len + 1, note); (void)memcpy(copy, str, len); copy[len] = '\0'; return copy; } /* * String functions */ short getline(char *str, size_t len, char **ret) { short i; for (i = 0; i < len; i++) { if (str[i] == '\r' || i == len - 1) { if (*ret == NULL) *ret = xmalloc(i + 1, "getline"); memcpy(*ret, str, i + 1); (*ret)[i] = '\0'; return i + 1; } } return 0; } const char * ordinal(unsigned short n) { switch (n % 100) { case 11: case 12: case 13: return "th"; default: switch (n % 10) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; default: return "th"; } } } size_t rtrim(char *str, char *chars) { size_t len, rlen, n, j; rlen = len = strlen(str); for (n = len; n > 0; n--) { for (j = 0; chars[j] != '\0'; j++) { if (str[n - 1] == chars[j]) { rlen--; str[n - 1] = '\0'; goto next_in_str; } } break; next_in_str: continue; } return rlen; } long strpos_quoted(char *str, char c) { long pos; unsigned char quot = 0; for (pos = 0; str[pos] != '\0'; pos++) { if (quot) { if (str[pos] == '\\') { pos++; continue; } if (str[pos] == quot) quot = 0; continue; } else { if (str[pos] == '"') { quot = str[pos]; continue; } if (str[pos] == c) return pos; } } return -1; } char * OSTypeToString(OSType type) { static char ostype_s[5]; ostype_s[0] = (unsigned char)((type >> 24) & 0xff); ostype_s[1] = (unsigned char)((type >> 16) & 0xff); ostype_s[2] = (unsigned char)((type >> 8) & 0xff); ostype_s[3] = (unsigned char)(type & 0xff); ostype_s[4] = 0; return ostype_s; } /* * BSD err(3) and warn(3) functions */ void vwarn(short alert_func, const char *format, va_list ap) { Rect bounds; short hit, itype; WindowPtr win, dialog; Handle ihandle; Rect irect; GetPort(&win); vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); ParamText(CtoPstr(err_str), "\p", "\p", "\p"); center_in_screen(320, 110, false, &bounds); dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, (WindowPtr)-1L, false, 0, alert_ditl_h); GetDItem(dialog, ALERT_DITL_ICON, &itype, &ihandle, &irect); switch (alert_func) { case CAUTION_ALERT: ihandle = GetIcon(cautionIcon); break; case NOTE_ALERT: ihandle = GetIcon(noteIcon); break; case APPICON_ALERT: ihandle = GetResource('ICN#', APPICON_ICN_ID); if (ihandle) break; default: ihandle = GetIcon(stopIcon); } SetDItem(dialog, ALERT_DITL_ICON, itype, ihandle, &irect); ShowWindow(dialog); for (;;) { ModalDialog(0, &hit); if (hit == ok) break; } ReleaseResource(ihandle); DisposDialog(dialog); /* * Pre-allocate for the next one while we have memory. We can't use * xNewHandle because we might already be alerting about an OOM * condition. */ DisposHandle(alert_ditl_h); alert_ditl_h = NewHandle(sizeof(alert_ditl)); if (alert_ditl_h == NULL) ExitToShell(); HLock(alert_ditl_h); memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl)); HUnlock(alert_ditl_h); SetPort(win); } void panic(const char *format, ...) { va_list ap; va_start(ap, format); vwarn(STOP_ALERT, format, ap); va_end(ap); ExitToShell(); } void err(short ret, const char *format, ...) { va_list ap; va_start(ap, format); vwarn(STOP_ALERT, format, ap); va_end(ap); ExitToShell(); } void warn(const char *format, ...) { va_list ap; va_start(ap, format); vwarn(CAUTION_ALERT, format, ap); va_end(ap); } void warnx(const char *format, ...) { va_list ap; va_start(ap, format); vwarn(CAUTION_ALERT, format, ap); va_end(ap); } void note(const char *format, ...) { va_list ap; va_start(ap, format); vwarn(NOTE_ALERT, format, ap); va_end(ap); } void appicon_note(const char *format, ...) { va_list ap; va_start(ap, format); vwarn(APPICON_ALERT, format, ap); va_end(ap); } short ask(const char *format, ...) { Rect bounds; short hit; WindowPtr win, dialog; va_list ap; GetPort(&win); va_start(ap, format); vsnprintf(err_str, ERROR_STRING_SIZE, format, ap); va_end(ap); ParamText(CtoPstr(err_str), "\p", "\p", "\p"); ask_ditl_h = xNewHandle(sizeof(ask_ditl)); HLock(ask_ditl_h); memcpy(*ask_ditl_h, ask_ditl, sizeof(ask_ditl)); HUnlock(ask_ditl_h); center_in_screen(300, 100, false, &bounds); dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, (WindowPtr)-1L, false, 0, ask_ditl_h); ShowWindow(dialog); for (;;) { ModalDialog(0, &hit); if (hit == 1 || hit == 2) break; } DisposDialog(dialog); DisposHandle(ask_ditl_h); SetPort(win); return (hit == 1); } void about(char *program_name) { VersRecHndl vers; char vers_s[255]; char short_vers[255] = { 0 }; short vlen, n; if ((vers = (VersRecHndl)GetResource('vers', 1))) { /* * vers "long version string" is a pascal string after the * short version pascal string */ HLock(vers); vlen = (*vers)->shortVersion[0]; memcpy(short_vers, (*vers)->shortVersion + vlen + 1, sizeof((*vers)->shortVersion) - vlen - 1); PtoCstr(short_vers); snprintf(vers_s, sizeof(vers_s), "%s %s", program_name, short_vers); for (n = 0; n < sizeof(vers_s); n++) { if (vers_s[n] == '©') { vers_s[n - 1] = '\r'; break; } } ReleaseResource(vers); appicon_note("%s", vers_s); } else warnx("Can't find version number!"); } void progress(char *format, ...) { static Str255 progress_s; Handle thandle; va_list argptr; Rect bounds; Rect trect; short ttype; if (format == NULL) { if (progress_dialog != NULL) { DisposDialog(progress_dialog); DisposHandle(progress_ditl_h); progress_dialog = NULL; } return; } va_start(argptr, format); vsnprintf((char *)progress_s, 256, format, argptr); va_end(argptr); CtoPstr(progress_s); if (progress_dialog == NULL) { progress_ditl_h = xNewHandle(sizeof(progress_ditl)); HLock(progress_ditl_h); memcpy(*progress_ditl_h, progress_ditl, sizeof(progress_ditl)); HUnlock(progress_ditl_h); center_in_screen(330, 60, false, &bounds); progress_dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc, (WindowPtr)-1L, false, 0, progress_ditl_h); } GetDItem(progress_dialog, 1, &ttype, &thandle, &trect); SetIText(thandle, progress_s); ShowWindow(progress_dialog); DrawDialog(progress_dialog); } void window_rect(WindowPtr win, Rect *ret) { *ret = (*(((WindowPeek)win)->strucRgn))->rgnBBox; ret->left += 1; ret->right -= 2; ret->top -= 1; ret->bottom -= 1; } void center_in_screen(short width, short height, bool titlebar, Rect *b) { b->left = ((screenBits.bounds.right - screenBits.bounds.left) / 2) - (width / 2); b->top = ((screenBits.bounds.bottom - screenBits.bounds.top) / 2) - (height / 2) + (titlebar ? GetMBarHeight() : 0); b->right = b->left + width; b->bottom = b->top + height; } Point centered_sfget_dialog(void) { Point p; Rect r; center_in_screen(364, 216, false, &r); p.h = r.left; p.v = r.top; return p; } Point centered_sfput_dialog(void) { Point p; Rect r; center_in_screen(320, 200, false, &r); p.h = r.left; p.v = r.top; return p; } /* * General Mac-specific non-GUI functions */ static unsigned long _xorshift_state = 0; unsigned long xorshift32(void) { unsigned long x = _xorshift_state; if (x == 0) x = Ticks; x ^= x << 13; x ^= x >> 17; x ^= x << 5; return _xorshift_state = x; } /* * Error checking wrappers for Mac toolkit functions */ Handle xNewHandle(size_t size) { Handle h; if (size == 0) panic("Zero xNewHandle size"); h = NewHandle(size); if (h == NULL) panic("Failed to NewHandle(%lu)", size); return h; } Handle xGetResource(ResType type, short id) { Handle h; h = GetResource(type, id); if (h == NULL) panic("Failed to find resource %d", id); return h; } StringHandle xGetString(short id) { StringHandle h; h = GetString(id); if (h == NULL) panic("Failed to find STR resource %d", id); return h; } char * xGetStringAsChar(short id) { StringHandle h; char *out; size_t l; h = xGetString(id); HLock(h); l = (*h)[0]; out = xmalloc(l + 1, "xGetStringAsChar"); memcpy((void *)out, (void *)(*h + 1), l); out[l] = '\0'; ReleaseResource(h); return out; } long xGetStringAsLong(short id) { char *c; long r; c = xGetStringAsChar(id); r = atol(c); xfree(&c); return r; } void xSetHandleSize(Handle h, Size s) { SetHandleSize(h, s); if (MemError()) panic("Failed to SetHandleSize to %ld", s); } /* * Filesystem utilities */ short getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file) { WDPBRec wdir; HVolumeParam wvol; DirInfo wcinfo; char *name = NULL; size_t retlen = 0, len; char *tmpret = NULL, *tmp = NULL; char *lastcolon; if (strchr((char *)fileName + 1, ':') != NULL) { /* already a full path */ memcpy(*ret, fileName, 256); if (!include_file) { PtoCstr(*ret); lastcolon = strrchr((char *)*ret, ':'); lastcolon[0] = '\0'; CtoPstr(*ret); } return 0; } name = xmalloc(FILENAME_MAX, "getpath"); wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum; wdir.ioWDIndex = 0; wdir.ioWDProcID = 0; wdir.ioNamePtr = (StringPtr)name; if (PBGetWDInfo(&wdir, 0) != noErr) { warn("Failed looking up directory"); xfree(&name); return 1; } wvol.ioNamePtr = (StringPtr)name; wvol.ioVRefNum = vRefNum; wvol.ioVolIndex = 0; if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) { warn("Failed getting volume info"); xfree(&name); return 1; } if (wvol.ioVSigWord != 0x4244) { warn("Unknown filesystem type 0x%x", wvol.ioVSigWord); xfree(&name); return 1; } wcinfo.ioVRefNum = vRefNum; wcinfo.ioNamePtr = (StringPtr)name; wcinfo.ioFDirIndex = -1; wcinfo.ioDrParID = wdir.ioWDDirID; wcinfo.ioDrDirID = wdir.ioWDDirID; tmp = xmalloc(FILENAME_MAX, "getpath"); tmpret = xmalloc(FILENAME_MAX, "getpath"); /* go backwards, prepending each folder's parent */ while (wcinfo.ioDrParID != 1) { wcinfo.ioDrDirID = wcinfo.ioDrParID; /* .. */ if (PBGetCatInfo((CInfoPBPtr)&wcinfo, 0) != noErr) break; len = name[0]; PtoCstr(name); if (retlen == 0) { retlen = len; strlcpy(tmpret, (char *)name, FILENAME_MAX); } else { strlcpy(tmp, tmpret, FILENAME_MAX); snprintf(tmpret, FILENAME_MAX, "%s:%s", name, tmp); } } if (include_file) { /* append the original path */ memcpy(name, fileName, FILENAME_MAX); PtoCstr(name); if (retlen == 0) strlcpy(tmpret, name, FILENAME_MAX); else { strlcat(tmpret, ":", FILENAME_MAX); strlcat(tmpret, name, FILENAME_MAX); } } else if (retlen == 0) { (*ret)[0] = 0; xfree(&tmp); xfree(&tmpret); xfree(&name); return 0; } CtoPstr(tmpret); memcpy(*ret, tmpret, FILENAME_MAX); xfree(&tmp); xfree(&tmpret); xfree(&name); return 0; } short stat(char *path, struct stat *sb) { char *ppath; short ret; ppath = xstrdup(path, "stat"); CtoPstr(ppath); ret = FStat((unsigned char *)ppath, sb); xfree(&ppath); return ret; } short FStat(Str255 path, struct stat *sb) { CInfoPBRec catblock = { 0 }; short ret; catblock.hFileInfo.ioNamePtr = path; ret = PBGetCatInfo(&catblock, 0); if (ret != noErr) return -1; /* data fork logical length + resource fork logical length */ sb->st_size = catblock.hFileInfo.ioFlLgLen + catblock.hFileInfo.ioFlRLgLen; sb->st_ctime = catblock.hFileInfo.ioFlCrDat; sb->st_mtime = catblock.hFileInfo.ioFlMdDat; sb->st_flags = catblock.hFileInfo.ioFlAttrib; return 0; } bool FIsDir(Str255 path) { struct stat st; short ret; if (FStat(path, &st) != 0) return false; /* bit 4 is set in ioFlAttrib if the item is a directory */ if (st.st_flags & (1 << 4)) return true; return false; } OSErr copy_file(Str255 source, Str255 dest, bool overwrite) { FInfo fi; IOParam pb; short error, source_ref, dest_ref; /* copy data fork */ error = GetFInfo(source, 0, &fi); if (error) return error; error = Create(dest, 0, fi.fdCreator, fi.fdType); if (error == dupFNErr && overwrite) { error = FSDelete(dest, 0); if (error) return error; error = Create(dest, 0, fi.fdCreator, fi.fdType); } if (error) return error; memset(&pb, 0, sizeof(pb)); pb.ioNamePtr = source; pb.ioPermssn = fsRdPerm; error = PBOpen(&pb, false); if (error) return error; source_ref = pb.ioRefNum; error = FSOpen(dest, 0, &dest_ref); if (error) { FSClose(source_ref); return error; } error = copy_file_contents(source_ref, dest_ref); FSClose(source_ref); FSClose(dest_ref); if (error) return error; /* * Copy resource fork, open source as shared read/write in case it's * an open resource file. */ source_ref = OpenRFPerm(source, 0, fsRdWrShPerm); if (source_ref == -1 && ResError() == eofErr) { /* no resource fork */ FSClose(source_ref); FSClose(dest_ref); return 0; } if (source_ref == -1) return ResError(); CreateResFile(dest); if (ResError()) { FSClose(source_ref); return ResError(); } error = OpenRF(dest, 0, &dest_ref); if (error) { FSClose(source_ref); return error; } error = copy_file_contents(source_ref, dest_ref); FSClose(source_ref); FSClose(dest_ref); return error; } OSErr copy_file_contents(short source_ref, short dest_ref) { char *buf; short error; long source_size, count; GetEOF(source_ref, &source_size); error = SetFPos(source_ref, fsFromStart, 0); if (error) return error; error = SetEOF(dest_ref, source_size); if (error) return error; error = SetFPos(dest_ref, fsFromStart, 0); if (error) return error; buf = xmalloc(1024, "copy_file_contents"); while (source_size > 0) { count = 1024; if (count > source_size) count = source_size; error = FSRead(source_ref, &count, buf); if (error && error != eofErr) break; source_size -= count; error = FSWrite(dest_ref, &count, buf); if (error && error != eofErr) break; } xfree(&buf); if (error && error != eofErr) return error; return 0; } /* read a \r-terminated line or the final non-line bytes of an open file */ OSErr FSReadLine(short frefnum, char *buf, size_t buflen) { char tbuf; size_t pos, fsize, rlen = 1, total_read = 0; short error; GetFPos(frefnum, &pos); GetEOF(frefnum, &fsize); for (; pos <= fsize; pos++) { if (total_read > buflen) return -1; error = FSRead(frefnum, &rlen, &tbuf); if (error) return -1; if (tbuf == '\r') return total_read; buf[total_read++] = tbuf; } /* nothing found until the end of the file */ return total_read; } /* * Gestalt functions */ char * gestalt_machine_type(void) { short error; long resp; error = Gestalt(gestaltMachineType, &resp); if (error) return NULL; switch (resp) { case gestaltClassic: return "Macintosh 128K"; case gestaltMacXL: return "Macintosh XL"; case gestaltMac512KE: return "Macintosh 512Ke"; case gestaltMacPlus: return "Macintosh Plus"; case gestaltMacSE: return "Macintosh SE"; case gestaltMacII: return "Macintosh II"; case gestaltMacIIx: return "Macintosh IIx"; case gestaltMacIIcx: return "Macintosh IIcx"; case gestaltMacSE030: return "Macintosh SE/30"; case gestaltPortable: return "Macintosh Portable"; case gestaltMacIIci: return "Macintosh IIci"; case gestaltMacIIfx: return "Macintosh IIfx"; case gestaltMacClassic: return "Macintosh Classic"; case gestaltMacIIsi: return "Macintosh IIsi"; case gestaltMacLC: return "Macintosh LC"; } return NULL; } char * get_version(bool long_version) { static char vers_s[256] = { 0 }; char *v; VersRecHndl vers; short len; vers = (VersRecHndl)GetResource('vers', 1); if (!vers) return "?.?"; HLock(vers); v = (char *)&(*vers)->shortVersion; len = v[0]; if (long_version) { v += len + 1; len = v[0]; } memcpy(vers_s, v, len + 1); ReleaseResource(vers); PtoCstr(vers_s); return vers_s; } /* * General Mac-specific GUI functions */ short FontHeight(short font_id, short size) { FMInput f_in; FMOutput *f_out; if (font_id == -1) font_id = thePort->txFont; if (size == -1) size = thePort->txSize; f_in.family = font_id; f_in.size = size; f_in.face = 0; f_in.needBits = false; f_in.device = 0; f_in.numer.h = f_in.numer.v = 1; f_in.denom.h = f_in.denom.v = 1; f_out = FMSwapFont(&f_in); return (((f_out->leading + f_out->ascent + f_out->descent) * f_out->numer.v) / f_out->denom.v); } void DrawGrowIconOnly(WindowPtr win) { Rect r; RgnHandle tmp; GetClip(tmp = NewRgn()); r = win->portRect; r.top = r.bottom - SCROLLBAR_WIDTH; r.left = r.right - SCROLLBAR_WIDTH + 1; ClipRect(&r); DrawGrowIcon(win); SetClip(tmp); DisposeRgn(tmp); } /* assumes a fixed-width style */ short TEGetWidth(short off, TEHandle te) { TextStyle style; short fheight, fwidth, ascent, old_font, old_size; TEGetStyle(off, &style, &fheight, &ascent, te); old_font = thePort->txFace; old_size = thePort->txSize; thePort->txFace = style.tsFont; thePort->txSize = style.tsSize; fwidth = CharWidth('m'); thePort->txFace = old_font; thePort->txSize = old_size; return fwidth; } #define ceildiv(a,b) ((a / b) + (!!(a % b))) void UpdateScrollbarForTE(GrafPtr win, ControlHandle control, TEHandle te, bool reset) { size_t vlines, telines; TERec *ter; RgnHandle rgn; short fheight, fwidth, max, val, per_line, horiz, max_chars, n; HLock(control); HLock(te); ter = *te; horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < ((*control)->contrlRect.right - (*control)->contrlRect.left)); if (horiz) { fwidth = TEGetWidth(0, te); per_line = ((ter->viewRect.right - ter->viewRect.left) / fwidth) - 1; for (max_chars = 0, n = 1; n < ter->nLines; n++) { if (ter->lineStarts[n] - ter->lineStarts[n - 1] > max_chars) max_chars = ter->lineStarts[n] - ter->lineStarts[n - 1]; } if (max_chars <= per_line) /* don't enable the scrollbar */ max = 1; else max = max_chars - per_line + 1; if (reset) { val = 1; TESetSelect(0, 0, te); } else { val = (ter->viewRect.left - ter->destRect.left); if (val < 0) val = 1; else { val = ceildiv(val, fwidth) + 1; if (val > max) max = val; } } } else { fheight = TEGetHeight(0, 0, te); vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight; telines = ter->nLines; /* telines is inaccurate if the last line doesn't have any chars */ //if (telines >= vlines) // telines++; max = telines - vlines + 1; if (max < 1) max = 1; if (reset) { val = 1; TESetSelect(0, 0, te); } else { val = (ter->viewRect.top - ter->destRect.top); if (val < 0) val = 1; else { val = ceildiv(val, fheight) + 1; if (val > max) max = val; } } } /* * Avoid SetCtlMax because it will redraw and then SetCtlValue will * redraw again, which can cause a jump if we're trying to keep the * scrollbar position in the same place (like at the bottom). */ (*control)->contrlMax = max; SetCtlValue(control, val); rgn = NewRgn(); RectRgn(rgn, &(*control)->contrlRect); UpdtControl(win, rgn); CloseRgn(rgn); HUnlock(te); HUnlock(control); } void SetTrackControlTE(TEHandle te) { track_control_te = te; } pascal void TrackMouseDownInControl(ControlHandle control, short part) { TERec *ter; short page, val, adj, fheight, fwidth, horiz, per_line; if (track_control_te == NULL) panic("TrackMouseDownInControl without SetTrackControlTE"); HLock(track_control_te); ter = *track_control_te; horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) < ((*control)->contrlRect.right - (*control)->contrlRect.left)); if (horiz) { fwidth = TEGetWidth(0, track_control_te); per_line = ((ter->viewRect.right - ter->viewRect.left) / fwidth) - 1; page = ceildiv(GetCtlMax(control) + per_line, ((per_line * 75) / 100)) + 1; } else { /* keep 1 line of context between pages */ fheight = TEGetHeight(0, 0, track_control_te); page = ((ter->viewRect.bottom - ter->viewRect.top) / fheight) - 1; } adj = 0; switch (part) { case inUpButton: adj = -1; break; case inPageUp: adj = -page; break; case inDownButton: adj = 1; break; case inPageDown: adj = page; break; } val = GetCtlValue(control); if (val + adj < GetCtlMin(control)) adj = -(val - GetCtlMin(control)); if (val + adj > GetCtlMax(control)) adj = (GetCtlMax(control) - val); if (adj == 0) return; if (horiz) TEScroll(-adj * fwidth, 0, track_control_te); else TEScroll(0, -adj * fheight, track_control_te); SetCtlValue(control, val + adj); HUnlock(track_control_te); } pascal bool ModalDialogFilter(DialogPtr dlg, EventRecord *event, short *hit) { WindowPtr event_win; short event_in; char key; switch (event->what) { case mouseDown: event_in = FindWindow(event->where, &event_win); switch (event_in) { case inGoAway: if (TrackGoAway(dlg, event->where)) { *hit = -1; return true; } } break; case keyDown: key = event->message & charCodeMask; if (event->modifiers & cmdKey) { switch (key) { case 'x': ZeroScrap(); DlgCut(dlg); break; case 'c': ZeroScrap(); DlgCopy(dlg); break; case 'v': if (TEFromScrap() == noErr) DlgPaste(dlg); break; } event->what = nullEvent; return false; } else if (key == 13 || key == 3) { /* OK button */ *hit = OK; return true; } break; } return false; } static short _password_dialog_ditl_id = -1; static char *_password_dialog_storage = NULL; static size_t _password_dialog_storage_len = 0; void PasswordDialogFieldFilterSetup(short ditl_id, char *storage, size_t len) { _password_dialog_ditl_id = ditl_id; _password_dialog_storage = storage; _password_dialog_storage_len = len; memset(_password_dialog_storage, 0, len); } pascal bool PasswordDialogFieldFilter(DialogPtr dlg, EventRecord *event, short *hit) { DialogPeek dlgp; short sel_start, sel_end; char key; dlgp = (DialogPeek)dlg; if (dlgp->editField == _password_dialog_ditl_id - 1) { sel_start = (*(dlgp->textH))->selStart; sel_end = (*(dlgp->textH))->selEnd; switch (event->what) { case keyDown: case autoKey: key = event->message & charCodeMask; if (event->modifiers & cmdKey) { /* TODO: implement DlgPaste for cmd+v? */ event->what = nullEvent; return false; } if (key == 8) { /* backspace */ if (sel_start == sel_end && sel_start > 0) memmove(_password_dialog_storage + sel_start - 1, _password_dialog_storage + sel_start, _password_dialog_storage_len - sel_start - 1); else if (sel_start != sel_end) memmove(_password_dialog_storage + sel_start, _password_dialog_storage + sel_end, _password_dialog_storage_len - sel_end - 1); } else if (sel_start >= _password_dialog_storage_len) { event->what = nullEvent; return false; } else if (key >= ' ' && key <= '~') { if (sel_start != sel_end) /* delete selection before making space for new char */ memmove(_password_dialog_storage + sel_start, _password_dialog_storage + sel_end, _password_dialog_storage_len - sel_end - 1); memmove(_password_dialog_storage + sel_start + 1, _password_dialog_storage + sel_start, _password_dialog_storage_len - sel_start - 1); _password_dialog_storage[sel_start] = key; event->message = '¥'; } _password_dialog_storage[(*(dlgp->textH))->teLength + 1] = '\0'; sel_start = 0; break; } } return ModalDialogFilter(dlg, event, hit); } void PasswordDialogFieldFinish(void) { _password_dialog_ditl_id = -1; _password_dialog_storage = NULL; _password_dialog_storage_len = 0; } /* (*(some_te))->caretHook = NullCaretHook; */ pascal void NullCaretHook(void) { asm { move.l (a7)+,d0 rts } } static bool menu_bar_hidden = false; static short old_mbar_height; static Rect mbar_rect; static RgnHandle mbar_save_region; void HideMenuBar(void) { RgnHandle mbar_region; WindowPeek win; old_mbar_height = GetMBarHeight(); SetRect(&mbar_rect, screenBits.bounds.left, screenBits.bounds.top, screenBits.bounds.right, screenBits.bounds.top + old_mbar_height); mbar_save_region = NewRgn(); mbar_region = NewRgn(); MBarHeight = 0; CopyRgn(GetGrayRgn(), mbar_save_region); RectRgn(mbar_region, &mbar_rect); UnionRgn(GetGrayRgn(), mbar_region, GetGrayRgn()); win = (WindowPeek)FrontWindow(); PaintOne(win, mbar_region); PaintBehind(win, mbar_region); CalcVis(win); CalcVisBehind(win, mbar_region); DisposeRgn(mbar_region); menu_bar_hidden = true; } void RestoreHiddenMenuBar(void) { WindowPeek win; if (!menu_bar_hidden) return; CopyRgn(mbar_save_region, GetGrayRgn()); MBarHeight = old_mbar_height; RectRgn(mbar_save_region, &mbar_rect); win = (WindowPeek)FrontWindow(); CalcVis(win); CalcVisBehind(win, mbar_save_region); DisposeRgn(mbar_save_region); menu_bar_hidden = false; HiliteMenu(0); DrawMenuBar(); } /* C Extensions */ /* * Appends src to string dst of size dsize (unlike strncat, dsize is the * full size of dst, not space left). At most dsize-1 characters * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). * Returns strlen(src) + MIN(dsize, strlen(initial dst)). * If retval >= dsize, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t dsize) { const char *odst = dst; const char *osrc = src; size_t n = dsize; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end. */ while (n-- != 0 && *dst != '\0') dst++; dlen = dst - odst; n = dsize - dlen; if (n-- == 0) return(dlen + strlen(src)); while (*src != '\0') { if (n != 0) { *dst++ = *src; n--; } src++; } *dst = '\0'; return(dlen + (src - osrc)); /* count does not include NUL */ } /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if ((*dst++ = *src++) == '\0') break; } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */ while (*src++) ; } return(src - osrc - 1); /* count does not include NUL */ } char * strndup(const char *str, size_t maxlen) { char *copy; const char *cp; size_t len; /* strnlen */ for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--) ; len = (size_t)(cp - str); copy = malloc(len + 1); if (copy != NULL) { (void)memcpy(copy, str, len); copy[len] = '\0'; } return copy; } /* * Get next token from string *stringp, where tokens are possibly-empty * strings separated by characters from delim. * * Writes NULs into the string at *stringp to end tokens. * delim need not remain constant from call to call. * On return, *stringp points past the last NUL written (if there might * be further tokens), or is NULL (if there are definitely no more tokens). * * If *stringp is NULL, strsep returns NULL. */ char * strsep(char **stringp, const char *delim) { char *s; const char *spanp; int c, sc; char *tok; if ((s = *stringp) == NULL) return (NULL); for (tok = s;;) { c = *s++; spanp = delim; do { if ((sc = *spanp++) == c) { if (c == 0) s = NULL; else s[-1] = 0; *stringp = s; return (tok); } } while (sc != 0); } return (NULL); } static struct format { unsigned leftJustify : 1; unsigned forceSign : 1; unsigned altForm : 1; unsigned zeroPad : 1; unsigned havePrecision : 1; unsigned hSize : 1; unsigned lSize : 1; unsigned LSize : 1; char sign; char exponent; int fieldWidth; int precision; } default_format; int bounded_vfprintf(FILE *fp, const char *fmt, va_list arg); static int nullio(FILE *fp, int i); int bounded_vfprintf(FILE *fp, const char *fmt, va_list arg) { register int c, i, j, nwritten = 0; register unsigned long n; long double x; register char *s; #define VFPRINTF_BUFLEN 512 char buf[VFPRINTF_BUFLEN], *digits, *t; struct format F; for (c = *fmt; c; c = *++fmt) { if (c != '%') goto copy1; F = default_format; /* decode flags */ for (;;) { c = *++fmt; if (c == '-') F.leftJustify = TRUE; else if (c == '+') F.forceSign = TRUE; else if (c == ' ') F.sign = ' '; else if (c == '#') F.altForm = TRUE; else if (c == '0') F.zeroPad = TRUE; else break; } /* decode field width */ if (c == '*') { if ((F.fieldWidth = va_arg(arg, int)) < 0) { F.leftJustify = TRUE; F.fieldWidth = -F.fieldWidth; } c = *++fmt; } else { for (; c >= '0' && c <= '9'; c = *++fmt) F.fieldWidth = (10 * F.fieldWidth) + (c - '0'); } /* decode precision */ if (c == '.') { if ((c = *++fmt) == '*') { F.precision = va_arg(arg, int); c = *++fmt; } else { for (; c >= '0' && c <= '9'; c = *++fmt) F.precision = (10 * F.precision) + (c - '0'); } if (F.precision >= 0) F.havePrecision = TRUE; } /* perform appropriate conversion */ s = &buf[VFPRINTF_BUFLEN]; if (F.leftJustify) F.zeroPad = FALSE; conv: switch (c) { /* 'h' size modifier */ case 'h': F.hSize = TRUE; c = *++fmt; goto conv; /* 'l' size modifier */ case 'l': F.lSize = TRUE; c = *++fmt; goto conv; /* 'L' size modifier */ case 'L': F.LSize = TRUE; c = *++fmt; goto conv; /* decimal (signed) */ case 'd': case 'i': if (F.lSize) n = va_arg(arg, long); else n = va_arg(arg, int); if (F.hSize) n = (short) n; if ((long) n < 0) { n = -n; F.sign = '-'; } else if (F.forceSign) F.sign = '+'; goto decimal; /* decimal (unsigned) */ case 'u': if (F.lSize) n = va_arg(arg, unsigned long); else n = va_arg(arg, unsigned int); if (F.hSize) n = (unsigned short) n; F.sign = 0; goto decimal; /* decimal (common code) */ decimal: if (!F.havePrecision) { if (F.zeroPad) { F.precision = F.fieldWidth; if (F.sign) --F.precision; } if (F.precision < 1) F.precision = 1; } for (i = 0; n; n /= 10, i++) *--s = n % 10 + '0'; for (; i < F.precision; i++) *--s = '0'; if (F.sign) { *--s = F.sign; i++; } break; /* octal (unsigned) */ case 'o': if (F.lSize) n = va_arg(arg, unsigned long); else n = va_arg(arg, unsigned int); if (F.hSize) n = (unsigned short) n; if (!F.havePrecision) { if (F.zeroPad) F.precision = F.fieldWidth; if (F.precision < 1) F.precision = 1; } for (i = 0; n; n /= 8, i++) *--s = n % 8 + '0'; if (F.altForm && i && *s != '0') { *--s = '0'; i++; } for (; i < F.precision; i++) *--s = '0'; break; /* hexadecimal (unsigned) */ case 'p': F.havePrecision = F.lSize = TRUE; F.precision = 8; /* ... */ case 'X': digits = "0123456789ABCDEF"; goto hexadecimal; case 'x': digits = "0123456789abcdef"; /* ... */ hexadecimal: if (F.lSize) n = va_arg(arg, unsigned long); else n = va_arg(arg, unsigned int); if (F.hSize) n = (unsigned short) n; if (!F.havePrecision) { if (F.zeroPad) { F.precision = F.fieldWidth; if (F.altForm) F.precision -= 2; } if (F.precision < 1) F.precision = 1; } for (i = 0; n; n /= 16, i++) *--s = digits[n % 16]; for (; i < F.precision; i++) *--s = '0'; if (F.altForm) { *--s = c; *--s = '0'; i += 2; } break; /* character */ case 'c': *--s = va_arg(arg, int); i = 1; break; /* string */ case 's': s = va_arg(arg, char *); if (F.altForm) { i = (unsigned char) *s++; if (F.havePrecision && i > F.precision) i = F.precision; } else { if (!F.havePrecision) i = strlen(s); else if (t = memchr(s, '\0', F.precision)) i = t - s; else i = F.precision; } break; /* store # bytes written so far */ case 'n': s = va_arg(arg, void *); if (F.hSize) * (short *) s = nwritten; else if (F.lSize) * (long *) s = nwritten; else * (int *) s = nwritten; continue; /* oops - unknown conversion, abort */ default: if (c != '%') goto done; copy1: putc(c, fp); /* disregard EOF */ ++nwritten; continue; } /* pad on the left */ if (i < F.fieldWidth && !F.leftJustify) { do { putc(' ', fp); /* disregard EOF */ ++nwritten; } while (i < --F.fieldWidth); } /* write the converted result */ fwrite(s, 1, i, fp); /* disregard EOF */ nwritten += i; /* pad on the right */ for (; i < F.fieldWidth; i++) { putc(' ', fp); /* disregard EOF */ ++nwritten; } } /* all done! */ done: return(nwritten); } int snprintf(char *s, size_t size, const char *fmt, ...) { return(vsnprintf(s, size, fmt, __va(fmt))); } static int nullio(FILE *fp, int i) { return(EOF); } int vsnprintf(char *s, size_t size, const char *fmt, void *p) { FILE f; int n; memset(&f, 0, sizeof(f)); f.refnum = -1; f.ptr = (unsigned char *) s; f.cnt = size; f.proc = nullio; f.dirty = 1; if ((n = bounded_vfprintf(&f, fmt, p)) >= 0) s[n] = 0; return(n); }