1 // sysfwin.cpp Copyright (C) 1989-2021 Codemist
2
3 //
4 // System-specific code for use with the "fwin" window interface code.
5 // The system will also build as a terminal-mode program as well as
6 // a windowed one.
7 //
8
9 /**************************************************************************
10 * Copyright (C) 2021, Codemist. A C Norman *
11 * *
12 * Redistribution and use in source and binary forms, with or without *
13 * modification, are permitted provided that the following conditions are *
14 * met: *
15 * *
16 * * Redistributions of source code must retain the relevant *
17 * copyright notice, this list of conditions and the following *
18 * disclaimer. *
19 * * Redistributions in binary form must reproduce the above *
20 * copyright notice, this list of conditions and the following *
21 * disclaimer in the documentation and/or other materials provided *
22 * with the distribution. *
23 * *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS *
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE *
28 * COPYRIGHT OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, *
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, *
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS *
31 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
32 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
33 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF *
34 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH *
35 * DAMAGE. *
36 *************************************************************************/
37
38
39 // $Id: sysfwin.cpp 5736 2021-03-16 10:41:22Z arthurcnorman $
40
41 #ifdef __CYGWIN__
42 #include <sys/cygwin.h>
43 #endif
44
45 #include "headers.h"
46
47 // There is platform-specific code in this file. Here are some of the
48 // issues it tries to encapsulate.
49 //
50 // WIN32 all Windows platforms that I support
51 // <else> Unix-like
52 //
53 // popen(cmd, dir) vs _popen(cms, dir)
54 // pclose(stream) vs _pclose(stream)
55 // fileno(file) vs _fileno(file)
56 // struct stat vs struct _stat
57 // stat vs _stat
58 // ftruncate(file) vs chsize(file)
59 // S_IFMT __S_IFMT to go with stat
60 // S_IFDIR __S_IFDIR
61 // DO_NOT_USE_GETUID is getuid available
62 // UNIX_TIMES how can I read the clock
63 // UTIME_TIME_T struct utimbuf
64
65
66 #include <sys/stat.h>
67 #include <sys/types.h>
68 #include <errno.h>
69
70 #ifdef HAVE_UNISTD_H
71 #include <unistd.h>
72 #endif
73
74 #ifdef HAVE_DIRENT_H
75 #include <dirent.h>
76 #endif
77
78 #ifdef HAVE_SYS_TIME_H
79 #include <sys/time.h>
80 #endif
81
82 #ifdef HAVE_SYS_TIMES_H
83 #include <sys/times.h>
84 #endif
85
86 #ifdef HAVE_SYSCALL_H
87 #include <syscall.h>
88 #endif
89
90 #ifdef HAVE_SCHED_H
91 #include <sched.h>
92 #endif
93
94 // At present CSL is single threaded - at least as regards file IO - and
95 // using the unlocked versions of putc and getc can be a MAJOR saving.
96 // I put these macros here not in some header to try to keep me reminded
97 // that if threads ever happened I would need to do my own buffering.
98
99 #ifdef HAVE_PUTC_UNLOCKED
100 #define PUTC(x, y) putc_unlocked((x), (y))
101 #else
102 #ifdef HAVE__PUTC_NOLOCK
103 #define PUTC(x, y) _putc_nolock((x), (y))
104 #else
105 #define PUTC(x, y) putc((x), (y))
106 #endif
107 #endif
108
109 // Jollies re GC statistics...
110
111 static char time_string[40], space_string[32];
112
113 // If I am running the CSL GUI then on the top bar of the window I
114 // display some status information about how much time and space the
115 // calculation has been using. These two functions update that display, and
116 // are called periodically - ideally so that the user gets to see things
117 // chance roughtly once per second.
118
report_time(int32_t t,int32_t gct)119 void report_time(int32_t t, int32_t gct)
120 {
121 #ifndef EMBEDDED
122 std::sprintf(time_string, "%ld.%.2ld+%ld.%.2ld secs ",
123 t/100L, t%100L, gct/100L, gct%100L);
124 if ((window_heading & 1) == 0) fwin_report_left(time_string);
125 #endif
126 }
127
report_space(uint64_t n,double percent,double mbytes)128 void report_space(uint64_t n, double percent, double mbytes)
129 {
130 #ifndef EMBEDDED
131 if (mbytes > 9500.0)
132 std::sprintf(space_string, "[GC %" PRIu64 "]:%.2f%% %dG",
133 n, percent, static_cast<int>((mbytes+500.0)/1000.0));
134 else if (mbytes > 700.0)
135 std::sprintf(space_string, "[GC %" PRIu64 "]:%.2f%% %.1fG",
136 n, percent, mbytes/1000.0);
137 else std::sprintf(space_string, "[GC %" PRIu64 "]:%.2f%% %dM",
138 n, percent, static_cast<int>(mbytes + 0.5));
139 if ((window_heading & 4) == 0) fwin_report_right(space_string);
140 #endif
141 }
142
flush_screen()143 void flush_screen()
144 { fwin_ensure_screen();
145 }
146
147 int terminal_eof_seen = 0;
148
149 #define CTRL_C 3
150 #define CTRL_D 4
151 #define CTRL_G 7
152
wimpget(char * buf)153 int wimpget(char *buf)
154 { int c, n=0;
155 ensure_screen();
156 while (n < 255)
157 { if (terminal_eof_seen) c = EOF;
158 else
159 { c = fwin_getchar();
160 stackcheck(); // Responds to exceptions!
161 if (c == EOF || c == CTRL_D) terminal_eof_seen = 1;
162 }
163 if (c == EOF) c = 0x1f & 'D';
164 buf[n++] = static_cast<char>(c);
165 if (c == '\n' || c == (0x1f & 'D')) break;
166 };
167 return n;
168 }
169
my_popen(const char * command,const char * direction)170 std::FILE *my_popen(const char *command, const char *direction)
171 {
172 #ifdef EMBEDDED
173 return nullptr;
174 #else // EMBEDDED
175 // Here I have something that might count as an ugliness. If I am on
176 // Windows and am using a console-mode binary then in fact I am using a
177 // cygwin-built version of Reduce. That means that WIN32 will not be
178 // defined and this special magic will not be activated - I will end up
179 // requiring that /usr/bin/gnuplot exists in the cygwin sense of the
180 // meaning of that path-name, and that X11 and DISPLAY are available. This
181 // may mildly astonish some people. However when I tried to use the
182 // Windows-specific code in my Cygwin build I found problems to do with
183 // (perhaps) collision between Cygwin and Windows libraries, or to do with
184 // availablity of the relevant Windows system-calls under cygwin. So for
185 // now that somewhat unsatisfactory arrangement will persist.
186 #ifdef WIN32
187 // Here I take a pretty shameless direction and spot the special case of
188 // opening an output pipe to gnuplot... and hook in a behind-the-scenes
189 // way.
190 return windowsFindGnuplot(command, direction);
191 #else // WIN32
192 // The following use of "signal" is so that pipe failure does not raise
193 // an exception and blow everything out of the water. I might have expected
194 // that "popen(command-that-does-not-exist, "w")" would return nullptr, but it
195 // seems that sometimes it returns a pipe handle, and puts works on that
196 // without visible pain and only when one does an fflush does a SIGPIPE get
197 // raised. This hurts when gnuplot has not been installed on a Unix-like host.
198 // The new arrangement leads to somewhat silent failure to plot in that
199 // case, but is probably better than having an abrupt exit from the system.
200 // I know that these days I am asked to use sigaction rather than signal, but
201 // even on recent Linux variants that seems only just available...
202 std::signal(SIGPIPE, SIG_IGN);
203 return popen(command, direction);
204 #endif // WIN32
205 #endif // EMBEDDED
206 }
207
208 #ifndef WIN32
my_pipe_putc(int c,std::FILE * f)209 int my_pipe_putc(int c, std::FILE *f)
210 { return PUTC(c, f);
211 }
212
my_pipe_flush(std::FILE * f)213 int my_pipe_flush(std::FILE *f)
214 { return std::fflush(f);
215 }
216
my_pclose(std::FILE * stream)217 void my_pclose(std::FILE *stream)
218 { pclose(stream);
219 }
220
221 #endif // WIN32
222
223 // Map file-names to expand references to shell variables etc.
224 // and to provide portability of names across operating systems.
225
226
look_in_lisp_variable(char * o,int prefix)227 char *look_in_lisp_variable(char *o, int prefix)
228 { LispObject var;
229 // I will start by tagging a '$' (or whatever) on in front of the
230 // parameter name.
231 o[0] = static_cast<char>(prefix);
232 var = make_undefined_symbol(o);
233 // make_undefined_symbol() could fail either if we had utterly run out
234 // of memory or if somebody generated an interrupt (eg ^C) around now. Ugh.
235 //
236 // If the variable $name was undefined then I use an empty replacement
237 // text for it. Otherwise I need to look harder at its value.
238 if (qvalue(var) == unset_var) return o;
239 else
240 { intptr_t len;
241 var = qvalue(var);
242 // Mostly I expect that the value will be a string or symbol.
243 #ifdef COMMON
244 if (complex_stringp(var)) var = simplify_string(var);
245 #endif // COMMON
246 if (symbolp(var)) var = get_pname(var);
247 else if (!is_vector(var) || !is_string(var)) return nullptr;
248 len = length_of_byteheader(vechdr(var)) - CELL;
249 // Copy the characters from the string or from the name of the variable
250 // into the file-name buffer. There could at present be a crash here
251 // if the expansion was very very long and overflowed my buffer. Tough
252 // luck for now - people doing that (maybe) get what they (maybe) deserve.
253 std::memcpy(o, reinterpret_cast<char *>(var) + (CELL - TAG_VECTOR),
254 (size_t)len);
255 o = o + len;
256 return o;
257 }
258 }
259
260
261 // What follows can be replaced by stuff from the C++ chrono:: package.
262
263 #if defined HAVE_CLOCK_GETTIME && defined HAVE_DECL_CLOCK_THREAD_CPUTIME_ID
264
265 // Where possible I read the time used by the current thread... I return
266 // a value expressed in microseconds, but of course there is no guarantee that
267 // I will have anything like that as my actual granularity!
268
read_clock_microsecond()269 uint64_t read_clock_microsecond()
270 { struct std::timespec tt;
271 clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tt);
272 double w1 = static_cast<double>(tt.tv_sec) + static_cast<double>
273 (tt.tv_nsec)/1000000000.0;
274 return (uint64_t)(1000000.0*w1);
275 }
276
277
278 #elif defined HAVE_SYS_TIME_H && defined HAVE_TIMES && !defined WIN32 && !defined EMBEDDED
279
280 // This is a BSD-style clock facility, possibly giving a resolution of
281 // only 1/100 second.
282
283 double unix_ticks = 0.0;
284
read_clock()285 uint64_t read_clock()
286 { struct tms tmsbuf;
287 times(&tmsbuf);
288 std::clock_t w1 = tmsbuf.tms_utime; // User time in UNIX_TIMES ticks
289 #ifdef HAVE_UNISTD_H
290 if (unix_ticks == 0.0) unix_ticks =
291 static_cast<double>(sysconf(_SC_CLK_TCK));
292 #endif
293 if (unix_ticks == 0.0) unix_ticks = 100.0;
294 return (uint64_t)((1000000.0/unix_ticks) * static_cast<double>(w1));
295 }
296
297 #elif defined WIN32
298 #else
299
300 // In cases where clock_t is a 32-bit data type this fallback version
301 // will wraps round after around 20 minutes of CPU time!
302
read_clock()303 uint64_t read_clock()
304 { return static_cast<uint64_t>((1000000.0/CLOCKS_PER_SEC)*
305 static_cast<double>(std::clock()));
306 }
307
308 #endif
309
batchp()310 int batchp()
311 { return !isatty(fileno(stdin));
312 }
313
314 // The next procedure is responsible for establishing information about
315 // where the main checkpoint image should be recovered from, and where
316 // and fasl files should come from.
317 //
318 // On the Macintosh if the path to my executable indicated that I am
319 // within an "Application Bundle" I will look for the image file there.
320 //
321 // Otherwise I will look in two places! If the path to the executable ends
322 // up rather like BINDIR then I will check PKGDATADIR. The idea behind this
323 // is that if the files have been put in place using "make install" then
324 // the executable may be in say "...../bin/reduce" and the corresponding
325 // image would the be "..../share/reduce/reduce.img". I accept this if there
326 // is an image file in the location so suggested.
327 //
328 // Finally I look for an image file adjacent to the executable.
329
330 #ifndef BINDIR
331 #define BINDIR /usr/local/bin
332 #endif
333
334 #ifndef PKGDATADIR
335 #define PKGDATADIR /usr/local/share/reduce
336 #endif
337
338 #define stringify(s) stringify_sub(s)
339 #define stringify_sub(s) #s
340
find_image_directory(int argc,const char * argv[])341 const char *find_image_directory(int argc, const char *argv[])
342 { int n;
343 char *w;
344 char xname[LONGEST_LEGAL_FILENAME];
345 std::memset(xname, 0, sizeof(xname));
346 #ifdef MACINTOSH
347 // There is a special oddity on the Macintosh (with the wxWidgets version
348 // where windowed versions are set up as "applications" in a directory that
349 // forms an "application bundle". The programDir here can then refer to
350 // ./reduce.app/Contents/MacOS/reduce (or whatever) and it is probably good
351 // to make the default image location be reduce.app/Contents/MacOS too.
352 // But then the vanilla console mode version is liable to
353 // be just ./reduce, and I want one image file to be used for both versions.
354 // Furthermore some kind person may have launched the executable that is
355 // within the application bundle directly from a console so that it is not
356 // really an application after all. I will do a load of rather curious
357 // tests here that are intended to detect the above cases and do special
358 // things! My tests will be based on file names and paths.
359 std::sprintf(xname, "/%s.app/Contents/MacOS", programName);
360 n = std::strlen(programDir) - std::strlen(xname);
361 if (n>=0 && std::strcmp(programDir+n, xname) == 0)
362 { // Seem to be being executed from within application bundle.
363 // This dates from when I thought I would put the image in merely Contents not
364 // in Contents/MacOS.
365 std::sprintf(xname, "%.*s/%s.img",
366 static_cast<int>(std::strlen(programDir)), programDir, programName);
367 }
368 else
369 { struct stat buf;
370 // If I am NOT within an application bundle but there is one next to me I
371 // will put the image file in the application directory. Of there is no
372 // such bundle I will put the image file in the location I would have used
373 // with Windows of X11.
374 std::sprintf(xname, "%s/%s.app/Contents/MacOS", programDir,
375 programName);
376 if (stat(xname, &buf) == 0 &&
377 (buf.st_mode & S_IFDIR) != 0)
378 { std::sprintf(xname, "%s/%s.app/Contents/MacOS/%s.img",
379 programDir, programName, programName);
380 }
381 else std::sprintf(xname, "%s/%s.img", programDir, programName);
382
383 }
384 #else
385 { const char *bin = stringify(BINDIR);
386 const char *data = stringify(PKGDATADIR);
387 // I will strip initial directory names from bin and pkgdatadir so long as
388 // they match. So if they start off as (eg) /usr/local/bin and
389 // /usr/local/share/reduce I will remove "/usr/local" from each leaving just
390 // "/bin" and "/share/reduce". The purpose of this is so that if (despite the
391 // use of "make install") somebody has copied the tree that contains Reduce
392 // to somewhere else I might still find my resources.
393 int i, j;
394 struct stat buf;
395 const char *pn = programName;
396 #if defined WIN32 || defined __CYGWIN__
397 // On Windows I can have reduce.exe, cygwin-reduce.exe and cygwin64-reduce.exe
398 // all present, and for immediate purposes I want them all to be treated as
399 // if merely called "reduce".
400 if (std::strncmp(pn, "cygwin-", 7) == 0) pn += 7;
401 else if (std::strncmp(pn, "cygwin64-", 9) == 0) pn += 9;
402 #endif // WIN32
403 for (;;)
404 { i = j = 0;
405 if (*bin == '/') while (bin[++i] != 0 && bin[i] != '/');
406 if (*data == '/') while (data[++j] != 0 && data[j] != '/');
407 if (i != 0 && i == j && std::strncmp(bin, data, i) == 0)
408 { bin += i;
409 data += i;
410 }
411 else break;
412 }
413 i = std::strlen(bin);
414 j = std::strlen(programDir);
415 if (j>=i && std::strcmp(programDir+j-i, bin)==0)
416 { std::sprintf(xname, "%.*s%s/%s.img", j-i, programDir, data, pn);
417 }
418
419 // If the name I just created does not correspond to a file I will fall
420 // back and use the older location, adjacent to my binary. Hmmm this is
421 // all interesting as regards building an image file for the first time.
422 // I think it tells us that you had better not try doing that using the
423 // installed version - do that with a copy that sits in your own private
424 // writable are of disc.
425 if (stat(xname, &buf) != 0)
426 std::sprintf(xname, "%s/%s.img", programDir, pn);
427 }
428 #endif
429 n = std::strlen(xname)+1;
430 w = new (std::nothrow) char[n];
431 if (w == nullptr) my_abort();
432 std::strcpy(w, xname);
433 return w;
434 }
435
436 #ifdef WIN32
437 #define GPNAME "wgnuplot.exe"
438 #define DIRCHAR '\\'
439 #else
440 #define GPNAME "gnuplot"
441 #define DIRCHAR '/'
442 #endif
443
444
445 // When Reduce wants to invoke gnuplot it needs a command-line to pass to
446 // "pipe-open". This procedure creates on (if it can), The idea is
447 // to try three possibilities in turn:
448 // (1) If an environment variable GNUPLOT is set then that should be set to
449 // a path within which the gnuplot executable exists. So eg if the
450 // value of GNUPLOT is "/usr/extras/gnuplotfiles" then the result
451 // here is liable to be "/usr/extras/gnuplotfiles/gnuplot".
452 // (2) If a file called "gnuplot" (or "wgnuplot.exe in the windows case) is
453 // present in the directory where the Reduce executable was found then
454 // it will be used.
455 // (3) If a file called "gnuplot" (or "wgnuplot.exe in the windows case) is
456 // present in the directory where the Reduce image would (by default)
457 // be found then it will be used. In some cases this is actually the
458 // same as (2) above, but it can differ if the executable is in
459 // .../bin and the image in .../share/reduce or some such.
460 // (4) A search will be made in the "standard place". For Windows this will
461 // involve scanning the registry to seek an installation of gnuplot,
462 // while otherwise it will be expected that the ordinary PATH will
463 // provide access.
464 // (5) Failing all else I will just hand back the name of the executable and
465 // hope that it is on a PATH.
466
executable_file(const char * name)467 int executable_file(const char *name)
468 { struct stat buf;
469 if (stat(name, &buf) == -1) return 0;
470 #ifndef S_ISUSR
471 return 1;
472 #else
473 return (buf.st_mode & S_IXUSR);
474 #endif
475 }
476
find_gnuplot(char * name)477 int find_gnuplot(char *name)
478 { const char *w = std::getenv("GNUPLOT");
479 size_t len;
480 if (w != nullptr && (len = std::strlen(w)) > 0)
481 { if (w[len-1] == '/' ||
482 w[len-1] == '\\') len--;
483 std::sprintf(name, "%.*s%c%s", static_cast<int>(len), w, DIRCHAR,
484 GPNAME);
485 if (executable_file(name)) return 1;
486 }
487 std::strcpy(name, programDir);
488 len = std::strlen(name);
489 while (len-- > 0 &&
490 name[len] != '/' &&
491 name[len] != '\\');
492 if (len != 0)
493 { std::strcpy(&name[len+1], GPNAME);
494 if (executable_file(name)) return 1;
495 }
496 std::strcpy(name, standard_directory);
497 len = std::strlen(name);
498 while (len-- > 0 &&
499 name[len] != '/' &&
500 name[len] != '\\');
501 if (len != 0)
502 { std::strcpy(&name[len+1], GPNAME);
503 if (executable_file(name)) return 1;
504 }
505 #ifdef __CYGWIN__
506 // As usual Cygwin is an odd case. If DISPLAY is set and /usr/bin/gnuplot
507 // exists then I will try for an X11 usage of gnuplot. That should be
508 // the "natural" case!
509 w = std::getenv("DISPLAY");
510 if (w!=nullptr && *w!=0 &&
511 executable_file("/usr/bin/gnuplot.exe"))
512 { std::strcpy(name, "/usr/bin/gnuplot.exe");
513 return 1;
514 }
515 // ... well actually people may be using the cygwin version of Reduce because
516 // they ran the console mode version of Reduce under Cygwin's mintty. But
517 // they may not have X11 available. In the case perhaps I should try for
518 // a native Windows version of gnuplot for them... I look for gnuplot.exe here
519 // rather than wgnuplot.exe and will use it via a pipe rather than using
520 // the windows-special interface method.
521 if (windowsFindGnuplot2(name) != 0) return 1;
522 #endif
523 #ifdef WIN32
524 if (windowsFindGnuplot1(name) != 0) return 1;
525 #endif // WIN32
526 std::strcpy(name, GPNAME);
527 return 1;
528 }
529
530 // The following function controls memory allocation policy
531
ok_to_grab_memory(int32_t current)532 int32_t ok_to_grab_memory(int32_t current)
533 { return 3*current + 2;
534 }
535
536 // I will provide a function that reports how many processors are
537 // available. This may be of importance for multi-core systems where I
538 // could exploit around that many threads to especial benefit. In cases when
539 // I can not obtain the information I will merely report "1" which should
540 // be a fail-safe fallback. Issues such as the user setting processor
541 // affinities etc may make the information obtained indicative rather than
542 // definitive!
543 //
544 // Well in the Old Days this was a mess with much system-dependent code,
545 // but C++11 provides just what I need directly!
546
547
number_of_processors()548 unsigned int number_of_processors()
549 { return std::thread::hardware_concurrency();
550 }
551
552 static int tmpSerial = 0;
553
554 static char tempname[LONGEST_LEGAL_FILENAME];
555
CSLtmpdir()556 const char *CSLtmpdir()
557 {
558 #ifdef __CYGWIN__
559 // First try the Windows path to "/tmp"
560 char *p;
561 if (cygwin_conv_path(CCP_POSIX_TO_WIN_A,
562 "/tmp", tempname, sizeof(tempname)) != 0)
563 { size_t n = windowsGetTempPath(sizeof(tempname), tempname);
564 if (n == 0 || n > sizeof(tempname)) return ".";
565 tempname[n-1] = 0; // Remove trailing "\"
566 }
567 for (p=tempname; *p!=0; p++)
568 if (*p == '\\') *p = '/';
569 return tempname;
570 #else
571 #if defined WIN32
572 size_t n = windowsGetTempPath(sizeof(tempname), tempname);
573 if (n == 0 || n > sizeof(tempname)) return ".";
574 tempname[n-1] = 0; // Remove trailing "\"
575 return tempname;
576 #else
577 return "/tmp";
578 #endif
579 #endif
580 }
581
CSLtmpnam(const char * suffix,size_t suffixlen)582 const char *CSLtmpnam(const char *suffix, size_t suffixlen)
583 { std::time_t t0 = std::time(nullptr);
584 std::clock_t c0 = std::clock();
585 unsigned long taskid;
586 char fname[LONGEST_LEGAL_FILENAME];
587 char tt[32];
588 char *s;
589 #ifdef WIN32
590 size_t len;
591 std::memset(fname, 0, sizeof(fname));
592 len = windowsGetTempPath(LONGEST_LEGAL_FILENAME, tempname);
593 if ((int64_t)len <= 0) return nullptr;
594 // I want to avoid name clashes fairly often, so I will use the current
595 // time of day and information about the current process as a seed for the
596 // generated file-name so that (with luck) clashes are at least not
597 // incredibly probable. I will also use my source of random numbers, which
598 // adds variation that changes each time I call this function.
599 taskid = static_cast<unsigned long>(GetCurrentThreadId())*169 +
600 static_cast<unsigned long>(GetCurrentProcessId());
601 #else
602 std::memset(fname, 0, sizeof(fname));
603 std::strcpy(tempname, "/tmp/");
604 taskid = static_cast<unsigned long>(getpid())*169 +
605 static_cast<unsigned long>(getuid());
606 #endif
607 taskid = 169*taskid + static_cast<unsigned long>(t0);
608 taskid = 169*taskid + static_cast<unsigned long>(c0);
609 taskid = 169 * taskid + tmpSerial++;
610 // The information I have gathered thus far may not change terribly rapidly,
611 // since the process id is static form any one instance of my code and the
612 // clock may tick very slowly compared with the CPU's activity.
613 for (;;)
614 { unsigned long n;
615 int i;
616 // The next line reduces taskid modulo the largest prime under 2^32, which
617 // may be a sensible thing to do of "unsigned long" had been a 64-bit
618 // data type.
619 n = taskid % 0xfffffffbUL;
620 // At this stage I have at most 32-bits of information, derived from the
621 // clock and process identification. I will combine in info from the
622 // random number generator I have elsewhere in this code, and do that in
623 // such a way that I can generate 8 characters of file-name.
624 s = tempname + std::strlen(tempname);
625 for (i=0; i<7; i++)
626 { int d = static_cast<int>(n % 36);
627 n = n / 36;
628 if (i == 1) n ^= static_cast<unsigned long>(Crand());
629 if (d < 10) d += '0';
630 else d += ('a' - 10); // now 0-9 or 1-z
631 *s++ = d;
632 }
633 n = n % 36;
634 if (n < 10) *s++ = '0' + static_cast<int>(n);
635 else *s++ = 'a' + static_cast<int>(n - 10);
636 if (suffix != nullptr)
637 { std::sprintf(s, ".%.*s", static_cast<int>(suffixlen), suffix);
638 }
639 else *s = 0;
640 // If the file whose name I have just invented already exists I need to
641 // try again. I will count of the "random" sequence from Crand to propose
642 // an alternative name for me.
643 if (file_exists(fname, tempname, std::strlen(tempname), tt))
644 { taskid ^= n;
645 continue;
646 }
647 break;
648 }
649 return tempname;
650 }
651
652 // The following functions are best described as delicate, and they are only
653 // present for debugging purposes. It is not clear to me how much performance
654 // penalty they introduce, and certainly in a multi-threaded context the
655 // state of availabaility of memory to one thread can be changed by a
656 // different thread, leaving any result found here out of date. The Windows
657 // code also hints at issues where pages are marked in a special manner to
658 // let them act as guard pages - and potential consequences of that (eg
659 // wrt stack extension) are not handled carefully here.
660
661 #if defined __CYGWIN__ || defined __unix__ || defined MACINTOSH
662
663 // This code is intended to discover whether the pointer that is passed
664 // is a valid address. This is JUST intended for debugging so that I can
665 // go things like
666 // my_assert(valid_address(p), [=]{ ... });
667 // with my own custom diagnostics in the code that reports trouble.
668 // The failure is not expected to arise except when I have an internal
669 // error in CSL.
670 // The write() function never causes an exception, but instead returns
671 // -1 if it fails, and sets errno to EFAULT if the buffer address that it
672 // is passed offends it.
673
674 #include <unistd.h>
675 #include <fcntl.h>
676
677 static std::FILE *file_handle;
678 static int fd_handle;
679 static bool file_handle_set = false;
680
valid_address(void * pointer)681 bool valid_address(void *pointer)
682 { if (!file_handle_set)
683 { if ((file_handle = std::tmpfile()) == nullptr) return false;
684 fd_handle = fileno(file_handle);
685 file_handle_set = true; // I will open the fd just once.
686 }
687 // I will not bother to check errno, and just take any failure as
688 // indicating a bad memory address in the pointer.
689 return (write(fd_handle, pointer, 1) != -1);
690 }
691
692 #elif defined WIN32
693 #else
694
valid_address(void * pointer)695 bool valid_address(void *pointer)
696 { return true;
697 }
698
699 #endif
700
valid_address(uintptr_t pointer)701 bool valid_address(uintptr_t pointer) // an overload to accept integer types
702 { return valid_address(reinterpret_cast<void *>(pointer));
703 }
704
705 // end of sysfwin.cpp
706