Commit Graph

67 Commits

Author SHA1 Message Date
Stephen Heumann 48371dc669 tmpnam: allow slightly longer temp directory name
ORCA/C's tmpnam() implementation is designed to use prefix 3 if it is defined and the path is sufficiently short. I think it was intended to allow up to a 15-character disk name to be specified, but it used a GS/OS result buffer size of 16, which only leaves 12 characters for the path, including initial and terminal : characters. As such, only up to a 10-character disk name could be used. This patch increases the specified buffer size to 21, allowing for a 17-character path that can encompass a 15-character disk name.
2023-03-08 18:59:10 -06:00
Stephen Heumann b81b4e1109 Fix several bugs in fgets() and gets().
Bugs fixes:
*fgets() would write 2 bytes in the buffer if called with n=1 (should be 1).
*fgets() would write 2 bytes in the buffer if it encountered EOF before reading any characters, but the EOF flag had not previously been set. (It should not modify the buffer in this case.)
*fgets() and gets() would return NULL if EOF was encountered after reading one or more characters. (They should return the buffer pointer).
2022-10-15 19:01:16 -05:00
Stephen Heumann 12f8d74c99 Do not use separate segments for __-prefixed versions of functions.
The __-prefixed versions were introduced for use in <stdio.h> macros that have since been removed, so they are not really necessary any more. However, they may be used in old object files, so those symbols are still included for now.
2022-07-05 18:24:18 -05:00
Stephen Heumann 463d24a028 Avoid errors caused by fseek after ungetc on read-only files.
The error could occur because fseek calls fflush followed by ftell. fflush would reset the file position as if the characters in the putback buffer were removed, but ftell would still see them and try to adjust for them (in the case of a read-only file). This could result in trying to seek before the beginning of the file, producing an error.

Here is a program that was affected:

#include <stdio.h>
int main(void) {
        FILE *f = fopen("somefile","r");
        if (!f) return 0;
        fgetc(f);
        ungetc('X', f);
        fseek(f, 0, SEEK_CUR);
        if (ferror(f)) puts("error encountered");
}
2022-07-03 21:58:00 -05:00
Stephen Heumann 219e4352a0 fseek: do not clear read/write flags for read-only/write-only streams.
This maintains the invariant that these flags stay set to reflect the setting of the stream as read-only or write-only, allowing code elsewhere to behave appropriately based on that.
2022-07-03 20:27:19 -05:00
Stephen Heumann 89b501f259 fread: do not try to read if EOF flag is set.
This behavior is implied by the specification of fread in terms of fgetc.
2022-07-03 20:04:12 -05:00
Stephen Heumann ef63f26c4f Allow writing immediately after reading to EOF.
This should be allowed (for read/write files), but it was leading to errors, both because fputc would error out if the EOF flag was set and because the FILE record was not fully reset from its "reading" state. In particular, the _IOREAD flag and the current buffer position pointer need to be reset.

Here is a program that demonstrates the problem:

#include <stdio.h>
int main(void) {
        FILE *f = fopen("somefile","r+");
        if (!f) return 0;
        while (!feof(f)) fgetc(f); /* or: fgetc then fread */
        if (fputc('X', f) == EOF)
                puts("fputc error");
}
2022-07-03 18:54:24 -05:00
Stephen Heumann c877c74b92 In fflush, reset the mark to account for flushing the buffer even on read-only files.
This is needed to keep the file mark consistent with the state of the buffer. Otherwise, subsequent reads may return data from the wrong place. (Using fflush on a read-only stream is undefined behavior, but other things in stdio call it that way, so it must work consistently with what they expect.)

Here is an example that was affected by this:

#include <stdio.h>
#include <string.h>

char buf[100];

int main(void) {
        FILE *f = fopen("somefile","r");
        if (!f) return 0;

        setvbuf(f, 0L, _IOFBF, 14);

        fgets(buf, 10, f);
        printf("first read : %s\n", buf);
        ftell(f);

        memset(buf, 0, sizeof(buf));
        fgets(buf, 10, f);
        printf("second read: %s\n", buf);

        fseek(f, 0, SEEK_CUR);
        memset(buf, 0, sizeof(buf));
        fgets(buf, 10, f);
        printf("third read : %s\n", buf);
}
2022-07-02 23:34:01 -05:00
Stephen Heumann 2d6ca8a7b2 Do not set read/write error indicator if ftell cannot get the file mark.
This is not really an IO operation on the file, and accordingly the C standards do not describe the error indicator as being set here. In particular, you cannot get the mark for a character device, but that is expected behavior, not really an error condition.

errno is still set, indicating that the ftell operation failed.
2022-07-02 22:09:56 -05:00
Stephen Heumann 3eb8a9cb55 Fix bug where ftell would inappropriately change the file mark.
ftell would set the mark as if the buffer and putback were being discarded, but not actually discard them. This resulted in characters being reread once the end of the buffer was reached.

Here is an example that illustrates the problem:

#include <stdio.h>
#include <string.h>
char buf[100];
int main(void) {
        FILE *f = fopen("somefile","r"); // a file with some text
        if (!f) return 0;
        setvbuf(f, 0L, _IOFBF, 14); // to see problem sooner
        fgets(buf, 10, f);
        printf("first read : %s\n", buf);
        ftell(f);
        memset(buf, 0, sizeof(buf));
        fgets(buf, 10, f);
        printf("second read: %s\n", buf);]
}
2022-07-02 22:05:40 -05:00
Stephen Heumann 36808404ca Fix fread bug causing it to discard buffered data.
If you read from a file using fgetc (or another function that calls it internally), and then later read from it using fread, any data left in the buffer from fgetc would be skipped over. The pattern causing this to happen was as follows:

fread called ~SetFilePointer, which (if there was buffered data) called fseek(f, 0, SEEK_CUR). fseek would call fflush, which calls ~InitBuffer. That zeros out the buffer count. fseek then calls ftell, which gets the current mark from GS/OS and then subtracts the buffer count.  But the count was set to 0 in ~InitBuffer, so ftell reflects the position after any previously buffered data. fseek sets the mark to the position returned by ftell, i.e. after any data that was previously read and buffered, so that data would get skipped over. (Before commits 0047b755c9 and c95bfc19fb the behavior would be somewhat different due to the issue with ~InitBuffer that they addressed, but you could still get similar symptoms.)

The fix is simply to use the buffered data (if any), rather than discarding it.

Here is a test program illustrating the problem:

#include <stdio.h>
char buf[BUFSIZ+1];
#define RECSIZE 2
int main(void) {
        FILE *f = fopen("somefile","r"); // a file with some data
        if (!f) return 0;
        fgetc(f);
        size_t count = fread(buf, RECSIZE, BUFSIZ/RECSIZE, f);
        printf("read %zu records: %s\n", count, buf);
}
2022-07-02 18:24:57 -05:00
Stephen Heumann 84f471474a Use newer, more efficient ph2/ph4 macros throughout ORCALib.
These push DP values with pei, rather than lda+pha as in the old versions of the macros.
2022-06-30 19:01:47 -05:00
Stephen Heumann c95bfc19fb Fix a logic error.
There was a problem with the fix in commit 0047b755c9660: an instruction should have had an immediate operand, but did not because it was missing the #. This might cause the code to behave incorrectly, depending on memory contents.
2022-06-28 22:25:10 -05:00
Stephen Heumann a2b3d4541a fread fixes.
This includes changes to fread to fix two problems. The first is that fread would ignore and discard characters put back with ungetc(). The second is that it would generally return the wrong value if reading from stdin with an element size other than 1 (it would return the count of bytes, not elements).

This fixes #9 (the first problem mentioned above).
2022-06-27 18:24:52 -05:00
Stephen Heumann 1f88a38e2e Clear the EOF flag on successful calls to ungetc().
This is specified by the C standards.
2022-06-27 17:59:21 -05:00
Stephen Heumann 38666ee111 Restore changes to allow ungetc of byte values 0x80 through 0xFF.
These are the stdio.asm changes that were present in the beta source code on Opus ][, but had been reverted in commit e3c0c962d4. As it turns out, the changes to stdio.asm were OK--the issue was simply that the definitions of stdin/stdout/stderr and the associated initialization code in vars.asm had not been updated to account for the new version of the FILE structure. That has now been done, allowing the changes to work properly.

This fixes #7.
2022-06-27 17:58:23 -05:00
Stephen Heumann 7c2fb70c4a freopen improvements
This adds a check for the filename argument being null, in which case it bails out and returns NULL. Previously, it would just dereference the null pointer and treat the contents of memory location 0 as a filename, with unpredictable results. A null filename indicates freopen should try to reopen the same file with a different mode, but the degree of support for that is implementation-defined. We still don't really support it, but at least this will report the failure and avoid unpredictable behavior.

It also removes some unused code at the end, but still forces fputc and fgetc to be linked in. These are needed because there are weak references to them in putchar and getchar, which may need to be used if stdin/stdout have been reopened.
2022-06-26 21:20:23 -05:00
Stephen Heumann 8cfb73a474 Force the file mark to EOF whenever writing to a stream in append mode.
This is what the standards require. Note that the mark is only set to EOF when actually writing to the underlying file, not merely buffering data to write later. This is consistent with the usual POSIX implementation using O_APPEND.
2022-06-26 18:59:57 -05:00
Stephen Heumann 0047b755c9 Fix bug with fseek(..., SEEK_CUR) on read-only streams.
This would seek to the wrong location, and in some cases try to seek before the beginning of the file, leading to an error condition.

The problem stemmed from fseek calling fflush, which sets up the stream's buffer state in a way appropriate for writing but not for reading. It then calls ftell, which (for a read-only stream) would misinterpret this state and miscalculate the current position.

The fix is to make fflush work correctly on a read-only stream, setting up the state for reading. (User code calling fflush on a read-only stream would be undefined behavior, but since fseek does it we need to make it work.)

This fixes #6.
2022-06-26 14:35:56 -05:00
Stephen Heumann 3581d20a7c Standardize indentation using spaces.
Most files already used spaces, but three used tabs for indentation. These have been converted to use spaces. This allows the files to be displayed with proper formatting in modern editors and on GitHub. It also removes any dependency on SysTabs settings when assembling them.

The spacing in fpextra.asm was also modified to use standard column positions.

There are no non-whitespace changes in this commit.
2022-06-25 18:27:20 -05:00
Stephen Heumann ab2f17c249 Clear the IO error indicator as part of rewind().
The C standards require it to do this, in addition to calling fseek.

Here is a test that can show the issue (in a realistic program, the indicator would be set due to an actual IO error, but for testing purposes this just sets it explicitly):

#include <stdio.h>
int main(void) {
        FILE *f = tmpfile();
        if (!f) return 0;
        f->_flag |= _IOERR;
        if (!ferror(f)) puts("bad ferror");
        rewind(f);
        if (ferror(f)) puts("rewind does not reset ferror");
        fclose(f);
}
2022-06-24 18:36:25 -05:00
Stephen Heumann ae504c6e4f Add support for EILSEQ errno value.
EILSEQ is required by C95 and later.
2021-10-02 14:34:35 -05:00
Stephen Heumann 9af245933c Change long long printing routine to a new technique not using division.
The core loop uses the 65816's decimal mode to create a BCD representation of the number, then we convert that to ASCII to print it. The idea is based on some code from Kent Dickey, with adjustments to fit this codebase. This technique should be faster, and also should typically result in smaller code, since the long long division routine will not need to be linked into every program that uses printf.
2021-02-21 18:33:04 -06:00
Stephen Heumann 6bf27c6743 Use some improved macros for 64-bit operations in the libraries.
The new m16.int64 file contains a new "negate8" macro (for 64-bit negation), as well as an improved version of "ph8". These have been added to the individual *.macros files, but if those are regenerated, m16.int64 should be used as an input to macgen ahead of m16.ORCA (which contains the original version of ph8).
2021-02-12 20:24:37 -06:00
Stephen Heumann c185face85 Add long long support for printf d/i/u conversions.
At this point, the long long support for the printf and scanf families of functions is complete.
2021-02-10 17:51:23 -06:00
Stephen Heumann 4f9b41938a Implement vscanf/vfscanf/vsscanf (from C99).
These are equivalent to the existing scanf calls, except that they use variable arguments from a va_list.
2021-02-09 22:09:00 -06:00
Stephen Heumann cfc3fd3468 scanf: if %c gets some characters but < field width, it's a matching failure.
EOF should not be returned in this case.

I think it shouldn't actually write anything to the destination in this case, but it currently does. That problem remains unfixed for the moment; addressing it would require us to have our own internal buffer.
2021-02-09 16:03:19 -06:00
Stephen Heumann 6626aad7f0 scanf: report EOF for 's' conversions, is appropriate.
They should be treated as input failures if they got EOF before finding a non-whitespace character.
2021-02-09 15:44:39 -06:00
Stephen Heumann bc21593507 scanf: 's' and '[' conversions should "put back" EOF if it is encountered.
This ensures that a subsequent %n conversion reports the right number of characters actually read (not including EOF).
2021-02-09 15:31:19 -06:00
Stephen Heumann 375d664ae1 Recognize 'L' as a length modifier for scanf.
Conversions using it will not actually work right yet (unless assignment is suppressed), because the necessary changes have not been made in SysFloat, but the infrastructure is now in place to allow for those changes.
2021-02-09 14:32:37 -06:00
Stephen Heumann 2f68708b47 scanf: Report EOF for o/u/x conversions, if appropriate.
This was not being reported unless assignment was suppressed, because the "sta ~eofFound" instruction was too late.

(Found based on test cases by Rich Felker.)
2021-02-09 13:14:24 -06:00
Stephen Heumann 66e1835175 scanf could trash a word of the caller's stack if it did not perform all possible assignments.
Code in ~scanf would call ~RemoveWord to remove the extra parameters, but ~RemoveWord is designed to be called from one of the ~Scan_... subroutines, which is (in effect) called by JSR and thus has an extra word on the stack for its return address. This stack misalignment caused ~RemoveWord to overwrite a word of the caller's stack when called from the code to remove extra parameters.

This could cause a crash in the following program:

#include <stdio.h>
void f(void) {
    int a,b;
    sscanf("Z", "%i%i", &a, &b);
}
int main(void) {
    f();
}
2021-02-09 13:09:04 -06:00
Stephen Heumann 61bfc70b9b scanf: for 'i' or 'd' specifiers, count a leading sign toward the field width. 2021-02-09 00:40:24 -06:00
Stephen Heumann de9f830c0c scanf: when an ordinary character cannot match due to EOF, flag an input failure.
This may cause scanf to return EOF, if no conversions have been done.

This seems to be the intent of the C standards, although the wording could be clearer. It is also consistent with all the other implementations I tested.
2021-02-08 00:19:23 -06:00
Stephen Heumann 23a27bd434 scanf: accept leading whitespace in a %% conversion.
The C standards say this should be accepted.
2021-02-08 00:08:45 -06:00
Stephen Heumann 539707344a scanf: a sign and/or 0x prefix with no following digits should be a matching failure.
This applies to + or - signs for 'd' or 'i' conversions, and to '0x' or '0X' prefixes for 'i' and 'x' conversions. (The new support for signs on u/o/x conversions did not have the problem.)

These cases were treated as if they successfully matched a number, but now they are correctly treated as a failure, causing no assignment to be done and causing the function to return without processing any further directives.

(Incidentally, the handling of '0x' without following digits is something that differs in modern C libraries: macOS libc, glibc, and musl are all different. Our new behavior should match that of musl, which I believe is what is correct under the C standards.)
2021-02-07 21:57:53 -06:00
Stephen Heumann 3ef9687b7e scanf: allow u/o/x conversions to match numbers with a leading sign.
The C standards say this should be allowed.
2021-02-07 18:45:52 -06:00
Stephen Heumann aa1351f84d Add support for long long in scanf.
The 'll' length modifier is now fully supported for the d, i, o, u, x, and X conversion specifiers. The 'n' conversion specifier can also store to a long long, but the value is still limited to 64k. The 'j' length modifier (for intmax_t) is also now treated as specifying a 64-bit value.
2021-02-07 08:35:18 -06:00
Stephen Heumann 80dbf0c34c printf/scanf: Add 'P' conversion specifier as a synonym for 'b'.
The 'b' conversion specifier is an ORCA extension to support p-strings, but lower case letters are reserved for use in future C standards, and in fact C23 is likely to use 'b' for integers in binary notation. For the time being, 'b' is still supported with its existing meaning, but it is considered deprecated. It may be removed in the future if we want to support the C23 behavior. Upper-case letters are available to use for extensions, so 'P' should remain available for our use.
2021-01-30 19:53:50 -06:00
Stephen Heumann bd1e822a43 printf: In d/i/o/u/x/X conversions with a precision specified, the 0 flag should be ignored.
For example, the output of the following should have only two leading zeros (and the rest of the field width padded with spaces):

#include <stdio.h>

int main(void) {
        printf("%016.5i\n", 123);
}
2021-01-30 17:51:55 -06:00
Stephen Heumann 7c4141f03d printf: properly handle negative field width or precision arguments.
If the field width is given by an argument (when * is used in the format specification) and the value given is negative, it should be treated like a positive field width with the - flag. If a negative argument is given for the precision, it should be treated as if the precision was not specified.

The following is an example of code that behaved incorrectly:

#include <stdio.h>
int main(void) {
        printf("%*iXX\n", -50, 123);
}
2021-01-30 16:55:27 -06:00
Stephen Heumann 71a25f0fef printf: %#x or %#X conversions should not print the 0x/0X prefix if the value is 0.
This is the behavior specified in all versions of the C standards.
2021-01-30 16:04:18 -06:00
Stephen Heumann 37c38a7077 Add partial support for 64-bit 'long long' types in printf.
Currently, it works as follows:
*The 'll' length modifier is recognized. 'j' (for intmax_t) is also now treated as denoting a 64-bit type.
*The 'x', 'X', and 'o' format specifiers have full support for 64-bit types.
*The 'n' format specifier can write a 64-bit integer, but only actually supports values up to 64k.
*The 'd', 'i', and 'u' format specifiers can consume a 64-bit value, but they only print it correctly if it is within the range of 32-bit long/unsigned long.
2021-01-30 14:41:07 -06:00
Stephen Heumann e9de42d6c4 fopen: do not require read access when opening a file for append only.
This can cause an error if the file's permissions deny read access.
2020-02-17 19:49:18 -06:00
Stephen Heumann e5360c9605 Spellcheck comments. 2020-02-16 13:29:53 -06:00
Stephen Heumann 573bc6efa9 Use consistent indentation within files.
Tabs have been expanded to spaces in several files that use mainly spaces for indentation.

The files ctype.asm, stdio.asm, and string.asm consistently use tabs for indentation. The tabs in these files have been left alone, except that a few tabs between sentences in comments were changed to spaces.  One space-indented line in stdio.asm was changed to use a tab.
2020-02-16 13:28:18 -06:00
Stephen Heumann 9af1c093cb Ensure tmpfile() doesn't access a file created by someone else.
This could happen due to a race condition, which shouldn't generally be an issue on the GS but is at least theoretically possible under GNO or Golden Gate.
2020-01-31 12:47:34 -06:00
Stephen Heumann c439f2e3ea Make tmpnam() return the pointer passed to it, if non-null.
This is what the standards call for. It was previously always returning a pointer to its internal buffer.
2020-01-31 12:45:02 -06:00
Stephen Heumann 27a32d82bd Allow 'b' to come before '+' in freopen modes (e.g. "wb+").
Previously, only forms like "w+b" worked.
2020-01-30 21:16:48 -06:00
Stephen Heumann c77c5927d9 Support "x" in fopen()/freopen() mode strings (C11).
If the mode string ends in "x", opening a file for writing will fail if the file already exists.
2020-01-30 20:15:45 -06:00