1 /* Copyright (C) 1989-2003 artofcode LLC. All rights reserved.
2
3 This software is provided AS-IS with no warranty, either express or
4 implied.
5
6 This software is distributed under license and may not be copied,
7 modified or distributed except as expressly authorized under the terms
8 of the license contained in the file LICENSE in this distribution.
9
10 For more information about licensing, please refer to
11 http://www.ghostscript.com/licensing/. For information on
12 commercial licensing, go to http://www.artifex.com/licensing/ or
13 contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14 San Rafael, CA 94903, U.S.A., +1(415)492-9861.
15 */
16
17 /* $Id: gp_vms.c,v 1.38 2004/04/08 16:18:25 giles Exp $ */
18 /* VAX/VMS specific routines for Ghostscript */
19
20 #include "string_.h"
21 #include "memory_.h"
22 #include "gx.h"
23 #include "gp.h"
24 #include "gpmisc.h"
25 #include "gsstruct.h"
26 #include <stat.h>
27 #include <stdlib.h> /* for exit() with some compiler versions */
28 #include <errno.h> /* for exit() with other compiler versions */
29 #include <unixio.h>
30
31 extern char *getenv(const char *);
32
33 /* Apparently gcc doesn't allow extra arguments for fopen: */
34 #ifdef VMS /* DEC C */
35 # define fopen_VMS fopen
36 #else /* gcc */
37 # define fopen_VMS(name, mode, m1, m2) fopen(name, mode)
38 #endif
39
40
41 /* VMS string descriptor structure */
42 #define DSC$K_DTYPE_T 14
43 #define DSC$K_CLASS_S 1
44 struct dsc$descriptor_s {
45 unsigned short dsc$w_length;
46 unsigned char dsc$b_dtype;
47 unsigned char dsc$b_class;
48 char *dsc$a_pointer;
49 };
50 typedef struct dsc$descriptor_s descrip;
51
52 /* VMS RMS constants */
53 #define RMS_IS_ERROR_OR_NMF(rmsv) (((rmsv) & 1) == 0)
54 #define RMS$_NMF 99018
55 #define RMS$_NORMAL 65537
56 #define NAM$C_MAXRSS 255
57
58 struct file_enum_s {
59 uint context, length;
60 descrip pattern;
61 gs_memory_t *memory;
62 };
63 gs_private_st_ptrs1(st_file_enum, struct file_enum_s, "file_enum",
64 file_enum_enum_ptrs, file_enum_reloc_ptrs, pattern.dsc$a_pointer);
65
66 extern uint
67 LIB$FIND_FILE(descrip *, descrip *, uint *, descrip *, descrip *,
68 uint *, uint *),
69 LIB$FIND_FILE_END(uint *),
70 SYS$FILESCAN(descrip *, uint *, uint *),
71 SYS$PUTMSG(uint *, int (*)(), descrip *, uint);
72
73 private uint
strlength(char * str,uint maxlen,char term)74 strlength(char *str, uint maxlen, char term)
75 {
76 uint i = 0;
77
78 while (i < maxlen && str[i] != term)
79 i++;
80 return i;
81 }
82
83 /* Do platform-dependent initialization. */
84 void
gp_init(void)85 gp_init(void)
86 {
87 }
88
89 /* Do platform-dependent cleanup. */
90 void
gp_exit(int exit_status,int code)91 gp_exit(int exit_status, int code)
92 {
93 }
94
95 /* Exit the program. */
96 void
gp_do_exit(int exit_status)97 gp_do_exit(int exit_status)
98 { /* The program returns exit_status = 0 for OK, 1 for failure; */
99 /* VMS has different conventions. */
100 switch (exit_status) {
101 case 0:
102 exit(exit_OK);
103 case 1:
104 exit(exit_FAILED);
105 }
106 exit(exit_status);
107 }
108
109 /* ------ Date and time ------ */
110
111 /* Read the current time (in seconds since Jan. 1, 1980) */
112 /* and fraction (in nanoseconds). */
113 void
gp_get_realtime(long * pdt)114 gp_get_realtime(long *pdt)
115 {
116 struct {
117 uint _l0, _l1;
118 } binary_date, now, difference;
119 long LIB$EDIV(), LIB$SUBX(), SYS$BINTIM(), SYS$GETTIM();
120 long units_per_second = 10000000;
121 char *jan_1_1980 = "1-JAN-1980 00:00:00.00";
122 descrip str_desc;
123
124 /* For those interested, Wednesday, November 17, 1858 is the base
125 of the Modified Julian Day system adopted by the Smithsonian
126 Astrophysical Observatory in 1957 for satellite tracking. (The
127 year 1858 preceded the oldest star catalog in use at the
128 observatory.) VMS uses quadword time stamps which are offsets
129 in 100 nanosecond units from November 17, 1858. With a 63-bit
130 absolute time representation (sign bit must be clear), VMS will
131 have no trouble with time until 31-JUL-31086 02:48:05.47. */
132
133 /* Convert January 1, 1980 into a binary absolute time */
134 str_desc.dsc$w_length = strlen(jan_1_1980);
135 str_desc.dsc$a_pointer = jan_1_1980;
136 (void)SYS$BINTIM(&str_desc, &binary_date);
137
138 /* Compute number of 100 nanosecond units since January 1, 1980. */
139 (void)SYS$GETTIM(&now);
140 (void)LIB$SUBX(&now, &binary_date, &difference);
141
142 /* Convert to seconds and nanoseconds. */
143 (void)LIB$EDIV(&units_per_second, &difference, &pdt[0], &pdt[1]);
144 pdt[1] *= 100;
145 }
146
147 /* Read the current user CPU time (in seconds) */
148 /* and fraction (in nanoseconds). */
149 void
gp_get_usertime(long * pdt)150 gp_get_usertime(long *pdt)
151 {
152 gp_get_realtime(pdt); /* Use an approximation for now. */
153 }
154
155
156 /* ------ Persistent data cache ------*/
157
158 /* insert a buffer under a (type, key) pair */
gp_cache_insert(int type,byte * key,int keylen,void * buffer,int buflen)159 int gp_cache_insert(int type, byte *key, int keylen, void *buffer, int buflen)
160 {
161 /* not yet implemented */
162 return 0;
163 }
164
165 /* look up a (type, key) in the cache */
gp_cache_query(int type,byte * key,int keylen,void ** buffer,gp_cache_alloc alloc,void * userdata)166 int gp_cache_query(int type, byte* key, int keylen, void **buffer,
167 gp_cache_alloc alloc, void *userdata)
168 {
169 /* not yet implemented */
170 return -1;
171 }
172
173 /* ------ Screen management ------ */
174
175 /* Get the environment variable that specifies the display to use. */
176 const char *
gp_getenv_display(void)177 gp_getenv_display(void)
178 {
179 return getenv("DECW$DISPLAY");
180 }
181
182 /* ------ Printer accessing ------ */
183
184 /* Open a connection to a printer. A null file name means use the */
185 /* standard printer connected to the machine, if any. */
186 /* Return NULL if the connection could not be opened. */
187 FILE *
gp_open_printer(char fname[gp_file_name_sizeof],int binary_mode)188 gp_open_printer(char fname[gp_file_name_sizeof], int binary_mode)
189 {
190 if (strlen(fname) == 0)
191 return 0;
192 if (binary_mode) { /*
193 * Printing must be done exactly byte to byte,
194 * using "passall". However the standard VMS symbiont
195 * does not treat stream-LF files correctly in this respect,
196 * but throws away \n characters. Giving the file
197 * the record type "undefined", but accessing it as a
198 * normal stream-LF file does the trick.
199 */
200 return fopen_VMS(fname, "w", "rfm = udf", "ctx = stm");
201 } else { /* Open as a normal text stream file. */
202 return fopen_VMS(fname, "w", "rfm = var", "rat = cr");
203 }
204 }
205
206 /* Close the connection to the printer. */
207 void
gp_close_printer(FILE * pfile,const char * fname)208 gp_close_printer(FILE * pfile, const char *fname)
209 {
210 fclose(pfile);
211 }
212
213 /* ------ File naming and accessing ------ */
214
215 /* Define the character used for separating file names in a list. */
216 const char gp_file_name_list_separator = ',';
217
218 /* Define the default scratch file name prefix. */
219 const char gp_scratch_file_name_prefix[] = "_temp_";
220
221 /* Define the name of the null output file. */
222 const char gp_null_file_name[] = "NLA0:";
223
224 /* Define the name that designates the current directory. */
225 const char gp_current_directory_name[] = "[]";
226
227 /* Define the string to be concatenated with the file mode */
228 /* for opening files without end-of-line conversion. */
229 const char gp_fmode_binary_suffix[] = "";
230
231 /* Define the file modes for binary reading or writing. */
232 const char gp_fmode_rb[] = "r";
233 const char gp_fmode_wb[] = "w";
234
235 /* Create and open a scratch file with a given name prefix. */
236 /* Write the actual file name at fname. */
237 FILE *
gp_open_scratch_file(const char * prefix,char fname[gp_file_name_sizeof],const char * mode)238 gp_open_scratch_file(const char *prefix, char fname[gp_file_name_sizeof],
239 const char *mode)
240 {
241 FILE *f;
242 char tmpdir[gp_file_name_sizeof];
243 int tdlen = gp_file_name_sizeof;
244 int flen[1];
245
246 if (!gp_file_name_is_absolute(prefix, strlen(prefix)) &&
247 gp_gettmpdir(tmpdir, &tdlen) == 0) {
248 flen[0] = gp_file_name_sizeof;
249 if (gp_file_name_combine(tmpdir, tdlen, prefix, strlen(prefix),
250 false, fname, flen ) != gp_combine_success ) {
251 return NULL;
252 }
253 fname[ *flen ] = 0;
254 } else {
255 strcpy(fname, prefix);
256 }
257 if (strlen(fname) + 6 >= gp_file_name_sizeof)
258 return 0; /* file name too long */
259 strcat(fname, "XXXXXX");
260 mktemp(fname);
261 f = fopen(fname, mode);
262
263 if (f == NULL)
264 eprintf1("**** Could not open temporary file %s\n", fname);
265 return f;
266 }
267
268 /* Open a file with the given name, as a stream of uninterpreted bytes. */
269 /* We have to do something special if the file was FTP'ed in binary mode. */
270 /* Unfortunately, only DEC C supports the extra arguments to fopen. */
271 FILE *
gp_fopen(const char * fname,const char * mode)272 gp_fopen(const char *fname, const char *mode)
273 {
274 #ifdef __DECC
275 #define FAB$C_FIX 1
276 stat_t buffer;
277
278 if (stat((char *)fname, &buffer) == 0)
279 if (buffer.st_fab_rfm == FAB$C_FIX)
280 return fopen(fname, mode, "rfm=stmlf", "ctx=stm");
281 #endif
282 return fopen(fname, mode);
283 }
284
285 /* Set a file into binary or text mode. */
286 int
gp_setmode_binary(FILE * pfile,bool binary)287 gp_setmode_binary(FILE * pfile, bool binary)
288 {
289 return 0; /* Noop under VMS */
290 }
291
292 /* ------ Wild card file search procedures ------ */
293
294 private void
gp_free_enumeration(file_enum * pfen)295 gp_free_enumeration(file_enum * pfen)
296 {
297 if (pfen) {
298 LIB$FIND_FILE_END(&pfen->context);
299 gs_free_object(pfen->memory, pfen->pattern.dsc$a_pointer,
300 "GP_ENUM(pattern)");
301 gs_free_object(pfen->memory, pfen,
302 "GP_ENUM(file_enum)");
303 }
304 }
305
306 /* Begin an enumeration. See gp.h for details. */
307
308 file_enum *
gp_enumerate_files_init(const char * pat,uint patlen,gs_memory_t * mem)309 gp_enumerate_files_init(const char *pat, uint patlen, gs_memory_t * mem)
310 {
311 file_enum *pfen;
312 uint i, len;
313 char *c, *newpat;
314 bool dot_in_filename = false;
315
316 pfen = gs_alloc_struct(mem, file_enum, &st_file_enum,
317 "GP_ENUM(file_enum)");
318 newpat = (char *)gs_alloc_bytes(mem, patlen + 2, "GP_ENUM(pattern)");
319 if (pfen == 0 || newpat == 0) {
320 gs_free_object(mem, newpat, "GP_ENUM(pattern)");
321 gs_free_object(mem, pfen, "GP_ENUM(file_enum)");
322 return (file_enum *) 0;
323 }
324 /* Copy the pattern removing backslash quoting characters and
325 * transforming unquoted question marks, '?', to percent signs, '%'.
326 * (VAX/VMS uses the wildcard '%' to represent exactly one character
327 * and '*' to represent zero or more characters. Any combination and
328 * number of interspersed wildcards is permitted.)
329 *
330 * Since VMS requires "*.*" to actually return all files, we add a
331 * special check for a path ending in "*" and change it into "*.*"
332 * if a "." wasn't part of the file spec. Thus "[P.A.T.H]*" becomes
333 * "[P.A.T.H]*.*" but "[P.A.T.H]*.*" or "[P.A.T.H]*.X*" are unmodified.
334 */
335 c = newpat;
336 for (i = 0; i < patlen; pat++, i++)
337 switch (*pat) {
338 case '?':
339 *c++ = '%';
340 break;
341 case '\\':
342 i++;
343 if (i < patlen)
344 *c++ = *++pat;
345 break;
346 case '.':
347 case ']':
348 dot_in_filename = *pat == '.';
349 default:
350 *c++ = *pat;
351 break;
352 }
353 /* Check for trailing "*" and see if we need to add ".*" */
354 if (pat[-1] == '*' && !dot_in_filename) {
355 *c++ = '.';
356 *c++ = '*';
357 }
358 len = c - newpat;
359
360 /* Pattern may not exceed 255 characters */
361 if (len > 255) {
362 gs_free_object(mem, newpat, "GP_ENUM(pattern)");
363 gs_free_object(mem, pfen, "GP_ENUM(file_enum)");
364 return (file_enum *) 0;
365 }
366 pfen->context = 0;
367 pfen->length = patlen;
368 pfen->pattern.dsc$w_length = len;
369 pfen->pattern.dsc$b_dtype = DSC$K_DTYPE_T;
370 pfen->pattern.dsc$b_class = DSC$K_CLASS_S;
371 pfen->pattern.dsc$a_pointer = newpat;
372 pfen->memory = mem;
373
374 return pfen;
375 }
376
377 /* Return the next file name in the enumeration. The client passes in */
378 /* a scratch string and a max length. If the name of the next file fits, */
379 /* the procedure returns the length. If it doesn't fit, the procedure */
380 /* returns max length +1. If there are no more files, the procedure */
381 /* returns -1. */
382
383 uint
gp_enumerate_files_next(file_enum * pfen,char * ptr,uint maxlen)384 gp_enumerate_files_next(file_enum * pfen, char *ptr, uint maxlen)
385 {
386 char *c, filnam[NAM$C_MAXRSS];
387 descrip result =
388 {NAM$C_MAXRSS, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
389 uint i, len;
390
391 result.dsc$a_pointer = filnam;
392
393 /* Find the next file which matches the pattern */
394 i = LIB$FIND_FILE(&pfen->pattern, &result, &pfen->context,
395 (descrip *) 0, (descrip *) 0, (uint *) 0, (uint *) 0);
396
397 /* Check the return status */
398 if (RMS_IS_ERROR_OR_NMF(i)) {
399 gp_free_enumeration(pfen);
400 return (uint)(-1);
401 } else if ((len = strlength(filnam, NAM$C_MAXRSS, ' ')) > maxlen)
402 return maxlen + 1;
403
404 /* Copy the returned filename over to the input string ptr */
405 c = ptr;
406 for (i = 0; i < len; i++)
407 *c++ = filnam[i];
408
409 return len;
410 }
411
412 /* Clean up a file enumeration. This is only called to abandon */
413 /* an enumeration partway through: ...next should do it if there are */
414 /* no more files to enumerate. This should deallocate the file_enum */
415 /* structure and any subsidiary structures, strings, buffers, etc. */
416
417 void
gp_enumerate_files_close(file_enum * pfen)418 gp_enumerate_files_close(file_enum * pfen)
419 {
420 gp_free_enumeration(pfen);
421 }
422
423 const char *
gp_strerror(int errnum)424 gp_strerror(int errnum)
425 {
426 return NULL;
427 }
428
429 /* -------------- Helpers for gp_file_name_combine_generic ------------- */
430
gp_file_name_root(const char * fname,uint len)431 uint gp_file_name_root(const char *fname, uint len)
432 {
433 /*
434 * The root for device:[root.][directory.subdirectory]filename.extension;version
435 * is device:[root.][
436 * The root for device:[directory.subdirectory]filename.extension;version
437 * is device:[
438 * The root for logical:filename.extension;version
439 * is logical:
440 */
441 int i, j;
442
443 if (len == 0)
444 return 0;
445 /* Search for ':' */
446 for (i = 0; i < len; i++)
447 if (fname[i] == ':')
448 break;
449 if (i == len)
450 return 0; /* No root. */
451 if (fname[i] == ':')
452 i++;
453 if (i == len || fname[i] != '[')
454 return i;
455 /* Search for ']' */
456 i++;
457 for (j = i; j < len; j++)
458 if (fname[j] == ']')
459 break;
460 if (j == len)
461 return i; /* No ']'. Allowed as a Ghostscript specifics. */
462 j++;
463 if (j == len)
464 return i; /* Appending "device:[directory.subdirectory]" with "filename.extension;version". */
465 if (fname[j] != '[')
466 return i; /* Can't append anything, but pass through for checking an absolute path. */
467 return j + 1; /* device:[root.][ */
468 }
469
gs_file_name_check_separator(const char * fname,int len,const char * item)470 uint gs_file_name_check_separator(const char *fname, int len, const char *item)
471 {
472 if (len > 0) {
473 /*
474 * Ghostscript specifics : an extended syntax like Mac OS.
475 * We intentionally don't consider ':' and '[' as separators
476 * in forward search, see gp_file_name_combine.
477 */
478 if (fname[0] == ']')
479 return 1; /* It is a file separator. */
480 if (fname[0] == '.')
481 return 1; /* It is a directory separator. */
482 if (fname[0] == '-') {
483 if (fname == item + 1 && item[0] == '-')
484 return 1; /* Two or more parents, cut the first one. */
485 return 1;
486 }
487 } else if (len < 0) {
488 if (fname[-1] == '.' || fname[-1] == ':' || fname[-1] == '[')
489 return 1;
490 }
491 return 0;
492 }
493
gp_file_name_is_parent(const char * fname,uint len)494 bool gp_file_name_is_parent(const char *fname, uint len)
495 { /* Ghostscript specifics : an extended syntax like Mac OS. */
496 return len == 1 && fname[0] == '-';
497 }
498
gp_file_name_is_current(const char * fname,uint len)499 bool gp_file_name_is_current(const char *fname, uint len)
500 { /* Ghostscript specifics : an extended syntax like Mac OS. */
501 return len == 0;
502 }
503
gp_file_name_separator(void)504 const char *gp_file_name_separator(void)
505 { return "]";
506 }
507
gp_file_name_directory_separator(void)508 const char *gp_file_name_directory_separator(void)
509 { return ".";
510 }
511
gp_file_name_parent(void)512 const char *gp_file_name_parent(void)
513 { return "-";
514 }
515
gp_file_name_current(void)516 const char *gp_file_name_current(void)
517 { return "";
518 }
519
gp_file_name_is_partent_allowed(void)520 bool gp_file_name_is_partent_allowed(void)
521 { return false;
522 }
523
gp_file_name_is_empty_item_meanful(void)524 bool gp_file_name_is_empty_item_meanful(void)
525 { return true;
526 }
527
528 gp_file_name_combine_result
gp_file_name_combine(const char * prefix,uint plen,const char * fname,uint flen,bool no_sibling,char * buffer,uint * blen)529 gp_file_name_combine(const char *prefix, uint plen, const char *fname, uint flen,
530 bool no_sibling, char *buffer, uint *blen)
531 {
532 /*
533 * Reduce it to the general case.
534 *
535 * Implementation restriction : fname must not contain a part of
536 * "device:[root.]["
537 */
538 uint rlen, flen1 = flen, plen1 = plen;
539 const char *fname1 = fname;
540
541 if ( plen > 0 && prefix[plen-1] == '\0' )
542 plen--;
543
544 if (plen == 0 && flen == 0) {
545 /* Not sure that we need this case. */
546 if (*blen == 0)
547 return gp_combine_small_buffer;
548 buffer[0] = '.';
549 *blen = 1;
550 }
551 rlen = gp_file_name_root(fname, flen);
552 if (rlen > 0 || plen == 0 || flen == 0) {
553 if (rlen == 0 && plen != 0) {
554 fname1 = prefix;
555 flen1 = plen;
556 }
557 if (flen1 + 1 > *blen)
558 return gp_combine_small_buffer;
559 memcpy(buffer, fname1, flen1);
560 buffer[flen1] = 0;
561 *blen = flen1;
562 return gp_combine_success;
563 }
564
565 if ( prefix[plen - 1] == ']' && fname[ 0 ] == '-' )
566 {
567 memcpy(buffer, prefix, plen - 1 );
568 fname1 = fname + 1;
569 flen1 = flen - 1;
570 memcpy(buffer + plen - 1 , fname1, flen1);
571 memcpy(buffer + plen + flen1 - 1 , "]" , 1 );
572 buffer[plen + flen1] = 0;
573 *blen = plen + flen1;
574 return gp_combine_success;
575 }
576
577 if ( prefix[plen - 1] == ':' || (prefix[plen - 1] == ']' &&
578 memchr(fname, ']', flen) == 0) )
579 {
580 /* Just concatenate. */
581 if (plen + flen + 1 > *blen)
582 return gp_combine_small_buffer;
583 memcpy(buffer, prefix, plen);
584 memcpy(buffer + plen, fname, flen);
585 buffer[plen + flen] = 0;
586 *blen = plen + flen;
587 return gp_combine_success;
588 }
589 if ( memchr( prefix , '[' , plen ) == 0 &&
590 memchr( prefix , '.' , plen ) == 0 )
591 {
592 char* tmp_prefix;
593 int tmp_plen;
594
595 if ( prefix[0] == '/' )
596 {
597 tmp_prefix = prefix + 1;
598 tmp_plen = plen - 1;
599 }
600 else
601 {
602 tmp_prefix = prefix;
603 tmp_plen = plen;
604 }
605 if ( tmp_plen + flen + 2 > *blen)
606 return gp_combine_small_buffer;
607 memcpy(buffer, tmp_prefix, tmp_plen);
608 memcpy(buffer + tmp_plen , ":" , 1 );
609 memcpy(buffer + tmp_plen + 1, fname, flen);
610 if ( memchr( fname , '.' , flen ) != 0 )
611 {
612 buffer[ tmp_plen + flen + 1] = 0;
613 *blen = tmp_plen + flen + 1;
614 }
615 else
616 {
617 memcpy(buffer + tmp_plen + flen + 1 , "." , 1 );
618 buffer[ tmp_plen + flen + 2] = 0;
619 *blen = tmp_plen + flen + 2;
620 }
621 return gp_combine_success;
622 }
623 if (prefix[plen - 1] != ']' && fname[0] == '[')
624 return gp_combine_cant_handle;
625 /* Unclose "][" :*/
626 if (fname[0] == '[') {
627 fname1 = fname + 1;
628 flen1 = flen - 1;
629 }
630 if (prefix[plen - 1] == ']')
631 plen1 = plen - 1;
632 return gp_file_name_combine_generic(prefix, plen1,
633 fname1, flen1, no_sibling, buffer, blen);
634 }
635
636 /* ------ Font enumeration ------ */
637
638 /* This is used to query the native os for a list of font names and
639 * corresponding paths. The general idea is to save the hassle of
640 * building a custom fontmap file.
641 */
642
gp_enumerate_fonts_init(gs_memory_t * mem)643 void *gp_enumerate_fonts_init(gs_memory_t *mem)
644 {
645 return NULL;
646 }
647
gp_enumerate_fonts_next(void * enum_state,char ** fontname,char ** path)648 int gp_enumerate_fonts_next(void *enum_state, char **fontname, char **path)
649 {
650 return 0;
651 }
652
gp_enumerate_fonts_free(void * enum_state)653 void gp_enumerate_fonts_free(void *enum_state)
654 {
655 }
656
657