1 //========================================================================
2 //
3 // gfile.cc
4 //
5 // Miscellaneous file and directory name manipulation.
6 //
7 // Copyright 1996-2003 Glyph & Cog, LLC
8 //
9 //========================================================================
10
11 #include <aconf.h>
12
13 #ifdef _WIN32
14 # undef WIN32_LEAN_AND_MEAN
15 # include <windows.h>
16 # include <time.h>
17 # include <direct.h>
18 # include <shobjidl.h>
19 # include <shlguid.h>
20 #else
21 # if !defined(ACORN)
22 # include <sys/types.h>
23 # include <sys/stat.h>
24 # include <fcntl.h>
25 # endif
26 # include <time.h>
27 # include <limits.h>
28 # include <string.h>
29 # if !defined(VMS) && !defined(ACORN)
30 # include <pwd.h>
31 # endif
32 # if defined(VMS) && (__DECCXX_VER < 50200000)
33 # include <unixlib.h>
34 # endif
35 #endif // _WIN32
36 #include "gmem.h"
37 #include "gmempp.h"
38 #include "GString.h"
39 #include "gfile.h"
40
41 // Some systems don't define this, so just make it something reasonably
42 // large.
43 #ifndef PATH_MAX
44 #define PATH_MAX 1024
45 #endif
46
47 //------------------------------------------------------------------------
48
getHomeDir()49 GString *getHomeDir() {
50 #ifdef VMS
51 //---------- VMS ----------
52 return new GString("SYS$LOGIN:");
53
54 #elif defined(__EMX__) || defined(_WIN32)
55 //---------- OS/2+EMX and Win32 ----------
56 char *s;
57 GString *ret;
58
59 if ((s = getenv("HOME")))
60 ret = new GString(s);
61 else
62 ret = new GString(".");
63 return ret;
64
65 #elif defined(ACORN)
66 //---------- RISCOS ----------
67 return new GString("@");
68
69 #else
70 //---------- Unix ----------
71 char *s;
72 struct passwd *pw;
73 GString *ret;
74
75 if ((s = getenv("HOME"))) {
76 ret = new GString(s);
77 } else {
78 if ((s = getenv("USER")))
79 pw = getpwnam(s);
80 else
81 pw = getpwuid(getuid());
82 if (pw)
83 ret = new GString(pw->pw_dir);
84 else
85 ret = new GString(".");
86 }
87 return ret;
88 #endif
89 }
90
getCurrentDir()91 GString *getCurrentDir() {
92 char buf[PATH_MAX+1];
93
94 #if defined(__EMX__)
95 if (_getcwd2(buf, sizeof(buf)))
96 #elif defined(_WIN32)
97 if (GetCurrentDirectoryA(sizeof(buf), buf))
98 #elif defined(ACORN)
99 if (strcpy(buf, "@"))
100 #else
101 if (getcwd(buf, sizeof(buf)))
102 #endif
103 return new GString(buf);
104 return new GString();
105 }
106
appendToPath(GString * path,const char * fileName)107 GString *appendToPath(GString *path, const char *fileName) {
108 #if defined(VMS)
109 //---------- VMS ----------
110 //~ this should handle everything necessary for file
111 //~ requesters, but it's certainly not complete
112 char *p0, *p1, *p2;
113 char *q1;
114
115 p0 = path->getCString();
116 p1 = p0 + path->getLength() - 1;
117 if (!strcmp(fileName, "-")) {
118 if (*p1 == ']') {
119 for (p2 = p1; p2 > p0 && *p2 != '.' && *p2 != '['; --p2) ;
120 if (*p2 == '[')
121 ++p2;
122 path->del(p2 - p0, p1 - p2);
123 } else if (*p1 == ':') {
124 path->append("[-]");
125 } else {
126 path->clear();
127 path->append("[-]");
128 }
129 } else if ((q1 = strrchr(fileName, '.')) && !strncmp(q1, ".DIR;", 5)) {
130 if (*p1 == ']') {
131 path->insert(p1 - p0, '.');
132 path->insert(p1 - p0 + 1, fileName, q1 - fileName);
133 } else if (*p1 == ':') {
134 path->append('[');
135 path->append(']');
136 path->append(fileName, q1 - fileName);
137 } else {
138 path->clear();
139 path->append(fileName, q1 - fileName);
140 }
141 } else {
142 if (*p1 != ']' && *p1 != ':')
143 path->clear();
144 path->append(fileName);
145 }
146 return path;
147
148 #elif defined(_WIN32)
149 //---------- Win32 ----------
150 GString *tmp;
151 char buf[256];
152 char *fp;
153
154 tmp = new GString(path);
155 tmp->append('/');
156 tmp->append(fileName);
157 GetFullPathNameA(tmp->getCString(), sizeof(buf), buf, &fp);
158 delete tmp;
159 path->clear();
160 path->append(buf);
161 return path;
162
163 #elif defined(ACORN)
164 //---------- RISCOS ----------
165 char *p;
166 int i;
167
168 path->append(".");
169 i = path->getLength();
170 path->append(fileName);
171 for (p = path->getCString() + i; *p; ++p) {
172 if (*p == '/') {
173 *p = '.';
174 } else if (*p == '.') {
175 *p = '/';
176 }
177 }
178 return path;
179
180 #elif defined(__EMX__)
181 //---------- OS/2+EMX ----------
182 int i;
183
184 // appending "." does nothing
185 if (!strcmp(fileName, "."))
186 return path;
187
188 // appending ".." goes up one directory
189 if (!strcmp(fileName, "..")) {
190 for (i = path->getLength() - 2; i >= 0; --i) {
191 if (path->getChar(i) == '/' || path->getChar(i) == '\\' ||
192 path->getChar(i) == ':')
193 break;
194 }
195 if (i <= 0) {
196 if (path->getChar(0) == '/' || path->getChar(0) == '\\') {
197 path->del(1, path->getLength() - 1);
198 } else if (path->getLength() >= 2 && path->getChar(1) == ':') {
199 path->del(2, path->getLength() - 2);
200 } else {
201 path->clear();
202 path->append("..");
203 }
204 } else {
205 if (path->getChar(i-1) == ':')
206 ++i;
207 path->del(i, path->getLength() - i);
208 }
209 return path;
210 }
211
212 // otherwise, append "/" and new path component
213 if (path->getLength() > 0 &&
214 path->getChar(path->getLength() - 1) != '/' &&
215 path->getChar(path->getLength() - 1) != '\\')
216 path->append('/');
217 path->append(fileName);
218 return path;
219
220 #else
221 //---------- Unix ----------
222 int i;
223
224 // appending "." does nothing
225 if (!strcmp(fileName, "."))
226 return path;
227
228 // appending ".." goes up one directory
229 if (!strcmp(fileName, "..")) {
230 for (i = path->getLength() - 2; i >= 0; --i) {
231 if (path->getChar(i) == '/')
232 break;
233 }
234 if (i <= 0) {
235 if (path->getChar(0) == '/') {
236 path->del(1, path->getLength() - 1);
237 } else {
238 path->clear();
239 path->append("..");
240 }
241 } else {
242 path->del(i, path->getLength() - i);
243 }
244 return path;
245 }
246
247 // otherwise, append "/" and new path component
248 if (path->getLength() > 0 &&
249 path->getChar(path->getLength() - 1) != '/')
250 path->append('/');
251 path->append(fileName);
252 return path;
253 #endif
254 }
255
grabPath(char * fileName)256 GString *grabPath(char *fileName) {
257 #ifdef VMS
258 //---------- VMS ----------
259 char *p;
260
261 if ((p = strrchr(fileName, ']')))
262 return new GString(fileName, p + 1 - fileName);
263 if ((p = strrchr(fileName, ':')))
264 return new GString(fileName, p + 1 - fileName);
265 return new GString();
266
267 #elif defined(__EMX__) || defined(_WIN32)
268 //---------- OS/2+EMX and Win32 ----------
269 char *p;
270
271 if ((p = strrchr(fileName, '/')))
272 return new GString(fileName, (int)(p - fileName));
273 if ((p = strrchr(fileName, '\\')))
274 return new GString(fileName, (int)(p - fileName));
275 if ((p = strrchr(fileName, ':')))
276 return new GString(fileName, (int)(p + 1 - fileName));
277 return new GString();
278
279 #elif defined(ACORN)
280 //---------- RISCOS ----------
281 char *p;
282
283 if ((p = strrchr(fileName, '.')))
284 return new GString(fileName, p - fileName);
285 return new GString();
286
287 #else
288 //---------- Unix ----------
289 char *p;
290
291 if ((p = strrchr(fileName, '/')))
292 return new GString(fileName, (int)(p - fileName));
293 return new GString();
294 #endif
295 }
296
isAbsolutePath(char * path)297 GBool isAbsolutePath(char *path) {
298 #ifdef VMS
299 //---------- VMS ----------
300 return strchr(path, ':') ||
301 (path[0] == '[' && path[1] != '.' && path[1] != '-');
302
303 #elif defined(__EMX__) || defined(_WIN32)
304 //---------- OS/2+EMX and Win32 ----------
305 return path[0] == '/' || path[0] == '\\' || path[1] == ':';
306
307 #elif defined(ACORN)
308 //---------- RISCOS ----------
309 return path[0] == '$';
310
311 #else
312 //---------- Unix ----------
313 return path[0] == '/';
314 #endif
315 }
316
makePathAbsolute(GString * path)317 GString *makePathAbsolute(GString *path) {
318 #ifdef VMS
319 //---------- VMS ----------
320 char buf[PATH_MAX+1];
321
322 if (!isAbsolutePath(path->getCString())) {
323 if (getcwd(buf, sizeof(buf))) {
324 path->insert(0, buf);
325 }
326 }
327 return path;
328
329 #elif defined(_WIN32)
330 //---------- Win32 ----------
331 char buf[_MAX_PATH];
332 char *fp;
333
334 buf[0] = '\0';
335 if (!GetFullPathNameA(path->getCString(), _MAX_PATH, buf, &fp)) {
336 path->clear();
337 return path;
338 }
339 path->clear();
340 path->append(buf);
341 return path;
342
343 #elif defined(ACORN)
344 //---------- RISCOS ----------
345 path->insert(0, '@');
346 return path;
347
348 #else
349 //---------- Unix and OS/2+EMX ----------
350 struct passwd *pw;
351 char buf[PATH_MAX+1];
352 GString *s;
353 char *p1, *p2;
354 int n;
355
356 if (path->getChar(0) == '~') {
357 if (path->getChar(1) == '/' ||
358 #ifdef __EMX__
359 path->getChar(1) == '\\' ||
360 #endif
361 path->getLength() == 1) {
362 path->del(0, 1);
363 s = getHomeDir();
364 path->insert(0, s);
365 delete s;
366 } else {
367 p1 = path->getCString() + 1;
368 #ifdef __EMX__
369 for (p2 = p1; *p2 && *p2 != '/' && *p2 != '\\'; ++p2) ;
370 #else
371 for (p2 = p1; *p2 && *p2 != '/'; ++p2) ;
372 #endif
373 if ((n = (int)(p2 - p1)) > PATH_MAX)
374 n = PATH_MAX;
375 strncpy(buf, p1, n);
376 buf[n] = '\0';
377 if ((pw = getpwnam(buf))) {
378 path->del(0, (int)(p2 - p1 + 1));
379 path->insert(0, pw->pw_dir);
380 }
381 }
382 } else if (!isAbsolutePath(path->getCString())) {
383 if (getcwd(buf, sizeof(buf))) {
384 #ifndef __EMX__
385 path->insert(0, '/');
386 #endif
387 path->insert(0, buf);
388 }
389 }
390 return path;
391 #endif
392 }
393
getModTime(char * fileName)394 time_t getModTime(char *fileName) {
395 #ifdef _WIN32
396 //~ should implement this, but it's (currently) only used in xpdf
397 return 0;
398 #else
399 struct stat statBuf;
400
401 if (stat(fileName, &statBuf)) {
402 return 0;
403 }
404 return statBuf.st_mtime;
405 #endif
406 }
407
openTempFile(GString ** name,FILE ** f,const char * mode,const char * ext)408 GBool openTempFile(GString **name, FILE **f,
409 const char *mode, const char *ext) {
410 #if defined(_WIN32)
411 //---------- Win32 ----------
412 char tempPath[MAX_PATH + 1];
413 GString *s, *s2;
414 FILE *f2;
415 DWORD n;
416 int t, i;
417
418 // this has the standard race condition problem, but I haven't found
419 // a better way to generate temp file names with extensions on
420 // Windows
421 n = GetTempPathA(sizeof(tempPath), tempPath);
422 if (n > 0 && n <= sizeof(tempPath)) {
423 s = new GString(tempPath);
424 if (tempPath[n-1] != '\\') {
425 s->append('\\');
426 }
427 } else {
428 s = new GString(".\\");
429 }
430 s->appendf("xpdf_{0:d}_{1:d}_",
431 (int)GetCurrentProcessId(), (int)GetCurrentThreadId());
432 t = (int)time(NULL);
433 for (i = 0; i < 1000; ++i) {
434 s2 = GString::format("{0:t}{1:d}", s, t + i);
435 if (ext) {
436 s2->append(ext);
437 }
438 if (!(f2 = fopen(s2->getCString(), "r"))) {
439 if (!(f2 = fopen(s2->getCString(), mode))) {
440 delete s2;
441 delete s;
442 return gFalse;
443 }
444 *name = s2;
445 *f = f2;
446 delete s;
447 return gTrue;
448 }
449 fclose(f2);
450 delete s2;
451 }
452 delete s;
453 return gFalse;
454 #elif defined(VMS) || defined(__EMX__) || defined(ACORN)
455 //---------- non-Unix ----------
456 char *s;
457
458 // There is a security hole here: an attacker can create a symlink
459 // with this file name after the tmpnam call and before the fopen
460 // call. I will happily accept fixes to this function for non-Unix
461 // OSs.
462 if (!(s = tmpnam(NULL))) {
463 return gFalse;
464 }
465 *name = new GString(s);
466 if (ext) {
467 (*name)->append(ext);
468 }
469 if (!(*f = fopen((*name)->getCString(), mode))) {
470 delete (*name);
471 *name = NULL;
472 return gFalse;
473 }
474 return gTrue;
475 #else
476 //---------- Unix ----------
477 char *s;
478 int fd;
479
480 if (ext) {
481 #if HAVE_MKSTEMPS
482 if ((s = getenv("TMPDIR"))) {
483 *name = new GString(s);
484 } else {
485 *name = new GString("/tmp");
486 }
487 (*name)->append("/XXXXXX")->append(ext);
488 fd = mkstemps((*name)->getCString(), (int)strlen(ext));
489 #else
490 if (!(s = tmpnam(NULL))) {
491 return gFalse;
492 }
493 *name = new GString(s);
494 (*name)->append(ext);
495 fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
496 #endif
497 } else {
498 #if HAVE_MKSTEMP
499 if ((s = getenv("TMPDIR"))) {
500 *name = new GString(s);
501 } else {
502 *name = new GString("/tmp");
503 }
504 (*name)->append("/XXXXXX");
505 fd = mkstemp((*name)->getCString());
506 #else // HAVE_MKSTEMP
507 if (!(s = tmpnam(NULL))) {
508 return gFalse;
509 }
510 *name = new GString(s);
511 fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
512 #endif // HAVE_MKSTEMP
513 }
514 if (fd < 0 || !(*f = fdopen(fd, mode))) {
515 delete *name;
516 *name = NULL;
517 return gFalse;
518 }
519 return gTrue;
520 #endif
521 }
522
createDir(char * path,int mode)523 GBool createDir(char *path, int mode) {
524 #ifdef _WIN32
525 return !mkdir(path);
526 #else
527 return !mkdir(path, mode);
528 #endif
529 }
530
executeCommand(char * cmd)531 GBool executeCommand(char *cmd) {
532 #ifdef VMS
533 return system(cmd) ? gTrue : gFalse;
534 #else
535 return system(cmd) ? gFalse : gTrue;
536 #endif
537 }
538
539 #ifdef _WIN32
fileNameToUTF8(char * path)540 GString *fileNameToUTF8(char *path) {
541 GString *s;
542 char *p;
543
544 s = new GString();
545 for (p = path; *p; ++p) {
546 if (*p & 0x80) {
547 s->append((char)(0xc0 | ((*p >> 6) & 0x03)));
548 s->append((char)(0x80 | (*p & 0x3f)));
549 } else {
550 s->append(*p);
551 }
552 }
553 return s;
554 }
555
fileNameToUTF8(wchar_t * path)556 GString *fileNameToUTF8(wchar_t *path) {
557 GString *s;
558 wchar_t *p;
559
560 s = new GString();
561 for (p = path; *p; ++p) {
562 if (*p < 0x80) {
563 s->append((char)*p);
564 } else if (*p < 0x800) {
565 s->append((char)(0xc0 | ((*p >> 6) & 0x1f)));
566 s->append((char)(0x80 | (*p & 0x3f)));
567 } else {
568 s->append((char)(0xe0 | ((*p >> 12) & 0x0f)));
569 s->append((char)(0x80 | ((*p >> 6) & 0x3f)));
570 s->append((char)(0x80 | (*p & 0x3f)));
571 }
572 }
573 return s;
574 }
575
fileNameToUCS2(const char * path,wchar_t * out,size_t outSize)576 wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize) {
577 const char *p;
578 size_t i;
579
580 for (p = path, i = 0; *p && i < outSize - 1; ++i) {
581 if ((p[0] & 0xe0) == 0xc0 &&
582 p[1] && (p[1] & 0xc0) == 0x80) {
583 out[i] = (wchar_t)(((p[0] & 0x1f) << 6) |
584 (p[1] & 0x3f));
585 p += 2;
586 } else if ((p[0] & 0xf0) == 0xe0 &&
587 (p[1] & 0xc0) == 0x80 &&
588 (p[2] & 0xc0) == 0x80) {
589 out[i] = (wchar_t)(((p[0] & 0x0f) << 12) |
590 ((p[1] & 0x3f) << 6) |
591 (p[2] & 0x3f));
592 p += 3;
593 } else {
594 out[i] = (wchar_t)(p[0] & 0xff);
595 p += 1;
596 }
597 }
598 out[i] = (wchar_t)0;
599 return out;
600 }
601 #endif
602
openFile(const char * path,const char * mode)603 FILE *openFile(const char *path, const char *mode) {
604 #if defined(_WIN32)
605 wchar_t wPath[_MAX_PATH + 1];
606 wchar_t wMode[8];
607 int i;
608
609 fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t));
610 for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) {
611 wMode[i] = (wchar_t)(mode[i] & 0xff);
612 }
613 wMode[i] = (wchar_t)0;
614 readWindowsShortcut(wPath, _MAX_PATH + 1);
615 return _wfopen(wPath, wMode);
616 #elif defined(VMS)
617 return fopen(path, mode, "ctx=stm");
618 #else
619 return fopen(path, mode);
620 #endif
621 }
622
623 #ifdef _WIN32
readWindowsShortcut(wchar_t * wPath,size_t wPathSize)624 void readWindowsShortcut(wchar_t *wPath, size_t wPathSize) {
625 size_t n = wcslen(wPath);
626 if (n < 4 || wcscmp(wPath + n - 4, L".lnk")) {
627 return;
628 }
629 IShellLinkW *shellLink;
630 HRESULT hres;
631 hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
632 IID_IShellLinkW, (LPVOID *)&shellLink);
633 bool needCoUninit = false;
634 if (hres == CO_E_NOTINITIALIZED) {
635 CoInitialize(NULL);
636 needCoUninit = true;
637 hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
638 IID_IShellLinkW, (LPVOID *)&shellLink);
639 }
640 if (FAILED(hres)) {
641 return;
642 }
643 IPersistFile *persistFile;
644 hres = shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
645 if (FAILED(hres)) {
646 return;
647 }
648 hres = persistFile->Load(wPath, STGM_READ);
649 if (FAILED(hres)) {
650 fprintf(stderr, "IPersistFile.Load failed: 0x%08x\n", hres);
651 exit(1);
652 }
653 wchar_t target[_MAX_PATH + 1];
654 hres = shellLink->GetPath(target, _MAX_PATH + 1, NULL, 0);
655 if (FAILED(hres)) {
656 return;
657 }
658 shellLink->Release();
659 if (needCoUninit) {
660 CoUninitialize();
661 }
662 if (wcslen(target) > wPathSize - 1) {
663 return;
664 }
665 wcscpy(wPath, target);
666 }
667 #endif
668
makeDir(const char * path,int mode)669 int makeDir(const char *path, int mode) {
670 #ifdef _WIN32
671 wchar_t wPath[_MAX_PATH + 1];
672 return _wmkdir(fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t)));
673 #else
674 return mkdir(path, (mode_t)mode);
675 #endif
676 }
677
getLine(char * buf,int size,FILE * f)678 char *getLine(char *buf, int size, FILE *f) {
679 int c, i;
680
681 i = 0;
682 while (i < size - 1) {
683 if ((c = fgetc(f)) == EOF) {
684 break;
685 }
686 buf[i++] = (char)c;
687 if (c == '\x0a') {
688 break;
689 }
690 if (c == '\x0d') {
691 c = fgetc(f);
692 if (c == '\x0a' && i < size - 1) {
693 buf[i++] = (char)c;
694 } else if (c != EOF) {
695 ungetc(c, f);
696 }
697 break;
698 }
699 }
700 buf[i] = '\0';
701 if (i == 0) {
702 return NULL;
703 }
704 return buf;
705 }
706
gfseek(FILE * f,GFileOffset offset,int whence)707 int gfseek(FILE *f, GFileOffset offset, int whence) {
708 #if HAVE_FSEEKO
709 return fseeko(f, offset, whence);
710 #elif HAVE_FSEEK64
711 return fseek64(f, offset, whence);
712 #elif HAVE_FSEEKI64
713 return _fseeki64(f, offset, whence);
714 #else
715 return fseek(f, offset, whence);
716 #endif
717 }
718
gftell(FILE * f)719 GFileOffset gftell(FILE *f) {
720 #if HAVE_FSEEKO
721 return ftello(f);
722 #elif HAVE_FSEEK64
723 return ftell64(f);
724 #elif HAVE_FSEEKI64
725 return _ftelli64(f);
726 #else
727 return ftell(f);
728 #endif
729 }
730
fixCommandLine(int * argc,char ** argv[])731 void fixCommandLine(int *argc, char **argv[]) {
732 #ifdef _WIN32
733 int argcw;
734 wchar_t **argvw;
735 GString *arg;
736 int i;
737
738 argvw = CommandLineToArgvW(GetCommandLineW(), &argcw);
739 if (!argvw || argcw < 0) {
740 return;
741 }
742
743 *argc = argcw;
744
745 *argv = (char **)gmallocn(argcw + 1, sizeof(char *));
746 for (i = 0; i < argcw; ++i) {
747 arg = fileNameToUTF8(argvw[i]);
748 (*argv)[i] = copyString(arg->getCString());
749 delete arg;
750 }
751 (*argv)[argcw] = NULL;
752
753 LocalFree(argvw);
754 #endif
755 }
756