Skip to content

Commit 5060cae

Browse files
committed
Improves parsing of Unix format file names, updates comments
* Adds a (static inline) helper, parse_file_version() to version parsing code. - Removes handling of "filename%" as version 0 (version 0 is not valid) - Adds handling of Alto/IFS version "filename!nnn" - Improves robustness of version parsing (no integer overflow errors) * Replaces private FNAMETOOLONG error code with ENAMETOOLONG standard error. * Replaces tests for "magic numbers" with standard ERRNO values (ENOENT, EXDEV) in error case for rename() operations. * Reduces storage requirement for versions from 16 bytes to 10 bytes to match the maximum acceptable version number, "999999999". * Updates various code comments to be correct and clearer.
1 parent 428e7cf commit 5060cae

File tree

1 file changed

+94
-76
lines changed

1 file changed

+94
-76
lines changed

inc/locfile.h

Lines changed: 94 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
/* Manufactured in the United States of America. */
99
/* */
1010
/************************************************************************/
11+
#include <ctype.h> /* for isdigit */
1112
#include <errno.h>
1213
#include <limits.h> /* for NAME_MAX */
14+
#include <string.h> /* for strlen */
15+
#include <sys/param.h> /* for MAXPATHLEN */
1316
#include <dirent.h> /* for MAXNAMLEN */
1417
#include "lispemul.h" /* for DLword */
1518

@@ -331,14 +334,10 @@ do { \
331334
/*
332335
* Name: UnixVersionToLispVersion
333336
*
334-
* Argument: char *pathname
335-
* UNIX syntax pathname.
336-
* int vlessp
337-
* If 0, versionless file is converted to version 1.
337+
* Argument: char *pathname UNIX syntax pathname.
338+
* int vlessp If 0, versionless file is converted to version 1.
338339
* Otherwise, remains as versionless.
339340
*
340-
* Value: If succeed, returns 1, otherwise 0.
341-
*
342341
* Side Effect: The version part of pathname is destructively modified.
343342
*
344343
* Description:
@@ -353,62 +352,20 @@ do { \
353352
* dealt with as version 1.
354353
*/
355354

356-
#define UnixVersionToLispVersion(pathname, vlessp) do { \
357-
\
358-
char *start; \
359-
char *end; \
360-
char *lf_cp; \
361-
int ver_no; \
362-
size_t len; \
363-
char ver_buf[VERSIONLEN]; \
364-
\
365-
if ((start = strchr(pathname, '~')) != NULL) { \
366-
/* First of all, find the version field in pathname. */ \
367-
end = start; \
368-
lf_cp = start + 1; \
369-
while (*lf_cp) { \
370-
if (*lf_cp == '~') { \
371-
start = end; \
372-
end = lf_cp; \
373-
lf_cp++; \
374-
} else { \
375-
lf_cp++; \
376-
} \
377-
} \
378-
\
379-
if (start != end && *(start - 1) == '.' && end == (lf_cp - 1)) { \
380-
/* \
381-
* pathname ends in the form ".~###~". But we \
382-
* check ### is a valid number or not. \
383-
*/ \
384-
len = (end - start) - 1; \
385-
strncpy(ver_buf, start + 1, len); \
386-
ver_buf[len] = '\0'; \
387-
NumericStringP(ver_buf, YES, NO); \
388-
YES: \
389-
*(start - 1) = ';'; \
390-
*start = '\0'; \
391-
*end = '\0'; \
392-
/* call strtoul() to eliminate leading 0s. */ \
393-
ver_no = strtoul(start + 1, (char **)NULL, 10); \
394-
sprintf(ver_buf, "%u", ver_no); \
395-
strcat(pathname, ver_buf); \
396-
goto CONT; \
397-
\
398-
NO: \
399-
/* Dealt with as version 1 unless vlessp */ \
400-
if (!(vlessp)) strcat(pathname, ";1"); \
401-
CONT: \
402-
lf_cp--; /* Just for label */ \
403-
} else { \
404-
/* Dealt with as version 1 unless vlessp. */ \
405-
if (!(vlessp)) strcat(pathname, ";1"); \
406-
} \
407-
} else { \
408-
/* Dealt with as version 1 unless vlessp. */ \
409-
if (!(vlessp)) strcat(pathname, ";1"); \
410-
} \
411-
} while (0)
355+
#define UnixVersionToLispVersion(pathname, vlessp) \
356+
do { \
357+
char *n_end; \
358+
char *v_start; \
359+
int v_len; \
360+
\
361+
if (!parse_file_version(pathname, 1, &n_end, &v_start, &v_len)) { \
362+
if (!vlessp) strcat(pathname, ";1"); \
363+
} else { \
364+
*n_end++ = ';'; \
365+
while (v_len-- > 0) *n_end++ = *v_start++; \
366+
*n_end = '\0'; \
367+
} \
368+
} while (0)
412369

413370
/*
414371
* Name: ConcDirAndName
@@ -481,9 +438,12 @@ do { \
481438
*
482439
* Concatenate the root file name and its version in UNIX format.
483440
*
441+
* XXX: this code is unsafe and could result in memory smashes if the
442+
* sizes of the arguments are not correctly specified
443+
*
484444
*/
485445

486-
#define ConcNameAndVersion(name, ver, rname) do { \
446+
#define ConcNameAndVersion(name, ver, rname) do { \
487447
if (*(ver) != '\0') { \
488448
strcpy(rname, name); \
489449
strcat(rname, ".~"); \
@@ -494,7 +454,7 @@ do { \
494454
} \
495455
} while (0)
496456

497-
#define VERSIONLEN 16
457+
#define VERSIONLEN 10
498458

499459
#define MAXVERSION 999999999
500460

@@ -576,9 +536,9 @@ do { \
576536
TIMEOUT(lf_rval=rename(x, y)); \
577537
if(lf_rval == -1){ \
578538
switch(errno){ \
579-
case 2: \
539+
case ENOENT: \
580540
return(1); \
581-
case 18: \
541+
case EXDEV: \
582542
*Lisp_errno = errno; \
583543
return(0); \
584544
default: \
@@ -601,20 +561,78 @@ do { \
601561
/*
602562
* For file name length check
603563
*/
604-
#define FNAMETOOLONG 200
605564

606-
#define FileNameTooLong(val) do { \
607-
*Lisp_errno = FNAMETOOLONG; \
565+
#define FileNameTooLong(val) do { \
566+
*Lisp_errno = ENAMETOOLONG; \
608567
return((val)); \
609568
} while (0)
610569

611-
612-
613-
614-
615-
616-
617-
570+
static inline int parse_file_version(char *name, int digitsonly, char **n_end,
571+
char **v_start, int *v_length)
572+
{
573+
char *sp, *ep;
574+
size_t name_len;
575+
576+
name_len = strlen(name);
577+
ep = &name[name_len - 1];
578+
579+
/* handle special case of Alto/IFS names with !nnn version.
580+
To be considered for this case the name MUST end with ![0-9]+, however
581+
version 0 is not valid.
582+
*/
583+
sp = strrchr(name, '!');
584+
if (sp != NULL) {
585+
sp++; /* "!nnn" => "nnn" or "!" => "" */
586+
if (*sp != '\0' && sp[strspn(sp, "0123456789")] == '\0') {
587+
/* it was all digits after the '!', so go with it */
588+
*n_end = sp - 1; /* name ends at '!' */
589+
while (*sp == '0' && sp < ep) sp++; /* skip leading zeroes */
590+
if (*sp == '0') return (0); /* version 0 is not valid */
591+
*v_start = sp; /* version start after '!' */
592+
*v_length = (ep - sp) + 1;
593+
return ((*v_length >= VERSIONLEN) ? 0 : 1); /* fail on version too long */
594+
}
595+
}
596+
597+
/* if the name is too short to have a name and a version number
598+
".~#~" or doesn't end with "~" then there is no version number
599+
*/
600+
if (name_len < 4 || *ep != '~')
601+
return (0);
602+
603+
/* The name ends with a "~" so scan back in the filename looking for
604+
another "~" terminating early if we need only digits and find
605+
something else
606+
*/
607+
sp = ep - 1;
608+
while (sp > name && *sp != '~') {
609+
if (digitsonly && !isdigit(*sp)) return (0);
610+
--sp;
611+
}
612+
613+
/* test for no initial "~" or no "." before "~", or
614+
* version number length not at least 1
615+
*/
616+
if (sp == name || *(sp - 1) != '.' || (ep - sp) - 1 < 1)
617+
return (0);
618+
619+
/* After this point we have a version number in the form .~#~ with sp
620+
pointing at the starting "~", ep pointing at the last "~",
621+
and there must be at least one digit. Scan past any leading
622+
zeros in the version, taking care not to remove the last digit.
623+
*/
624+
625+
*n_end = sp - 1; /* save location of "." */
626+
627+
sp++; /* skip over the "." */
628+
while (*sp == '0' && sp < (ep - 1)) {
629+
sp++;
630+
}
631+
if (*sp == '0') return (0); /* version 0 is not valid */
632+
*v_start = sp; /* save location of first significant digit in version */
633+
*v_length = (ep - sp); /* save length of version */
634+
return ((*v_length >= VERSIONLEN) ? 0 : 1); /* fail on version too long */
635+
}
618636

619637
/********************************************************/
620638
/* file-system-specific defns */

0 commit comments

Comments
 (0)