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