cdrtools/libvms/vms_misc.c
2025-06-15 04:19:58 +08:00

390 lines
10 KiB
C

/*
* Miscellaneous VMS-specific functions.
*/
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include "schily/mconfig.h"
#include "vms_init.h"
#include "vms_misc.h"
/*--------------------------------------------------------------------*/
/*
* 2005-03-14 SMS.
* openfd_vms().
*
* VMS-specific _openfd() function.
*
* When open() (as in _openfd()) is used to open a file, and then
* fdopen() is used to get a FILE pointer (as in _fcons()) with "b",
* then the original open() must specify "ctx=bin". Otherwise, fdopen()
* fails with "%SYSTEM-?-BADPARAM, bad parameter value".
*
* We use a phony O_BINARY flag bit (defined in xmconfig.h) to retain
* this datum, but (because it is phony) do not pass it through to
* open() here.
*
* The original requirement for this feature was READCD, so it makes
* sense to use the callback function to set the usual big-binary-file
* RMS parameters.
*
* If we had clues to the caller's intent, we could, where appropriate,
* add other performance helpers like "fop=sqo", but as-is we can't tell
* where it would be appropriate.
*/
int
openfd_vms(const char *name, int omode)
{
int sts;
static int open_id = 2;
if (omode& O_BINARY) {
omode &= (~O_BINARY);
sts = open(name, /* File name. */
omode, /* Flags. */
0777, /* Mode for default protection. */
"ctx=bin", /* Binary. */
"rfm=fix", /* Fixed-length, */
"mrs=512", /* 512-byte records. */
"acc", acc_cb, /* Access callback function. */
&open_id); /* Access callback argument. */
} else {
sts = open(name, omode);
}
return (sts);
}
/*--------------------------------------------------------------------*/
/*
* Judge availability of str[n]casecmp() in C RTL.
* (Note: This must follow a "#include <decc$types.h>" in something to
* ensure that __CRTL_VER is as defined as it will ever be. DEC C on
* VAX may not define it itself.)
*/
/*
* 2004-09-25 SMS.
* str[n]casecmp() replacement for old C RTL.
* Assumes a prehistorically incompetent toupper().
*/
#ifndef HAVE_STRCASECMP
int
strncasecmp(char *s1, char *s2, size_t n)
{
/* Initialization prepares for n == 0. */
char c1 = '\0';
char c2 = '\0';
while (n-- > 0) {
/* Set c1 and c2. Convert l-case characters to u-case. */
if (islower(c1 = *s1))
c1 = toupper(c1);
if (islower(c2 = *s2))
c2 = toupper(c2);
/* Quit at inequality or NUL. */
if ((c1 != c2) || (c1 == '\0'))
break;
s1++;
s2++;
}
return ((unsigned int) c1- (unsigned int) c2);
}
#ifndef UINT_MAX
#define UINT_MAX 4294967295U
#endif
#define strcasecmp(s1, s2) strncasecmp(s1, s2, UINT_MAX)
#endif /* ndef HAVE_STRCASECMP */
/*--------------------------------------------------------------------*/
/*
* Character property table for (re-)escaping ODS5 extended file names.
* Note that this table ignore Unicode, and does not identify invalid
* characters.
*
* ODS2 valid characters: 0-9 A-Z a-z $ - _
*
* ODS5 Invalid characters:
* C0 control codes (0x00 to 0x1F inclusive)
* Asterisk (*)
* Question mark (?)
*
* ODS5 Invalid characters only in VMS V7.2 (which no one runs, right?):
* Double quotation marks (")
* Backslash (\)
* Colon (:)
* Left angle bracket (<)
* Right angle bracket (>)
* Slash (/)
* Vertical bar (|)
*
* Characters escaped by "^":
* SP ! # % & ' ( ) + , . ; = @ [ ] ^ ` { } ~
*
* Either "^_" or "^ " is accepted as a space. Period (.) is a special
* case. Note that un-escaped < and > can also confuse a directory
* spec.
*
* Characters put out as ^xx:
* 7F (DEL)
* 80-9F (C1 control characters)
* A0 (nonbreaking space)
* FF (Latin small letter y diaeresis)
*
* Other cases:
* Unicode: "^Uxxxx", where "xxxx" is four hex digits.
*
* Property table values:
* Normal escape: 1
* Space: 2
* Dot: 4
* Hex-hex escape: 8
* -------------------
* Hex digit: 64
*/
unsigned char char_prop[ 256] = {
/* NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* SP ! " # $ % & ' ( ) * + , - . / */
2, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 4, 0,
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, 1, 1, 1, 1,
/* @ A B C D E F G H I J K L M N O */
1, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* P Q R S T U V W X Y Z [ \ ] ^ _ */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
/* ` a b c d e f g h i j k l m n o */
1, 64, 64, 64, 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* p q r s t u v w x y z { | } ~ DEL */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8
};
/*--------------------------------------------------------------------*/
/*
* 2004-09-27 SMS.
* eat_carets().
*
* Delete ODS5 extended file name escape characters ("^") in the
* original buffer.
* Note that the current scheme does not handle all EFN cases, but it
* could be made more complicated.
*/
void
eat_carets(char *str)
/* char *str; Source pointer. */
{
char *strd; /* Destination pointer. */
char hdgt;
unsigned char uchr;
unsigned char prop;
/* Skip ahead to the first "^", if any. */
while ((*str != '\0') && (*str != '^'))
str++;
/* If no caret was found, quit early. */
if (*str != '\0') {
/* Shift characters leftward as carets are found. */
strd = str;
while (*str != '\0') {
uchr = *str;
if (uchr == '^') {
/* Found a caret. */
/* Skip it, and check the next character. */
uchr = *(++str);
prop = char_prop[ uchr];
if (prop& 64) {
/* Hex digit. Get char code from this and next hex digit. */
if (uchr <= '9') {
hdgt = uchr- '0'; /* '0' - '9' -> 0 - 9. */
} else {
hdgt = ((uchr- 'A')& 7)+ 10; /* [Aa] - [Ff] -> 10 - 15. */
}
hdgt <<= 4; /* X16. */
uchr = *(++str); /* Next char must be hex digit. */
if (uchr <= '9') {
uchr = hdgt+ uchr- '0';
} else {
uchr = hdgt+ ((uchr- 'A')& 15)+ 10;
}
} else if (uchr == '_') {
/* Convert escaped "_" to " ". */
uchr = ' ';
} else if (uchr == '/') {
/* Convert escaped "/" (invalid UNIX) to "?" (invalid VMS). */
uchr = '?';
}
/*
* Else, not a hex digit. Must be a simple escaped character
* (or Unicode, which is not yet handled here).
*/
}
/* Else, not a caret. Use as-is. */
*strd = uchr;
/* Advance destination and source pointers. */
strd++;
str++;
}
/* Terminate the destination string. */
*strd = '\0';
}
}
/*--------------------------------------------------------------------*/
/*
* 2005-02-04 SMS.
* find_dir().
*
* Find directry boundaries in an ODS2 or ODS5 file spec.
* Returns length (zero if no directory, negative if error),
* and sets "start" argument to first character (typically "[") location.
*
* No one will care about the details, but the return values are:
*
* 0 No dir.
* -2 [, no end. -3 <, no end.
* -4 [, multiple start. -5 <, multiple start.
* -8 ], no start. -9 >, no start.
* -16 ], wrong end. -17 >, wrong end.
* -32 ], multiple end. -33 >, multiple end.
*
* Note that the current scheme handles only simple EFN cases, but it
* could be made more complicated.
*/
int
find_dir(char *file_spec, char **start)
{
char *cp;
char chr;
char *end_tmp = NULL;
char *start_tmp = NULL;
int lenth = 0;
for (cp = file_spec; cp < file_spec+ strlen(file_spec); cp++) {
chr = *cp;
if (chr == '^') {
/* Skip ODS5 extended name escaped characters. */
cp++;
/* If escaped char is a hex digit, skip the second hex digit, too. */
if (char_prop[ (unsigned char) *cp]& 64)
cp++;
} else if (chr == '[') {
/* Found start. */
if (start_tmp == NULL) {
/* First time. Record start location. */
start_tmp = cp;
/* Error if no end. */
lenth = -2;
} else {
/* Multiple start characters. */
lenth = -4;
break;
}
} else if (chr == '<') {
/* Found start. */
if (start_tmp == NULL) {
/* First time. Record start location. */
start_tmp = cp;
/* Error if no end. */
lenth = -3;
} else {
/* Multiple start characters. */
lenth = -5;
break;
}
} else if (chr == ']') {
/* Found end. */
if (end_tmp == NULL) {
/* First time. */
if (lenth == 0) {
/* End without start. */
lenth = -8;
break;
} else if (lenth != -2) {
/* Wrong kind of end. */
lenth = -16;
break;
}
/* End ok. Record end location. */
end_tmp = cp;
lenth = end_tmp+ 1- start_tmp;
/* Could break here, ignoring excessive end characters. */
} else {
/* Multiple end characters. */
lenth = -32;
break;
}
} else if (chr == '>') {
/* Found end. */
if (end_tmp == NULL) {
/* First time. */
if (lenth == 0) {
/* End without start. */
lenth = -9;
break;
} else if (lenth != -3) {
/* Wrong kind of end. */
lenth = -17;
break;
}
/* End ok. Record end location. */
end_tmp = cp;
lenth = end_tmp+ 1- start_tmp;
/* Could break here, ignoring excessive end characters. */
} else {
/* Multiple end characters. */
lenth = -33;
break;
}
}
}
/* If both start and end were found, then set result pointer where safe. */
if (lenth > 0) {
if (start != NULL) {
*start = start_tmp;
}
}
return (lenth);
}
/*--------------------------------------------------------------------*/