1 // fwin.cpp                                 Copyright A C Norman 2003-2021
2 //
3 //
4 // Window interface for old-fashioned C/C++ applications. Intended to
5 // be better than just running them within rxvt/xterm, but some people will
6 // always believe that running them under emacs is best!
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 // Note that the above terms apply and must persist regardless of where or how
40 // this code is used. A copy of this will be included within a modified
41 // version of the FOX library and in that context the whole work has to
42 // be treated subject to the constraints of the LGPL (and not the FOX
43 // license addendum that would have granted static linking rights, because
44 // that only applies to unmodified versions of FOX as sanctioned by its
45 // original author). But this code can also be compiled and used without
46 // the GUI components, in that case LGPL obligations do not apply but BSD
47 // ones do.
48 
49 // $Id: fwin.cpp 5744 2021-03-19 22:16:59Z arthurcnorman $
50 
51 // The "#ifdef" mess here has been getting out of control. The major
52 // choice are:
53 //   Sometimes this file will live within my copy of the FOX library as
54 //   an extension. If however I am building a system without a GUI at all
55 //   I can use this file to link on to my own terminal handling code. This
56 //   is controlled by HAVE_CONFIG_H and through that PART_OF_FOX.
57 //
58 //   Then there are variations as between Windows, OX X and Linux. At
59 //   present systems other then Windows and OS X will use the Linux
60 //   parts, which really represents a UNix-family system using X11 and
61 //   so should cover BSD too. Because when this code is built within the
62 //   FOX library it has a different configure.ac file to configure it
63 //   I can not comfortably rely on the machine-selection abstractions I
64 //   use elsewhere in CSL. So I use __APPLE__ to detect the OSX case and
65 //   WIN32 for Windows - those are set automatically by the compilers that
66 //   I use. I do not believe thet I have any code here sensitive to the
67 //   distinction between 32 and 64-bit windows, but within the Linux
68 //   sections I will sometimes need to be conditional on __CYGWIN__.
69 //
70 //   If EMBEDDED is defined a somewhat abbreviated version will be built
71 //   since in that context simplicity trumps capability.
72 
73 #ifdef HAVE_CONFIG_H
74 #include "config.h"
75 #else
76  // HAVE_CONFIG_H
77 #define PART_OF_FOX 1
78 extern void initThreadLocals();
79 #endif // HAVE_CONFIG_H
80 
81 // For the bulk of CSL the tests on C++ dialect are done in "machine.h",
82 // but this file is shared with FOX and can not use that header, so it has
83 // its own copies of the tests.
84 
85 #ifndef __has_cpp_attribute
86 #define __has_cpp_attribute(name) 0
87 #endif // C++17 support
88 
89 #if __has_cpp_attribute(maybe_unused)
90 // C++17 introduced [[maybe_unused]] to avoid warnings about unused variables
91 // and functions. Earlier versions of gcc and clang supported [[gnu::unused]]
92 // as a non-standard annotation with similar effect.
93 #define UNUSED_NAME [[maybe_unused]]
94 #elif defined __GNUC__
95 #define UNUSED_NAME [[gnu::unused]]
96 #else // [[maybe_unused]] or [[gnu::unused]] availability
97 // In any other case I just omit any annotation and if I get warnings about
98 // unused things then so be it.
99 #define UNUSED_NAME
100 #endif // annotation for unused things
101 
102 // I am now insisting that CSL be built with C++17 and so the following
103 // paragraph should be fading in relevance!
104 
105 // On some platforms it will APPEAR that <filesystem> and std::filesystem
106 // are available but they will not be. This can perhaps be a consequenec of
107 // transition arrangements in the C++ comnpiler, library and even the
108 // operating system being used. On sufficiently old platforms there will be
109 // no pretence of their availability so I will not have trouble, and on
110 // fully up to date ones this part of the C++17 standard should be properly
111 // supported, but somewher in between there can be "trouble"! So I have a
112 // configure time test that can set FILESYSTEM_NOT_USABLE in the case when
113 // the notionally standard-conforming test will not suffice.
114 
115 // I am really sorry about the following test! It is because on a Mac
116 // one can have #include <filesystem> work and __cpp_lib_filesystem get
117 // defined for you but std::filesystem may still not be properly available
118 // because at least some parts of it were not supported until 10.15. I rather
119 // firmly believe that __cpp_lib_filesystem ought not to have ended up
120 // defined if the C++17 feature was not actually going to be available. But
121 // there we go!
122 
123 //#define STR_HELPER(x) #x
124 //#define STR(x) STR_HELPER(x)
125 //#pragma message "MIN VER: " STR(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__)
126 
127 #if defined __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ && \
128             __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101500 && \
129             !defined FILESYSTEM_NOT_USABLE
130 
131 #pragma message "Disabling use of std::filesystem because of OS version min"
132 #define FILESYSTEM_NOT_USABLE 1
133 
134 #endif // Hack for Macintosh.
135 
136 #if !defined FILESYSTEM_NOT_USABLE
137 
138 #ifndef __has_include
139 #define __has_include(name) 0
140 #endif
141 
142 // I will allow config.h to define HAVE_FILESYSTEM as a promise thae this
143 // is all OK!
144 #if !defined HAVE_FILESYSTEM && __has_include(<filesystem>)
145 #define HAVE_FILESYSTEM 1
146 #endif // HAVE_FILESYSTEM
147 
148 #ifdef HAVE_FILESYSTEM
149 #include <filesystem>
150 #endif // HAVE_FILESYSTEM
151 
152 #endif
153 
154 // Now I can test __cpp_lib_filesystem to see if std::filesystem is
155 // actually available. If I use it I may need to link -lstdc++fs in gcc
156 // or -lc++fs in clang!
157 
158 #include "fwin.h"
159 
160 extern int fwin_main(int argc, const char *argv[]);
161 
162 #ifdef WIN32
163 #include <windows.h>
164 #include <io.h>
165 #endif // WIN32
166 
167 #include <cstring>
168 #include <cstdio>
169 #include <cstdint>
170 #include <cinttypes>
171 #include <cstdlib>
172 #include <cstdarg>
173 #include <cctype>
174 #include <cwchar>
175 #include <cwctype>
176 #include <ctime>
177 #include <csignal>
178 #include <thread>
179 #include <chrono>
180 #include <atomic>
181 #include <iostream>
182 #include <string>
183 #include <vector>
184 #include <algorithm>
185 
186 using std::atomic;
187 
188 #if HAVE_UNISTD_H
189 #include <unistd.h>
190 #else // HAVE_UNISTD_H
191 // The declaration here is an expression of optimism!
192 extern "C" char *getcwd(const char *s, size_t n);
193 #endif // HAVE_UNISTD_H
194 
195 #include <sys/stat.h>
196 #include <sys/types.h>
197 #include <cerrno>
198 
199 #ifdef WIN32
200 #include <windows.h>
201 #else // WIN32
202 #include <glob.h>
203 #endif // WIN32
204 
205 #ifdef HAVE_DIRENT_H
206 #include <dirent.h>
207 #elif defined WIN32
208 #include <direct.h>
209 #else
210 #include <sys/dir.h>
211 #endif // HAVE_DIRENT_H
212 
213 #ifdef __APPLE__
214 
215 #include <Carbon/Carbon.h>
216 #include <CoreServices/CoreServices.h>
217 
218 #endif // __APPLE__
219 
220 #include "termed.h"
221 
222 #include <cstdio>
223 #include <cstdlib>
224 
225 using std::int32_t;
226 using std::int64_t;
227 using std::uint32_t;
228 using std::uint64_t;
229 using std::string;
230 
231 // An "my_assert" scheme that lets me write in my own code to print the
232 // diagnostics. Included here because this files does not icnlude "fx.h".
233 
my_abort()234 [[noreturn]] inline void my_abort()
235 {   std::exit(EXIT_FAILURE);
236 }
237 
my_abort(const char * msg)238 [[noreturn]] inline void my_abort(const char *msg)
239 {   std::fprintf(stderr, "\n\n!!! Aborting: %s\n\n", msg);
240     std::fflush(stderr);
241     std::exit(EXIT_FAILURE);
242 }
243 
244 template <typename F>
my_assert(bool ok,F && action)245 inline void my_assert(bool ok, F&& action)
246 {
247 #ifndef NDEBUG
248 // Use this as in
249 //     my_assert(predicate, [&]{...});
250 // where the "..." is an arbitrary sequence of actions to be taken
251 // if the assertion fails.
252     if (!ok)
253     {   action();
254         my_abort();
255     }
256 #endif //NDEBUG
257 }
258 
259 // I have a bunch of macros that I use for desparation-mode debugging,
260 // and in particular when I have bugs that wriggle back into their lairs
261 // when I try running under "gdb" or whatever. These print dull messages
262 // to stderr. The "do..while" idiom is to keep C syntax safe with regard to
263 // semicolons.
264 
265 #define D do {                                                         \
266           const char *_f_ = std::strrchr(__FILE__, '/');               \
267           if (_f_ == nullptr) _f_ = std::strrchr(__FILE__, '\\');      \
268           if (_f_ == nullptr) _f_ = __FILE__; else _f_++;              \
269           std::fprintf(stderr, "Line %d File %s\n", __LINE__, _f_);    \
270           std::fflush(stderr);                                         \
271           } while (0)
272 
273 #define DS(s) do {                                                           \
274           const char *_f_ = std::strrchr(__FILE__, '/');                     \
275           if (_f_ == nullptr) _f_ = std::strrchr(__FILE__, '\\');            \
276           if (_f_ == nullptr) _f_ = __FILE__; else _f_++;                    \
277           std::fprintf(stderr, "Line %d File %s: %s\n", __LINE__, _f_, (s)); \
278           std::fflush(stderr);                                               \
279           } while (0)
280 
281 #define DX(s) do {                                                          \
282           const char *_f_ = std::strrchr(__FILE__, '/');                    \
283           if (_f_ == nullptr) _f_ = std::strrchr(__FILE__, '\\');           \
284           if (_f_ == nullptr) _f_ = __FILE__; else _f_++;                   \
285           std::fprintf(stderr, "Line %d File %s: %llx\n", __LINE__, _f_,    \
286                           (long long unsigned)(s));                         \
287           std::fflush(stderr);                                              \
288           } while (0)
289 
290 #define DF(f,...) do {                                               \
291           const char *_f_ = std::strrchr(__FILE__, '/');             \
292           if (_f_ == nullptr) _f_ = std::strrchr(__FILE__, '\\');    \
293           if (_f_ == nullptr) _f_ = __FILE__; else _f_++;            \
294           std::fprintf(stderr, "Line %d File %s: ", __LINE__, _f_);  \
295           std::fprintf(stderr, f, __VA_ARGS__);                      \
296           std::fprintf(stderr, "\n");                                \
297           std::fflush(stderr);                                       \
298           } while (0)
299 
300 
301 // The value LONGEST_LEGAL_FILENAME should be seen as a problem wrt
302 // buffer overflow! I will just blandly assume throughout all my code that
303 // no string that denotes a full file-name (including its path) is ever
304 // longer than this.
305 #ifndef LONGEST_LEGAL_FILENAME
306 #define LONGEST_LEGAL_FILENAME 1024
307 #endif // LONGEST_LEGAL_FILENAME
308 
309 #ifdef DEBUG
310 
311 // This will be used as in FWIN_LOG(format,arg,...)
312 // If DEBUG was enabled it send log information
313 // to a file with the name fwin-debug.log: I hope that will not (often)
314 // clash with any file the user has or requires. if programDir has been
315 // set when you first generate log output then the log file will be put
316 // there (ie alongside the executable). If not it will go in /tmp. So
317 // if debugging you might want to ensure that such a directory exists!
318 
319 static std::FILE *fwin_logfile = nullptr;
320 
321 #define LOGFILE_NAME "fwin-debug.log"
322 
fwin_write_log(const char * s,...)323 void fwin_write_log(const char *s, ...)
324 {
325 // I expect vfprintf and fflush to be thread-safe, however the test
326 // on fwin_logfile and the code that creates it could lead to a race
327 // if two threads both tried to open the log file at the same time. I
328 // believe that since this is JUST to be used for debugging everything can
329 // be made safe by insisting that any code that uses threads must execute
330 // FWIN_LOG() before it starts any thread, so that the log file will get
331 // created when there is not concurrency to cause confusion. That seems
332 // lighter weight than messing with a further critical section here.
333     int create = (fwin_logfile == nullptr);
334     std::va_list x;
335 // Note that I create this file in "a" (append) mode so that previous
336 // information there is not lost.
337     if (create)
338     {   char logfile_name[LONGEST_LEGAL_FILENAME];
339         std::memset(logfile_name, 0, sizeof(logfile_name));
340         if (std::strcmp(programDir, ".") == 0)
341             std::sprintf(logfile_name, "/tmp/%s", LOGFILE_NAME);
342 #ifdef __APPLE__
343 // If the executable I am running exists as
344 //    ...../something.app/Contents/MacOS/something
345 // then I will place the log file adjacant to the .app directory rather
346 // than in the MacOS directory next to the actual raw executable.
347         else if (std::sprintf(logfile_name, "%s.app/Contents/MacOS",
348                               programName),
349                  std::strlen(programDir) >= std::strlen(logfile_name) &&
350                  std::strcmp(programDir+std::strlen(programDir)-std::strlen(
351                                  logfile_name),
352                              logfile_name) == 0)
353         {   std::sprintf(logfile_name, "%.*s/%s",
354                          static_cast<int>(std::strlen(programDir)-std::strlen(programName)-19),
355                          programDir, LOGFILE_NAME);
356         }
357 #endif // __APPLE__
358         else std::sprintf(logfile_name, "%s/%s", programDir, LOGFILE_NAME);
359         fwin_logfile = std::fopen(logfile_name, "a");
360 // I provide a fallback in case (perhaps) permissions fail me.
361         if (fwin_logfile == nullptr) fwin_logfile =
362                 std::fopen("/tmp/fwin.log", "w");
363     }
364     if (fwin_logfile == nullptr) return; // the file can not be used
365     if (create)
366     {   std::time_t tt = std::time(nullptr);
367         struct std::tm *tt1 = std::localtime(&tt);
368         std::fprintf(fwin_logfile, "Log segment starting: %s\n",
369                      std::asctime(tt1));
370     }
371     va_start(x, s);
372     std::vfprintf(fwin_logfile, s, x);
373     va_end(x);
374     va_start(x, s);
375     std::vfprintf(stderr, s, x);
376     va_end(x);
377     std::fflush(fwin_logfile);
378 }
379 
380 #endif // DEBUG
381 
382 // The next few are not exactly useful if FOX is not available
383 // and hence this code will run in line-mode only. However it is
384 // convenient to leave them available.
385 //
386 // Note that FOX as used here is licensed under the LGPL. Its terms
387 // require that if the work displays a copyright notice during execution
388 // than the FOX copyright notice and a reference directing users to
389 // a copy of the LGPL must be displayed too. Thus my suggestion is that the
390 // about-box information here should not purport to be a copyright notice,
391 // merely a reminder of who the key authors are. However despite not
392 // then being obliged to say anything at all about FOX I will put in the
393 // URL of its web-site, and of course that is a place where the LGPL can
394 // be found. Anything beyond that would make the size of the about box
395 // grow in a way I view as clumsy. The wording I use is as requested (but
396 // under LGPL not actually required!) by the FOX authors.
397 
398 char about_box_title[40]       = "About XXX";
399 char about_box_description[40] = "XXX version 1.1";
400 //                               <icon appears here>
401 char about_box_rights_1[40]    = "Author info";
402 char about_box_rights_2[40]    = "Additional author";
403 char about_box_rights_3[40]    = "This software uses the FOX Toolkit";
404 char about_box_rights_4[40]    = "(http://www.fox-toolkit.org)";
405 
406 const char *colour_spec = "-";
407 
408 char fwin_prompt_string[MAX_PROMPT_LENGTH] = "> ";
409 
410 int fwin_linelength = 80;
411 
412 delay_callback_t *delay_callback;
413 
414 extern const char *my_getenv(const char *s);
415 
416 #ifdef WIN32
417 bool programNameDotCom = false;
418 #endif // WIN32
419 
420 #ifdef __APPLE__
421 static int macApp = 0;
422 #endif // __APPLE__
423 
424 int windowed = 0;
425 bool fwin_pause_at_end = false;
426 bool texmacs_mode = false;
427 
428 #ifdef HAVE_LIBXFT
429 bool fwin_use_xft = true;
430 #else // HAVE_LIBXFT
431 bool fwin_use_xft = false;
432 #endif // HAVE_LIBXFT
433 
434 #ifdef __APPLE__
435 
mac_deal_with_application_bundle(int argc,const char * argv[])436 void mac_deal_with_application_bundle(int argc, const char *argv[])
437 {
438 // If I will be wanting to use a GUI and if I have just loaded an
439 // executable that is not within an application bundle then I will
440 // use "open" to launch the corresponding application bundle. Doing this
441 // makes resources (eg fonts) that are within the bundle available and
442 // it also seems to cause things to terminate more neatly.
443     if (!macApp)
444     {   char xname[LONGEST_LEGAL_FILENAME];
445 // Here the binary I launched was NOT being from an application bundle.
446 // I will try to re-launch it so it is.
447         struct stat buf;
448         std::memset(xname, 0, sizeof(xname));
449         std::sprintf(xname, "%s.app", fullProgramName);
450         if (stat(xname, &buf) == 0 &&
451             (buf.st_mode & S_IFDIR) != 0)
452         {
453 // Well foo.app exists and is a directory, so I will try to use it. Here
454 // I will let "new" throw an exception if it fails!
455             const char **nargs = new const char *[argc+3];
456             int i;
457 #ifdef DEBUG
458 // Since I am about to restart the program I do not want the new version to
459 // find that the log file is open and hence not accessible.
460             if (fwin_logfile != nullptr)
461             {   std::fclose(fwin_logfile);
462                 fwin_logfile = nullptr;
463             }
464 #endif // DEBUG
465             nargs[0] = "/usr/bin/open";
466             nargs[1] = xname;
467             nargs[2] = "--args";
468             for (i=1; i<argc; i++)
469                 nargs[i+2] = argv[i];
470             nargs[argc+2] = nullptr;
471 // /usr/bin/open foo.app --args [any original arguments]
472             execv("/usr/bin/open", const_cast<char * const *>(nargs));
473 // execv should NEVER return, but if it does I might like to at least
474 // attempt to display a report including the error code.
475             std::fprintf(stderr,
476                          "Returned from execv with error code %d\n", errno);
477 // These daya I can not even be certain that calling std::exit() will cause
478 // and application to terminate (I think) but the use here should NEVER get
479 // called and so just what happens here is not that important!
480             std::exit(1);
481         }
482     }
483 }
484 
485 #endif // __APPLE__
486 
487 #ifdef PART_OF_FOX
488 
489 #ifdef WIN32
490 
491 // This seems to be needed to ensure that if a windows console is closed
492 // and you launched your program from a cygwin shell (via the cygwin
493 // execv(e) family) it exits nicely. Otherwise it can be retried several
494 // times. When that happens it looks really weird!
CtrlHandler(DWORD x)495 BOOL CtrlHandler(DWORD x)
496 {   switch (x)
497     {   case CTRL_CLOSE_EVENT:
498         case CTRL_LOGOFF_EVENT:
499         case CTRL_SHUTDOWN_EVENT:
500         case CTRL_BREAK_EVENT:
501 // I had tried the use of ExitProcess(1) here and that was not strong enough
502 // to avoid program-restart! So I take drastic action with TerminateProcess.
503             TerminateProcess(GetCurrentProcess(), 1);
504         default:
505             return 0;
506     }
507 }
508 
consoleWait()509 void consoleWait()
510 {
511 // If the console had to be created specially to view this information
512 // it is probable that it will close as soon as the program closes, and so
513 // to give at least a minimal chance for the user to inspect it I will
514 // put in a delay here. I will still use atexit() with this because I feel
515 // reasonably confident that it does not interact with any CSL data at all
516 // and so the order of invocatio nof it and any other termination processes
517 // should be unimportant.
518     for (int i=5; i!=0; i--)
519     {   char title[32];
520         std::sprintf(title, "Exiting after %d seconds", i);
521         SetConsoleTitle(title);
522         std::this_thread::sleep_for(std::chrono::seconds(1));
523     }
524 }
525 
526 static int ssh_client = 0;
527 
windows_checks(int is_windowed)528 int windows_checks(int is_windowed)
529 {
530 // I have tried various messy Windows API calls here to get this right.
531 // But so far I find that the cases that apply to me are
532 //    (a) windows command prompt : normal case
533 //    (b) windows command prompt : stdin redirected via "<" on command line
534 //                                 or application invoked via a pipe
535 //    (c) windows, but launched by a double-click, .com version
536 //    (d) windows, but launched by a double-click, .exe version
537 //    (e) old (cmd based) cygwin shell : normal case
538 //    (f) old (cmd based) cygwin shell : stdin redirected via "<"
539 //    (g) mintty or via ssh : normal case
540 //    (h) mintty or via ssh : stdin redirected
541 //
542 // For each of the above I now document what the windows API tells
543 // me about my situation...
544 //    (a) stdin exists and is a tty, a char device and a Console
545 //    (b) stdin exists and is a pipe or a file NOT a tty
546 //        (is "< /dev/null" a special case here?)
547 //    (c) as (a)
548 //    (d) stdin seems to exist but is not a tty
549 //    (e) as (a)
550 //    (f) as (b)
551 //    (g) stdin exists and is a pipe
552 //    (h) as (g)
553 //
554 // I want (b), (c) and (f) to force a non-windowed treatment.
555 // I also want (g) to force a non-windowed treatment, but when that
556 // happens it needs to use curses rather than the Windows Console API to
557 // cope with local editing and prompt colouring, because there is no
558 // (visible) Windows console available. So far as I can see there is no
559 // way for an application not linked against cygwin1.dll to distinguish
560 // between cases (g) and (h), and no way for it then switch its input
561 // into raw mode, however a cygwin application can use its version of
562 // isatty to make this test...
563 //
564 // So I will need to make some decisions before actually starting this
565 // code, and the file "gui-or-not.txt" in the source tree discusses just
566 // what I do.
567     if (programNameDotCom && is_windowed == 2)
568     {
569 // The program was named "xxx.com". I will assume that that means it was
570 // a console-mode application and it is being launched directly from a
571 // Windows console.  Why do I feel these are plausible:
572 //  . The Makefile.in & configure.ac stuff arranges to build xxx.com as
573 //    console mode and xxx.exe as subsystem:windows
574 //  . A Windows command prompt will launch xxx.com in preference to xxx.exe
575 //    if both are present
576 //  . xxx.com is not given an icon, while xxx.exe is - people should not
577 //    double-click on the .com version (please)
578 // Obviously users can subvert this by copying xxx.exe to yyy.com, by
579 // double clicking where I did not want or by specifying an explicit
580 // extension when they launch a command from a console prompt. But in such
581 // cases I will take the view that they will get what they deserve!
582         HANDLE h;
583         DWORD w;
584         CONSOLE_SCREEN_BUFFER_INFO csb;
585 // If either standard input or output has been redirected I will force use
586 // of console rather than windowed mode. Thus
587 //         xxx             launch in a window
588 //         xxx --nogui     run as console application
589 //         xxx -w          run as console application
590 //         xxx < yyy       run as console application
591 //         xxx > yyy       run as console application
592 // My hope is that the detection of redirected stdin/stdout will help
593 // when the application is used in a script. There may remain a dodgy case!
594 // if xxx is run under a debugger at least some debuggers intercept standard
595 // input & output so debugging the windowed mode may be harded here. But I
596 // will defer that worry since the ".exe" not the ".com" file is the version
597 // with windowed use its prime interface.
598 //
599 // New versions of cygwin install a terminal that is not just a regular
600 // DOS window running bash, but is closer to everything a Unix user might
601 // expect - however this possibly messes up the tests I make to see if I
602 // want to run a terminal or a windowed version of everything.
603         const char *ssh = my_getenv("SSH_CLIENT");
604         if (ssh != nullptr && *ssh != 0)
605         {   FWIN_LOG("SSH_CLIENT set on Windows, so treat as console app\n");
606             ssh_client = 1;
607             is_windowed = 0;
608         }
609         else
610         {   h = GetStdHandle(STD_INPUT_HANDLE);
611             if (GetFileType(h) != FILE_TYPE_CHAR) is_windowed = 0;
612             else if (!GetConsoleMode(h, &w)) is_windowed = 0;
613             else
614             {   h = GetStdHandle(STD_OUTPUT_HANDLE);
615                 if (GetFileType(h) != FILE_TYPE_CHAR) is_windowed = 0;
616                 else if (!GetConsoleScreenBufferInfo(h, &csb)) is_windowed = 0;
617             }
618         }
619     }
620     else if (is_windowed == 2)
621     {
622 // The program was named "xxx.exe". I am going to suppose that this has NOT
623 // been launched from a normal Windows command prompt (since xxx.com would
624 // have been preferred). I am left with two scenarios. One is that the
625 // program was launched by double-clicking, and in that case it detached
626 // from its console as it started. The other is that it was launched from
627 // a cygwin prompt (which looks for xxx.exe but not xxx.com when you type
628 // just xxx).
629 //
630         HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
631 // The discrimination I make here is based on an empirical check of what
632 // seems to happen under Windows XP with the version of cygwin current as
633 // of September 2004. What I find is that when stdin has been redirected by
634 // a shell (either the Windows command shell or cygwin, and in the cygwin
635 // case either with "<file" or "<<TAG") my standard input handle exists
636 // and identifies itself as type DISK. The the case of launching the code
637 // by double-clicking on the .exe file the handle is probably invalid, but
638 // GetFileType returns FILE_TYPE_UNKNOWN. The end effect is that I can
639 // detect cases where input has been redirected in a way that appears to
640 // work in both cases.  Note that if a user wants to launch an application
641 // via a pipe then they should EITHER launch the ".com" version or (better)
642 // explictly provide a "-w" flag to indicate that the application should
643 // work in stream/console mode.
644         const char *ssh = my_getenv("SSH_CLIENT");
645         if (ssh != nullptr && *ssh != 0)
646         {   FWIN_LOG("SSH_CLIENT set\n");
647             ssh_client = 1;
648             is_windowed = 0;
649         }
650         else if (GetFileType(h) == FILE_TYPE_DISK) is_windowed = 0;
651     }
652     return is_windowed;
653 }
654 
sort_out_windows_console(int is_windowed)655 void sort_out_windows_console(int is_windowed)
656 {
657 // If I am running under Windows and I have set command line options
658 // that tell me to run in a console then I will create one if one does
659 // not already exist.
660     if (is_windowed == 0)
661     {   int consoleCreated = AllocConsole();
662         if (consoleCreated)
663         {   if (ssh_client)
664             {
665 // This situation seems totally odd to me. I just launched a Windowed-mode
666 // application and normally when I do that it detaches from the console.
667 // however rather than having a proper console here I am connected over
668 // ssh (I am testing this using the cygwin openssh server on Windows 7).
669 // It appears to be the case that I *DO* have stdin and stdout available to
670 // me in this case even though I think that when I launch exactly the same
671 // binary from a directly attached console it detaches and I need to use
672 // the newly created console....
673 // The code I have here is based on empirical observation in cases that
674 // most people will probably not trigger!
675                 FWIN_LOG("Running windowed mode application via ssh.\n");
676             }
677             else
678             {
679 #ifdef __CYGWIN__
680                 std::freopen("/dev/conin", "r+", stdin);
681                 std::freopen("/dev/conout", "w+", stdout);
682                 std::freopen("/dev/conout", "w+", stderr);
683 #else // __CYGWIN__
684 // I try rather hard here to leave things properly connected to
685 // the new console. Note opening CONOUT in "w+" mode so it has
686 // GENERIC_READ_ACCESS.
687                 HANDLE h;
688                 std::freopen("CONIN$", "r+", stdin);
689                 std::freopen("CONOUT$", "w+", stdout);
690                 std::freopen("CONOUT$", "w+", stderr);
691                 SetStdHandle(STD_INPUT_HANDLE,
692                              CreateFile("CONIN$",
693                                         GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, nullptr,
694                                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
695                 SetStdHandle(STD_OUTPUT_HANDLE,
696                              h = CreateFile("CONOUT$",
697                                             GENERIC_READ|GENERIC_WRITE, FILE_SHARE_WRITE, nullptr,
698                                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
699                 SetStdHandle(STD_ERROR_HANDLE, h);
700 #endif // __CYGWIN__
701 // I will also pause for 5 seconds at the end...
702                 std::atexit(consoleWait);
703             }
704         }
705         SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
706     }
707 }
708 
709 #else // WIN32
710 
711 #ifdef __APPLE__
712 
unix_and_osx_checks(int xwindowed)713 static int unix_and_osx_checks(int xwindowed)
714 {   const char *disp;
715 // If stdin or stdout is not from a "tty" I will run in non-windowed mode.
716 // This may help when the system is used in scripts. I worry a bit about
717 // what the status of stdin/stdout are when launched not from a command line
718 // but by clicking on an icon...
719     if (xwindowed != 0)
720     {
721 // On Unix-like systems I will check the DISPLAY variable, and if it is not
722 // set I will suppose that I can not create a window. That case will normally
723 // arise when you have gained remote access to the system eg via telnet or
724 // ssh without X forwarding. I will also insist that if set it has a ":" in
725 // its value... that is to avoid trouble with it getting set to an empty
726 // string. Note that on a Macintosh when I am using the FOX GUI toolkit (as
727 // is the case here) I need X11 and hence DISPLAY. If I was using the Mac
728 // native display I would merely omit this test.
729         disp = my_getenv("DISPLAY");
730         if (disp == nullptr || std::strchr(disp, ':')==nullptr) xwindowed = 0;
731     }
732 // This may be a proper way to test if I am really running in an application
733 // bundle.
734     {   CFBundleRef mainBundle = CFBundleGetMainBundle();
735         if (mainBundle == nullptr) macApp = 0;
736         else
737         {   CFDictionaryRef d = CFBundleGetInfoDictionary(mainBundle);
738             if (d == nullptr) macApp = 0;
739             else
740             {   CFStringRef s =
741                     (CFStringRef)CFDictionaryGetValue(d,
742                                                       CFSTR("ATSApplicationFontsPath"));
743                 macApp = (s != nullptr);
744             }
745         }
746     }
747 // If stdin or stdout is not from a "tty" I will run in non-windowed mode.
748 // This may help when the system is used in scripts. I worry a bit about
749 // what the status of stdin/stdout are when launched not from a command line
750 // but by clicking on an icon...
751     if (!macApp &&
752         (!isatty(fileno(stdin)) || !isatty(fileno(stdout)))) xwindowed = 0;
753 // If I am using X11 as my GUI then I am happy to use remote access via
754 // SSH since I can be using X forwarding - provided DISPLAY is set all can
755 // be well. However on a Macintosh I do NOT want to launch a window if I
756 // have connected via ssh since I will not have the desktop forwarded.
757     {   const char *ssh = my_getenv("SSH_CLIENT");
758         if (ssh != nullptr && *ssh != 0)
759         {   FWIN_LOG("SSH_CLIENT set on MacOSX\n");
760 //          ssh_client = 1;
761             xwindowed = 0;
762         }
763     }
764     return xwindowed;
765 }
766 
767 #else // __APPLE__
768 
unix_and_osx_checks(int is_windowed)769 static int unix_and_osx_checks(int is_windowed)
770 {   const char *disp;
771 // If stdin or stdout is not from a "tty" I will run in non-windowed mode.
772 // This may help when the system is used in scripts. I worry a bit about
773 // what the status of stdin/stdout are when launched not from a command line
774 // but by clicking on an icon...
775     if (is_windowed != 0)
776     {   if (!isatty(fileno(stdin)) ||
777             !isatty(fileno(stdout))) is_windowed = 0;
778 // On Unix-like systems I will check the DISPLAY variable, and if it is not
779 // set I will suppose that I can not create a window. That case will normally
780 // arise when you have gained remote access to the system eg via telnet or
781 // ssh without X forwarding. I will also insist that if set it has a ":" in
782 // its value... that is to avoid trouble with it getting set to an empty
783 // string. Note that on a Macintosh when I am using the FOX GUI toolkit (as
784 // is the case here) I need X11 and hence DISPLAY. If I was using the Mac
785 // native display I would merely omit this test.
786         disp = my_getenv("DISPLAY");
787         if (disp == nullptr ||
788             std::strchr(disp, ':')==nullptr) is_windowed = 0;
789     }
790 // If stdin or stdout is not from a "tty" I will run in non-windowed mode.
791 // This may help when the system is used in scripts. I worry a bit about
792 // what the status of stdin/stdout are when launched not from a command line
793 // but by clicking on an icon...
794     if ((!isatty(fileno(stdin)) ||
795          !isatty(fileno(stdout)))) is_windowed = 0;
796 // On Unix-like systems I will check the DISPLAY variable, and if it is not
797 // set I will suppose that I can not create a window. That case will normally
798 // arise when you have gained remote access to the system eg via telnet or
799 // ssh without X forwarding. I will also insist that if set it has a ":" in
800 // its value... that is to avoid trouble with it getting set to an empty
801 // string.
802     disp = my_getenv("DISPLAY");
803     if (disp == nullptr ||
804         std::strchr(disp, ':')==nullptr) is_windowed = 0;
805     return is_windowed;
806 }
807 
808 #endif // __APPLE__
809 #endif // WIN32
810 #endif // PART_OF_FOX
811 
812 #ifndef EMBEDDED
813 
814 // The condition here is so that I can use fwin as a stand-alone interface
815 // other than as part of CSL/Reduce. This could be useful to somebody wanting
816 // to use it outside the CSL project
817 #if defined PART_OF_FOX || defined CSL
fwin_startup(int argc,const char * argv[],fwin_entrypoint * fwin_main)818 int fwin_startup(int argc, const char *argv[],
819                  fwin_entrypoint *fwin_main)
820 {
821 #else // defined PART_OF_FOX || defined CSL
822 int main(int argc, const char *argv[])
823 {   init_thread_locals();
824 #endif // defined PART_OF_FOX || defined CSL
825     int i;
826 // I want to know the path to the directory from which this
827 // code was launched. Note that in some cases the prints to stderr
828 // shown here will be totally ineffective and the code will just seem
829 // to exit abruptly. Eg that can be the situation if the version being run
830 // has been linked on Windows as a window-mode (as distinct from console-mode)
831 // binary, or if it is on a Macintosh not associated with a console. In such
832 // cases you will just need to debug the code even without a clue!
833     if (argc == 0)
834     {   std::fprintf(stderr,
835                      "argc == 0. You tried to launch the code in a funny way?\n");
836         return 1;
837     }
838     if ((i = find_program_directory(argv[0])) != 0)
839     {   std::fprintf(stderr,
840                      "Unable to identify program name and directory (%d)\n", i);
841         return 1;
842     }
843     texmacs_mode = false;
844 // An option "--my-path" just prints the path to the executable
845 // and stops. An option "--args" indicates that I should not look at any
846 // more arguments - they may be used by the program that is to be run.
847     for (i=1; i<argc; i++)
848     {   if (std::strcmp(argv[i], "--my-path") == 0)
849         {   std::printf("%s\n", programDir);
850             std::exit(0);  // Such a special case that I will exit this way.
851         }
852         else if (std::strcmp(argv[i], "--args") == 0) break;
853     }
854 
855 #ifdef PART_OF_FOX
856 // As the very first thing I will do, I will seek an argument
857 // that is just "-w", and if it is present record that I will want to
858 // run in text mode, not windowed mode. I also detected "--"
859 // and use it to flag up a request to run minimised.
860 // Note that "-w" takes precedence over "--" here...
861 //
862 // Well the fuller explanation of the options goes:
863 //     (none)               run in gui mode in "sensible" cases
864 //     -w   -w-   --nogui   run in console mode
865 //     -w+  --gui           run in gui mode if at all possible, fail otherwise.
866 //     -w.  --guimin        run in guie mode but start minimised.
867 //
868 // I run as a minimise window (by default) in the "--" case since I can use
869 // the window title-bar to report progress even when all output is directed to
870 // file.
871     windowed = 2;
872     for (i=1; i<argc; i++)
873     {   if (std::strcmp(argv[i], "--args") == 0) break;
874         else if (std::strcmp(argv[i], "--texmacs") == 0) texmacs_mode = true;
875         else if (std::strncmp(argv[i], "-w", 2) == 0)
876         {   if (argv[i][2] == '+') windowed = 1;
877             else if (argv[i][2] == '.') windowed = -1;
878             else windowed = 0;
879             break;
880         }
881         else if (std::strcmp(argv[i], "--gui") == 0) windowed  = 1;
882         else if (std::strcmp(argv[i], "--nogui") == 0) windowed  = 0;
883         else if (std::strcmp(argv[i], "--guimin") == 0) windowed  = -1;
884         else if (std::strcmp(argv[i], "-h") == 0 ||
885                  std::strcmp(argv[i], "-H") == 0)
886 #ifdef HAVE_LIBXFT
887             fwin_use_xft = 0;
888 #else // HAVE_LIBXFT
889             ; // Ignore "-h" option if Xft not available
890 #endif // HAVE_LIBXFT
891 // Note well that I detect just "--" as an entire argument here, so that
892 // extended options "--option" do not interfere.
893         else if (std::strcmp(argv[i], "--") == 0 &&
894                  windowed != 0) windowed = -1;
895     }
896     if (texmacs_mode) windowed = 0;
897 // If there had not been any command-line option to give direction
898 // about whether to run in a window I will use system-dependent
899 // schemes to try to decide what to do. The overall policy I want to
900 // follow is that if I have a graphical environment available I should
901 // use it. On an X11-based system this can usually be judged by
902 // looking for a DISPLAY environment variable. On both Windows and
903 // other systems if the application has been invoked from a pipe
904 // or using input (or output) redirection then that signals that it
905 // is expected to use stdin/stdout rather than a GUI.
906 #ifdef WIN32
907     windowed = windows_checks(windowed);
908 #else // WIN32
909     windowed = unix_and_osx_checks(windowed);
910 #endif // WIN32
911 #endif // PART_OF_FOX
912 
913 // REGARDLESS of any decisions about windowing made so for things can be
914 // forced by command line options.
915 //    -w+ forces an attempt to run in a window even if it looks as if that
916 //        would not make sense or would fail. It is mainly for debugging.
917 //    -w. forces use of a window, but starts it minimised.
918 //    -w  forces command-line rather than windowed use (can also write
919 //        "-w-" for this case).
920 // I also look for some other CSL-specific options that make me feel I
921 // should adjust behaviour:
922 //    --  All output will be going to a file. So if the program is to run in
923 //        windowed mode I will start it off minimised.
924 //    --texmacs   force the run NOT to try to create its own window,
925 //        because it is being invoked via a pipe from TeXmacs,
926     for (i=1; i<argc; i++)
927     {   if (std::strcmp(argv[i], "--args") == 0) break;
928         else if (std::strcmp(argv[i], "--texmacs") == 0) texmacs_mode = true;
929         else if (std::strncmp(argv[i], "-w", 2) == 0)
930         {   if (argv[i][2] == '+') windowed = 1;
931             else if (argv[i][2] == '.') windowed = -1;
932             else windowed = 0;
933             break;
934         }
935 // Note well that I detect just "--" as an entire argument here, so that
936 // extended options "--option" do not interfere.
937         else if (std::strcmp(argv[i], "--") == 0 &&
938                  windowed != 0) windowed = -1;
939     }
940     if (texmacs_mode) windowed = 0;
941 #if defined PART_OF_FOX && defined WIN32
942     sort_out_windows_console(windowed);
943 #endif // PART_OF_FOX && WIN32
944 
945 // Windowed or not, if there is an argument "-b" or "-bxxxx" then the
946 // string xxx will do something about screen colours. An empty string
947 // will suggest no colouring, the string "-" (as in -b-) whatever default
948 // I choose.
949     colour_spec = "-";
950     for (i=1; i<argc; i++)
951     {   if (std::strcmp(argv[i], "--args") == 0) break;
952         else if (std::strncmp(argv[i], "-b", 2) == 0)
953         {   colour_spec = argv[i]+2;
954             break;
955         }
956     }
957 
958 #ifdef __APPLE__
959     if (windowed != 0) mac_deal_with_application_bundle(argc, argv);
960 #endif // __APPLE__
961 
962 #ifdef PART_OF_FOX
963     if (windowed == 0) return plain_worker(argc, argv, fwin_main);
964     else return windowed_worker(argc, argv, fwin_main);
965 #else // PART_OF_FOX
966     return plain_worker(argc, argv, fwin_main);
967 #endif // PART_OF_FOX
968 }
969 
970 // SIGINT really ought not to happen, because when I am using a terminal
971 // I set it into raw mode, so ^C is treated as input not a request for an
972 // exception. However some external source could still signal me, so I will
973 // do what ^C would have.
974 
975 #ifdef HAVE_SIGACTION
976 void sigint_handler(int signo, siginfo_t *t, void *v)
977 #else // !HAVE_SIGACTION
978 void sigint_handler(int signo)
979 #endif // !HAVE_SIGACTION
980 {   if (async_interrupt_callback != nullptr) (
981             *async_interrupt_callback)(QUIET_INTERRUPT);
982 }
983 
984 #endif // !EMBEDDED
985 
986 int plain_worker(int argc, const char *argv[],
987                  fwin_entrypoint *fwin_main)
988 {
989 #ifndef EMBEDDED
990 // Even though these days I mostly intend ^C to be detected by observing
991 // it as a character read in raw mode, I probably need to support some
992 // external task explictly raising the signal. So I trap it here.
993 #ifdef HAVE_SIGACTION
994     struct sigaction sa;
995     sa.sa_sigaction = sigint_handler;
996     sigemptyset(&sa.sa_mask);
997     sa.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
998 // (a) restart system calls after signal (if possible),
999 // (b) use handler that gets more information,
1000 // (c) use alternative stack for the handler,
1001 // (d) leave the SIGINT unmasked while the handler is active.
1002     if (sigaction(SIGINT, &sa, nullptr) == -1)
1003         /* I can not thing of anything useful to do if I fail here! */;
1004 #else // !HAVE_SIGACTION
1005     std::signal(SIGINT, sigint_handler);
1006 #endif // !HAVE_SIGACTION
1007 #endif // !EMBEDDED
1008     TermSetup ts(argv[0], colour_spec);
1009     std::strcpy(fwin_prompt_string, "> ");
1010     int r = (*fwin_main)(argc, argv);
1011     return r;
1012 }
1013 
1014 
1015 #define INPUT_BUFFER_SIZE 100
1016 
1017 static const char *current_line;
1018 UNUSED_NAME static char input_buffer[INPUT_BUFFER_SIZE];
1019 static int chars_left = 0;
1020 UNUSED_NAME static int prompt_needed = 1;
1021 
1022 int fwin_plain_getchar()
1023 {   int ch;
1024     {   while (chars_left == 0)
1025         {   term_setprompt(fwin_prompt_string);
1026             current_line = term_getline();
1027             if (current_line == nullptr) return EOF;  // failed or EOF
1028             chars_left = std::strlen(current_line);
1029         }
1030     }
1031     chars_left--;
1032     ch = *current_line++;
1033     if (ch == (0x1f & 'D')) ch = EOF;
1034     return ch;
1035 }
1036 
1037 #ifndef PART_OF_FOX
1038 
1039 void fwin_restore()
1040 {
1041 }
1042 
1043 void fwin_putchar(int c)
1044 {
1045 // Despite using termed during keyboard input I will just use the
1046 // ordinary C stream functions for normal output. Provided I do an
1047 // fflush(stdout) before requesting input I should be OK.
1048 #ifdef __CYGWIN__
1049 // If I have built the system under Cygwin then we are running under
1050 // Windows. To keep files tidy I will (mostly) insert CRs at line-end
1051 // in case Cygwin does not...
1052     if (c == '\n') std::putchar('\r');
1053 #endif // __CYGWIN__
1054     std::putchar(c);
1055 }
1056 
1057 void fwin_puts(const char *s)
1058 {
1059 // See comment above where putchar() is used...
1060 #ifdef __CYGWIN__
1061     while (*s != 0) fwin_putchar(*s++);
1062 #else // __CYGWIN__
1063     std::puts(s);
1064 #endif // __CYGWIN__
1065 }
1066 
1067 
1068 void fwin_printf(const char *fmt, ...)
1069 {   std::va_list a;
1070     va_start(a, fmt);
1071 // See comment above where putchar() is used...
1072 #ifdef __CYGWIN__
1073 // NOT reconstructed yet @@@
1074     std::vfprintf(stdout, fmt, a);
1075 #else // __CYGWIN__
1076     std::vfprintf(stdout, fmt, a);
1077 #endif // __CYGWIN__
1078     va_end(a);
1079 }
1080 
1081 void fwin_vfprintf(const char *fmt, std::va_list a)
1082 {
1083 // See comment above where putchar() is used...
1084 #ifdef __CYGWIN__
1085 // Not reconstructed yet @@@
1086     std::vfprintf(stdout, fmt, a);
1087 #else // __CYGWIN__
1088     std::vfprintf(stdout, fmt, a);
1089 #endif // __CYGWIN__
1090 }
1091 
1092 void fwin_ensure_screen()
1093 {   std::fflush(stdout);
1094 }
1095 
1096 void fwin_report_left(const char *s)
1097 {
1098 }
1099 
1100 void fwin_report_mid(const char *s)
1101 {
1102 }
1103 
1104 void fwin_report_right(const char *s)
1105 {
1106 }
1107 
1108 atomic<bool> mustQuit(false);
1109 
1110 int fwin_getchar()
1111 {   return fwin_plain_getchar();
1112 }
1113 
1114 
1115 void fwin_set_prompt(const char *s)
1116 {   std::strncpy(fwin_prompt_string, s, sizeof(fwin_prompt_string));
1117     fwin_prompt_string[sizeof(fwin_prompt_string)-1] = 0;
1118     term_setprompt(fwin_prompt_string);
1119 }
1120 
1121 void fwin_menus(char **modules, char **switches,
1122                 review_switch_settings_function *f)
1123 {
1124 }
1125 
1126 void fwin_refresh_switches(char **switches, char **packages)
1127 {
1128 }
1129 
1130 void fwin_set_help_file(const char *key, const char *path)
1131 {
1132 }
1133 
1134 void fwin_acknowledge_tick()
1135 {
1136 }
1137 
1138 int fwin_windowmode()
1139 {   return 0;
1140 }
1141 
1142 #endif // PART_OF_FOX
1143 
1144 int get_current_directory(char *s, size_t n)
1145 {   if (getcwd(s, n) == 0)
1146     {   switch(errno)
1147         {   case ERANGE: return -2; // negative return value flags an error.
1148             case EACCES: return -3;
1149             default:     return -4;
1150         }
1151     }
1152     else return std::strlen(s);
1153 }
1154 
1155 // The next procedure is responsible for establishing information about
1156 // both the "short-form" name of the program launched and the directory
1157 // it was found in. This latter directory may be a good place to keep
1158 // associated resources.
1159 //
1160 // The way of finding the information concerned differs between Windows and
1161 // Unix/Linux, as one might expect.
1162 //
1163 // return non-zero value if failure.
1164 
1165 const char *fullProgramName        = "./fwin.exe";
1166 const char *programName            = "fwin.exe";
1167 const char *programDir             = ".";
1168 
1169 #ifdef WIN32
1170 
1171 static char this_executable[LONGEST_LEGAL_FILENAME];
1172 
1173 int find_program_directory(const char *argv0)
1174 {   char *w, *w1;
1175     char ww[LONGEST_LEGAL_FILENAME];
1176     int len, ndir, npgm;
1177 // In older code I believed that I could rely on Windows giving me
1178 // the full path of my executable in argv[0]. With bits of mingw/cygwin
1179 // anywhere near me that may not be so, so I grab the information directly
1180 // from the Windows APIs. Except that that turns out to be no good for
1181 // a scheme I have that chains to an executable so it can pick which
1182 // variant to use, so if argv0 looks like a fully rooted windows path
1183 // I will use it!
1184     std::strcpy(this_executable, argv0);
1185 // In argv0 was enclosed in single or double quotes then remove them.
1186     if (this_executable[0]=='\'')
1187     {   for (unsigned int i=0;; i++)
1188         {   int c = this_executable[i+1];
1189             if (c == '\'') c = 0;
1190             this_executable[i] = c;
1191             if (c == 0) break;
1192         }
1193     }
1194     if (this_executable[0]=='"')
1195     {   for (unsigned int i=0;; i++)
1196         {   int c = this_executable[i+1];
1197             if (c == '"') c = 0;
1198             this_executable[i] = c;
1199             if (c == 0) break;
1200         }
1201     }
1202      if (!(std::isalpha(this_executable[0]) &&
1203           this_executable[1] == ':' &&
1204           this_executable[2] == '\\'))
1205     {   GetModuleFileName(nullptr, this_executable,
1206                           LONGEST_LEGAL_FILENAME-2);
1207         argv0 = this_executable;
1208     }
1209     std::strncpy(ww, this_executable, sizeof(ww));
1210     ww[sizeof(ww)-1] = 0;
1211     w = ww;
1212 // I turn every "\" into a "/". This make for better uniformity with other
1213 // platforms.
1214     while (*w != 0)
1215     {   if (*w == '\\') *w = '/';
1216         w++;
1217     }
1218     programNameDotCom = false;
1219     if (ww[0] == 0)      // should never happen - name is empty string!
1220     {   programDir = ".";
1221         programName = "fwin";  // nothing really known!
1222         fullProgramName = "./fwin.exe";
1223         return 0;
1224     }
1225 
1226     w = new (std::nothrow) char[1+std::strlen(ww)];
1227     if (w == nullptr) return 5;           // 5 = new fails
1228     std::strcpy(w, ww);
1229     fullProgramName = w;
1230     len = std::strlen(ww);
1231 // If the current program is called c:/aaa/xxx.exe, then the directory
1232 // is just c:/aaa and the simplified program name is just xxx
1233 // Similarly if the name is xxx.js I want to strip off the ".js".
1234     if (len > 3 &&
1235         w[len-3] == '.' &&
1236         (std::tolower(w[len-2]) == 'j' &&
1237          std::tolower(w[len-1]) == 's'))
1238     {   programNameDotCom = false;
1239         len -= 3;
1240         w[len] = 0;
1241     }
1242     else if (len > 4 &&
1243         w[len-4] == '.' &&
1244         ((std::tolower(w[len-3]) == 'e' &&
1245           std::tolower(w[len-2]) == 'x' &&
1246           std::tolower(w[len-1]) == 'e') ||
1247          (std::tolower(w[len-3]) == 'c' &&
1248           std::tolower(w[len-2]) == 'o' &&
1249           std::tolower(w[len-1]) == 'm')))
1250     {   programNameDotCom = (std::tolower(w[len-3]) == 'c');
1251         len -= 4;
1252         w[len] = 0;
1253     }
1254 // I will strip any "win" prefix from the application name and also any
1255 // "32" suffix.
1256     w1 = w;
1257     if (std::strlen(w) > 2)
1258     {   w += std::strlen(w) - 2;
1259         if (w[0] == '3' && w[1] == '2') w[0] = 0;
1260     }
1261     w = w1;
1262     while (*w != 0) w++;
1263     while (w != w1 && *w != '/'  && *w != '\\') w--;
1264     if (*w == '/' || *w == '\\') w++;
1265     if (std::strncmp(w, "win", 3) == 0)
1266     {   char *w2 = w + 3;
1267         while (*w2 != 0) *w++ = *w2++;
1268         *w = 0;
1269     }
1270     for (npgm=0; npgm<len; npgm++)
1271     {   int c = fullProgramName[len-npgm-1];
1272         if (c == '/') break;
1273     }
1274     ndir = len - npgm - 1;
1275     if (ndir < 0) programDir = ".";  // none really visible
1276     else
1277     {   if ((w = new (std::nothrow) char[ndir+1]) == nullptr) return 1;
1278         std::strncpy(w, fullProgramName, ndir);
1279         w[ndir] = 0;
1280         programDir = w;
1281     }
1282     if ((w = new (std::nothrow) char[npgm+1]) == nullptr) return 1;
1283     std::strncpy(w, fullProgramName + len - npgm, npgm);
1284     w[npgm] = 0;
1285     programName = w;
1286     return 0;
1287 }
1288 
1289 #else // WIN32
1290 
1291 // Different systems put or do not put underscores in front of these
1292 // names. My adaptation here should give me a chance to work whichever
1293 // way round it goes.
1294 
1295 #ifndef S_IFMT
1296 # ifdef __S_IFMT
1297 #  define S_IFMT __S_IFMT
1298 # endif
1299 #endif // S_IFMT
1300 
1301 #ifndef S_IFDIR
1302 # ifdef __S_IFDIR
1303 #  define S_IFDIR __S_IFDIR
1304 # endif
1305 #endif // S_IFDIR
1306 
1307 #ifndef S_IFREG
1308 # ifdef __S_IFREG
1309 #  define S_IFREG __S_IFREG
1310 # endif
1311 #endif // S_IFREG
1312 
1313 #ifndef S_ISLNK
1314 # ifdef S_IFLNK
1315 #  ifdef S_IFMT
1316 #   define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
1317 #  endif
1318 # endif
1319 #endif // S_ISLNK
1320 
1321 // I will not take any action at all to deal with UTF-8 or Unicode issues
1322 // in filenames or paths. Indeed most of Linux and certainly most of my
1323 // code will risk terribly confusion with various perfectly ordinary
1324 // 7-bit characters such as blank (' ') within filenames, so the issue
1325 // of international alphabets there is something I will not really fuss
1326 // about yet.
1327 
1328 int find_program_directory(const char *argv0)
1329 {   char pgmname[LONGEST_LEGAL_FILENAME];
1330     const char *w;
1331     char *w1;
1332     int n, n1;
1333     std::memset(pgmname, 0, sizeof(pgmname));
1334 // If the main reduce executable is has a full path-name /xxx/yyy/zzz then
1335 // I will use /xxx/yyy as its directory To find this I need to find the full
1336 // path for the executable. I ATTEMPT to follow the behaviour of "sh",
1337 // "bash" and "csh".  But NOTE WELL that if anybody launches this code in
1338 // an unusual manner (eg using an "exec" style function) that could confuse
1339 // me substantially. What comes in via argv[0] is typically just the final
1340 // component of the program name - what I am doing here is scanning to
1341 // see what path it might have corresponded to.
1342 //
1343 // If the name of the executable starts with a "/" it is already an
1344 // absolute path name. I believe that if the user types (to the shell)
1345 // something like $DIR/bin/$PGMNAME or ~user/subdir/pgmname then the
1346 // environment variables and user-name get expanded out by the shell before
1347 // the command is actually launched.
1348     if (argv0 == nullptr ||
1349         argv0[0] == 0) // Information not there - return
1350     {   programDir = (const char *)"."; // some sort of default.
1351         programName = (const char *)"fwin";
1352         fullProgramName = (const char *)"./fwin";
1353         return 0;
1354     }
1355 // I will treat 3 cases here
1356 // (a)   /abc/def/ghi      fully rooted: already an absolute name;
1357 // (b)   abc/def/ghi       treat as ./abc/def/ghi;
1358 // (c)   ghi               scan $PATH to see where it may have come from.
1359     else if (argv0[0] == '/') fullProgramName = argv0;
1360     else
1361     {   for (w=argv0; *w!=0 && *w!='/'; w++) {}   // seek a "/"
1362         if (*w == '/')      // treat as if relative to current dir
1363         {   // If the thing is actually written as "./abc/..." then
1364             // strip of the initial "./" here just to be tidy.
1365             if (argv0[0] == '.' && argv0[1] == '/') argv0 += 2;
1366             n = get_current_directory(pgmname, sizeof(pgmname));
1367             if (n < 0) return 1;    // fail! 1=current directory failure
1368             if (n + std::strlen(argv0) + 2 >= sizeof(pgmname) ||
1369                 pgmname[0] == 0)
1370                 return 2; // Current dir unavailable or full name too long
1371             else
1372             {   pgmname[n] = '/';
1373                 std::strcpy(&pgmname[n+1], argv0);
1374                 fullProgramName = pgmname;
1375             }
1376         }
1377         else
1378         {   const char *path = my_getenv("PATH");
1379 // I omit checks for names of shell built-in functions, since my code is
1380 // actually being executed by here. So I get my search path and look
1381 // for an executable file somewhere on it. I note that the shells back this
1382 // up with hash tables, and so in cases where "rehash" might be needed this
1383 // code may become confused.
1384             struct stat buf;
1385             uid_t myuid = geteuid(), hisuid;
1386             gid_t mygid = getegid(), hisgid;
1387             int protection;
1388             int ok = 0;
1389 // I expect $PATH to be a sequence of directories with ":" characters to
1390 // separate them. I suppose it COULD be that somebody used directory names
1391 // that had embedded colons, and quote marks or escapes in $PATH to allow
1392 // for that. In such case this code will just fail to cope.
1393             if (path != nullptr)
1394             {   while (*path != 0)
1395                 {   while (*path == ':') path++; // skip over ":"
1396                     n = 0;
1397                     while (*path != 0 && *path != ':')
1398                     {   pgmname[n++] = *path++;
1399                         if (n > static_cast<int>(sizeof(pgmname)-3-std::strlen(argv0)))
1400                             return 3; // fail! 3=$PATH element overlong
1401                     }
1402 // Here I have separated off the next segment of my $PATH and put it at
1403 // the start of pgmname. Observe that to avoid buffer overflow I
1404 // exit abruptly if the entry on $PATH is itself too big for my buffer.
1405                     pgmname[n++] = '/';
1406                     std::strcpy(&pgmname[n], argv0);
1407 // see if the file whose name I have just built up exists at all.
1408                     if (stat(pgmname, &buf) == -1) continue;
1409                     hisuid = buf.st_uid;
1410                     hisgid = buf.st_gid;
1411                     protection = buf.st_mode; // info about the file found
1412 // I now want to check if there is a file of the right name that is
1413 // executable by the current (effective) user.
1414                     if (protection & S_IXOTH ||
1415                         (mygid == hisgid && protection & S_IXGRP) ||
1416                         (myuid == hisuid && protection & S_IXUSR))
1417                     {   ok = 1;   // Haha - I have found the one we ...
1418                         break;    // are presumably executing!
1419                     }
1420                 }
1421             }
1422             if (!ok) return 4;    // executable not found via $PATH
1423 // Life is not yet quite easy! $PATH may contain some items that do not
1424 // start with "/", ie that are still local paths relative to the
1425 // current directory. I want to be able to return an absolute fully
1426 // rooted path name! So unless the item we have at present starts with "/"
1427 // I will stick the current directory's location in front.
1428             if (pgmname[0] != '/')
1429             {   char temp[LONGEST_LEGAL_FILENAME];
1430                 std::memset(temp, 0, sizeof(temp));
1431                 std::strcpy(temp, pgmname);
1432                 n = get_current_directory(pgmname, sizeof(pgmname));
1433                 if (n < 0) return 1;    // fail! 1=current directory failure
1434                 if ((n + std::strlen(temp) + 1) >= sizeof(pgmname)) return 9;
1435                 pgmname[n++] = '/';
1436                 std::strcpy(&pgmname[n], temp);
1437             }
1438             fullProgramName = pgmname;
1439         }
1440     }
1441 // Now if I have a program name I will try to see if it is a symbolic link
1442 // and if so I will follow it.
1443     {   struct stat buf;
1444         char temp[LONGEST_LEGAL_FILENAME];
1445         std::memset(temp, 0, sizeof(temp));
1446         if (lstat(fullProgramName, &buf) != -1 &&
1447             S_ISLNK(buf.st_mode) &&
1448             (n1 = readlink(fullProgramName,
1449                            temp, sizeof(temp)-1)) > 0)
1450         {   temp[n1] = 0;
1451             std::strcpy(pgmname, temp);
1452             fullProgramName = pgmname;
1453         }
1454     }
1455 // Now fullProgramName is set up, but may refer to an array that
1456 // is stack allocated. I need to make it proper!
1457     w1 = new (std::nothrow) char[1+std::strlen(fullProgramName)];
1458     if (w1 == nullptr) return 5;           // 5 = new fails
1459     std::strcpy(w1, fullProgramName);
1460     fullProgramName = w1;
1461     size_t len = std::strlen(w1);
1462     if (len > 3 &&
1463         w1[len-3] == '.' &&
1464         (std::tolower(w1[len-2]) == 'j' &&
1465          std::tolower(w1[len-1]) == 's'))
1466     {
1467 #ifdef WIN32
1468         programNameDotCom = false;
1469 #endif
1470         len -= 3;
1471         w1[len] = 0;
1472     }
1473 #ifdef __CYGWIN__
1474 // Now if I built on raw cygwin I may have an unwanted ".com" or ".exe"
1475 // suffix, so I will purge that! This code exists here because the raw
1476 // cygwin build has a somewhat schitzo view as to whether it is a Windows
1477 // or a Unix-like system. When I am using raw cygwin I am really not
1478 // living in a Windows world.
1479     else if (len > 4)
1480     {   char *w2 = w1 + len - 4;
1481         if (w2[0] == '.' &&
1482             ((std::tolower(static_cast<unsigned char>(w2[1])) == 'e' &&
1483               std::tolower(static_cast<unsigned char>(w2[2])) == 'x' &&
1484               std::tolower(static_cast<unsigned char>(w2[3])) == 'e') ||
1485              (std::tolower(static_cast<unsigned char>(w2[1])) == 'c' &&
1486               std::tolower(static_cast<unsigned char>(w2[2])) == 'o' &&
1487               std::tolower(static_cast<unsigned char>(w2[3])) == 'm'))) w2[0] = 0;
1488     }
1489     if (len > 2)
1490     {   char *w2 = w1 + len - 2;
1491         if (w2[0] == '3' && w2[1] == '2') w2[0] = 0;
1492     }
1493 // If I am building a cygwin version I will remove any prefix
1494 // "cygwin-", "cygwin64-" or "win" from the front of the name of the
1495 // executable and also any "32" suffix.
1496     while (*w1 != 0) w1++;
1497     while (w1 != fullProgramName && *w1 != '/'  && *w1 != '\\') w1--;
1498     if (*w1 == '/' || *w1 == '\\') w1++;
1499     if (std::strncmp(w1, "cygwin-", 7) == 0)
1500     {   char *w2 = w1 + 7;
1501         while (*w2 != 0) *w1++ = *w2++;
1502         *w1 = 0;
1503     }
1504     else if (std::strncmp(w1, "cygwin64-", 9) == 0)
1505     {   char *w2 = w1 + 9;
1506         while (*w2 != 0) *w1++ = *w2++;
1507         *w1 = 0;
1508     }
1509     if (std::strncmp(w1, "win", 3) == 0)
1510     {   char *w2 = w1 + 3;
1511         while (*w2 != 0) *w1++ = *w2++;
1512         *w1 = 0;
1513     }
1514 #endif // __CYGWIN__
1515 // OK now I have the full name, which is of the form
1516 //   abc/def/fgi/xyz
1517 // and I need to split it at the final "/" (and by now I very fully expect
1518 // there to be at least one "/".
1519     for (n=std::strlen(fullProgramName)-1; n>=0; n--)
1520         if (fullProgramName[n] == '/') break;
1521     if (n < 0) return 6;               // 6 = no "/" in full file path
1522     w1 = new (std::nothrow) char[n+1];
1523     if (w1 == nullptr) return 7;           // 7 = new fails
1524     std::strncpy(w1, fullProgramName, n);
1525     w1[n] = 0;
1526 // Note that if the executable was "/foo" then programDir will end up as ""
1527 // so that programDir + "/" + programName works out properly.
1528     programDir = w1;
1529     n1 = std::strlen(fullProgramName) - n;
1530     w1 = new (std::nothrow) char[n1];
1531     if (w1 == nullptr) return 8;           // 8 = new fails
1532     std::strncpy(w1, fullProgramName+n+1, n1-1);
1533     w1[n1-1] = 0;
1534     programName = w1;
1535     return 0;                          // whew!
1536 }
1537 
1538 #endif // WIN32
1539 
1540 
1541 #ifndef S_IRUSR
1542 #ifdef __S_IRUSR
1543 #define S_IRUSR __S_IRUSR
1544 #endif // __S_IRUSR
1545 #endif // S_IRUSR
1546 
1547 #ifndef S_IWUSR
1548 #ifdef __S_IWUSR
1549 #define S_IWUSR __S_IWUSR
1550 #endif // __S_IWUSR
1551 #endif // S_IWUSR
1552 
1553 #ifndef S_IXUSR
1554 #ifdef __S_IXUSR
1555 #define S_IXUSR __S_IXUSR
1556 #endif // __S_IXUSR
1557 #endif // S_IXUSR
1558 
1559 extern int get_home_directory(char *b, size_t len);
1560 extern int get_users_home_directory(char *b, size_t len);
1561 
1562 static lookup_function *look_in_variable = nullptr;
1563 
1564 void fwin_set_lookup(lookup_function *f)
1565 {   look_in_variable = f;
1566 }
1567 
1568 void process_file_name(char *filename, const char *old, size_t n)
1569 // This procedure maps filenames by expanding some environment
1570 // variables.  It is very thoroughly system specific, which is why it
1571 // is in this file.  See also LONGEST_LEGAL_FILENAME in "tags.h" for a
1572 // limit on the permitted size of an expanded filename.
1573 // The input (old) is not necessarily properly terminated as a C string,
1574 // so n says how many characters to inspect.  Build a converted name
1575 // in filename.
1576 // At present the expansions I allow are:
1577 //
1578 //    $xxx   (terminated by '.', '/' or '\' with at least one char x)
1579 //    ${xxx} (self-terminating)
1580 //           First check for a Lisp variable @xxx. If this is set (and is
1581 //           a string or a symbol) then its value is used. If not then
1582 //           next inspect the environment variable xxx and dump its
1583 //           value into the output.  If the variable is unset then a check
1584 //           is made for the value of a global lisp variable called $xxx,
1585 //           and if that exists and is a string or symbol it is used.
1586 //           If $xxx is undefined a null string is inserted.
1587 //           If one of the variables is defined but has an improper value
1588 //           then the whole file-translation fails.
1589 //           The use of two Lisp variables makes it possible to control
1590 //           precedence between these and shell variables.
1591 //           At one stage I make the search order $xxx, env, @xxx, but then
1592 //           in shell scripts it is easier to go "-D@xxx=..." because
1593 //           "-D$xxx=..." tends to get subject to shell expansion. So now
1594 //           I give priority to the version I use most, to avoid being bitten
1595 //           when somebody has a stray shell variable set.
1596 //
1597 //    ~      ) followed by '.', '/' or '\'
1598 //    ~xxx   )
1599 //           On Unix these try to find home directories using
1600 //           getpwuid(getuid()) for '~' and getpwnam() for ~xxx.
1601 //           If that fails ~ expands into nothing at all.
1602 //           This syntax is only recognised at the very start of a file-name.
1603 //           For systems other than Unix this syntax will not be useful and
1604 //           should be avoided, however as an experimental place-holder I
1605 //           may do things with environment variables called HOME etc.
1606 //
1607 // I convert file-names of the form aaa/bbb/ccc.ddd into something
1608 // acceptable to the system being used, even though this may result in
1609 // some native file titles that include '/' characters becoming unavailable.
1610 // The reasoning here is that scripts and programs can then use Unix-like
1611 // names and non-Unix hosts will treat them forgivingly.
1612 //
1613 {   int i;
1614     int c;
1615     char *o;
1616     if (n == 0)
1617     {   *filename = 0;
1618         return;    // deem zero-length name to be illegal
1619     }
1620     o = filename;
1621     c = *old;
1622 // First I deal with a leading "~"
1623     if (c == '~')
1624     {   old++;
1625         n--;
1626         while (n != 0)
1627         {   c = *old;
1628             if (c == '.' || c == '/' || c == '\\') break;
1629             old++;
1630             n--;
1631             *o++ = static_cast<char>(c);
1632         }
1633         *o = 0;
1634 // actually deciding what the home directory is is passed down to a
1635 // system-specific call, but it is not to be relied upon especially
1636 // on personal computers.
1637         if (o == filename)  // '~' on its own
1638         {   get_home_directory(filename, LONGEST_LEGAL_FILENAME);
1639             o = filename + std::strlen(filename);
1640         }
1641         else
1642         {   get_users_home_directory(filename, LONGEST_LEGAL_FILENAME);
1643             o = filename + std::strlen(filename);
1644         }
1645     }
1646 // Having copies a user-name across (if there was one) I now copy the
1647 // rest of the file-name, expanding $xxx and ${xxx} as necessary.
1648     while (n != 0)
1649     {   c = *old++;
1650         n--;
1651 // If I find a "$" that is either at the end of the file-name or that is
1652 // immediately followed by ".", "/" or "\" then I will not use it for
1653 // parameter expansion. This at least gives me some help with the RISCOS
1654 // file-name $.abc.def where the "$" is used to indicate the root of the
1655 // current disc. Well RISCOS is no longer supported here so this does
1656 // not worry me a lot!
1657         if (c == '$' && n != 0 &&
1658             (c = *old) != '.' && c != '/' && c != '\\')
1659         {   char *p = o;
1660             const char *w;
1661 // I collect the name of the parameter at the end of my file-name buffer,
1662 // but will over-write it later on when I actually do the expansion.
1663             if (c == '{')
1664             {   old++;
1665                 n--;
1666                 while (n != 0)
1667                 {   c = *old++;
1668                     n--;
1669                     if (c == '}') break;
1670                     *p++ = static_cast<char>(c);
1671                 }
1672             }
1673             else
1674             {   while (n != 0)
1675                 {   c = *old;
1676                     if (c == '.' || c == '/' || c == '\\') break;
1677                     old++;
1678                     n--;
1679                     *p++ = static_cast<char>(c);
1680                 }
1681             }
1682             *p = 0;
1683             i = std::strlen(o) + 2;
1684             while (i-- != 0) o[i] = o[i-1];
1685             if (look_in_variable != nullptr &&
1686                 (p = (*look_in_variable)(o, '@')) != nullptr &&
1687                 p != o) o = p;
1688             else if ((w = my_getenv(o+1)) != nullptr)   // Shell variable?
1689             {   std::strcpy(o, w);
1690                 o = o + std::strlen(o);
1691             }
1692             else if (look_in_variable != nullptr &&
1693                      (p = (*look_in_variable)(o, '$')) != nullptr)
1694                 o = p;
1695             else
1696             {   *filename = 0;  // return reporting failure
1697                 return;
1698             }
1699         }
1700         else *o++ = static_cast<char>(c);
1701     }
1702     *o = 0;
1703 #ifdef WIN32
1704 // Now the filename has had $ and ~ prefix things expanded - I "just"
1705 // need to deal with sub-directory representation issues. Specifically I need
1706 // to map "/" separators into "\" so that if a user presents a file
1707 // name such as aaa/bbb/ccc.d it gets passed to the operating system
1708 // as aaa\bbb\ccc.d
1709 // Note that I enable this code under the heading MS_DOS but really it
1710 // means any file-system (eg Windows too) that uses "\" as its main
1711 // directory separator character.
1712 // As of September 2004 I will also map an intial sequence
1713 //         /cygdrive/x/
1714 // onto    x:\ (ie the Windows style path)
1715 
1716     if (std::strncmp(filename, "/cygdrive/", 10) == 0 &&
1717         filename[11] == '/')
1718     {   char *p = filename+2, *tail = filename+11;
1719         filename[0] = filename[10];
1720         filename[1] = ':';
1721         while (*tail != 0) *p++ = *tail++;
1722         *p = 0;
1723     }
1724 // I map "/" characters in MSDOS filenames into "\" so that users
1725 // can give file names with Unix-like slashes as separators if they want.
1726 // People who WANT to use filenames with '/' in them will be hurt.
1727     {   int j;
1728         char *tail = filename;
1729         while ((j = *tail) != 0)
1730         {   if (j == '/') *tail = '\\';
1731             tail++;
1732         }
1733 // stat and friends do not like directories referred to as "\foo\", so check
1734 // for a trailing slash, being careful to respect directories with names
1735 // like "\" and "a:\".
1736         j = std::strlen(filename);
1737         if (j > 0 && j != 1 && !(j == 3 && *(filename+1) == ':'))
1738         {   if ( (*(tail - 1) == '\\')) *(tail - 1) = 0;
1739         }
1740     }
1741 #else // WIN32
1742 #if defined __APPLE__ && !defined EMBEDDED
1743 // For MacOS the issue of "aliases" arises. The "preferred" file system
1744 // is HFS+ and that supports both links and aliases, but at the very least
1745 // some old users and legacy applications will certainly continue to use
1746 // links. However the Posix-style APIs do not provide any way to deal with
1747 // them! So here I used to have some Carbon calls to map a path to an
1748 // alias into a path to the file it refers to. This code was requested by
1749 // Thomas Sturm who provided a skeleton chunk of code showing what APIs
1750 // needed to be used and references to the documentation to them, so thanks
1751 // are due.
1752 // Unfortunately Apple have now deprecated the APIs used here, and because
1753 // they now did that quite a long while ago I have removed the code - which
1754 // when present led to compile-time warnings about its status.
1755 // So now Mac aliases are not supported and symbolic or hard links should
1756 // be used instead!
1757 // In a further while I intend to remove this comment as well as the code
1758 // that used to come with it.
1759 #endif // __APPLE__
1760 #endif // WIN32
1761 }
1762 
1763 // datestamps that I store away have given me significant
1764 // trouble with regard to portability - so now I deal with times by
1765 // talking to the system in terms of broken down local time (struct tm).
1766 // I then pack things up for myself to get 32-bit timestamps. The
1767 // encoding I use aims at simplicity - it treats all months as 31 days
1768 // and thus does not have to worry about leap years etc.  The effect will be
1769 // rather as if dates were stored as strings. And MAYBE I thereby avoid
1770 // some of the oddities that arise when data files containing packed dates
1771 // are transported across time-zones.
1772 //
1773 // NOTE: dates here are based from 1970, and this will lead to overflow
1774 // beyond 32-bit signed offsets in January 2038. At the time of writing that
1775 // is around some years ahead, and I intend not to worry. Note it is important here to
1776 // us an unsigned number or else the overflow is sooner and might even cause
1777 // genuine pain! But by using unsigned values I put myself in a situation
1778 // where I can not talk about dates earlier then 1970, but I can look
1779 // forward to around 2099 and that does seem far enough ahead to be not much
1780 // of a problem.
1781 //
1782 // ANOTHER NOTE: I only allow the "seconds" field to run from 0 to 59.
1783 // In consequence I am quite possibly going to mess up when there are
1784 // leap seconds, and this confusion could make times processed here
1785 // disagree across systems by up to the number of leap seconds that
1786 // have been used to date. Well I have quite severe doubts about time
1787 // agreement closer than a few seconds anyway and so again I am going to
1788 // ignore this oddity! But those who keep systems synchronised at a
1789 // millisecond or microsecond resolution (GPS anybody?) might need to
1790 // know I have been sloppy.
1791 
1792 void unpack_date(unsigned long int r,
1793                  int *year, int *mon, int *day,
1794                  int *hour, int *min, int *sec)
1795 {   *sec  = r%60; r = r/60;
1796     *min  = r%60; r = r/60;
1797     *hour = r%24; r = r/24;
1798     *day  = r%32; r = r/32;
1799     *mon  = r%12; r = r/12;
1800 // Please note that the Standard C "struct tm" structure specifies dates
1801 // in terms of years since 1900. Thus from the year 2000 on the year will
1802 // be a value of at least 100, but that is not supposed to be any special
1803 // cause of disaster. In particular the calculation involving "+70"
1804 // rather than "+1970" is NOT a bug here!
1805     *year = 70+r;
1806 }
1807 
1808 unsigned long int pack_date(int year, int mon, int day,
1809                             int hour, int min, int sec)
1810 {   unsigned long int r = (year-70)*12 + mon;
1811     r = r*32 + day;
1812     r = r*24 + hour;
1813     r = r*60 + min;
1814     return r*60 + sec;
1815 }
1816 
1817 // getenv() is a mild pain: Windows seems
1818 // to have a strong preference for upper case names.  To allow for
1819 // all this I do not call getenv() directly but go via the following
1820 // code that can patch things up.
1821 
1822 const char *my_getenv(const char *s)
1823 {
1824 #ifdef WIN32
1825     char uppercasename[LONGEST_LEGAL_FILENAME];
1826     char *p = uppercasename;
1827     int c;
1828     std::memset(uppercasename, 0, sizeof(uppercasename));
1829     while ((c = *s++) != 0) *p++ = std::toupper(c);
1830     *p = 0;
1831     return std::getenv(uppercasename);
1832 #else // WIN32
1833     return std::getenv(s);
1834 #endif // WIN32
1835 }
1836 
1837 
1838 int my_system(const char *s)
1839 {   return std::system(s);
1840 }
1841 
1842 #define DO_NOT_USE_GETUID 1   // For MinGW
1843 
1844 #ifndef DO_NOT_USE_GETUID
1845 // "machine.h" should set DO_NOT_USE_GETUID if that function is not
1846 // properly available. Not having it will make the treatment of
1847 // (eg) "~xxx/..." in filenames less satisfactory.
1848 
1849 #include <pwd.h>
1850 
1851 int get_home_directory(char *b, size_t len)
1852 {   int i;
1853     struct passwd *pw = getpwuid(getuid());
1854     std::strcpy(b, pw->pw_dir);
1855     i = std::strlen(b);
1856 // Here the directory handed back has "/" forced in as its final character
1857     if (b[i-1] != '/')
1858     {   b[i++] = '/';
1859         b[i] = 0;
1860     }
1861     return i;
1862 }
1863 
1864 int get_users_home_directory(char *b, size_t len)
1865 {   struct passwd *pw = getpwnam(b);
1866     if (pw != nullptr) std::strcpy(b, pw->pw_dir);
1867     else std::strcpy(b,
1868                          ".");    // use current directory if getpwnam() fails
1869     return std::strlen(b);
1870 }
1871 
1872 #else // DO_NOT_USE_GETUID
1873 
1874 int get_home_directory(char *b, size_t len)
1875 {   size_t i;
1876     const char *s =
1877         std::getenv("HOME"); // Probably works with most shells
1878     if ((i = std::strlen(s)) > len) s = "~";
1879     std::strcpy(b, s);
1880     if ( b[i-1] != '/')
1881     {   b[i++] = '/';
1882         b[i] = 0;
1883     }
1884     return i;
1885 }
1886 
1887 int get_users_home_directory(char *b, size_t len)
1888 {   static_cast<void>(len);
1889     std::strcpy(b,
1890                 ".");    // use current directory if getpwnam() no available
1891     return 1;
1892 }
1893 
1894 #endif // DO_NOT_USE_GUID
1895 
1896 typedef void filescan_function(string name, string leafname,
1897                                int why, long int size);
1898 
1899 // On a Macintosh it seems that __cpp_lib_filesystem can be defined without
1900 // needing to #include <filesystem> and in cases when code that uses
1901 // std::filesystem will then not even compile!
1902 
1903 #if defined __cpp_lib_filesystem && !defined FILESYSTEM_NOT_USABLE
1904 
1905 // Here is some code thar uses std::filesystem, or that might be able to!
1906 // If there are functions here that could not exploit the C++17 facilities
1907 // then they can be moved outside the scope of the #ifdef.
1908 
1909 #ifdef WIN32
1910 
1911 #include "windows.h"
1912 
1913 int Cmkdir(const char *name)
1914 {   SECURITY_ATTRIBUTES s;
1915     s.nLength = sizeof(s);
1916     s.lpSecurityDescriptor = nullptr;
1917     s.bInheritHandle = FALSE;
1918     return CreateDirectory(name, &s);
1919 }
1920 
1921 int truncate_file(std::FILE *f, long int where)
1922 {   if (std::fflush(f) != 0) return 1;
1923 #ifdef __CYGWIN__
1924     if (std::fflush(f) != 0) return 1;
1925     return ftruncate(fileno(f), where);  // Returns zero if success
1926 #else // __CYGWIN__
1927     return chsize(fileno(f), where);    // Returns zero if success
1928 #endif // __CYGWIN__
1929 }
1930 
1931 void set_filedate(char *name, unsigned long int datestamp,
1932                   unsigned long int filetype)
1933 {   HANDLE h = CreateFile(name, GENERIC_WRITE, 0, nullptr,
1934                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
1935     SYSTEMTIME st;
1936     FILETIME ft;
1937     int yr, mon, day, hr, min, sec;
1938 // Here datestamp is a time expressed (sort of) in seconds since the start
1939 // of 1970. * I need to convert it into a broken-down SYSTEMTIME so that I
1940 // can then re-pack it as a Windows-NT FILETIME....
1941     unpack_date(datestamp, &yr, &mon, &day, &hr, &min, &sec);
1942     st.wMilliseconds = 0;
1943     st.wYear = yr + 1900;  // Windows NT uses full dates since the year 0
1944     st.wMonth = mon + 1;
1945     st.wDay = day;
1946     st.wHour = hr;
1947     st.wMinute = min;
1948     st.wSecond = sec;
1949     SystemTimeToFileTime(&st, &ft);
1950     SetFileTime(h, nullptr, nullptr, &ft);
1951     CloseHandle(h);
1952 }
1953 
1954 void put_fileinfo(date_and_type *p, char *name)
1955 {   unsigned long int datestamp, filetype;
1956     struct stat file_info;
1957     struct std::tm *st;
1958     stat(name, &file_info);
1959     st = std::localtime(&(file_info.st_mtime));
1960     datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday,
1961                           st->tm_hour, st->tm_min, st->tm_sec);
1962     filetype = 0xfff;
1963     p->date = datestamp;
1964     p->type = filetype;
1965 }
1966 
1967 #else // WIN32
1968 
1969 // On some Unix variants I may want this declaration inserted and on others
1970 // it would clash with a system-provided header file. Ugh! With luck the C
1971 // compiler will invent a suitable calling convention even if a declaration
1972 // is not present.
1973 // extern ftruncate(int, int);
1974 
1975 int truncate_file(std::FILE *f, long int where)
1976 {   if (std::fflush(f) != 0) return 1;
1977     return ftruncate(fileno(f), where);  // Returns zero if success
1978 }
1979 
1980 // extern void mkdir(const char *, unsigned short int);
1981 
1982 int Cmkdir(const char *s)
1983 {   mkdir(s, 0775);
1984     return 1;
1985 }
1986 
1987 #include <utime.h>
1988 
1989 #if defined EMBEDDED && defined __ARM_EABI__ && !defined __linux__
1990 
1991 void utime(const char *s, struct utimbuf *t);
1992 
1993 #endif // EMBEDDED etc
1994 
1995 void set_filedate(char *name, unsigned long int datestamp,
1996                   unsigned long int filetype)
1997 {
1998 #ifndef EMBEDDED
1999 #ifdef UTIME_TIME_T
2000     std::time_t tt[2];
2001 #else // UTIME_TIME_T
2002     struct utimbuf tt;
2003 #endif // UTIME_TIME_T
2004     std::time_t t0;
2005     struct std::tm st;
2006     unpack_date(datestamp, &st.tm_year, &st.tm_mon, &st.tm_mday,
2007                 &st.tm_hour, &st.tm_min, &st.tm_sec);
2008     st.tm_isdst = -1;
2009     t0 = std::mktime(&st);
2010 #ifdef UTIME_TIME_T
2011     tt[0] = tt[1] = t0;
2012 #else // UTIME_TIME_T
2013     tt.actime = tt.modtime = t0;
2014 #endif // UTIME_TIME_T
2015     utime(name, &tt);
2016 #endif // EMBEDDED
2017 }
2018 
2019 void put_fileinfo(date_and_type *p, char *name)
2020 {   unsigned long int datestamp, filetype;
2021     struct stat file_info;
2022     struct std::tm *st;
2023 // Read file parameters...
2024     stat(name, &file_info);
2025     st = std::localtime(&(file_info.st_mtime));
2026     datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday,
2027                           st->tm_hour, st->tm_min, st->tm_sec);
2028     filetype = 0xfff;  // should get access status here?
2029     p->date = datestamp;
2030     p->type = filetype;
2031 }
2032 
2033 #endif // WIN32
2034 
2035 // If I am to process directories I need a set of routines that will
2036 // scan sub-directories for me.  This is necessarily dependent on
2037 // the operating system I am running under, hence the conditional compilation
2038 // here.  The specification I want is:
2039 //       void scan_directory(string dir,
2040 //                    void (*proc)(string name, string leafname,
2041 //                                 int why, long int size));
2042 //
2043 // This is called with a directory-name as its first argument
2044 // and a function as its second.
2045 // It calls the function for every directory and every file that can be found
2046 // rooted from the given place.
2047 // When a simple file is found the procedure is called with the name of the
2048 // file, why=0, and the length (in bytes) of the file.  For a directory
2049 // the function is called with why=1, then the contents of the directory are
2050 // processed. Files are returned in alphabetic order. There is no
2051 // guarantee of useful behaviour if some of the files to be scanned are
2052 // flagged as  "invisible" or "not readable" or if they are otherwise special.
2053 //
2054 // I also provide a similar function scan_files() with the same arguments that
2055 // does just the same except that it does not recurse into sub-directories,
2056 
2057 // For CSL's purposes the following 3 are in syscsl.h, but in general I do not
2058 // want to use that header with random fwin applications...
2059 #define SCAN_FILE       0
2060 #define SCAN_DIR        1
2061 
2062 // If I am using C++17 it has std::filesystem and that provides pretty well
2063 // exactly the facilities that I want. Because at the time of writing this I
2064 // can not guarantee that C++17 will always be available I will also provide
2065 // some system-specific fallback code, but I look forward to the time that
2066 // it feels safe to discard that!
2067 
2068 
2069 void scan_directory(string dir, filescan_function *proc)
2070 {   const std::filesystem::path pathToShow{dir};
2071     if (!std::filesystem::is_directory(pathToShow)) return;
2072     std::vector<std::filesystem::directory_entry> res;
2073     for (const auto& entry :
2074              std::filesystem::recursive_directory_iterator(pathToShow))
2075     {   res.push_back(entry);
2076     }
2077     std::sort(res.begin(), res.end(),
2078         [](std::filesystem::directory_entry &a,
2079            std::filesystem::directory_entry &b) -> bool
2080         {  return a.path().string().compare(b.path().string()) < 0;
2081         });
2082     for (auto entry : res)
2083     {   const auto filenameStr = entry.path().filename().string();
2084         const auto fullnameStr = entry.path().string();
2085         if (entry.is_directory())
2086             proc(fullnameStr, filenameStr, SCAN_DIR, 0);
2087         else if (entry.is_regular_file())
2088             proc(fullnameStr, filenameStr, SCAN_FILE,
2089                  std::filesystem::file_size(fullnameStr));
2090     }
2091 }
2092 
2093 void scan_files(string dir, filescan_function *proc)
2094 {   const std::filesystem::path pathToShow{dir};
2095     if (!std::filesystem::is_directory(pathToShow)) return;
2096     std::vector<std::filesystem::directory_entry> res;
2097     for (const auto& entry : std::filesystem::directory_iterator(pathToShow))
2098     {   res.push_back(entry);
2099     }
2100     std::sort(res.begin(), res.end(),
2101         [](std::filesystem::directory_entry &a,
2102            std::filesystem::directory_entry &b) -> bool
2103         {  return a.path().string().compare(b.path().string()) < 0;
2104         });
2105     for (auto entry : res)
2106     {   const auto filenameStr = entry.path().filename().string();
2107         const auto fullnameStr = entry.path().string();
2108         if (entry.is_directory())
2109             proc(fullnameStr, filenameStr, SCAN_DIR, 0);
2110         else if (entry.is_regular_file())
2111             proc(fullnameStr, filenameStr, SCAN_FILE,
2112                  std::filesystem::file_size(fullnameStr));
2113     }
2114 }
2115 
2116 
2117 std::FILE *open_file(char *filename, const char *old, size_t n,
2118                      const char *mode, std::FILE *old_file)
2119 {
2120 // mode is something like "r" or "w" or "rb", as needed by fopen(),
2121 // and old_file is nullptr normally, but can be a (FILE *) to indicate
2122 // the use of freopen rather than fopen.
2123     std::FILE *ff;
2124     process_file_name(filename, old, n);
2125     if (*filename == 0) return nullptr;
2126     if (old_file == nullptr) ff = std::fopen(filename, mode);
2127     else ff = std::freopen(filename, mode, old_file);
2128 // In suitable cases when the first attempt to open the file fails I
2129 // will try creating any necessary directories and then try again.
2130     if (ff==nullptr && *mode=='w')
2131     {   char *p = filename;
2132         while (*p != 0)
2133         {   int ch = *p;
2134             if (ch == '/' || ch == '\\')
2135             {   *p = 0;
2136                 Cmkdir(filename);
2137                 *p = ch;
2138             }
2139             p++;
2140         }
2141         if (old_file == nullptr) ff = std::fopen(filename, mode);
2142         else ff = std::freopen(filename, mode, old_file);
2143     }
2144     if (ff != nullptr) std::setvbuf(ff, nullptr, _IOFBF, 0x10000);
2145     return ff;
2146 }
2147 
2148 
2149 static char err_buf[LONGEST_LEGAL_FILENAME+100];
2150 
2151 char *change_directory(char *filename, const char *old, size_t n)
2152 {   process_file_name(filename, old, n);
2153     if (*filename == 0)
2154     {   std::sprintf(err_buf, "Filename \"%s\" invalid.", old);
2155         return err_buf;
2156     }
2157     std::error_code ec;
2158     std::filesystem::current_path(
2159         std::filesystem::path(filename),
2160         ec);
2161     if (ec)
2162     {   std::strncpy(err_buf, ec.message().c_str(), sizeof(err_buf)-1);
2163         return err_buf;
2164     }
2165     return nullptr;
2166 }
2167 
2168 int create_directory(char *filename, const char *old, size_t n)
2169 {   process_file_name(filename, old, n);
2170     if (*filename == 0) return 1;
2171     std::error_code ec;
2172     std::filesystem::create_directory(
2173         std::filesystem::path(filename),
2174         ec);
2175     if (ec) return 1;
2176     return 0;
2177 }
2178 
2179 
2180 int delete_file(char *filename, const char *old, size_t n)
2181 {   process_file_name(filename, old, n);
2182     if (*filename != 0)
2183     {   std::error_code ec;
2184         std::filesystem::remove_all(std::filesystem::path(filename), ec);
2185         if (ec) return 1;
2186     }
2187     return 0;
2188 }
2189 
2190 int delete_wildcard(char *filename, const char *old, size_t n)
2191 {   process_file_name(filename, old, n);
2192     if (*filename == 0) return 0;
2193     {
2194 #ifdef WIN32
2195         HANDLE h;
2196         WIN32_FIND_DATA gg;
2197         h = FindFirstFile(filename, &gg);
2198         if (h != INVALID_HANDLE_VALUE)
2199         {   for (;;)
2200             {   std::error_code ec;
2201                 std::filesystem::remove_all(
2202                     std::filesystem::path(gg.cFileName), ec);
2203                 if (!FindNextFile(h, &gg)) break;
2204             }
2205             FindClose(h);
2206         }
2207 #else // WIN32
2208         glob_t gg;
2209         size_t i;
2210         if (glob(filename, GLOB_NOSORT, nullptr, &gg) == 0)
2211         {   std::error_code ec;
2212              for (i=0; i<gg.gl_pathc; i++)
2213                 std::filesystem::remove_all(
2214                     std::filesystem::path(gg.gl_pathv[i]), ec);
2215             globfree(&gg);
2216         }
2217 #endif // WIN32
2218     }
2219     return 0;
2220 }
2221 
2222 int64_t file_length(char *filename, const char *old, size_t n)
2223 {   process_file_name(filename, old, n);
2224     if (*filename == 0) return 0;
2225     std::filesystem::path p(filename);
2226     if (!std::filesystem::exists(p)) return -1;
2227     std::error_code ec;
2228     std::uintmax_t len = std::filesystem::file_size(p, ec);
2229     if (ec) return 0;
2230     return static_cast<int64_t>(len);
2231 }
2232 
2233 void list_directory_members(char *filename, const char *old,
2234                             size_t n,
2235                             filescan_function *fn)
2236 {   process_file_name(filename, old, n);
2237     scan_files(filename, fn);
2238 }
2239 
2240 // The next function is provided because C++17 and gcc-9 have some issues
2241 // about more direct conversion from file_time_type to time_t. This is
2242 // a stackoverflow response to the issue, based on the expectation that
2243 // subtracting time values to obtain a duration is liable to be easy.
2244 
2245 template <typename TP>
2246 std::time_t to_time_t(TP tp)
2247 {   using namespace std::chrono;
2248     auto sctp = time_point_cast<system_clock::duration>(tp - TP::clock::now()
2249               + system_clock::now());
2250     return system_clock::to_time_t(sctp);
2251 }
2252 
2253 bool file_exists(char *filename, const char *old, size_t n, char *tt)
2254 // This returns YES if the file exists, and as a side-effect copies a
2255 // textual form of the last-changed-time of the file into the buffer tt.
2256 {   process_file_name(filename, old, n);
2257     if (*filename == 0) return false;
2258     std::error_code ec;
2259     if (!std::filesystem::exists(std::filesystem::path(filename), ec) || ec)
2260         return false;
2261     std::filesystem::file_time_type datestamp =
2262         std::filesystem::last_write_time(std::filesystem::path(filename), ec);
2263     std::time_t cftime = to_time_t(datestamp);
2264 //      decltype(datestamp)::clock::to_time_t(datestamp); // a more proper way!
2265     std::strcpy(tt, std::ctime(&cftime));
2266     return true;
2267 }
2268 
2269 bool directoryp(char *filename, const char *old, size_t n)
2270 {   process_file_name(filename, old, n);
2271     if (*filename == 0) return false;
2272     if (!std::filesystem::exists(std::filesystem::path(filename)))
2273         return false;
2274     std::error_code ec;
2275     return std::filesystem::is_directory(
2276         std::filesystem::path(filename), ec) && !ec;
2277 }
2278 
2279 
2280 char *get_truename(char *filename, const char *old, size_t n)
2281 {   struct stat buf;
2282     char *temp, *fn, *dir;
2283     char pwd[LONGEST_LEGAL_FILENAME];
2284     std::memset(pwd, 0, sizeof(pwd));
2285 
2286     process_file_name(filename, old, n);
2287     if (*filename == 0)
2288     {   std::strcpy(filename, "truename");
2289         return nullptr;
2290     }
2291 
2292     // Find out whether we have a file or a directory
2293     if (stat(filename,&buf) == -1)
2294     {   std::strcpy(filename, "truename: cannot stat file");
2295         return nullptr;
2296     }
2297 
2298     // Store current directory
2299     if (get_current_directory(pwd, LONGEST_LEGAL_FILENAME) < 0)
2300     {   std::strcpy(filename,
2301                     "truename: cannot get current working directory");
2302         return nullptr;
2303     }
2304 
2305     if ((buf.st_mode & S_IFMT) == S_IFDIR)
2306     {   // We have a directory
2307         char *dir1;
2308         if (chdir(filename) != 0)
2309         {   std::strcpy(filename, "truename: cannot change directory");
2310             return nullptr;
2311         }
2312         dir1 = new (std::nothrow) char[LONGEST_LEGAL_FILENAME];
2313         if (dir1 == nullptr)
2314         {   std::strcpy(filename, "truename: run out of memory");
2315             return nullptr;
2316         }
2317         if (getcwd(dir1,LONGEST_LEGAL_FILENAME) == nullptr)
2318         {   std::strcpy(filename,
2319                         "truename: cannot get current working directory");
2320             delete [] dir1;
2321             return nullptr;
2322         }
2323         if (chdir(pwd) != 0)
2324         {   std::strcpy(filename, "truename: cannot change directory");
2325             delete [] dir1;
2326             return nullptr;
2327         }
2328 // Axiom-specific hack: truename preserves '/' at the end of
2329 // a path
2330         if (old[n-1] == '/' && dir1[std::strlen(dir1)-1] != '/')
2331         {   n = std::strlen(dir1);
2332             dir1[n]   = '/';
2333             dir1[n+1] = '\0';
2334         }
2335         return dir1;
2336     }
2337     else
2338     {   // Assume we have some kind of file
2339         temp = std::strrchr(filename,'/');
2340         if (temp)
2341         {   // Found a directory component
2342             char theDir[LONGEST_LEGAL_FILENAME];
2343             std::memset(theDir, 0, sizeof(theDir));
2344             fn = new (std::nothrow) char[1+std::strlen(temp)];
2345             if (fn == nullptr)
2346             {   std::strcpy(filename, "truename: run out of memory");
2347                 return nullptr;
2348             }
2349             std::strcpy(fn, temp);
2350             *temp = '\0';
2351 // fn is now "/file" and filename is the directory
2352             if (chdir(filename) != 0)
2353             {   std::strcpy(filename, "truename: cannot change directory");
2354                 delete [] fn;
2355                 return nullptr;
2356             }
2357             if (get_current_directory(theDir, LONGEST_LEGAL_FILENAME) < 0)
2358             {   std::strcpy(filename,
2359                             "truename: cannot get current working directory");
2360                 delete [] fn;
2361                 return nullptr;
2362             }
2363             temp = theDir;
2364             if (chdir(pwd) != 0)
2365             {   std::strcpy(filename, "truename: cannot change directory");
2366                 delete [] fn;
2367                 return nullptr;
2368             }
2369             dir = new (std::nothrow)
2370                 char[std::strlen(temp) + std::strlen(fn) + 1];
2371             if (dir == nullptr)
2372             {   std::strcpy(filename, "truename: run out of memory");
2373                 delete [] fn;
2374                 return nullptr;
2375             }
2376             std::strcpy(dir, temp);
2377             std::strcat(dir, fn);
2378             delete [] fn;
2379             return dir;
2380         }
2381         else
2382         {   dir = new (std::nothrow)
2383                 char[std::strlen(pwd) + std::strlen(filename) + 2];
2384             if (dir == nullptr)
2385             {   std::strcpy(filename, "truename: run out of memory");
2386                 return nullptr;
2387             }
2388             std::strcpy(dir, pwd);
2389             std::strcat(dir, "/");
2390             std::strcat(dir, filename);
2391             return dir;
2392         }
2393     }
2394 }
2395 
2396 // The tests here are probably rather WRONG_MINDED in that they check the
2397 // status of the file and report whether its OWNER could read, write or
2398 // execute it, rather than whether the current user could. However what
2399 // I do here will hold the fort for now.
2400 
2401 
2402 bool file_readable(char *filename, const char *old, size_t n)
2403 {   process_file_name(filename, old, n);
2404     if (*filename == 0) return false;
2405     std::error_code ec;
2406     auto s = std::filesystem::status(std::filesystem::path(filename), ec);
2407     if (ec) return false;
2408     return (s.permissions() & std::filesystem::perms::owner_read) !=
2409            std::filesystem::perms::none;
2410 }
2411 
2412 
2413 bool file_writeable(char *filename, const char *old, size_t n)
2414 {   process_file_name(filename, old, n);
2415     if (*filename == 0) return false;
2416     std::error_code ec;
2417     auto s = std::filesystem::status(std::filesystem::path(filename));
2418     if (ec) return false;
2419     return (s.permissions() & std::filesystem::perms::owner_write) !=
2420            std::filesystem::perms::none;
2421 }
2422 
2423 
2424 bool file_executable(char *filename, const char *old, size_t n)
2425 {   process_file_name(filename, old, n);
2426     if (*filename == 0) return false;
2427     std::error_code ec;
2428     auto s = std::filesystem::status(std::filesystem::path(filename));
2429     if (ec) return false;
2430     return (s.permissions() & std::filesystem::perms::owner_exec) !=
2431            std::filesystem::perms::none;
2432 }
2433 
2434 
2435 int rename_file(char *from_name, const char *from_old,
2436                 size_t from_size,
2437                 char *to_name, const char *to_old, size_t to_size)
2438 {   process_file_name(from_name, from_old, from_size);
2439     process_file_name(to_name, to_old, to_size);
2440     if (*from_name == 0 || *to_name == 0) return 0;
2441     return std::rename(from_name,to_name);
2442 }
2443 
2444 #else // __cpp_lib_filesystem
2445 
2446 #ifdef WIN32
2447 
2448 #include "windows.h"
2449 
2450 int Cmkdir(const char *name)
2451 {   SECURITY_ATTRIBUTES s;
2452     s.nLength = sizeof(s);
2453     s.lpSecurityDescriptor = nullptr;
2454     s.bInheritHandle = FALSE;
2455     return CreateDirectory(name, &s);
2456 }
2457 
2458 int truncate_file(std::FILE *f, long int where)
2459 {   if (std::fflush(f) != 0) return 1;
2460 #ifdef __CYGWIN__
2461     if (std::fflush(f) != 0) return 1;
2462     return ftruncate(fileno(f), where);  // Returns zero if success
2463 #else // __CYGWIN__
2464     return chsize(fileno(f), where);    // Returns zero if success
2465 #endif // __CYGWIN__
2466 }
2467 
2468 void set_filedate(char *name, unsigned long int datestamp,
2469                   unsigned long int filetype)
2470 {   HANDLE h = CreateFile(name, GENERIC_WRITE, 0, nullptr,
2471                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
2472     SYSTEMTIME st;
2473     FILETIME ft;
2474     int yr, mon, day, hr, min, sec;
2475 // Here datestamp is a time expressed (sort of) in seconds since the start
2476 // of 1970. * I need to convert it into a broken-down SYSTEMTIME so that I
2477 // can then re-pack it as a Windows-NT FILETIME....
2478     unpack_date(datestamp, &yr, &mon, &day, &hr, &min, &sec);
2479     st.wMilliseconds = 0;
2480     st.wYear = yr + 1900;  // Windows NT uses full dates since the year 0
2481     st.wMonth = mon + 1;
2482     st.wDay = day;
2483     st.wHour = hr;
2484     st.wMinute = min;
2485     st.wSecond = sec;
2486     SystemTimeToFileTime(&st, &ft);
2487     SetFileTime(h, nullptr, nullptr, &ft);
2488     CloseHandle(h);
2489 }
2490 
2491 void put_fileinfo(date_and_type *p, char *name)
2492 {   unsigned long int datestamp, filetype;
2493     struct stat file_info;
2494     struct std::tm *st;
2495     stat(name, &file_info);
2496     st = std::localtime(&(file_info.st_mtime));
2497     datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday,
2498                           st->tm_hour, st->tm_min, st->tm_sec);
2499     filetype = 0xfff;
2500     p->date = datestamp;
2501     p->type = filetype;
2502 }
2503 
2504 #else // WIN32
2505 
2506 // On some Unix variants I may want this declaration inserted and on others
2507 // it would clash with a system-provided header file. Ugh! With luck the C
2508 // compiler will invent a suitable calling convention even if a declaration
2509 // is not present.
2510 // extern ftruncate(int, int);
2511 
2512 int truncate_file(std::FILE *f, long int where)
2513 {   if (std::fflush(f) != 0) return 1;
2514     return ftruncate(fileno(f), where);  // Returns zero if success
2515 }
2516 
2517 // extern void mkdir(const char *, unsigned short int);
2518 
2519 int Cmkdir(const char *s)
2520 {   mkdir(s, 0775);
2521     return 1;
2522 }
2523 
2524 #include <utime.h>
2525 
2526 #if defined EMBEDDED && defined __ARM_EABI__ && !defined __linux__
2527 
2528 void utime(const char *s, struct utimbuf *t);
2529 
2530 #endif // EMBEDDED etc
2531 
2532 void set_filedate(char *name, unsigned long int datestamp,
2533                   unsigned long int filetype)
2534 {
2535 #ifndef EMBEDDED
2536 #ifdef UTIME_TIME_T
2537     std::time_t tt[2];
2538 #else // UTIME_TIME_T
2539     struct utimbuf tt;
2540 #endif // UTIME_TIME_T
2541     std::time_t t0;
2542     struct std::tm st;
2543     unpack_date(datestamp, &st.tm_year, &st.tm_mon, &st.tm_mday,
2544                 &st.tm_hour, &st.tm_min, &st.tm_sec);
2545     st.tm_isdst = -1;
2546     t0 = std::mktime(&st);
2547 #ifdef UTIME_TIME_T
2548     tt[0] = tt[1] = t0;
2549 #else // UTIME_TIME_T
2550     tt.actime = tt.modtime = t0;
2551 #endif // UTIME_TIME_T
2552     utime(name, &tt);
2553 #endif // EMBEDDED
2554 }
2555 
2556 void put_fileinfo(date_and_type *p, char *name)
2557 {   unsigned long int datestamp, filetype;
2558     struct stat file_info;
2559     struct std::tm *st;
2560 // Read file parameters...
2561     stat(name, &file_info);
2562     st = std::localtime(&(file_info.st_mtime));
2563     datestamp = pack_date(st->tm_year, st->tm_mon, st->tm_mday,
2564                           st->tm_hour, st->tm_min, st->tm_sec);
2565     filetype = 0xfff;  // should get access status here?
2566     p->date = datestamp;
2567     p->type = filetype;
2568 }
2569 
2570 #endif // WIN32
2571 
2572 // If I am to process directories I need a set of routines that will
2573 // scan sub-directories for me.  This is necessarily dependent on
2574 // the operating system I am running under, hence the conditional compilation
2575 // here.  The specification I want is:
2576 //       void scan_directory(string dir,
2577 //                    void (*proc)(string name, string leafname,
2578 //                                 int why, long int size));
2579 //
2580 // This is called with a directory-name as its first argument
2581 // and a function as its second.
2582 // It calls the function for every directory and every file that can be found
2583 // rooted from the given place.
2584 // When a simple file is found the procedure is called with the name of the
2585 // file, why=0, and the length (in bytes) of the file.  For a directory
2586 // the function is called with why=1, then the contents of the directory are
2587 // processed. Files are returned in alphabetic order. There is no
2588 // guarantee of useful behaviour if some of the files to be scanned are
2589 // flagged as  "invisible" or "not readable" or if they are otherwise special.
2590 //
2591 // I also provide a similar function scan_files() with the same arguments that
2592 // does just the same except that it does not recurse into sub-directories,
2593 
2594 // For CSL's purposes the following 3 are in syscsl.h, but in general I do not
2595 // want to use that header with random fwin applications...
2596 #define SCAN_FILE       0
2597 #define SCAN_DIR        1
2598 
2599 
2600 // I use a (static) flag to indicate how sub-directories should be
2601 // handled, and what to do about case. By default I fold to lower case
2602 // on windows. setting hostcase non-zero causes case to be preserved.
2603 
2604 static int recursive_scan, hostcase = 0;
2605 
2606 void set_hostcase(int fg)
2607 {   hostcase = fg;
2608 }
2609 
2610 #ifdef WIN32
2611 
2612 // Hmm - buffer overflow worry with the next line!
2613 static char win_filename[LONGEST_LEGAL_FILENAME];
2614 int scan_leafstart;
2615 
2616 static WIN32_FIND_DATA *found_files = nullptr;
2617 static int n_found_files = 0, max_found_files = 0;
2618 
2619 #define TABLE_INCREMENT 50
2620 
2621 static int more_files()
2622 {   if (n_found_files > max_found_files - 5)
2623     {   WIN32_FIND_DATA *fnew =
2624             new (std::nothrow) WIN32_FIND_DATA[max_found_files+TABLE_INCREMENT];
2625         if (fnew == nullptr) return 1;  // failure flag
2626         std::memcpy(fnew, found_files,
2627             sizeof(WIN32_FIND_DATA)*max_found_files);
2628         delete [] found_files;
2629         found_files = fnew;
2630         max_found_files += TABLE_INCREMENT;
2631     }
2632     return 0;
2633 }
2634 
2635 int alphasort_files(const void *a, const void *b)
2636 {   const WIN32_FIND_DATA *fa = (const WIN32_FIND_DATA *)a,
2637                                *fb = (const WIN32_FIND_DATA *)b;
2638     return std::strncmp(fb->cFileName, fa->cFileName,
2639                         sizeof(fa->cFileName));
2640 }
2641 
2642 static void exall(int namelength, filescan_function *proc)
2643 // This procedure scans a directory-full of files, calling the given procedure
2644 // to process each one it finds.
2645 {
2646 #ifdef EMBEDDED
2647     std::printf("exall function called - but not implemented here\n");
2648     return; // Dummy version here
2649 #else // EMBEDDED
2650     WIN32_FIND_DATA found;
2651     int rootlen = namelength, first = n_found_files;
2652     HANDLE hSearch = FindFirstFile(win_filename, &found);
2653     if (hSearch == INVALID_HANDLE_VALUE) return;  // No files found at all
2654     for (;;)
2655     {   if (more_files()) break;
2656         found_files[n_found_files++] = found;
2657         if (!FindNextFile(hSearch, &found)) break;
2658     }
2659     FindClose(hSearch);
2660     std::qsort(reinterpret_cast<void *>(&found_files[first]),
2661                n_found_files-first,
2662                sizeof(WIN32_FIND_DATA),
2663                alphasort_files);
2664     while (rootlen>=0 && win_filename[rootlen]!='\\' &&
2665            win_filename[rootlen]!='/')
2666         rootlen--;
2667     while (n_found_files != first)
2668     {   char *p = reinterpret_cast<char *>(
2669                   &found_files[--n_found_files].cFileName);
2670         int c;
2671 // Fill out filename with the actual name I grabbed, i.e. with
2672 // wild-cards expanded.
2673         namelength = rootlen+1;
2674 // I fold DOS filenames into lower case because it does not matter much
2675 // to DOS and I think it looks better - furthermore it helps when I move
2676 // archives to other systems.  So I do the same on NT.
2677         while ((c = *p++) != 0)
2678         {   if (!hostcase) if (std::isupper(c)) c = std::tolower(c);
2679             win_filename[namelength++] = static_cast<char>(c);
2680         }
2681         win_filename[namelength] = 0;
2682         if (found_files[n_found_files].dwFileAttributes &
2683             FILE_ATTRIBUTE_DIRECTORY)
2684         {   if (found_files[n_found_files].cFileName[0] != '.')
2685 // I filter out directory names that start with '.'.
2686 // This is to avoid calamity with recursion though chains such as .\.\.\.....
2687             {   proc(string(win_filename),
2688                      string(win_filename+scan_leafstart),
2689                      SCAN_DIR, 0);
2690                 if (!recursive_scan) continue;
2691                 std::strcpy(&win_filename[namelength], "\\*.*");
2692 // Append "\*.*" to the directory-name and try again, thereby scanning
2693 // its contents.
2694                 exall(namelength+4, proc);
2695                 win_filename[namelength] = 0;
2696             }
2697         }
2698         else proc(string(win_filename),
2699                   string(win_filename+scan_leafstart),
2700                   SCAN_FILE,
2701                   found_files[n_found_files].nFileSizeLow);
2702     }
2703     return;
2704 #endif // EMBEDDED
2705 }
2706 
2707 void scan_directory(string Cdir, filescan_function *proc)
2708 {   recursive_scan = 1;
2709     const char *dir = Cdir.c_str();
2710     if (std::strcmp(dir,".")==0)
2711     {   dir = "*.*";
2712         scan_leafstart = 0;
2713     }
2714     else scan_leafstart = std::strlen(dir)+1;
2715     std::strcpy(win_filename, dir);
2716     exall(std::strlen(win_filename), proc);
2717 }
2718 
2719 void scan_files(string Cdir, filescan_function *proc)
2720 {   recursive_scan = 0;
2721     const char *dir = Cdir.c_str();
2722     if (std::strcmp(dir,".")==0)
2723     {   std::strcpy(win_filename, "*.*");
2724         scan_leafstart = 0;
2725     }
2726     else
2727     {   scan_leafstart = std::strlen(dir);    // +1 ??????
2728         std::strcpy(win_filename, dir);
2729         if (win_filename[scan_leafstart-1] == '\\')
2730         {   // Root directory
2731             std::strcpy(win_filename+scan_leafstart, "*.*");
2732             --scan_leafstart;
2733         }
2734         else std::strcpy(win_filename+scan_leafstart, "\\*.*");
2735         scan_leafstart++;
2736     }
2737     exall(std::strlen(win_filename), proc);
2738 }
2739 
2740 #else // WIN32
2741 
2742 static char posix_filename[LONGEST_LEGAL_FILENAME];
2743 static int scan_leafstart = 0;
2744 
2745 // The code here uses opendir, readdir and closedir and as such ought to
2746 // be Posix compatible. The macro USE_DIRECT_H can cause an older variant
2747 // on this idea to be used. BUt it may need adjustment for different
2748 // systems.
2749 
2750 static char **found_files = nullptr;
2751 
2752 int n_found_files = 0, max_found_files = 0;
2753 
2754 #define TABLE_INCREMENT 50
2755 
2756 static int more_files()
2757 {   if (n_found_files > max_found_files - 5)
2758     {   char **fnew =
2759             new (std::nothrow) char *[max_found_files + TABLE_INCREMENT];
2760         if (fnew == nullptr) return 1;  // failure flag
2761         std::memcpy(fnew, found_files, sizeof(char *)*max_found_files);
2762         delete [] found_files;
2763         found_files = fnew;
2764         max_found_files += TABLE_INCREMENT;
2765     }
2766     return 0;
2767 }
2768 
2769 int alphasort_files(const void *a, const void *b)
2770 {   const char *fa = *(const char **)a,
2771                *fb = *(const char **)b;
2772     return std::strcmp(fb, fa);
2773 }
2774 
2775 static void scan_file(int namelength, filescan_function *proc);
2776 
2777 static void exall(int namelength, filescan_function *proc)
2778 {
2779 #ifdef EMBEDDED
2780     std::printf("exall function called - but not implemented here\n");
2781     return; // Dummy version here
2782 #else // EMBEDDED
2783     DIR *d;
2784 #ifdef USE_DIRECT_H
2785     struct direct *dd;
2786 #else // USE_DIRECT_H
2787     struct dirent *dd;
2788 #endif // USE_DIRECT_H
2789     int rootlen = namelength, first = n_found_files;
2790     proc(string(posix_filename),
2791          string(posix_filename+scan_leafstart),
2792          SCAN_DIR, 0);
2793     d = opendir(posix_filename);
2794     if (d != nullptr)
2795     {   while ((dd = readdir(d)) != nullptr)
2796         {   char *leafname = dd->d_name;
2797             char *copyname;
2798 // readdir hands back both "." and ".." but I had better not recurse
2799 // into either!
2800             if (std::strcmp(leafname, ".") == 0 ||
2801                 std::strcmp(leafname, "..") == 0) continue;
2802             if (more_files()) break;
2803             copyname = new (std::nothrow) char[1+std::strlen(leafname)];
2804             if (copyname == nullptr) break;
2805             std::strcpy(copyname, leafname);
2806             found_files[n_found_files++] = copyname;
2807         }
2808         closedir(d);
2809     }
2810 
2811     std::qsort(reinterpret_cast<void *>(&found_files[first]),
2812                n_found_files-first,
2813                sizeof(char *),
2814                alphasort_files);
2815     posix_filename[rootlen] = '/';
2816     while (n_found_files != first)
2817     {   char *p = found_files[--n_found_files];
2818         int c;
2819         namelength = rootlen+1;
2820         while ((c = *p++) != 0) posix_filename[namelength++] =
2821                 static_cast<char>(c);
2822         delete [] found_files[n_found_files];
2823         posix_filename[namelength] = 0;
2824         scan_file(namelength, proc);
2825     }
2826     posix_filename[rootlen] = 0;
2827 #endif // EMBEDDED
2828 }
2829 
2830 #ifndef S_IFMT
2831 # ifdef __S_IFMT
2832 #  define S_IFMT __S_IFMT
2833 # endif
2834 #endif // S_IFMT
2835 
2836 #ifndef S_IFDIR
2837 # ifdef __S_IFDIR
2838 #  define S_IFDIR __S_IFDIR
2839 # endif
2840 #endif // S_IFDIR
2841 
2842 #ifndef S_IFREG
2843 # ifdef __S_IFREG
2844 #  define S_IFREG __S_IFREG
2845 # endif
2846 #endif // S_IFREG
2847 
2848 static void scan_file(int namelength, filescan_function *proc)
2849 {   struct stat buf;
2850     stat(posix_filename, &buf);
2851     if ((buf.st_mode & S_IFMT) == S_IFDIR)
2852     {   if (!recursive_scan) proc(string(posix_filename),
2853                                   string(posix_filename+scan_leafstart),
2854                                   SCAN_DIR, 0);
2855         else exall(namelength, proc);
2856     }
2857     else if ((buf.st_mode & S_IFMT) == S_IFREG)
2858         proc(string(posix_filename),
2859              string(posix_filename+scan_leafstart),
2860              SCAN_FILE, buf.st_size);
2861 //  else fprintf(stderr, "Mode of %s is %o\n", posix_filename, buf.st_mode);
2862 }
2863 
2864 void scan_directory(string Cdir, filescan_function *proc)
2865 {   recursive_scan = 1;
2866     const char *dir = Cdir.c_str();
2867     scan_leafstart = std::strlen(dir)+1;
2868     std::strcpy(posix_filename, dir);
2869     scan_file(scan_leafstart-1, proc);
2870 }
2871 
2872 void scan_files(string Cdir, filescan_function *proc)
2873 {   recursive_scan = 0;
2874     const char *dir = Cdir.c_str();
2875     scan_leafstart = std::strlen(dir)+1;
2876     std::strcpy(posix_filename, dir);
2877     exall(scan_leafstart-1, proc);
2878 }
2879 
2880 #endif // WIN32
2881 
2882 // Maybe the above shows how helpful the C++ std::filesystem stuff is here!
2883 
2884 
2885 std::FILE *open_file(char *filename, const char *old, size_t n,
2886                      const char *mode, std::FILE *old_file)
2887 {
2888 // mode is something like "r" or "w" or "rb", as needed by fopen(),
2889 // and old_file is nullptr normally, but can be a (FILE *) to indicate
2890 // the use of freopen rather than fopen.
2891     std::FILE *ff;
2892     process_file_name(filename, old, n);
2893     if (*filename == 0) return nullptr;
2894     if (old_file == nullptr) ff = std::fopen(filename, mode);
2895     else ff = std::freopen(filename, mode, old_file);
2896 // In suitable cases when the first attempt to open the file fails I
2897 // will try creating any necessary directories and then try again.
2898     if (ff==nullptr && *mode=='w')
2899     {   char *p = filename;
2900         while (*p != 0)
2901         {   int ch = *p;
2902             if (ch == '/' || ch == '\\')
2903             {   *p = 0;
2904                 Cmkdir(filename);
2905                 *p = ch;
2906             }
2907             p++;
2908         }
2909         if (old_file == nullptr) ff = std::fopen(filename, mode);
2910         else ff = std::freopen(filename, mode, old_file);
2911     }
2912     if (ff != nullptr) std::setvbuf(ff, nullptr, _IOFBF, 0x10000);
2913     return ff;
2914 }
2915 
2916 
2917 static char err_buf[LONGEST_LEGAL_FILENAME+100];
2918 
2919 char *change_directory(char *filename, const char *old, size_t n)
2920 {   process_file_name(filename, old, n);
2921     if (*filename == 0)
2922     {   std::sprintf(err_buf, "Filename \"%s\" invalid.", old);
2923         return err_buf;
2924     }
2925     if (chdir(filename))
2926     {   const char *msg;
2927         switch (errno)
2928         {   case ENOTDIR:
2929                 msg = "A component of %s is not a directory.";
2930                 break;
2931             case ENOENT:
2932                 msg = "The directory %s does not exist.";
2933                 break;
2934             case EACCES:
2935                 msg = "Insufficient permission for %s.";
2936                 break;
2937             case ENAMETOOLONG:
2938                 msg = "The pathname %s is too long.";
2939                 break;
2940             default:
2941                 msg = "Cannot change directory to %s.";
2942                 break;
2943         }
2944         std::sprintf(err_buf, msg, filename);
2945         return err_buf;
2946     }
2947     else return nullptr;
2948 }
2949 
2950 int create_directory(char *filename, const char *old, size_t n)
2951 {   process_file_name(filename, old, n);
2952     if (*filename == 0) return 1;
2953     return Cmkdir(filename);
2954 }
2955 
2956 
2957 static void remove_files(string name, string leafname, int dirp, long int size)
2958 // Remove a file, or a directory and all its contents
2959 {   switch (dirp)
2960     {   case 0:               // SCAN_FILE
2961             std::remove(name.c_str());
2962             return;
2963         case 2:               // SCAN_ENDDIR
2964             rmdir(name.c_str());
2965             return;
2966         default:              // 1 == SCAN_STARTDIR
2967             return;
2968     }
2969 }
2970 
2971 int delete_file(char *filename, const char *old, size_t n)
2972 {   process_file_name(filename, old, n);
2973     if (*filename == 0) return 0;
2974     //
2975     // We cannot simply use remove here, since this will not
2976     // work with directories and their contents.  Hence the
2977     // use of scan_directory.
2978     //
2979     string dir = filename;
2980     scan_directory(dir, remove_files);
2981     return 0;
2982 }
2983 
2984 int delete_wildcard(char *filename, const char *old, size_t n)
2985 {   process_file_name(filename, old, n);
2986     if (*filename == 0) return 0;
2987     {
2988 #ifdef WIN32
2989         HANDLE h;
2990         WIN32_FIND_DATA gg;
2991         h = FindFirstFile(filename, &gg);
2992         if (h != INVALID_HANDLE_VALUE)
2993         {   for (;;)
2994             {   scan_directory(gg.cFileName, remove_files);
2995                 if (!FindNextFile(h, &gg)) break;
2996             }
2997             FindClose(h);
2998         }
2999 #else // WIN32
3000         glob_t gg;
3001         size_t i;
3002         if (glob(filename, GLOB_NOSORT, nullptr, &gg) == 0)
3003         {   for (i=0; i<gg.gl_pathc; i++)
3004                 scan_directory(gg.gl_pathv[i], remove_files);
3005             globfree(&gg);
3006         }
3007 #endif // WIN32
3008     }
3009     return 0;
3010 }
3011 
3012 int64_t file_length(char *filename, const char *old, size_t n)
3013 {   struct stat buf;
3014     process_file_name(filename, old, n);
3015     if (*filename == 0) return 0;
3016     if (stat(filename,&buf) == -1) return -1;
3017     return (int64_t)(buf.st_size);
3018 }
3019 
3020 void list_directory_members(char *filename, const char *old,
3021                             size_t n,
3022                             filescan_function *fn)
3023 {   process_file_name(filename, old, n);
3024     scan_files(filename, fn);
3025 }
3026 
3027 bool file_exists(char *filename, const char *old, size_t n, char *tt)
3028 // This returns YES if the file exists, and as a side-effect copies a
3029 // textual form of the last-changed-time of the file into the buffer tt.
3030 {   struct stat statbuff;
3031     process_file_name(filename, old, n);
3032     if (*filename == 0) return false;
3033     if (stat(filename, &statbuff) != 0) return false;
3034     std::strcpy(tt, std::ctime(&(statbuff.st_mtime)));
3035     return true;
3036 }
3037 
3038 bool directoryp(char *filename, const char *old, size_t n)
3039 {   struct stat buf;
3040     process_file_name(filename, old, n);
3041     if (*filename == 0) return false;
3042     if (stat(filename,&buf) == -1) return false;
3043     return ((buf.st_mode & S_IFMT) == S_IFDIR);
3044 }
3045 
3046 
3047 char *get_truename(char *filename, const char *old, size_t n)
3048 {   struct stat buf;
3049     char *temp, *fn, *dir;
3050     char pwd[LONGEST_LEGAL_FILENAME];
3051     std::memset(pwd, 0, sizeof(pwd));
3052 
3053     process_file_name(filename, old, n);
3054     if (*filename == 0)
3055     {   std::strcpy(filename, "truename");
3056         return nullptr;
3057     }
3058 
3059     // Find out whether we have a file or a directory
3060     if (stat(filename,&buf) == -1)
3061     {   std::strcpy(filename, "truename: cannot stat file");
3062         return nullptr;
3063     }
3064 
3065     // Store current directory
3066     if (get_current_directory(pwd, LONGEST_LEGAL_FILENAME) < 0)
3067     {   std::strcpy(filename,
3068                     "truename: cannot get current working directory");
3069         return nullptr;
3070     }
3071 
3072     if ((buf.st_mode & S_IFMT) == S_IFDIR)
3073     {   // We have a directory
3074         char *dir1;
3075         if (chdir(filename) != 0)
3076         {   std::strcpy(filename, "truename: cannot change directory");
3077             return nullptr;
3078         }
3079         dir1 = new (std::nothrow) char[LONGEST_LEGAL_FILENAME];
3080         if (dir1==nullptr)
3081         {   std::strcpy(filename, "truename: run out of memory");
3082             return nullptr;
3083         }
3084         if (getcwd(dir1,LONGEST_LEGAL_FILENAME) == nullptr)
3085         {   std::strcpy(filename,
3086                         "truename: cannot get current working directory");
3087             delete [] dir1;
3088             return nullptr;
3089         }
3090         if (chdir(pwd) != 0)
3091         {   std::strcpy(filename, "truename: cannot change directory");
3092             delete [] dir1;
3093             return nullptr;
3094         }
3095 // Axiom-specific hack: truename preserves '/' at the end of
3096 // a path
3097         if (old[n-1] == '/' && dir1[std::strlen(dir1)-1] != '/')
3098         {   n = std::strlen(dir1);
3099             dir1[n]   = '/';
3100             dir1[n+1] = '\0';
3101         }
3102         return dir1;
3103     }
3104     else
3105     {   // Assume we have some kind of file
3106         temp = std::strrchr(filename,'/');
3107         if (temp)
3108         {   // Found a directory component
3109             char theDir[LONGEST_LEGAL_FILENAME];
3110             std::memset(theDir, 0, sizeof(theDir));
3111             fn = new (std::nothrow) char[1+std::strlen(temp)];
3112             if (fn == nullptr)
3113             {   std::strcpy(filename, "truename: run out of memory");
3114                 return nullptr;
3115             }
3116             std::strcpy(fn, temp);
3117             *temp = '\0';
3118 // fn is now "/file" and filename is the directory
3119             if (chdir(filename) != 0)
3120             {   std::strcpy(filename, "truename: cannot change directory");
3121                 delete [] fn;
3122                 return nullptr;
3123             }
3124             if (get_current_directory(theDir, LONGEST_LEGAL_FILENAME) < 0)
3125             {   std::strcpy(filename,
3126                             "truename: cannot get current working directory");
3127                 delete [] fn;
3128                 return nullptr;
3129             }
3130             temp = theDir;
3131             if (chdir(pwd) != 0)
3132             {   std::strcpy(filename, "truename: cannot change directory");
3133                 delete [] fn;
3134                 return nullptr;
3135             }
3136             dir = new (std::nothrow)
3137                 char[std::strlen(temp) + std::strlen(fn) + 1];
3138             if (dir == nullptr)
3139             {   std::strcpy(filename, "truename: run out of memory");
3140                 return nullptr;
3141             }
3142             std::strcpy(dir, temp);
3143             std::strcat(dir, fn);
3144             delete [] fn;
3145             return dir;
3146         }
3147         else
3148         {   dir = new (std::nothrow)
3149                 char[std::strlen(pwd) + std::strlen(filename) + 2];
3150             if (dir == nullptr)
3151             {   std::strcpy(filename, "truename: run out of memory");
3152                 return nullptr;
3153             }
3154             std::strcpy(dir, pwd);
3155             std::strcat(dir, "/");
3156             std::strcat(dir, filename);
3157             return dir;
3158         }
3159     }
3160 }
3161 
3162 // The tests here are probably rather WRONG_MINDED in that they check the
3163 // status of the file and report whether its OWNER could read, write or
3164 // execute it, rather than whether the current user could. However what
3165 // I do here will hold the fort for now.
3166 
3167 
3168 bool file_readable(char *filename, const char *old, size_t n)
3169 {   struct stat buf;
3170     process_file_name(filename, old, n);
3171     if (*filename == 0) return false;
3172     if (stat(filename,&buf) == -1)
3173         return false; // File probably does not exist
3174 #ifndef S_IRUSR
3175     return true;
3176 #else // S_IRUSR
3177     return (buf.st_mode & S_IRUSR);
3178 #endif // S_IRUSR
3179 }
3180 
3181 
3182 bool file_writeable(char *filename, const char *old, size_t n)
3183 {   struct stat buf;
3184     process_file_name(filename, old, n);
3185     if (*filename == 0) return false;
3186     if (stat(filename,&buf) == -1)
3187         return false; // Should we check to see if the directory is writeable?
3188 #ifndef S_IWUSR
3189     return true;
3190 #else // S_IWUSR
3191     return (buf.st_mode & S_IWUSR);
3192 #endif // S_IWUSR
3193 }
3194 
3195 
3196 bool file_executable(char *filename, const char *old, size_t n)
3197 {   struct stat buf;
3198     process_file_name(filename, old, n);
3199     if (*filename == 0) return false;
3200     if (stat(filename,&buf) == -1)
3201         return false; // Should we check to see if the directory is writeable?
3202 #ifndef S_IXUSR
3203     return true;
3204 #else // S_IXUSR
3205     return (buf.st_mode & S_IXUSR);
3206 #endif // S_IXUSR
3207 }
3208 
3209 
3210 int rename_file(char *from_name, const char *from_old,
3211                 size_t from_size,
3212                 char *to_name, const char *to_old, size_t to_size)
3213 {   process_file_name(from_name, from_old, from_size);
3214     process_file_name(to_name, to_old, to_size);
3215     if (*from_name == 0 || *to_name == 0) return 0;
3216     return std::rename(from_name,to_name);
3217 }
3218 
3219 #endif // __cpp_lib_filesystem
3220 
3221 
3222 // end of fwin.cpp
3223