| | 1 | /* This file implements some of the functions described in |
| | 2 | * tads2/osifc.h. Only functions needed by the TADS 3 compiler are |
| | 3 | * implemented here. |
| | 4 | * |
| | 5 | * The functions are "portable"; they don't make use of curses/ncurses. |
| | 6 | */ |
| | 7 | #include "common.h" |
| | 8 | |
| | 9 | #include <stdio.h> |
| | 10 | #include <string.h> |
| | 11 | extern "C" { |
| | 12 | #include <sys/stat.h> |
| | 13 | #include <dirent.h> |
| | 14 | } |
| | 15 | |
| | 16 | #ifdef HAVE_GLOB_H |
| | 17 | #include <glob.h> |
| | 18 | #endif |
| | 19 | |
| | 20 | #include "os.h" |
| | 21 | |
| | 22 | |
| | 23 | /* Set the game title. |
| | 24 | * |
| | 25 | * Does nothing in the compiler. This should actually be implemented in |
| | 26 | * tads3/os_stdio.cpp, but for some reason it's not. |
| | 27 | */ |
| | 28 | void |
| | 29 | os_set_title( const char* ) |
| | 30 | { |
| | 31 | } |
| | 32 | |
| | 33 | |
| | 34 | /* Get the modification time for a file. |
| | 35 | */ |
| | 36 | int |
| | 37 | os_get_file_mod_time( os_file_time_t* t, const char* fname ) |
| | 38 | { |
| | 39 | struct stat inf; |
| | 40 | |
| | 41 | if (stat(fname, &inf) != 0) return 1; |
| | 42 | t->t = inf.st_mtime; |
| | 43 | return 0; |
| | 44 | } |
| | 45 | |
| | 46 | |
| | 47 | /* Compare two file time values. |
| | 48 | */ |
| | 49 | int |
| | 50 | os_cmp_file_times( const os_file_time_t* a, const os_file_time_t* b ) |
| | 51 | { |
| | 52 | if (a->t < b->t) return -1; |
| | 53 | if (a->t == b->t) return 0; |
| | 54 | return 1; |
| | 55 | } |
| | 56 | |
| | 57 | |
| | 58 | /* Install/uninstall the break handler. |
| | 59 | * |
| | 60 | * I don't think we need a break handler. |
| | 61 | */ |
| | 62 | void |
| | 63 | os_instbrk( int ) |
| | 64 | { |
| | 65 | } |
| | 66 | |
| | 67 | |
| | 68 | /* We implement the file searching routines using <glob.h>. It's not |
| | 69 | * available on all systems though, so we provide dummy fallbacks that |
| | 70 | * do nothing if <glob.h> doesn't exist. |
| | 71 | */ |
| | 72 | #ifdef HAVE_GLOB |
| | 73 | /* Search context structure. |
| | 74 | */ |
| | 75 | struct oss_find_ctx_t |
| | 76 | { |
| | 77 | glob_t* pglob; |
| | 78 | // Index of current filename in pglob->gl_pathv. |
| | 79 | size_t current; |
| | 80 | // Original search path prefix (we'll allocate more to fit the |
| | 81 | // string). |
| | 82 | char path[1]; |
| | 83 | }; |
| | 84 | |
| | 85 | |
| | 86 | /* Service routine for searching - build the full output path name. |
| | 87 | */ |
| | 88 | static void |
| | 89 | oss_build_outpathbuf( char* outpathbuf, size_t outpathbufsiz, const char* path, |
| | 90 | const char* fname ) |
| | 91 | { |
| | 92 | // If there's a full path buffer, build the full path. |
| | 93 | if (outpathbuf != 0) { |
| | 94 | size_t lp; |
| | 95 | size_t lf; |
| | 96 | |
| | 97 | // Copy the path prefix. |
| | 98 | lp = strlen(path); |
| | 99 | if (lp > outpathbufsiz - 1) { |
| | 100 | lp = outpathbufsiz - 1; |
| | 101 | } |
| | 102 | memcpy(outpathbuf, path, lp); |
| | 103 | |
| | 104 | // Add the filename if there's any room. |
| | 105 | lf = strlen(fname); |
| | 106 | if (lf > outpathbufsiz - lp - 1) { |
| | 107 | lf = outpathbufsiz - lp - 1; |
| | 108 | } |
| | 109 | memcpy(outpathbuf + lp, fname, lf); |
| | 110 | |
| | 111 | // Null-terminate the result. |
| | 112 | outpathbuf[lp + lf] = '\0'; |
| | 113 | } |
| | 114 | } |
| | 115 | |
| | 116 | |
| | 117 | /* Find the first file matching a given pattern. |
| | 118 | */ |
| | 119 | void* |
| | 120 | os_find_first_file( const char* dir, const char* pattern, char* outbuf, size_t outbufsiz, |
| | 121 | int* isdir, char* outpathbuf, size_t outpathbufsiz) |
| | 122 | { |
| | 123 | char realpat[OSFNMAX]; |
| | 124 | size_t l; |
| | 125 | size_t path_len; |
| | 126 | const char* lastsep; |
| | 127 | const char* p; |
| | 128 | struct oss_find_ctx_t* ctx = 0; |
| | 129 | |
| | 130 | strcpy(realpat, dir); |
| | 131 | if ((l = strlen(realpat)) != 0 && realpat[l - 1] != '/') { |
| | 132 | realpat[l++] = '/'; |
| | 133 | } |
| | 134 | |
| | 135 | if (pattern == 0) { |
| | 136 | strcpy(realpat + l, "*"); |
| | 137 | } else { |
| | 138 | strcpy(realpat + l, pattern); |
| | 139 | } |
| | 140 | |
| | 141 | // Find the last separator in the original path. |
| | 142 | for (p = realpat, lastsep = 0 ; *p != '\0' ; ++p) { |
| | 143 | // If this is a separator, remember it. |
| | 144 | if (*p == '/') lastsep = p; |
| | 145 | } |
| | 146 | |
| | 147 | // If we found a separator, the path prefix is everything up to |
| | 148 | // and including the separator; otherwise, there's no path |
| | 149 | // prefix. |
| | 150 | if (lastsep != 0) { |
| | 151 | // The length of the path includes the separator. |
| | 152 | path_len = lastsep + 1 - realpat; |
| | 153 | } else { |
| | 154 | // There's no path prefix - everything is in the |
| | 155 | // current directory. |
| | 156 | path_len = 0; |
| | 157 | } |
| | 158 | |
| | 159 | // Allocate a context. |
| | 160 | ctx = static_cast(struct oss_find_ctx_t*)(malloc(sizeof(struct oss_find_ctx_t) + path_len)); |
| | 161 | ctx->pglob = static_cast(glob_t *)(malloc(sizeof(glob_t))); |
| | 162 | ctx->current = -1; |
| | 163 | |
| | 164 | // Copy the path to the context. we may need it later to build |
| | 165 | // a full path. |
| | 166 | memcpy(ctx->path, realpat, path_len); |
| | 167 | ctx->path[path_len] = '\0'; |
| | 168 | |
| | 169 | // Problem: before calling glob() we need to escape all [] in |
| | 170 | // realpat. Count them first. |
| | 171 | int nbracket = 0; |
| | 172 | for (p = realpat; *p != '\0' ; ++p) { |
| | 173 | if (*p == '[' || *p == ']') ++nbracket; |
| | 174 | } |
| | 175 | |
| | 176 | // Allocate string for escaped pattern. |
| | 177 | char* escaped_realpat = static_cast(char*)(malloc(strlen(realpat) + nbracket + 1)); |
| | 178 | |
| | 179 | // Copy pattern to new string, escaping all [ and ] with \. |
| | 180 | char* c = realpat; |
| | 181 | char* k = escaped_realpat; |
| | 182 | while (*c != '\0') { |
| | 183 | if (*c == '[' || *c == ']') { |
| | 184 | *k++ = '\\'; |
| | 185 | } |
| | 186 | *k++ = *c++; |
| | 187 | } |
| | 188 | *k++ = '\0'; |
| | 189 | |
| | 190 | int res = glob(escaped_realpat, GLOB_ERR | GLOB_MARK, NULL, ctx->pglob); |
| | 191 | if (res != 0) { |
| | 192 | // We don't really care what kind of error occured. |
| | 193 | // Just free the resources and return error. Note that |
| | 194 | // "no files found" is an error. |
| | 195 | if (ctx->pglob != 0) { |
| | 196 | globfree(ctx->pglob); |
| | 197 | free(ctx->pglob); |
| | 198 | } |
| | 199 | if (ctx != 0) { |
| | 200 | free(ctx); |
| | 201 | } |
| | 202 | free(escaped_realpat); |
| | 203 | *isdir = 0; |
| | 204 | return 0; |
| | 205 | } |
| | 206 | |
| | 207 | // If we get there, there was a match. set current index for |
| | 208 | // os_find_next(). |
| | 209 | ctx->current = 0; |
| | 210 | |
| | 211 | // glob() returns filename with directory, but we need just the |
| | 212 | // basename. Copy first filename into the caller's buffer. |
| | 213 | char* ptr = &ctx->pglob->gl_pathv[0][path_len]; |
| | 214 | l = strlen(ptr); |
| | 215 | |
| | 216 | if (l > outbufsiz - 1) { |
| | 217 | l = outbufsiz - 1; |
| | 218 | } |
| | 219 | memcpy(outbuf, ptr, l); |
| | 220 | outbuf[l] = '\0'; |
| | 221 | |
| | 222 | // Build the full path, if desired. |
| | 223 | oss_build_outpathbuf(outpathbuf, outpathbufsiz, ctx->path, ptr); |
| | 224 | |
| | 225 | // Return the directory indication. |
| | 226 | *isdir = (ptr[l-1] == '/') ? true : false; |
| | 227 | |
| | 228 | free(escaped_realpat); |
| | 229 | return ctx; |
| | 230 | } |
| | 231 | |
| | 232 | /* Find the next matching file, continuing a search started with |
| | 233 | * os_find_first_file(). |
| | 234 | */ |
| | 235 | void* |
| | 236 | os_find_next_file( void* ctx0, char* outbuf, size_t outbufsiz, int* isdir, char* outpathbuf, |
| | 237 | size_t outpathbufsiz ) |
| | 238 | { |
| | 239 | struct oss_find_ctx_t* ctx = static_cast(struct oss_find_ctx_t*)(ctx0); |
| | 240 | size_t l; |
| | 241 | |
| | 242 | // If the search has already ended, do nothing. |
| | 243 | if (ctx == 0) return 0; |
| | 244 | |
| | 245 | if (++ctx->current > ctx->pglob->gl_pathc - 1) { |
| | 246 | // No more files - delete the context and give up. |
| | 247 | globfree(ctx->pglob); |
| | 248 | free(ctx->pglob); |
| | 249 | free(ctx); |
| | 250 | // Indicate that the search is finished. |
| | 251 | return 0; |
| | 252 | } |
| | 253 | |
| | 254 | // Return the name. See comments in os_find_first_file(). |
| | 255 | l = strlen(ctx->path); |
| | 256 | char* ptr = &ctx->pglob->gl_pathv[ctx->current][l]; |
| | 257 | |
| | 258 | l = strlen(ptr); |
| | 259 | |
| | 260 | if (l > outbufsiz - 1) { |
| | 261 | l = outbufsiz - 1; |
| | 262 | } |
| | 263 | memcpy(outbuf, ptr, l); |
| | 264 | outbuf[l] = '\0'; |
| | 265 | |
| | 266 | // Return the directory indication. |
| | 267 | *isdir = (ptr[l-1] == '/') ? true : false; |
| | 268 | |
| | 269 | // Build the full path, if desired. |
| | 270 | oss_build_outpathbuf(outpathbuf, outpathbufsiz, ctx->path, ptr); |
| | 271 | |
| | 272 | // Indicate that the search was successful by returning the |
| | 273 | // non-null context pointer -- the context has been updated for |
| | 274 | // the new position in the search, so we just return the same |
| | 275 | // context structure that we've been using all along. |
| | 276 | return ctx; |
| | 277 | } |
| | 278 | |
| | 279 | /* Cancel a search. |
| | 280 | */ |
| | 281 | void |
| | 282 | os_find_close( void* ctx0 ) |
| | 283 | { |
| | 284 | struct oss_find_ctx_t* ctx = static_cast(struct oss_find_ctx_t*)(ctx0); |
| | 285 | |
| | 286 | // If the search was already cancelled, do nothing. |
| | 287 | if (ctx == 0) return; |
| | 288 | |
| | 289 | // Delete the search context. |
| | 290 | if (ctx->pglob != 0) { |
| | 291 | globfree(ctx->pglob); |
| | 292 | free(ctx->pglob); |
| | 293 | } |
| | 294 | free(ctx); |
| | 295 | } |
| | 296 | |
| | 297 | #else |
| | 298 | |
| | 299 | /* <glob.h> isn't available. We just provide do-nothing dummies. |
| | 300 | */ |
| | 301 | void* |
| | 302 | os_find_first_file( const char* /*dir*/, const char* /*pattern*/, char* /*outbuf*/, size_t /*outbufsiz*/, |
| | 303 | int* /*isdir*/, char* /*outpathbuf*/, size_t /*outpathbufsiz*/ ) |
| | 304 | { |
| | 305 | return 0; |
| | 306 | } |
| | 307 | |
| | 308 | |
| | 309 | void* |
| | 310 | os_find_next_file( void* /*ctx*/, char* /*outbuf*/, size_t /*outbufsiz*/, int* /*isdir*/, |
| | 311 | char* /*outpathbuf*/, size_t /*outpathbufsiz*/ ) |
| | 312 | { |
| | 313 | return 0; |
| | 314 | } |
| | 315 | |
| | 316 | |
| | 317 | void |
| | 318 | os_find_close( void* /*ctx*/ ) |
| | 319 | { |
| | 320 | } |
| | 321 | #endif // HAVE_GLOB |
| | 322 | |
| | 323 | |
| | 324 | /* Determine if the given filename refers to a special file. |
| | 325 | * |
| | 326 | * tads2/osnoui.c defines its own version when MSDOS is defined. |
| | 327 | */ |
| | 328 | #ifndef MSDOS |
| | 329 | os_specfile_t |
| | 330 | os_is_special_file( const char* fname ) |
| | 331 | { |
| | 332 | // We also check for "./" and "../" instead of just "." and |
| | 333 | // "..". (We use OSPATHCHAR instead of '/' though.) |
| | 334 | const char selfWithSep[3] = {'.', OSPATHCHAR, '\0'}; |
| | 335 | const char parentWithSep[4] = {'.', '.', OSPATHCHAR, '\0'}; |
| | 336 | if ((strcmp(fname, ".") == 0) or (strcmp(fname, selfWithSep) == 0)) return OS_SPECFILE_SELF; |
| | 337 | if ((strcmp(fname, "..") == 0) or (strcmp(fname, parentWithSep) == 0)) return OS_SPECFILE_PARENT; |
| | 338 | return OS_SPECFILE_NONE; |
| | 339 | } |
| | 340 | #endif |
| | 341 | |
| | 342 | |
| | 343 | /* Initialize. |
| | 344 | */ |
| | 345 | int |
| | 346 | os_init( int*, char**, const char*, char*, int ) |
| | 347 | { |
| | 348 | return 0; |
| | 349 | } |
| | 350 | |
| | 351 | |
| | 352 | /* Uninitialize. |
| | 353 | */ |
| | 354 | void |
| | 355 | os_uninit( void ) |
| | 356 | { |
| | 357 | } |
| | 358 | |
| | 359 | |
| | 360 | /* ===================================================================== |
| | 361 | * These functions are currently not used by the compiler. If the |
| | 362 | * linker barks, we must implement them. |
| | 363 | */ |
| | 364 | |
| | 365 | /* Get the creation time for a file. |
| | 366 | */ |
| | 367 | /* |
| | 368 | int |
| | 369 | os_get_file_cre_time( os_file_time_t* t, const char* fname ) |
| | 370 | { |
| | 371 | } |
| | 372 | */ |
| | 373 | |
| | 374 | /* Get the access time for a file. |
| | 375 | */ |
| | 376 | /* |
| | 377 | int |
| | 378 | os_get_file_acc_time( os_file_time_t* t, const char* fname ) |
| | 379 | { |
| | 380 | } |
| | 381 | */ |
| | 382 | |
| | 383 | /* Switch to a new working directory. |
| | 384 | */ |
| | 385 | /* |
| | 386 | oid |
| | 387 | os_set_pwd( const char* dir ) |
| | 388 | { |
| | 389 | } |
| | 390 | */ |
| | 391 | |
| | 392 | /* Switch the working directory to the directory containing the given |
| | 393 | * file. |
| | 394 | */ |
| | 395 | /* |
| | 396 | void |
| | 397 | os_set_pwd_file( const char* filename ) |
| | 398 | { |
| | 399 | } |
| | 400 | */ |