| /* |
| * Copyright (c) 2021-2025 Symas Corporation |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of the Symas Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| #include <ctype.h> |
| #include <ctype.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <algorithm> |
| |
| #include "config.h" |
| |
| #include "ec.h" |
| #include "io.h" |
| #include "common-defs.h" |
| #include "gcobolio.h" |
| #include "libgcobol.h" |
| #include "gfileio.h" |
| #include "charmaps.h" |
| |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #pragma GCC diagnostic ignored "-Wunused-result" |
| |
| /* Ordinary error-handling flow: |
| |
| file->errnum is always set to the value of the system ferror() function |
| file->io_status is always set to the COBOL specific value established |
| by the __gg__file_status_word function. |
| |
| When __gg__file_status_word is called with (FsErrno), it returns a value |
| based on the value of errno. Otherwise it returns the value it was called |
| with. |
| |
| So, after each operation, if we can figure out the COBOL response, we set |
| file->io_status t to that value. Otherwise, we set it to FsErrno and |
| let __gg__file_status_word establish the file->io_status_t value. |
| |
| */ |
| |
| /* Incomplete notes on file organization, written at the time I reworked |
| ORGANIZATION IS SEQUENTIAL |
| |
| GnucCOBOL: Defaults to ORGANIZATION IS SEQUENTIAL |
| GCOBOL: Defaults to ORGANIZATION IS LINE SEQUENTIAL |
| |
| SEQUENTIAL Where min/max record sizes are equal: |
| All records are output verbatim |
| SEQUENTIAL Where FD min/max record sizes are not the same |
| A 4-byte preamble is prepended to each record. |
| SEQUENTIAL Where FD record-clause is FORMAT 1 |
| [RECORD CONTAINS 10 CHARACTERS] |
| Just like sequential: The sizes of the FD records determine |
| whether or not preambles are issued. GnuCOBOL issues a warning |
| if an FD record size is greater than the record-clause size |
| SEQUENTIAL Where FD record-clause is FORMAT 2 [RECORD VARYING] |
| When all FD records are the same size: No preamble |
| When all FD records are different sizes: Preamble |
| SEQUENTIAL Where FD record-clause is FORMAT 2 [RECORD VARYING 70 TO 72] |
| When all FD records are the same size: No preamble |
| When all FD records are different sizes: Preamble |
| SEQUENTIAL Where FD record-clause is FORMAT 2 [RECORD VARYING DEPENDING ON] |
| There is always a preamble |
| SEQUENTIAL Where FD record-clause is FORMAT 3 |
| [RECORD CONTAINS I TO J CHARACTERS] |
| There is always a preamble |
| |
| */ |
| |
| extern "C" |
| void |
| __gg__handle_error(const char *function, const char *msg) |
| { |
| if(0) |
| { |
| fflush(stdout); |
| char ach[1024]; |
| snprintf(ach, sizeof(ach), "%s(): %s", function, msg); |
| perror(ach); |
| } |
| } |
| |
| static bool |
| handle_ferror(cblc_file_t *file, const char *function, const char *msg) |
| { |
| // This routine gets called after an operation that might result in a |
| // failure, or in an end-of-file |
| bool retval = false; // Indicates no error |
| file->errnum = ferror(file->file_pointer); |
| |
| // First, check for an end-of-file |
| if( file->file_pointer ) |
| { |
| if( feof(file->file_pointer) ) |
| { |
| // This is an end-of-file, which has its own COBOL status code, so it |
| // is error-ish: |
| retval = true; |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| } |
| else if( ferror(file->file_pointer) ) |
| { |
| // There was some kind of actionable error: |
| retval = true; |
| // Optionally tell the world of our troubles: |
| if(0) |
| { |
| fflush(stdout); |
| char ach[1024]; |
| snprintf(ach, sizeof(ach), "%s(): %s", function, msg); |
| perror(ach); |
| } |
| |
| // Set up for the next I/O operation by clearing out the error condition |
| clearerr(file->file_pointer); |
| } |
| } |
| else |
| { |
| // There is no file pointer. The most likely cause an attempt to open a |
| // file that doesn't exist. |
| fprintf(stderr, "%s(): called with NULL file->file_pointer\n", __func__); |
| abort(); |
| return true; |
| } |
| return retval; |
| } |
| |
| static bool |
| handle_errno(cblc_file_t *file, const char *function, const char *msg) |
| { |
| // This routine gets called after an operation that resulted in a |
| // errno failure |
| bool retval = false; // Indicates no error |
| file->errnum = errno; |
| |
| if( errno ) |
| { |
| // There was some kind of actionable error: |
| retval = true; |
| // Optionally tell the world of our troubles: |
| if(0) |
| { |
| fflush(stdout); |
| char ach[1024]; |
| snprintf(ach, sizeof(ach), "%s(): %s", function, msg); |
| perror(ach); |
| } |
| } |
| return retval; |
| } |
| |
| static |
| char * |
| get_filename( cblc_file_t *file, |
| int is_quoted) |
| { |
| static size_t fname_size = MINIMUM_ALLOCATION_SIZE; |
| static char *fname = (char *)malloc(MINIMUM_ALLOCATION_SIZE); |
| fname = internal_to_console(&fname, |
| &fname_size, |
| file->filename, |
| strlen(file->filename)); |
| |
| if( !is_quoted ) |
| { |
| // We have been given something that might be the name of an |
| // environment variable that contains the filename: |
| char *p_from_environment = getenv(fname); |
| if( p_from_environment ) |
| { |
| if( strlen(p_from_environment)+1 > fname_size ) |
| { |
| fname_size = strlen(p_from_environment)+1; |
| free(fname); |
| fname = (char *)malloc(fname_size); |
| } |
| strcpy(fname, p_from_environment); |
| } |
| } |
| |
| if(*fname) |
| { |
| // COBOL strings are space-filled to the right, so we have to get rid |
| // of any spaces out there. If somebody *wants* a filename space-filled |
| // to the right, well, at this juncture I am not prepared to be complicit |
| // in that particular flavor of lunacy. |
| size_t n = strlen(fname)-1; |
| // Note the conditional that terminates the loop when n goes from zero |
| // to a huge positive number in the event that the string is all SPACES |
| while( n < strlen(fname) && fname[n] == ascii_space ) |
| { |
| fname[n--] = '\0'; |
| } |
| } |
| return fname; |
| } |
| |
| static void |
| establish_status(cblc_file_t *file, long read_location) |
| { |
| // Call this routine with fs->errnum already set to errno. |
| // |
| // Establish file->io_status with either FsErrno or a specific value |
| // before calling this routine |
| |
| // Some operations have some state associated with them: |
| file->prior_read_location = read_location; |
| |
| // When this routine is called, file->io_status has been explicitly set to |
| // a COBOL status (in which case it will be left alone), or it is still |
| // at FsErrno, which means that errno will be used to translate to a COBOL |
| // status word. |
| file->io_status = __gg__file_status_word(file->io_status, file->errnum); |
| __gg__int128_to_field(file->status, |
| file->io_status, |
| 0, |
| truncation_e, |
| NULL); |
| // Set the EC-EXCEPTION accoring the status code |
| __gg__set_exception_file(file); |
| } |
| |
| extern "C" |
| void |
| __gg__set_user_status(cblc_field_t *ustatus, cblc_file_t *file) |
| { |
| __gg__int128_to_field(ustatus, |
| file->io_status, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| |
| static long |
| max_value(cblc_field_t *key) |
| { |
| long retval; |
| if( key->digits ) |
| { |
| retval = (long)__gg__power_of_ten(key->digits)-1 ; |
| } |
| else |
| { |
| switch( key->capacity ) |
| { |
| case 1: |
| retval = 99; |
| break; |
| case 2: |
| retval = 9999; |
| break; |
| default: |
| retval = 999999999; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__file_init( |
| cblc_file_t *file, |
| const char *name, |
| cblc_field_t **keys, |
| int *key_numbers, |
| int *uniques, |
| cblc_field_t *default_record, |
| cblc_field_t *password, |
| cblc_field_t *user_status, |
| cblc_field_t *vsam_status, |
| cblc_field_t *record_length, |
| cblc_field_t *status, |
| size_t reserve, |
| int org, |
| int padding, |
| int access, |
| int optional, |
| size_t record_area_min, |
| size_t record_area_max) |
| { |
| if( !(file->flags & file_flag_initialized_e) ) |
| { |
| file->name = strdup(name); |
| file->filename = NULL ; |
| file->file_pointer = NULL ; |
| file->keys = keys; |
| file->key_numbers = key_numbers; |
| file->uniques = uniques; |
| file->default_record = default_record; |
| file->password = password ; |
| file->user_status = user_status ; |
| file->vsam_status = vsam_status ; |
| file->record_length = record_length ; |
| file->status = status ; |
| file->reserve = reserve ; |
| file->org = (cbl_file_org_t)org ; |
| file->padding = padding ; |
| file->access = (cbl_file_access_t)access ; |
| file->errnum = 0 ; |
| file->io_status = FsSuccess ; |
| file->delimiter = internal_newline ; |
| file->flags = file_flag_none_e; |
| file->flags |= (optional ? file_flag_optional_e : file_flag_none_e) |
| + file_flag_initialized_e; |
| file->record_area_min = record_area_min; |
| file->record_area_max = record_area_max; |
| file->prior_read_location = 0; |
| file->prior_op = file_op_none; |
| |
| if( file->access == file_inaccessible_e ) |
| { |
| file->access = file_access_seq_e; |
| } |
| } |
| } |
| |
| enum relative_file_mode |
| { |
| // MicroFocus uses a zero-byte prefix, and a two-byte postfix. The |
| // final byte is 0x0A for a valid record. |
| rfm_microfocus_e, |
| }; |
| |
| enum indexed_file_mode |
| { |
| // Data file is the same as rfm_microfocus. We use maps and multimaps for |
| // the keys, in an extravaganza of expedience. |
| ifm_dubner_e, |
| }; |
| |
| struct relative_file_parameters |
| { |
| long preamble_size; |
| long payload_size; |
| long postamble_size; |
| long record_size; |
| long file_size; |
| long key_value; |
| long record_position; |
| long flag_position; |
| long current_file_position; |
| int fd; |
| bool inside_existing_file; |
| }; |
| |
| #define IGNORE_LIMITS false |
| #define RESPECT_LIMITS true |
| #define DONT_INIT_KEY false |
| #define INIT_KEY true |
| |
| static bool |
| relative_file_parameters_get( struct relative_file_parameters &rfp, // OUTPUT |
| relative_file_mode rfm, // INPUTS |
| cblc_file_t *file, |
| bool respect_limits, |
| bool initialize_key, |
| bool is_random) |
| { |
| bool retval = false; // False means "okay" |
| switch(rfm) |
| { |
| // Note that the rfm is a nice idea, but at this point is not really being |
| // used. |
| case rfm_microfocus_e: |
| { |
| if( file->record_area_min == file->record_area_max ) |
| { |
| // Set MicroFocus-specific sizes: |
| rfp.preamble_size = 0; |
| rfp.payload_size = (long)file->record_area_max; |
| rfp.postamble_size = 2; |
| } |
| else |
| { |
| rfp.preamble_size = 8; |
| rfp.payload_size = (long)file->record_area_max; |
| rfp.postamble_size = 0; |
| } |
| rfp.record_size = rfp.preamble_size |
| + rfp.payload_size |
| + rfp.postamble_size; |
| |
| // We need to know the current file size: |
| errno = 0; |
| file->errnum = 0; |
| rfp.fd = fileno(file->file_pointer); |
| if( rfp.fd == -1 ) |
| { |
| file->io_status = FsErrno; |
| handle_errno(file, __func__, "fileno() error" ); |
| retval = true; |
| goto done; |
| } |
| |
| struct stat file_status; |
| errno = 0; |
| file->errnum = 0; |
| if( fstat(rfp.fd, &file_status) == -1 ) |
| { |
| file->io_status = FsErrno; |
| handle_errno(file, __func__, "fstat() error"); |
| retval = true; |
| goto done; |
| } |
| rfp.file_size = file_status.st_size; |
| |
| rfp.current_file_position = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| file->io_status = FsErrno; |
| retval = true; |
| goto done; |
| } |
| |
| if( !is_random ) |
| { |
| rfp.key_value = rfp.current_file_position/rfp.record_size + 1; |
| rfp.record_position = (rfp.key_value-1) * rfp.record_size; |
| rfp.inside_existing_file |
| = rfp.record_position + rfp.record_size <= rfp.file_size; |
| } |
| else |
| { |
| // Pick up the relative_key value: |
| if( initialize_key ) |
| { |
| rfp.key_value = rfp.current_file_position/rfp.record_size + 1; |
| } |
| else |
| { |
| int rdigits; |
| if( !file->keys[0] ) |
| { |
| warnx("%s(): %s file->keys[0] is NULL, and it shouldn't be\n", |
| __func__, |
| file->name); |
| if( !file->keys[0] ) |
| { |
| __gg__abort("relative_file_parameters_get(): file->keys is empty"); |
| } |
| } |
| rfp.key_value = (long)__gg__binary_value_from_field(&rdigits, |
| file->keys[0]); |
| } |
| |
| rfp.record_position = (rfp.key_value-1) * rfp.record_size; |
| if( rfp.record_position < 0 ) |
| { |
| // The record can't be found before the beginning of the file |
| file->io_status = FsNotFound; // "23" |
| retval = true; |
| goto done; |
| } |
| |
| rfp.inside_existing_file |
| = rfp.record_position + rfp.record_size <= rfp.file_size; |
| |
| if( respect_limits |
| && !rfp.inside_existing_file |
| && file->mode_char == 'r') |
| { |
| // This is a READ operation, but the targeted location is not inside |
| // the file |
| file->io_status = FsNotFound; // "23" |
| retval = true; |
| goto done; |
| } |
| } |
| |
| // For Microfocus, the flag is the final byte of the record: |
| rfp.flag_position = rfp.record_position + rfp.record_size - 1; |
| |
| break; |
| } |
| |
| default: |
| warnx("%s(): Unhandled relative_file_mode %d", __func__, rfm); |
| exit(1); |
| break; |
| } |
| done: |
| if( retval ) |
| { |
| establish_status(file, -1); |
| } |
| return retval; |
| } |
| |
| static void |
| relative_file_delete_varying(cblc_file_t *file, bool is_random) |
| { |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| size_t payload_length; |
| |
| unsigned char *stash = (unsigned char *)malloc(file->default_record->capacity); |
| memcpy(stash, file->default_record->data, file->default_record->capacity); |
| long starting_pos = ftell(file->file_pointer); |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to delete in a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| // We have to be in I-O mode |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| relative_file_parameters rfp; |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| if( !is_random ) |
| { |
| // Check that the prior operation was a successful read: |
| if( file->prior_read_location < 0) |
| { |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| |
| // Turn that valid record into an empty one: |
| fseek(file->file_pointer, file->prior_read_location, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| payload_length = 0; |
| fwrite(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| else |
| { |
| // We are doing a random access: |
| |
| // Let's check to make sure the slot for this record is currently |
| // occupied: |
| |
| errno = 0; |
| file->errnum = 0; |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| fread(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| |
| if( !payload_length ) |
| { |
| // There isn't a record there for us to delete, which is an error |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| // Turn that valid record into an empty one: |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| payload_length = 0; |
| fwrite(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| |
| done: |
| memcpy(file->default_record->data, stash, file->default_record->capacity); |
| free(stash); |
| fseek(file->file_pointer, starting_pos, SEEK_SET); |
| |
| establish_status(file, -1); |
| } |
| |
| static void |
| relative_file_delete(cblc_file_t *file, bool is_random) |
| { |
| if( file->record_area_min != file->record_area_max ) |
| { |
| return relative_file_delete_varying(file, is_random); |
| } |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| char record_marker; |
| |
| unsigned char *stash = (unsigned char *)malloc(file->default_record->capacity); |
| memcpy(stash, file->default_record->data, file->default_record->capacity); |
| |
| long starting_pos = ftell(file->file_pointer); |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to delete in a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| // We have to be in I-O mode |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| relative_file_parameters rfp; |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| if( !is_random ) |
| { |
| // Check that the prior operation was a successful read: |
| if( file->prior_read_location < 0) |
| { |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| |
| // Turn that valid record into an empty one: |
| record_marker = 0x00; |
| errno = 0; |
| file->errnum = 0; |
| if( pwrite( rfp.fd, |
| &record_marker, |
| 1, |
| file->prior_read_location |
| + rfp.record_size - 1 ) == -1 ) |
| { |
| handle_errno(file, __func__, "pwrite() error"); |
| goto done; |
| } |
| } |
| else |
| { |
| // We are doing a random access: |
| |
| // Let's check to make sure the slot for this record is currently |
| // occupied: |
| |
| errno = 0; |
| file->errnum = 0; |
| ssize_t presult = pread(rfp.fd, &record_marker, 1, rfp.flag_position); |
| if( presult < 0 ) |
| { |
| handle_errno(file, __func__, "pread() error"); |
| goto done; |
| } |
| |
| if( presult == 0 || record_marker != internal_newline ) |
| { |
| // There isn't a record there for us to delete, which is an error |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| // We now clobber the 0x0A record marker: |
| record_marker = 0x00; |
| errno = 0; |
| file->errnum = 0; |
| if( pwrite(rfp.fd, &record_marker, 1, rfp.flag_position) == -1 ) |
| { |
| file->errnum = errno; |
| handle_ferror(file, __func__, "pwrite() error"); |
| goto done; |
| } |
| } |
| |
| done: |
| memcpy(file->default_record->data, stash, file->default_record->capacity); |
| free(stash); |
| fseek(file->file_pointer, starting_pos, SEEK_SET); |
| establish_status(file, -1); |
| } |
| |
| static std::vector<unsigned char> |
| file_indexed_make_key(cblc_file_t *file, int key_number) |
| { |
| std::vector<unsigned char> retval; |
| int index = 0; |
| while( file->key_numbers[index] != -1 ) // -1 is the guardrail |
| { |
| if( file->key_numbers[index] == key_number ) |
| { |
| unsigned char *location = file->keys[index]->data; |
| for(size_t i=0; i<file->keys[index]->capacity; i++ ) |
| { |
| retval.push_back(*location++); |
| } |
| } |
| index += 1; |
| } |
| return retval; |
| } |
| |
| static long |
| file_indexed_first_position(cblc_file_t *file, int key_number) |
| { |
| // We need to find the file position for the given key: |
| long retval = -1; |
| |
| // Pick up our structure for this key_number |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| |
| // Build the key value for the given key_number |
| std::vector<unsigned char> key = file_indexed_make_key(file, key_number); |
| |
| // Find the pair of pointers to the first and last matches |
| std::pair < std::multimap<std::vector<unsigned char>, long>::iterator, |
| std::multimap<std::vector<unsigned char>, long>::iterator> ret; |
| ret = file_index->key_to_position.equal_range(key); |
| file_index->current_iterator = ret.first; |
| file_index->ending_iterator = ret.second; |
| |
| if( ret.first != ret.second ) |
| { |
| // There is one or more entries that match the key. We are returning |
| // the location from the first. We leave "recent_indicator" as the one |
| // we found. |
| retval = ret.first->second; |
| } |
| return retval; |
| } |
| |
| static int |
| read_an_indexed_record( cblc_file_t *file, |
| long max_bytes, |
| long &record_length, |
| int &flag) |
| { |
| int retval = 2; // zero is okay; 1 is EOF, 2 is an ERROR |
| size_t bytes_read; |
| size_t bytes_to_read; |
| |
| // Call this routine with the file positioned at the record to read |
| |
| // We need to read in the four-byte preamble plus record_area_min bytes |
| // of the record to be deleted. We need those bytes in order to calculate |
| // all of the keys of indexes that might have to be removed: |
| |
| unsigned char ach[4]; |
| bytes_read = fread(ach, 1, 4, file->file_pointer); |
| file->errnum = ferror(file->file_pointer); |
| if( feof(file->file_pointer) || bytes_read < 4 ) |
| { |
| clearerr(file->file_pointer); |
| retval = 1; // Flag the EOF |
| goto done; |
| } |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| |
| record_length = ach[0]<<8; |
| record_length += ach[1]; |
| if(ach[2] != 0) |
| { |
| warnx("Bad file format in read_an_indexed_record(). " |
| "Third byte should be zero"); |
| abort(); |
| } |
| |
| flag = ach[3]; |
| |
| bytes_to_read = record_length; |
| if( record_length > max_bytes ) |
| { |
| // The record length in the file is too big for us to read into our |
| // record area. |
| |
| // Read as many bytes as we can: |
| bytes_to_read = max_bytes; |
| } |
| |
| file->errnum = 0; |
| bytes_read = fread(file->default_record->data, |
| 1, |
| bytes_to_read, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| if( bytes_read != bytes_to_read) |
| { |
| // Weird. We couldn't read a minimal number of bytes |
| goto done; |
| } |
| |
| if( record_length > max_bytes ) |
| { |
| // The record length in the file was too big. So, we read what we could. |
| // Now we need to adjust the file pointer to skip past the bytes we weren't |
| // able to read: |
| fseek(file->file_pointer, record_length - max_bytes, SEEK_CUR); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| retval = 0; // Indicate that all is well |
| done: |
| return retval; |
| } |
| |
| struct position_state_t |
| { |
| int recent_key; |
| long starting_position; |
| std::vector<std::multimap<std::vector<unsigned char>, long>::iterator> currents; |
| std::vector<std::multimap<std::vector<unsigned char>, long>::iterator> endings; |
| }; |
| |
| static void |
| position_state_preserve(cblc_file_t *file, position_state_t &state) |
| { |
| state.recent_key = file->recent_key; |
| state.starting_position = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| exit(1); |
| } |
| for(size_t i=1; i<file->supplemental->indexes.size(); i++) |
| { |
| state.currents.push_back(file->supplemental->indexes[i].current_iterator); |
| state.endings.push_back (file->supplemental->indexes[i].ending_iterator ); |
| } |
| } |
| |
| static void |
| position_state_restore(cblc_file_t *file, position_state_t &state) |
| { |
| file->recent_key = state.recent_key; |
| fseek(file->file_pointer, state.starting_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| exit(1); |
| } |
| for(size_t i=1; i<file->supplemental->indexes.size(); i++) |
| { |
| // Remember that key numbers range from 1 to N, whilst the vector we |
| // we created to stash the iterators started at zero. |
| file->supplemental->indexes[i].current_iterator = state.currents[i-1]; |
| file->supplemental->indexes[i].ending_iterator = state.endings[i-1]; |
| } |
| } |
| |
| static void |
| indexed_file_delete(cblc_file_t *file, bool is_random) |
| { |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long fpos; |
| unsigned char *stash = NULL; |
| long record_length; |
| int flag; |
| file_hole_t new_hole; |
| position_state_t position_state; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to delete in a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| // We have to be in I-O mode |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( !is_random && file->prior_read_location == -1 ) |
| { |
| // When the access mode is sequential, the prior operation shall have been a |
| // successful read statement. It is that record which is removed. Not |
| // meeting that requirement gets its own error code: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| |
| // For both sequential and random access, we need to read the record to be |
| // deleted into the record area. It could be I am being overly didactic, but |
| // my reading of the ISO spec indicates that there has to be a successful read |
| // of the record in sequential mode, but I don't see a requirement that the] |
| // programmer leave the record area untouched before trying to delete it. |
| |
| // Likewise, for random access, the primary key has to be valid, but I see |
| // no requirement that the alternate keys be valid. We need the alternate |
| // keys in place in order to delete them from the indexes. |
| |
| // The requirements for a delete are that both the file position indicator |
| // and the record area itself are unchanged by the delete operation. |
| |
| // So, we save the current record area: |
| stash = (unsigned char *)malloc(file->record_area_max); |
| memcpy(stash, file->default_record->data, file->record_area_max); |
| |
| // And the position state of our file |
| position_state_preserve(file, position_state); |
| |
| if( !is_random ) |
| { |
| // For sequential, re-read the recent successful read: |
| fpos = file->prior_read_location; |
| } |
| else |
| { |
| // We are doing a random read. Figure out the position from the primary |
| // key: |
| fpos = file_indexed_first_position(file, 1); |
| if( fpos == -1 ) |
| { |
| // The primary key does not exist |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| } |
| |
| // Read the record to be deleted into the record area: |
| fseek(file->file_pointer, fpos, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| if( read_an_indexed_record( file, |
| file->record_area_max, |
| record_length, |
| flag) ) |
| { |
| goto done; |
| } |
| |
| // The record we just read *has* to be a good record: |
| if( flag != 1 ) |
| { |
| warnx("Bad file format in indexed_file_delete(). " |
| "Fourth byte should be one"); |
| abort(); |
| } |
| |
| // Because we are deleting it, we need to remove it from all of the indexes. |
| // Given our indexes, we need to scan all of them looking for the matching |
| // fpos value. And then we have to start over again, because the deletion |
| // changes the tree that implements the multimap. If speed becomes an issue, |
| // a bandaid fix would be to create a multi-map of fpos to iterator. |
| |
| // The real fix is a real database, but somewhere before that we need to |
| // really design an indexed file system, instead of this business of putting |
| // it on a stick an banging a few nails through it. |
| |
| for(size_t key_number=1; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| if( file->supplemental->uniques[key_number] ) |
| { |
| // This key does not allow duplicates, so we don't have to scan for it, |
| // because we know we have to delete it. |
| file_indexed_first_position(file, key_number); |
| file_index->key_to_position.erase(file_index->current_iterator); |
| // and continue to the next key number |
| continue; |
| } |
| else |
| { |
| // This particular key allows duplicates |
| |
| // We have to scan the entire index for keys that point to our fpos. When |
| // we find one, we check to see if the keys match. If the keys don't |
| // match, then we have to remove the existing one from the index. |
| |
| std::vector<unsigned char> the_key |
| = file_indexed_make_key(file, key_number); |
| bool deleting = true; |
| while(deleting) |
| { |
| deleting = false; |
| std::multimap<std::vector<unsigned char>, long>::iterator it |
| = file_index->key_to_position.begin(); |
| while( it != file_index->key_to_position.end() ) |
| { |
| if( it->second == fpos ) |
| { |
| // We have found an index that points to our record. |
| // It needs to be deleted. |
| file_index->key_to_position.erase(it); |
| deleting = true; |
| break; |
| } |
| it++; |
| } |
| } |
| } |
| } |
| |
| // We need to turn this record into a hole by changing the fourth byte from |
| // one to zero |
| |
| fseek(file->file_pointer, fpos+3, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| fputc(0, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error") ) |
| { |
| goto done; |
| } |
| |
| // We just created a hole; put it in the list: |
| new_hole.location = fpos; |
| new_hole.size = record_length; |
| file->supplemental->holes.push_back(new_hole); |
| |
| done: |
| if( stash ) |
| { |
| // Restore the stashed record area: |
| memcpy(file->default_record->data, stash, file->record_area_min); |
| free(stash); |
| stash = NULL; |
| position_state_restore(file, position_state); |
| } |
| |
| establish_status(file, -1); |
| } |
| |
| static void |
| __io__file_delete(cblc_file_t *file, bool is_random) |
| { |
| switch(file->org) |
| { |
| case file_relative_e: |
| relative_file_delete(file, is_random); |
| break; |
| |
| case file_indexed_e: |
| indexed_file_delete(file, is_random); |
| break; |
| |
| default: |
| warnx("%s(): Unhandled file organization", __func__); |
| exit(1); |
| break; |
| } |
| |
| if( file->io_status < FhNotOkay ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| file->prior_op = file_op_delete; |
| } |
| |
| static void |
| indexed_file_start( cblc_file_t *file, |
| int relop, |
| int key_number, |
| size_t length) |
| { |
| static const int first_key = 1; |
| //fprintf(stderr, " length is %ld\n", length); |
| file->io_status = FsNotFound; // "23" |
| |
| size_t length_of_key = 0; |
| bool direction_reverse; |
| std::multimap<std::vector<unsigned char>, long>::iterator it; |
| file_index_t *file_index; |
| std::vector<unsigned char> the_key; |
| |
| if( key_number == -1 ) |
| { |
| // This is FIRST: |
| file_index = &file->supplemental->indexes[first_key]; |
| if( file_index->key_to_position.size() ) |
| { |
| it = file_index->key_to_position.begin(); |
| file->io_status = FsErrno; |
| key_number = first_key; |
| } |
| goto done; |
| } |
| if( key_number == -2 ) |
| { |
| // This is LAST: |
| file_index = &file->supplemental->indexes[first_key]; |
| if( file_index->key_to_position.size() ) |
| { |
| it = file_index->key_to_position.end(); |
| it--; |
| file->io_status = FsErrno; |
| key_number = first_key; |
| } |
| goto done; |
| } |
| |
| // Pick up our file index for this key number |
| file_index = &file->supplemental->indexes[key_number]; |
| |
| // We are going to need the key from the existing record: |
| the_key = file_indexed_make_key(file, key_number); |
| length_of_key = the_key.size(); |
| |
| // Make sure the length parameter is within the size of the actual key |
| if( length < 1 || length > length_of_key ) |
| { |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| direction_reverse = (relop==lt_op || relop==le_op); |
| |
| if( direction_reverse ) |
| { |
| // We will do a forward search for the biggest index that is smaller than |
| // the key. |
| std::multimap<std::vector<unsigned char>, long>::iterator bestie = |
| file_index->key_to_position.end(); |
| it = file_index->key_to_position.begin(); |
| while( it != file_index->key_to_position.end() ) |
| { |
| const unsigned char *the_index = it->first.data(); |
| |
| // Do the comparison: |
| int result = 0; |
| |
| for(size_t i=0; i<length; i++) |
| { |
| result = the_index[i] - the_key[i] ; |
| if( result ) |
| { |
| break; |
| } |
| } |
| |
| if( result > 0 ) |
| { |
| // The index is bigger than the key, so, for better or for worse, we |
| // are done looking. |
| break; |
| } |
| |
| if( result == 0 ) |
| { |
| // The index and key are equal |
| if( relop == le_op ) |
| { |
| // We have equal, and we were looking for equality. Set a conditional |
| // winner, and then keep looking in case there are more indexes that |
| // are equal to the key |
| bestie = it; |
| file->io_status = FsErrno; |
| } |
| } |
| else if( result < 0 ) |
| { |
| // The index is less than the key. |
| if( relop == lt_op |
| || relop == le_op ) |
| { |
| // This is a conditional winner, so we flag it as such and keep going, |
| // because there might be more indexes that are bigger than this one |
| // but less than the key |
| bestie = it; |
| file->io_status = FsErrno; |
| } |
| } |
| // At this point, the index is less than the key, so we keep looking: |
| it++; |
| } |
| if( file->io_status == FsErrno ) |
| { |
| // We found something |
| it = bestie; |
| } |
| } |
| else |
| { |
| // We are doing a forward search for the first index entry that matches |
| // the criterion. |
| |
| it = file_index->key_to_position.begin(); |
| while( it != file_index->key_to_position.end() ) |
| { |
| const unsigned char *the_index = it->first.data(); |
| |
| // Do the comparison: |
| int result = 0; |
| |
| for(size_t i=0; i<length; i++) |
| { |
| result = the_index[i] - the_key[i] ; |
| if( result ) |
| { |
| break; |
| } |
| } |
| |
| if( result == 0 ) |
| { |
| // The index and key are equal |
| if( relop == eq_op |
| || relop == ge_op ) |
| { |
| // We have equal, and we were looking for equality: |
| file->io_status = FsErrno; |
| goto done; |
| } |
| else |
| { |
| // The operation is gt_op, so we fall through and keep looking |
| } |
| } |
| else if( result > 0 ) |
| { |
| // The the index is bigger than the key |
| if( relop == eq_op ) |
| { |
| // The index is bigger than the key, so we will never find |
| // equality. |
| goto done; |
| } |
| // At the point, the operation has to be ge_op or gt_op, so our |
| // criterion is satisfied |
| file->io_status = FsErrno; |
| goto done; |
| } |
| // At this point, the index is less than the key, so we keep looking: |
| it++; |
| } |
| // We went through the whole index without finding anything. |
| } |
| done: |
| if( file->io_status == FsErrno ) |
| { |
| // The it iterator points to a valid found index: |
| file_index->current_iterator = it; |
| file_index->ending_iterator = file_index->key_to_position.end(); |
| |
| fseek(file->file_pointer, it->second, SEEK_SET); |
| handle_ferror(file, __func__, "fseek() error"); |
| |
| file->recent_key = key_number; |
| } |
| } |
| |
| static long |
| relative_file_start(cblc_file_t *file, |
| int first_last_key, |
| int relop) |
| { |
| long fpos = -1; |
| relative_file_parameters rfp; |
| int rdigits; |
| long total_record_length; |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| IGNORE_LIMITS, |
| INIT_KEY, |
| true) ) |
| { |
| goto done; |
| } |
| total_record_length |
| = rfp.preamble_size + rfp.payload_size + rfp.postamble_size; |
| if( first_last_key == -1 ) |
| { |
| // This is FIRST. Search forward from the beginning |
| rfp.key_value = 1; |
| relop = ge_op; |
| } |
| else if( first_last_key == -2 ) |
| { |
| // This is LAST. Search reverse from the end |
| rfp.key_value = rfp.file_size / total_record_length; |
| relop = le_op; |
| } |
| else |
| { |
| rfp.key_value = (long)__gg__binary_value_from_field(&rdigits, |
| file->keys[0]); |
| } |
| |
| bool forward; |
| switch( relop ) |
| { |
| case eq_op: |
| case ge_op: |
| forward = true; |
| break; |
| case le_op: |
| forward = false; |
| break; |
| case gt_op: |
| rfp.key_value += 1; |
| forward = true; |
| break; |
| case lt_op: |
| rfp.key_value -= 1; |
| forward = false; |
| break; |
| default: |
| warnx("%s(): relop is %d, which we don't know how to handle", |
| __func__, |
| relop); |
| exit(1); |
| break; |
| } |
| |
| if( forward ) |
| { |
| // We are searching forward, so we want the key_value to be 1 or more |
| rfp.key_value = std::max( rfp.key_value, (long)1); |
| } |
| else |
| { |
| // We are searching backward, so we want the rfp.key_value to be at most |
| // the last possible record based on the file size: |
| rfp.key_value = std::min( rfp.key_value, |
| rfp.file_size / total_record_length); |
| } |
| |
| // Let's initialize for searching for a valid record: |
| rfp.record_position = (rfp.key_value-1) * total_record_length; |
| rfp.flag_position = rfp.record_position + rfp.record_size - 1; |
| |
| // And let the searching begin: |
| |
| while( rfp.record_position >= 0 |
| && rfp.record_position+total_record_length <= rfp.file_size ) |
| { |
| char record_marker; |
| ssize_t presult = pread(rfp.fd, &record_marker, 1, rfp.flag_position); |
| if( presult < 0 ) |
| { |
| handle_errno(file, __func__, "pread() error"); |
| goto done; |
| } |
| if( presult == 0 ) |
| { |
| // end of file |
| goto done; |
| } |
| if( record_marker == internal_newline ) |
| { |
| // The record is a valid one |
| fpos = rfp.record_position; |
| goto done; |
| } |
| // That position in the file did not have valid data in it. Move on to the |
| // next record |
| if( forward ) |
| { |
| rfp.key_value += 1; |
| rfp.record_position += total_record_length; |
| rfp.flag_position += total_record_length; |
| } |
| else |
| { |
| rfp.key_value -= 1; |
| rfp.record_position -= total_record_length; |
| rfp.flag_position -= total_record_length; |
| } |
| } |
| |
| if( fpos == -1) |
| { |
| // We didn't find a valid record |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| // Position the file at the requested record: |
| done: |
| if( fpos >= 0) |
| { |
| fseek(file->file_pointer, fpos, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| // It's unclear to me whether or not START is supposed to update keys like |
| // this |
| // Update the key with the one we found |
| // __gg__int128_to_field(file->keys[0], |
| // rfp.key_value, |
| // 0, |
| // truncation_e, |
| // NULL); |
| } |
| |
| return fpos; |
| } |
| |
| |
| static void |
| __io__file_start( cblc_file_t *file, |
| int relop, |
| int first_last_key, |
| size_t length) |
| { |
| // According to IBM Language Reference 6.3, whether or not 'key' is specified, |
| // the value used is from the RELATIVE KEY clause. See page 421 "Relative |
| // Files" "Whether or not the KEY phrase is specified, the key data item used |
| // in the comparison is the RELATIVE KEY data item." |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| long fpos = -1; |
| |
| bool okay; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to START a file that isn't open |
| if( file->flags & file_flag_optional_e ) |
| { |
| file->io_status = FsNotFound; // "23" |
| } |
| else |
| { |
| file->io_status = FsReadNotOpen; // "47" |
| } |
| goto done; |
| } |
| |
| okay = (file->org == file_indexed_e || file->org == file_relative_e) |
| && ( file->access == file_access_seq_e |
| || file->access == file_access_dyn_e) |
| && (file->mode_char == 'r' || file->mode_char == '+') ; |
| if( !okay ) |
| { |
| file->io_status = FsReadNotOpen; // "47" |
| goto done; |
| } |
| |
| if( file->org == file_relative_e ) |
| { |
| fpos = relative_file_start(file, first_last_key, relop); |
| } |
| else |
| { |
| // first_last_key is the key_number |
| indexed_file_start(file, relop, first_last_key, length); |
| if( file->io_status == FsErrno ) |
| { |
| fpos = 0; |
| } |
| } |
| |
| done: |
| if( file->io_status == FsErrno ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| |
| establish_status(file, fpos); |
| if( file->io_status < FhNotOkay ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| file->prior_op = file_op_start; |
| } |
| |
| static void |
| sequential_file_rewrite( cblc_file_t *file, size_t length ) |
| { |
| // Handles sequential files |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long starting_position = -1; |
| bool okay; |
| size_t bytes_to_write; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to read a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| okay = (file->mode_char == '+'); |
| |
| if( !okay ) |
| { |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| starting_position = ftell(file->file_pointer); |
| // Check for EOF before calling ferror, which clears feof |
| if( feof( file->file_pointer) ) |
| { |
| // Trying to do a rewrite at the end-of-file position has its own code: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| goto done; |
| } |
| |
| if( file->prior_read_location == -1 ) |
| { |
| // The prior operation was not a successful read: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| |
| fseek(file->file_pointer, file->prior_read_location, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| size_t existing_size; |
| if( file->record_area_min != file->record_area_max ) |
| { |
| // Because of the different sizes, we are expecting a preamble: |
| |
| // An error at this point is either an error, or a true end-of-file. |
| unsigned char preamble[4]; |
| size_t characters_read = fread(preamble, 1, 4, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| // This also comes back true for an ordinary end-of-file |
| goto done; |
| } |
| |
| if( characters_read != 4 ) |
| { |
| // If the preamble is incomplete, treat that as an EOF |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| |
| // Extract the count of bytes in the record from the preamble |
| existing_size = ((size_t)preamble[0]<<8) + preamble[1]; |
| } |
| else |
| { |
| // Because the min and max are the same, we figure on writing that many |
| // characters: |
| existing_size = file->record_area_max; |
| } |
| |
| // We need to make sure that the number of bytes we've been asked |
| // to write is valid: |
| |
| // By default, we write what we've been asked to write. |
| bytes_to_write = length; |
| |
| // But that can be overridden by a depending_on from the |
| // FILE-CONTROL paragraph: |
| |
| if( file->record_length ) |
| { |
| int rdigits; |
| bytes_to_write = (size_t)__gg__binary_value_from_field( |
| &rdigits, |
| file->record_length); |
| } |
| |
| if( bytes_to_write < file->record_area_min |
| || bytes_to_write > file->record_area_max |
| || bytes_to_write != existing_size ) |
| { |
| file->io_status = FsBoundWrite; // "44" |
| goto done; |
| } |
| |
| if( file->record_area_min != file->record_area_max ) |
| { |
| unsigned char preamble[4] = |
| { |
| (unsigned char)(bytes_to_write>>8), |
| (unsigned char)(bytes_to_write), |
| 0, |
| 0 |
| }; |
| |
| fwrite( preamble, |
| 4, |
| 1, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| |
| fwrite( file->default_record->data, |
| bytes_to_write, |
| 1, |
| file->file_pointer ); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| done: |
| // Per the standard, return the file location pointer back to whence it came: |
| fseek(file->file_pointer, starting_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| establish_status(file, starting_position); |
| } |
| |
| static void |
| relative_file_rewrite_varying( cblc_file_t *file, bool is_random ) |
| { |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long starting_position = -1; |
| relative_file_parameters rfp; |
| size_t payload_size; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to rewrite a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| starting_position = ftell(file->file_pointer); |
| // Check for EOF before calling ferror, which clears feof |
| if( feof( file->file_pointer) ) |
| { |
| // Trying to do a rewrite at the end-of-file position has its own code: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| goto done; |
| } |
| |
| if( !is_random ) |
| { |
| // The access mode is sequential, so we can leverage |
| // sequential_file_rewrite |
| rfp.record_position = starting_position; |
| goto do_the_write; |
| } |
| else |
| { |
| // This is like a write, except the place we are putting |
| // it has to be occupied instead of empty. |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| do_the_write: |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| fread(&payload_size, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| |
| if( !payload_size ) |
| { |
| // The record is not empty: |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| payload_size = file->default_record->capacity; |
| if( payload_size < file->record_area_min |
| || payload_size > file->record_area_max) |
| { |
| file->io_status = FsBoundWrite; // "44" |
| goto done; |
| } |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| // We can overwrite the valid record: |
| fwrite( &payload_size, |
| 8, |
| 1, |
| file->file_pointer ); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| fwrite( file->default_record->data, |
| file->default_record->capacity, |
| 1, |
| file->file_pointer ); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| |
| done: |
| // Per the standard, return the file location pointer back to whence it came: |
| fseek(file->file_pointer, starting_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| establish_status(file, starting_position); |
| } |
| |
| static void |
| relative_file_rewrite( cblc_file_t *file, size_t length, bool is_random ) |
| { |
| if( file->record_area_min != file->record_area_max ) |
| { |
| return relative_file_rewrite_varying(file, is_random); |
| } |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long starting_position = -1; |
| relative_file_parameters rfp; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to rewrite a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| starting_position = ftell(file->file_pointer); |
| // Check for EOF before calling ferror, which clears feof |
| if( feof( file->file_pointer) ) |
| { |
| // Trying to do a rewrite at the end-of-file position has its own code: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| goto done; |
| } |
| |
| if(!is_random) |
| { |
| // The access mode is sequential, so we can leverage |
| // sequential_file_rewrite |
| sequential_file_rewrite(file, length); |
| goto done; |
| } |
| else |
| { |
| // This is like a write, except the place we are putting |
| // it has to be occupied instead of empty. |
| char record_marker; |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| ssize_t presult = pread(rfp.fd, &record_marker, 1, rfp.flag_position); |
| if( presult < 0 ) |
| { |
| handle_errno(file, __func__, "pread() error"); |
| goto done; |
| } |
| |
| if( presult == 0 || record_marker != internal_newline ) |
| { |
| // The record is not specified: |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| // We can overwrite the valid record: |
| fwrite( file->default_record->data, |
| file->default_record->capacity, |
| 1, |
| file->file_pointer ); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| |
| done: |
| // Per the standard, return the file location pointer back to whence it came: |
| fseek(file->file_pointer, starting_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| establish_status(file, starting_position); |
| } |
| |
| static bool |
| file_indexed_update_indices(cblc_file_t *file, |
| long record_position, |
| bool dupes_okay = false) |
| { |
| // We have a record in the file->record_area. |
| |
| bool okay = true; |
| |
| // We have to do this in two passes. |
| |
| // First, we check to see that for all keys flagged as unique, that |
| // the key doesn't already exist: |
| |
| for(size_t key_number=1; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| if( file->supplemental->uniques[key_number] ) |
| { |
| // This key has to be unique: |
| std::vector<unsigned char>key_value = |
| file_indexed_make_key(file, key_number); |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| std::multimap<std::vector<unsigned char>, long>::const_iterator it = |
| file_index->key_to_position.find(key_value); |
| if( it != file_index->key_to_position.end() ) |
| { |
| // This key is already in the index |
| if( !dupes_okay || it->second != record_position ) |
| { |
| // We have a key violation: |
| file->io_status = FsDupWrite; // "22" |
| okay = false; |
| break; |
| } |
| } |
| } |
| } |
| |
| if( okay ) |
| { |
| // There were no key violations, so we can do the second pass, which inserts |
| // the keys into the indexes. |
| for(size_t key_number=1; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| // But we don't want the key to end up in here twice, which we have to |
| // avoid when updating the indexes after a REWRITE |
| |
| bool safe_to_insert = true; |
| file_indexed_first_position(file, key_number); |
| while( file_index->current_iterator != file_index->ending_iterator ) |
| { |
| if( file_index->current_iterator->second == record_position) |
| { |
| safe_to_insert = false; |
| break; |
| } |
| file_index->current_iterator++; |
| } |
| |
| if( safe_to_insert ) |
| { |
| std::vector<unsigned char>key_value = |
| file_indexed_make_key(file, key_number); |
| std::pair<std::vector<unsigned char>, long> |
| to_insert(key_value, record_position); |
| file_index->key_to_position.insert(to_insert); |
| } |
| } |
| } |
| return okay; |
| } |
| |
| static void |
| indexed_file_rewrite( cblc_file_t *file, size_t /*length*/, bool is_random ) |
| { |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long fpos = -1; |
| position_state_t position_state; |
| long record_length; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to work on a file that isn't open |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| if( file->mode_char != '+' ) |
| { |
| // For an indexed_file REWRITE, the mode has to be '+' |
| file->io_status = FsNoDelete; // "49" |
| goto done; |
| } |
| |
| // We need to preserve the file position state |
| position_state_preserve(file, position_state); |
| |
| if( !is_random ) |
| { |
| // In the case of sequential access, the prior operation needed to be a |
| // successful read: |
| if( file->prior_read_location == -1 ) |
| { |
| // The prior operation was not a successful read: |
| file->io_status = FsNoRead; // "43" |
| goto done; |
| } |
| |
| // The prior read was successful. Let's see if the primary key has changed |
| fpos = file_indexed_first_position(file, 1); |
| if( fpos != file->prior_read_location ) |
| { |
| // Between the successful read and this attempt at a rewrite, somebody |
| // changed the primary key in the record. This gets its own special error |
| // code |
| file->io_status = FsKeySeq; // "21" |
| goto done; |
| } |
| } |
| else |
| { |
| // It's not a sequential read, so it's got to be random. Let's determine |
| // that the primary key is valid: |
| fpos = file_indexed_first_position(file, 1); |
| if( fpos == -1 ) |
| { |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| } |
| |
| // We need to know the record length: |
| fseek(file->file_pointer, fpos, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| unsigned char preamble[4]; |
| fread(preamble, 1, 4, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| record_length = (preamble[0]<<8) + preamble[1]; |
| // Note that after the read, the file position is at the beginning of the |
| // record. |
| |
| // We know we have a good primary key. It points to fpos. |
| |
| // We now need to check any alternate keys flagged as unique. We need to |
| // make sure that any such key this record has already points to fpos. |
| |
| // If it points somewhere else, then our attempt to rewrite the record would |
| // end up that key pointing to two different records, which means a failure: |
| |
| for(size_t key_number=2; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| if( file->supplemental->uniques[key_number] ) |
| { |
| // This particular alternative index does not allow duplicates |
| long altfpos = file_indexed_first_position(file, key_number); |
| if( altfpos != -1 && altfpos != fpos ) |
| { |
| // This alternate key would create a duplicate index entry where none |
| // such are allowed: |
| file->io_status = FsDupWrite; // "22" |
| goto done; |
| } |
| } |
| } |
| |
| // At this point we know we are going to write the record, because there will |
| // be no key violations. But because an alternate key might have changed, we |
| // we have to scan for all such and remove them from the indexes: |
| for(size_t key_number=2; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| if( !file->supplemental->uniques[key_number] ) |
| { |
| // This particular alternative index allows duplicates |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| |
| // We have to scan the entire index for keys that point to our fpos. When |
| // we find one, we check to see if the keys match. If the keys don't |
| // match, then we have to remove the existing one from the index. |
| |
| std::vector<unsigned char> the_key |
| = file_indexed_make_key(file, key_number); |
| bool deleting = true; |
| while(deleting) |
| { |
| deleting = false; |
| std::multimap<std::vector<unsigned char>, long>::iterator it |
| = file_index->key_to_position.begin(); |
| while( it != file_index->key_to_position.end() ) |
| { |
| if( it->second == fpos ) |
| { |
| // We have found an index entry that points to our record. If the |
| // index's key doesn't match our calculated key, we need to delete |
| // it. |
| const unsigned char *the_index = it->first.data(); |
| int result = 0; |
| for(size_t i=0; i<the_key.size(); i++) |
| { |
| result = the_key[i] - the_index[i]; |
| if( result ) |
| { |
| break; |
| } |
| } |
| if(result) |
| { |
| // We found an index that needs to be deleted |
| file_index->key_to_position.erase(it); |
| deleting = true; |
| break; |
| } |
| } |
| it++; |
| } |
| } |
| } |
| } |
| |
| // To recap: The primary key is valid, and any remaining alternative keys |
| // are already pointing to fpos, and if a new one gets entered, it won't |
| // conflict with any existing key flagged as unique. |
| |
| // We can now write the data out, at the given location: |
| |
| fwrite( file->default_record->data, |
| 1, |
| record_length, |
| file->file_pointer ); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| // And it is safe to update the keys: |
| file_indexed_update_indices(file, |
| fpos, |
| true); // Set to true to indicate that might |
| // // occur and should be ignored |
| |
| done: |
| // Per the standard, return thefile location pointer back to whence it came: |
| if( fpos != -1 ) |
| { |
| position_state_restore(file, position_state); |
| } |
| |
| establish_status(file, fpos); |
| file->prior_read_location = -1; |
| } |
| |
| static void |
| __io__file_rewrite(cblc_file_t *file, size_t length, bool is_random) |
| { |
| switch(file->org) |
| { |
| case file_relative_e: |
| relative_file_rewrite(file, length, is_random); |
| break; |
| |
| case file_sequential_e: |
| sequential_file_rewrite(file, length); |
| break; |
| |
| case file_indexed_e: |
| indexed_file_rewrite(file, length, is_random); |
| break; |
| |
| default: |
| warnx("%s(): Unhandled file organization", __func__); |
| exit(1); |
| break; |
| } |
| if( file->io_status < FhNotOkay ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| file->prior_op = file_op_rewrite; |
| } |
| |
| static void |
| relative_file_write_varying(cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| bool is_random) |
| { |
| // This routine handles variable-length writes to RELATIVE files |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long necessary_file_size; |
| size_t payload_length; |
| |
| relative_file_parameters rfp; |
| |
| if( is_random ) |
| { |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| IGNORE_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| // If the file isn't big enough, we need to expand it: |
| necessary_file_size = rfp.record_position |
| + rfp.record_size; |
| if( rfp.file_size < necessary_file_size ) |
| { |
| // Position the file position indicator to the very end of the file |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| // Expand the file to the necessary length: |
| while( rfp.file_size++ < necessary_file_size-1 ) |
| { |
| fputc(0, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error [2]") ) |
| { |
| goto done; |
| } |
| } |
| } |
| |
| // Position the file pointer at the slot: |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| fread(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| |
| if( payload_length != 0 ) |
| { |
| // The slot has something in it already: |
| file->io_status = FsDupWrite; // "22" |
| goto done; |
| } |
| |
| // Position the file pointer at the slot: |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| else |
| { |
| // We need to do a sequential write: |
| if( file->mode_char == 'a' ) |
| { |
| // Position the file pointer at the end: |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| } |
| |
| // Write out the record. |
| // We need a length. |
| |
| if( file->record_length ) |
| { |
| // df |
| int rdigits; |
| length = (size_t)__gg__binary_value_from_field( &rdigits, |
| file->record_length); |
| } |
| |
| payload_length = length; |
| |
| if( payload_length < file->record_area_min |
| || payload_length > file->record_area_max) |
| { |
| file->io_status = FsBoundWrite; // "44" |
| goto done; |
| } |
| |
| fwrite(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| fwrite(location, 1, payload_length, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| while( payload_length < file->record_area_max ) |
| { |
| fputc(internal_space, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error") ) |
| { |
| goto done; |
| } |
| payload_length += 1; |
| } |
| |
| if( is_random ) |
| { |
| // Per the COBOL specification, put the file position back to where |
| // it was when we started this exercise: |
| fseek(file->file_pointer, rfp.current_file_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek(starting_pos) error") ) |
| { |
| goto done; |
| } |
| } |
| |
| done: |
| establish_status(file, -1); |
| } |
| |
| static void |
| relative_file_write(cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| bool is_random) |
| { |
| // This routine handles writes to RELATIVE files |
| |
| if( file->record_area_min != file->record_area_max ) |
| { |
| return relative_file_write_varying(file, location, length, is_random); |
| } |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| long necessary_file_size; |
| unsigned char achPostamble[] = {internal_cr, internal_newline}; |
| |
| relative_file_parameters rfp; |
| |
| if( is_random ) |
| { |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| IGNORE_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| necessary_file_size = rfp.record_position |
| + rfp.preamble_size |
| + rfp.payload_size |
| + rfp.postamble_size; |
| if( rfp.file_size < necessary_file_size ) |
| { |
| // Position the file position indicator to the very end of the file |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| |
| // Expand the file to the necessary length: |
| while( rfp.file_size++ < necessary_file_size-1 ) |
| { |
| fputc(0, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error [2]") ) |
| { |
| goto done; |
| } |
| } |
| } |
| // Let's check to make sure the slot for this record is currently available: |
| char record_marker; |
| ssize_t presult = pread(rfp.fd, &record_marker, 1, rfp.flag_position); |
| if( presult < 0 ) |
| { |
| handle_errno(file, __func__, "pread() error"); |
| goto done; |
| } |
| |
| if( presult == 1 && record_marker == internal_newline ) |
| { |
| // The slot has something in it already: |
| file->io_status = FsDupWrite; // "22" |
| goto done; |
| } |
| // Either the record is available, or else the write is taking place at the |
| // end of the current file size. |
| |
| // Position the file pointer at the slot: |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| else |
| { |
| // We need to do a sequential write: |
| if( file->mode_char == 'a' ) |
| { |
| // Position the file pointer at the end: |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| } |
| |
| // Write out the data: |
| fwrite(location, length, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| if( file->record_area_max > length ) |
| { |
| size_t padding = file->record_area_max - length; |
| while(padding--) |
| { |
| fputc(internal_space, file->file_pointer); |
| } |
| } |
| |
| // Write out the rfm_microfocus "valid record" postamble: |
| fwrite(achPostamble, 1, 2, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| if( is_random ) |
| { |
| // Per the COBOL specification, put the file position back to where |
| // it was when we started this exercise: |
| fseek(file->file_pointer, rfp.current_file_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek(starting_pos) error") ) |
| { |
| goto done; |
| } |
| } |
| |
| done: |
| establish_status(file, -1); |
| } |
| |
| static void |
| sequential_file_write(cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| int after, |
| int lines) |
| { |
| // This code handles SEQUENTIAL and LINE SEQUENTIAl |
| char ch = '\0'; |
| size_t characters_to_write; |
| |
| int lcount; |
| |
| if( lines < -1 ) |
| { |
| // We are using -666 for a form feed |
| ch = internal_ff; // Form feed |
| lcount = 1; |
| } |
| else if( lines == -1 ) |
| { |
| // -1 is a flag that no vertical control is requested |
| lcount = 0; |
| } |
| else if( lines == 0 ) |
| { |
| lcount = 1; |
| ch = internal_return; |
| } |
| else /* if( lines > 0 ) */ |
| { |
| lcount = lines; |
| ch = internal_newline; |
| } |
| |
| // By default, we write out the number of characters in the record area |
| characters_to_write = length; |
| |
| // That gets overridden if there is a record_length |
| if( file->record_length ) |
| { |
| int rdigits; |
| characters_to_write = (int)__gg__binary_value_from_field( |
| &rdigits, |
| file->record_length); |
| } |
| |
| if( file->org == file_line_sequential_e ) |
| { |
| // If file-sequential, then trailing spaces are removed: |
| while( characters_to_write > 0 |
| && location[characters_to_write-1] == internal_space ) |
| { |
| characters_to_write -= 1; |
| } |
| } |
| |
| if( after && file->org == file_line_sequential_e && ch == internal_newline ) |
| { |
| // In general, we terminate every line with a newline. Because this |
| // line is supposed to start with a newline, we decrement the line |
| // counter by one if we had already sent one. |
| if( lcount && ( file->recent_char == internal_newline |
| || file->recent_char == internal_ff) ) |
| { |
| lcount -= 1; |
| } |
| } |
| |
| if( after ) |
| { |
| while(lcount--) |
| { |
| fputc(ch, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error [3]") ) |
| { |
| goto done; |
| } |
| file->recent_char = ch; |
| } |
| // That might have been a formfeed; switch back to newline: |
| ch = internal_newline; |
| } |
| |
| switch(file->org) |
| { |
| case file_line_sequential_e: |
| if( characters_to_write ) |
| { |
| fwrite( location, |
| characters_to_write, |
| 1, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| file->recent_char = '\0'; |
| break; |
| |
| case file_sequential_e: |
| if( characters_to_write ) |
| { |
| // File sequential records can start off with a four-byte |
| // preamble. |
| |
| if( characters_to_write < file->record_area_min |
| || characters_to_write > file->record_area_max) |
| { |
| file->io_status = FsBoundWrite; // "44" |
| goto done; |
| } |
| |
| if( file->record_area_min != file->record_area_max ) |
| { |
| // Because of the min/max mismatch, we require a preamble: |
| // The first two bytes are the big-endian character count |
| unsigned char preamble[4] = |
| { |
| (unsigned char)(characters_to_write>>8), |
| (unsigned char)(characters_to_write), |
| 0, |
| 0 |
| }; |
| |
| fwrite( preamble, |
| 4, |
| 1, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| |
| fwrite( location, |
| characters_to_write, |
| 1, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| } |
| file->recent_char = '\0'; |
| break; |
| |
| default: |
| fprintf(stderr, |
| "%s(): Unhandled cbl_file_org_t %d\n", |
| __func__, |
| file->org); |
| exit(1); |
| break; |
| } |
| |
| if( after && lines>0 && file->org == file_line_sequential_e ) |
| { |
| // Special case: when AFTER NON-ZERO lines, we stick a newline on the |
| // end of this record: |
| fputc(ch, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error [4]") ) |
| { |
| goto done; |
| } |
| file->recent_char = internal_newline; |
| } |
| |
| if( !after ) |
| { |
| // We did the output BEFORE, so now it's time to send some internal_newlines |
| while(lcount--) |
| { |
| fputc(ch, file->file_pointer); |
| if( handle_ferror(file, __func__, "fputc() error [5]") ) |
| { |
| goto done; |
| } |
| file->recent_char = ch; |
| } |
| } |
| |
| done: |
| establish_status(file, -1); |
| } |
| |
| static void |
| indexed_file_write( cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| bool is_random) |
| { |
| // This routine handles FILE WRITE to INDEXED files |
| |
| // Each record starts with a four-byte preamble. The first two bytes are |
| // a big-endian binary value indicating the length of the record (not |
| // including the preamble). The third byte is zero; the fourth byte is zero |
| // for a deleted record and one for an active record. |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| long position_to_write; |
| |
| int key_number = 1; // We are concerned with the primary key: |
| // Pick up our structure for the primary_key |
| file_index_t *file_index = &file->supplemental->indexes[key_number]; |
| |
| // Check to make sure the WRITE is consistent with the mode: |
| if( !is_random ) |
| { |
| // sequential mode is OUTPUT or EXTEND. We can only add things at the end |
| if( file->mode_char != 'w' && file->mode_char != 'a' ) |
| { |
| file->io_status = FsNoWrite; // "48" |
| goto done; |
| } |
| |
| if( file_index->key_to_position.size() == 0 ) |
| { |
| // We are dealing with an empty file, so we'll be writing at |
| // starting_position, which is set to the end |
| } |
| else |
| { |
| // The primary key for a new record in an indexed file in sequential |
| // access mode has to be greater than the biggest existing one: |
| |
| std::multimap<std::vector<unsigned char>, long>::const_reverse_iterator |
| last_element = file_index->key_to_position.crbegin(); |
| std::vector<unsigned char> biggest_key = last_element->first; |
| |
| std::vector<unsigned char> new_key = file_indexed_make_key( file, |
| key_number); |
| |
| // Quick & dirty comparison to make sure our new key is greater than |
| // the biggest_key: |
| bool okay = (memcmp(new_key.data(), biggest_key.data(), new_key.size() ) > 0); |
| if( !okay ) |
| { |
| // Create out-of-sequence INVALID KEY condition |
| file->io_status = FsKeySeq; // "21" |
| goto done; |
| } |
| // We are allowed to do the write. Because this is "w" or "a", |
| // it will be at the end |
| } |
| } |
| else |
| { |
| // Because access is random or dynamic the mode has to be OUTPUT or I-O |
| if( file->mode_char != 'w' && file->mode_char != '+' ) |
| { |
| file->io_status = FsNoWrite; // "48" |
| goto done; |
| } |
| |
| // We are allowed to do the write, but only if there will be no key |
| // violations as a result: |
| |
| for(size_t key_number=1; |
| key_number<file->supplemental->indexes.size(); |
| key_number++) |
| { |
| if( file->supplemental->uniques[key_number] ) |
| { |
| long record_position = file_indexed_first_position(file, key_number); |
| if( record_position != -1 ) |
| { |
| // No can do, because we already have a unique key with that value |
| file->io_status = FsDupWrite; // "22" |
| goto done; |
| } |
| } |
| } |
| |
| // This record is not in the data file; figure out where to put it |
| if( file->mode_char == '+' ) |
| { |
| // This is a '+' for I-O, so we can write into a hole. |
| |
| // See if we have a hole that is the right size: |
| bool found_hole = false; |
| for( size_t i=0; i<file->supplemental->holes.size(); i++ ) |
| { |
| if( file->supplemental->holes[i].size == length ) |
| { |
| // We found a hole, and we're going to use it |
| |
| // Pick up the position |
| position_to_write = file->supplemental->holes[i].location; |
| |
| // Get rid of the hole we just filled: |
| file->supplemental->holes[i] = file->supplemental->holes.back(); |
| file->supplemental->holes.pop_back(); |
| |
| // Position the file, ready to write: |
| fseek(file->file_pointer, position_to_write, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| found_hole = true; |
| break; |
| } |
| } |
| if( !found_hole ) |
| { |
| // There is no hole, so make sure we are writing at the end |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| } |
| } |
| |
| // The file is positioned for where the write is to take place. And we know |
| // that there will be no key violation when we update the indices. |
| |
| position_to_write = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| goto done; |
| } |
| |
| // We are currently located where the new data must be written. |
| unsigned char ach[4]; |
| ach[0] = (unsigned char)(length>>8); |
| ach[1] = (unsigned char)length; |
| ach[2] = 0; |
| ach[3] = 1; |
| |
| // Write out the preamble: |
| fwrite(ach, 4, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| // Write out the data: |
| fwrite(location, length, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fwrite() error") ) |
| { |
| goto done; |
| } |
| |
| file_indexed_update_indices(file, position_to_write); |
| |
| done: |
| establish_status(file, -1); |
| } |
| |
| static void |
| __io__file_write( cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| int after, |
| int lines, |
| int is_random ) |
| { |
| // After an epic discussion with Marty, a determination has been made to |
| // ignore the IBM and ISO specifications, and treat an unadorned WRITE as |
| // if it were the same as WRITE xxx BEFORE 1 LINE |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to write a file that isn't open |
| file->io_status = FsNoWrite; // "48" |
| goto done; |
| } |
| |
| switch( file->access ) |
| { |
| case file_access_seq_e: |
| if( file->mode_char != 'w' && file->mode_char != 'a' ) |
| { |
| // File is open, but not in OUTPUT or EXTEND mode |
| file->io_status = FsNoWrite; // "48" |
| goto done; |
| } |
| break; |
| |
| case file_access_rnd_e: |
| case file_access_dyn_e: |
| if( file->mode_char != 'w' && file->mode_char != '+' ) |
| { |
| // File is open, but not in I-O or OUTPUT or |
| file->io_status = FsNoWrite; // "48" |
| goto done; |
| } |
| break; |
| |
| default: |
| warnx("%s(): Thanks for playing. Next contestant, please", __func__); |
| abort(); |
| break; |
| } |
| |
| if( file->record_length ) |
| { |
| int rdigits; |
| length = (size_t)__gg__binary_value_from_field( &rdigits, |
| file->record_length); |
| } |
| |
| switch(file->org) |
| { |
| case file_line_sequential_e: |
| case file_sequential_e: |
| sequential_file_write(file, location, length, after, lines); |
| break; |
| |
| case file_relative_e: |
| { |
| relative_file_write(file, location, length, is_random); |
| break; |
| } |
| |
| case file_indexed_e: |
| { |
| indexed_file_write(file, location, length, is_random); |
| break; |
| } |
| |
| default: |
| fprintf(stderr, "%s(): Unhandled cbl_file_org_t %d\n", |
| __func__, |
| file->org); |
| exit(1); |
| break; |
| } |
| done: |
| establish_status(file, -1); |
| if( file->io_status < FhNotOkay ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| file->prior_op = file_op_write; |
| } |
| |
| static void |
| line_sequential_file_read( cblc_file_t *file) |
| { |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| size_t characters_read = 0; |
| size_t remaining; |
| bool hit_eof; |
| |
| // According to IBM: |
| |
| // Characters are read one at a time until: |
| // - A delimiter is reached. It is discarded, and the |
| // record area is filled with spaces. |
| // - The entire record area is filled. If the next unread |
| // character is the delimiter, it is discarded. Otherwise, |
| // it becomes the first character read by the next READ |
| // - EOF is encountered; the remainder of the record area |
| // is filled with spaces. |
| |
| // This contradicts the ISO/IEC 2014 standard, which says |
| // in section 14.9.29.3, paragraph 14) on page 554 that excess |
| // characters are discarded, and too-short records have |
| // characters to the right as undefined. I'm going with IBM, |
| // it makes more sense to me. |
| |
| // We first stage the data into the record area. |
| int ch; |
| |
| long fpos = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| hit_eof = false; |
| while( characters_read < file->record_area_max ) |
| { |
| ch = fgetc(file->file_pointer); |
| file->errnum = ferror(file->file_pointer); |
| if( ch == file->delimiter ) |
| { |
| break; |
| } |
| if( ch == file->delimiter || ch == EOF ) |
| { |
| hit_eof = true; |
| clearerr(file->file_pointer); |
| break; |
| } |
| if( handle_ferror(file, __func__, "fgetc() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| file->default_record->data[characters_read] = (char)ch; |
| characters_read += 1; |
| } |
| remaining = characters_read; |
| while(remaining < file->record_area_max ) |
| { |
| // Space fill shorty records |
| file->default_record->data[remaining++] = internal_space; |
| } |
| |
| if( hit_eof && !characters_read) |
| { |
| // We got an end-of-file without characters |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| } |
| else if( hit_eof ) |
| { |
| // We got an end-of-file whilst reading characters |
| // Override the FsEofSeq. We'll get an actual EOF if the programmer |
| // does another READ: |
| file->io_status = FsErrno; |
| } |
| else if (characters_read < file->record_area_max) |
| { |
| // Just discard an early record delimiter |
| file->io_status = FsRecordLength; // "04" |
| } |
| else // We filled the whole record area. Look ahead one character |
| { |
| #ifdef POSSIBLY_IBM |
| // In this code, unread characters before the internal_newline |
| // are read next time. See page 133 of the IBM Language Reference |
| // Manual: "If the first unread character is the record delimiter, it |
| // is discarded. Otherwise, the first unread character becomes the first |
| // character read by the next READ statement." |
| ch = fgetc(file->file_pointer); |
| file->errnum = ferror(); |
| // If that next character isn't a delimiter, put it back: |
| if( ch != file->delimiter && ch != EOF) |
| { |
| ungetc(ch, file->file_pointer); |
| } |
| else if( handle_ferror(file->file_pointer, __func__, "fgetc() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| #else |
| // In this code, extra characters before the internal_newline |
| // are read next time are discarded. GnuCOBOL works this way, and |
| // the Michael Coughlin "Beginning COBOL" examples require this mode. |
| // The ISO/IEC 2014 standard is silent on the question of LINE |
| // SEQUENTIAL; it describes only SEQUENTIAL. |
| for(;;) |
| { |
| ch = fgetc(file->file_pointer); |
| file->errnum = ferror(file->file_pointer); |
| // We can't use handle_ferror() directly, because an EOF is |
| // a legitimate way to end the last line. |
| if( ch == file->delimiter || ch == EOF) |
| { |
| clearerr(file->file_pointer); |
| break; |
| } |
| if( ferror(file->file_pointer) |
| && handle_ferror(file, __func__, "fgetc() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| file->io_status = FsRecordLength; // "04" |
| } |
| #endif |
| } |
| |
| if( file->record_length ) |
| { |
| __gg__int128_to_field(file->record_length, |
| characters_read, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| done: |
| establish_status(file, fpos); |
| } |
| |
| static size_t |
| sequential_file_read( cblc_file_t *file) |
| { |
| unsigned char preamble[4]; |
| size_t characters_read; |
| size_t bytes_in_record; |
| size_t bytes_to_read; |
| long fpos; |
| |
| file->errnum = 0; |
| |
| if( file->io_status >= FsEofSeq ) // "10" |
| { |
| // There is a special error code for trying to read a file after an error |
| // or after an EOF has been encountered |
| file->io_status = FsReadError; // "46" |
| fpos = -1; |
| goto done; |
| } |
| |
| file->io_status = FsErrno; |
| |
| fpos = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| if( file->record_area_min != file->record_area_max ) |
| { |
| // Because of the different sizes, we are expecting a preamble: |
| |
| // An error at this point is either an error, or a true end-of-file. |
| characters_read = fread(preamble, 1, 4, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| // This also comes back true for an ordinary end-of-file |
| fpos = -1; |
| goto done; |
| } |
| |
| if( characters_read != 4 ) |
| { |
| // If the preamble is incomplete, treat that as an EOF |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| fpos = -1; |
| goto done; |
| } |
| |
| // Extract the count of bytes in the record from the preamble |
| bytes_in_record = ((size_t)preamble[0]<<8) + preamble[1]; |
| } |
| else |
| { |
| // Because the min and max are the same, we figure on reading that many |
| // characters: |
| bytes_in_record = file->record_area_max; |
| } |
| |
| |
| // We are now going to read that many bytes from the file into the record area |
| // Let's make sure that bogus input doesn't cause us to fall off the end of |
| // the world: |
| bytes_to_read = std::min( bytes_in_record, |
| file->record_area_max); |
| |
| characters_read = fread(file->default_record->data, |
| 1, |
| bytes_to_read, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| if( characters_read < bytes_in_record ) |
| { |
| memset(file->default_record->data, internal_space, bytes_to_read); |
| file->io_status = FsEofSeq; // "10" |
| fpos = -1; |
| goto done; |
| } |
| |
| // Let the caller know if we got too few or too many characters |
| if( bytes_in_record < file->record_area_min |
| || bytes_in_record > file->record_area_max ) |
| { |
| file->io_status = FsRecordLength; // "04" |
| } |
| |
| if( bytes_in_record > file->record_area_max ) |
| { |
| // Let's fix the misalignment |
| fseek(file->file_pointer, |
| bytes_in_record - file->record_area_max, |
| SEEK_CUR); |
| } |
| |
| if( file->record_length ) |
| { |
| __gg__int128_to_field(file->record_length, |
| characters_read, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| done: |
| establish_status(file, fpos); |
| return characters_read; |
| } |
| |
| static void |
| relative_file_read_varying( cblc_file_t *file, |
| int where) |
| { |
| // where -2 PREVIOUS |
| // where -1 NEXT |
| // where 0 who the hell knows |
| // where 1 or more: random read |
| |
| bool is_random = where > 0; |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| relative_file_parameters rfp; |
| |
| size_t characters_read = 0; |
| long fpos = -1; |
| size_t payload_length; |
| |
| if( where >= 1 ) |
| { |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| if( rfp.record_position >= rfp.file_size ) |
| { |
| // We're falling off the end of the file, which means our key doesn't |
| // exist |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| else if( where == -1) |
| { |
| // This is a sequential NEXT read of a RELATIVE file |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| IGNORE_LIMITS, |
| INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| } |
| else |
| { |
| warnx("Whatever are we doing here?"); |
| abort(); |
| } |
| |
| // We are now poised to read a record, |
| for(;;) |
| { |
| if( !rfp.inside_existing_file ) |
| { |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| |
| fread(&payload_length, 8, 1, file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| if( payload_length ) |
| { |
| // We have a good record to read: |
| |
| // Read the characters into the record area: |
| fread(file->default_record->data, |
| 1, |
| file->record_area_max, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| // Having read that that data, set up for a subsequent delete/rewrite. |
| // Set fpos to the current_file_position, and then update the current |
| // file position. |
| fpos = rfp.current_file_position; |
| |
| // We need to change the file position pointer to point to the next |
| // record. |
| |
| rfp.current_file_position += rfp.record_size; |
| fseek(file->file_pointer, |
| rfp.current_file_position, |
| SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| break; |
| } |
| else |
| { |
| // There isn't a record in this slot |
| if( where == -1 ) |
| { |
| // But we are in next_record mode. It is our duty and obligation |
| // to skip merrily through the file looking for the next valid |
| // record for the lazy bums who called us. |
| rfp.record_position += rfp.record_size; |
| rfp.flag_position += rfp.record_size; |
| rfp.inside_existing_file |
| = rfp.record_position + rfp.record_size <= rfp.file_size; |
| rfp.key_value = 1 + rfp.record_position |
| / (rfp.preamble_size |
| + rfp.payload_size |
| + rfp.postamble_size); |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| rfp.current_file_position = rfp.record_position; |
| continue; |
| } |
| break; |
| } |
| } |
| |
| characters_read = payload_length; |
| if( characters_read == 0 ) |
| { |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| else |
| { |
| if( where < 0 ) |
| { |
| // We did a FORMAT 1 read, so we need to update the key, if there is one |
| if( file->keys[0] ) |
| { |
| long max_key = max_value(file->keys[0]); |
| if( rfp.key_value >= max_key ) |
| { |
| // This is an oddball COBOL error: A sequential read would result in |
| // reading a record whose relative record number is too big for |
| // file->key to hold |
| file->io_status = FsEofRel; // "14" |
| goto done; |
| } |
| __gg__int128_to_field(file->keys[0], |
| rfp.key_value, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| } |
| } |
| done: |
| if( file->record_length ) |
| { |
| __gg__int128_to_field(file->record_length, |
| payload_length, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| establish_status(file, fpos); |
| } |
| |
| static void |
| relative_file_read( cblc_file_t *file, |
| int where) |
| { |
| // where -2 means PREVIOUS |
| // where -1 means NEXT |
| // where 0 means random read, based on the key |
| |
| if( file->record_area_min != file->record_area_max ) |
| { |
| return relative_file_read_varying(file, where); |
| } |
| |
| bool is_random = where > 0; |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| relative_file_parameters rfp; |
| |
| size_t characters_read = 0; |
| long fpos = -1; |
| |
| if( where >= 1 ) |
| { |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| RESPECT_LIMITS, |
| DONT_INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| |
| if( rfp.record_position >= rfp.file_size ) |
| { |
| // We're falling off the end of the file, which means our key doesn't |
| // exist |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| else if( where == -1) |
| { |
| // This is a sequential NEXT read of a RELATIVE file |
| if( relative_file_parameters_get( rfp, |
| rfm_microfocus_e, |
| file, |
| IGNORE_LIMITS, |
| INIT_KEY, |
| is_random) ) |
| { |
| goto done; |
| } |
| } |
| else |
| { |
| warnx("Whatever are we doing here?"); |
| abort(); |
| } |
| |
| // The following code is predicated on rfm_microfocus_e. |
| |
| // We are now poised to read a record, provided the flag byte is |
| // indicates this is a good record |
| for(;;) |
| { |
| if( !rfp.inside_existing_file ) |
| { |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| char record_marker; |
| if( pread(rfp.fd, &record_marker, 1, rfp.flag_position) <= 0) |
| { |
| goto done; |
| } |
| if(record_marker == internal_newline) |
| { |
| // We have a good record to read: |
| |
| // We need to change the file_position_pointer to reflect any |
| // preamble: |
| if( rfp.preamble_size ) |
| { |
| fseek(file->file_pointer, rfp.preamble_size, SEEK_CUR); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| } |
| // Read the characters into the record area: |
| characters_read = fread(file->default_record->data, |
| 1, |
| file->record_area_max, |
| file->file_pointer); |
| if( handle_ferror(file, __func__, "fread() error") ) |
| { |
| goto done; |
| } |
| // Having read that that data, set up for a subsequent delete/rewrite. |
| // Set fpos to the current_file_position, and then update the current |
| // file position. |
| fpos = rfp.current_file_position; |
| |
| // We need to change the file position pointer to point to the next |
| // record. |
| |
| rfp.current_file_position += rfp.record_size; |
| fseek(file->file_pointer, |
| rfp.current_file_position, |
| SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| break; |
| } |
| else |
| { |
| // There isn't a record in this slot |
| if( where == -1 ) |
| { |
| // But we are in next_record mode. It is our duty and obligation |
| // to skip merrily through the file looking for the next valid |
| // record for the lazy bums who called us. |
| rfp.record_position += rfp.record_size; |
| rfp.flag_position += rfp.record_size; |
| rfp.inside_existing_file |
| = rfp.record_position + rfp.record_size <= rfp.file_size; |
| rfp.key_value = 1 + rfp.record_position |
| / (rfp.preamble_size |
| + rfp.payload_size |
| + rfp.postamble_size); |
| |
| fseek(file->file_pointer, rfp.record_position, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| goto done; |
| } |
| rfp.current_file_position = rfp.record_position; |
| continue; |
| } |
| break; |
| } |
| } |
| |
| if( characters_read == 0 ) |
| { |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| else |
| { |
| if( where < 0 ) |
| { |
| // We did a FORMAT 1 read, so we need to update the key, if there is one |
| if( file->keys[0] ) |
| { |
| long max_key = max_value(file->keys[0]); |
| if( rfp.key_value >= max_key ) |
| { |
| // This is an oddball COBOL error: A sequential read would result in |
| // reading a record whose relative record number is too big for |
| // file->key to hold |
| file->io_status = FsEofRel; // "14" |
| goto done; |
| } |
| __gg__int128_to_field(file->keys[0], |
| rfp.key_value, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| } |
| } |
| done: |
| if( file->record_length ) |
| { |
| __gg__int128_to_field(file->record_length, |
| characters_read, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| establish_status(file, fpos); |
| } |
| |
| static void |
| indexed_file_read( cblc_file_t *file, |
| int key_number) |
| { |
| long record_length; |
| int flag; |
| int read_res; |
| |
| if( key_number == 0 ) |
| { |
| // This is an implicit next |
| key_number = -1; |
| } |
| |
| size_t characters_read = 0; |
| file_index_t *file_index; |
| long fpos = -1; |
| |
| if( key_number >= 1 ) |
| { |
| // It is meat and potatoes time. We need to pick up the first record |
| // with the specified key: |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| file->recent_key = key_number; |
| file_index = &file->supplemental->indexes[key_number]; |
| fpos = file_indexed_first_position(file, key_number); |
| if( fpos == -1 ) |
| { |
| file->io_status = FsNotFound; // "23" |
| goto done; |
| } |
| fpos = file_indexed_first_position(file, key_number); |
| file_index->current_iterator++; |
| } |
| else if( key_number == -1 ) |
| { |
| // We are ready to do a sequential read of an INDEXED file |
| |
| // There is a special code for trying to read after a preceding unsuccessful |
| // statement: |
| |
| if( file->io_status >= FsEofSeq ) // "10" |
| { |
| file->io_status = FsReadError; // "46" |
| goto done; |
| } |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| file_index = &file->supplemental->indexes[file->recent_key]; |
| |
| // When you arrive here, the current_indicator points to the iterator |
| // *after* the prior successful read |
| if( file_index->current_iterator == file_index->key_to_position.end() ) |
| { |
| // We have hit the end of keys |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| |
| fpos = file_index->current_iterator->second; |
| |
| if( file_index->current_iterator == file_index->key_to_position.end() ) |
| { |
| __gg__abort("indexed_file_read(): fell off of file_index->key_to_position"); |
| } |
| file_index->current_iterator++; |
| } |
| else if( key_number == -2 ) |
| { |
| // We are ready to do a sequential read PREVIOUS of an INDEXED file |
| |
| // There is a special code for trying to read after a preceding unsuccessful |
| // statement: |
| if( file->io_status >= FsEofSeq ) // "10" |
| { |
| file->io_status = FsReadError; // "46" |
| goto done; |
| } |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| |
| file_index = &file->supplemental->indexes[file->recent_key]; |
| |
| /* We need to do a little thinking. After an OPEN or START, |
| * file_index->current_iterator points to the next record to read for |
| * either a READ NEXT or READ PREVIOUS. |
| * |
| * But after a READ, a subsequent READ PREVIOUS needs to back the iterator |
| * down by 2 to get the correct record. |
| */ |
| |
| if( file->prior_op == file_op_read ) |
| { |
| // We just read record 10, so the iterator points to 11. We need to back |
| // it down to 9, if we can. (There might not be enough records) |
| |
| // We know we can back it down at least one record |
| file_index->current_iterator--; // Make it point back to 10 |
| if( file_index->current_iterator == file_index->key_to_position.begin() ) |
| { |
| // but we can't back it down another |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| file_index->current_iterator--; // Make it point back to 9 |
| } |
| else if( file->prior_op == file_op_delete ) |
| { |
| if( file->prior_read_location == -1 ) |
| { |
| // The prior operation deleted the record we are set up to read. |
| // Let's finesse the positioning. |
| if( file_index->current_iterator == file_index->key_to_position.end() ) |
| { |
| // We have hit the end of the keys. |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| file_index->current_iterator--; |
| } |
| } |
| else |
| { |
| // This must be after an OPEN or START. Check to make sure that the |
| // file isn't actually empty |
| |
| if( file_index->current_iterator == file_index->key_to_position.end() ) |
| { |
| // We have hit the end of the keys. |
| file->io_status = FsEofSeq; // "10" |
| file->prior_read_location = -1; |
| goto done; |
| } |
| } |
| |
| // We are ready to proceed |
| |
| fpos = file_index->current_iterator->second; |
| if( file_index->current_iterator == file_index->key_to_position.end() ) |
| { |
| __gg__abort("indexed_file_read(): fell off of file_index->key_to_position"); |
| } |
| file_index->current_iterator++; |
| } |
| |
| // fpos is where the data are: |
| fseek(file->file_pointer, fpos, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() error") ) |
| { |
| fpos = -1; |
| goto done; |
| } |
| |
| read_res = read_an_indexed_record(file, |
| file->record_area_max, |
| record_length, |
| flag); |
| if( read_res ) |
| { |
| // We hit an end of file or an error |
| fpos = -1; |
| goto done; |
| } |
| |
| if(flag != 1) |
| { |
| warnx("The file isn't right; key number %d found a deleted record", |
| key_number); |
| abort(); |
| } |
| |
| characters_read = record_length; |
| |
| done: |
| if( file->record_length ) |
| { |
| __gg__int128_to_field(file->record_length, |
| characters_read, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| establish_status(file, fpos); |
| } |
| |
| static void |
| __io__file_read(cblc_file_t *file, |
| int where) |
| { |
| // where = -2 means PREVIOUS |
| // where = -1 means NEXT |
| // where = 1 or more means key N, where N is one-based |
| |
| file->errnum = 0; |
| |
| if( !(file->flags & file_flag_existed_e) |
| && (file->flags & file_flag_optional_e)) |
| { |
| // Trying to read a file that didn't exist during file_open. |
| if( file->org == file_sequential_e || file->org == file_line_sequential_e ) |
| { |
| if( file->io_status < FhNotOkay ) |
| { |
| // This is a format 1 read |
| file->io_status = FsEofSeq; // "10" |
| } |
| else |
| { |
| file->io_status = FsReadError; // "46" |
| } |
| establish_status(file, -1); |
| return; |
| } |
| else |
| { |
| // The indexed or relative file didn't exist, so set an INVALID KEY |
| // condition: |
| if( where <= 0 ) |
| { |
| if( file->io_status < FhNotOkay ) |
| { |
| // This is a format 1 read |
| file->io_status = FsEofSeq; // "10" |
| } |
| else |
| { |
| file->io_status = FsReadError; // "46" |
| } |
| establish_status(file, -1); |
| } |
| else |
| { |
| // This is a format 2 read |
| file->io_status = FsNotFound; // "23" |
| establish_status(file, -1); |
| } |
| return; |
| } |
| } |
| |
| if( !file->file_pointer ) |
| { |
| // Attempting to read a file that isn't open |
| file->io_status = FsReadNotOpen; // "47" |
| establish_status(file, -1); |
| return; |
| } |
| |
| if( file->mode_char != 'r' && file->mode_char != '+' ) |
| { |
| // The file is open, but not in INPUT or I-O mode: |
| file->io_status = FsReadNotOpen; // "47" |
| establish_status(file, -1); |
| return; |
| } |
| |
| switch(file->org) |
| { |
| case file_line_sequential_e: |
| { |
| line_sequential_file_read(file); |
| break; |
| } |
| |
| case file_sequential_e: |
| { |
| sequential_file_read(file); |
| break; |
| } |
| |
| case file_relative_e: |
| { |
| relative_file_read(file, where); |
| break; |
| } |
| |
| case file_indexed_e: |
| { |
| indexed_file_read(file, where); |
| break; |
| } |
| |
| default: |
| fprintf(stderr, |
| "%s(): Unhandled cbl_file_org_t %d\n", |
| __func__, |
| file->org); |
| exit(1); |
| break; |
| } |
| if( file->io_status < FhNotOkay ) |
| { |
| file->flags |= file_flag_existed_e; |
| } |
| file->prior_op = file_op_read; |
| } |
| |
| static void |
| file_indexed_open(cblc_file_t *file) |
| { |
| // The file was supposed to be open when you got here. |
| if( !file->file_pointer ) |
| { |
| __gg__abort("file_indexed_open(): file_pointer is NULL"); |
| } |
| |
| file->supplemental = new supplemental_t; |
| |
| // We need one multimap for each key. Whenever we are told about a key, it'll |
| // be by key_number. key_number 1 is by convention the primary key. There |
| // will be no key_number zero, so we are going to start our vector of indexes |
| // with an empty placeholder: |
| |
| unsigned char *stash = NULL; |
| |
| file_index_t file_index; |
| file->supplemental->indexes.push_back(file_index); |
| file->supplemental->uniques.push_back(0); |
| |
| // Add one entry to the indexes for each key number: |
| int current_key_number = 0; |
| size_t index = 0; |
| while(file->key_numbers[index] != -1) |
| { |
| if( file->key_numbers[index] != current_key_number ) |
| { |
| file_index_t file_index; |
| file->supplemental->indexes.push_back(file_index); |
| current_key_number = file->key_numbers[index]; |
| file->supplemental->uniques.push_back(file->uniques[index]); |
| } |
| index += 1; |
| } |
| |
| // To build indexes, we need to be at the beginning: |
| fseek(file->file_pointer, 0, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() after fopen() failed") ) |
| { |
| goto done; |
| } |
| |
| switch( file->mode_char ) |
| { |
| case 'w': |
| // OUTPUT mode causes an empty file to be created, so the indices |
| // are empty as well |
| break; |
| |
| case 'r': |
| case 'a': |
| case '+': |
| if( file->flags & file_flag_existed_e ) |
| { |
| // We need to open the file for reading, and build the |
| // maps for each index: |
| static size_t fname_size = MINIMUM_ALLOCATION_SIZE; |
| static char *fname = (char *)malloc(fname_size); |
| |
| internal_to_console(&fname, |
| &fname_size, |
| file->filename, strlen(file->filename)); |
| |
| // We are going to scan through the entire file, building index |
| // entries for each record. |
| |
| // It should already be at the beginning: |
| if( ftell(file->file_pointer) != 0 ) |
| { |
| __gg__abort("file_indexed_open():" |
| " file_pointer should be at the beginning"); |
| } |
| |
| // Stash the existing record area: |
| stash = (unsigned char *)malloc(file->record_area_max); |
| memcpy( stash, |
| file->default_record->data, |
| file->record_area_max); |
| |
| for(;;) |
| { |
| // Remember where we are right now: |
| long record_length; |
| int flag; |
| long record_position = ftell(file->file_pointer); |
| if( handle_ferror(file, __func__, "ftell() error") ) |
| { |
| goto done; |
| } |
| |
| int read_result = read_an_indexed_record( file, |
| file->record_area_max, |
| record_length, |
| flag); |
| if( read_result == 1 ) |
| { |
| // Don't panic; it's just an end-of-file |
| break; |
| } |
| else if( read_result > 1 ) |
| { |
| // It was an error of some kind |
| goto done; |
| } |
| |
| if( flag == 1 ) |
| { |
| // We have a good record in our record area: |
| |
| if( !file_indexed_update_indices(file, record_position) ) |
| { |
| // There must have been a duplicate UNIQUE index, or some other |
| // problem. |
| goto done; |
| } |
| } |
| else |
| { |
| // This is a hole in the file; make it available for a WRITE or |
| // EXTEND |
| file_hole_t hole = {record_position, (size_t)record_length}; |
| file->supplemental->holes.push_back(hole); |
| } |
| } |
| |
| file->io_status = FsErrno; |
| fseek(file->file_pointer, 0, SEEK_SET); |
| if( handle_ferror(file, __func__, "fseek() after fopen() failed") ) |
| { |
| goto done; |
| } |
| } |
| break; |
| |
| default: |
| warnx( "%s(): This is weird. mode_char is '%c' (%d)?\n", |
| __func__, |
| file->mode_char, |
| file->mode_char); |
| __gg__abort("file_indexed_open(): Unknown mode_char"); |
| break; |
| } |
| done: |
| // We need to initialize the iterators for every index: |
| for( size_t i=1; i<file->supplemental->indexes.size(); i++ ) |
| { |
| file->supplemental->indexes[i].current_iterator = |
| file->supplemental->indexes[i].key_to_position.begin(); |
| file->supplemental->indexes[i].ending_iterator = |
| file->supplemental->indexes[i].key_to_position.end(); |
| } |
| file->recent_key = 1; |
| |
| if( stash ) |
| { |
| // Restore the original record area: |
| memcpy( file->default_record->data, |
| stash, |
| file->record_area_max); |
| free(stash); |
| } |
| |
| fseek(file->file_pointer, 0, SEEK_SET); |
| handle_ferror(file, __func__, "fseek() error"); |
| |
| } |
| |
| static void |
| file_indexed_close(cblc_file_t *file) |
| { |
| delete file->supplemental; |
| file->supplemental = NULL; |
| } |
| |
| static void |
| report_open_failure(const char *type, |
| const char *structure_name, |
| const char *filename) |
| { |
| bool quiet = true; |
| if( !quiet ) |
| { |
| if( getenv(filename) ) |
| { |
| fprintf(stderr, |
| "Trying to 'OPEN %s %s %s -> \"%s\"', which doesn't exist\n", |
| type, |
| structure_name, |
| filename, |
| getenv(filename)); |
| } |
| else |
| { |
| fprintf(stderr, |
| "Trying to 'OPEN %s %s \"%s\"', which doesn't exist\n", |
| type, |
| structure_name, |
| filename); |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__file_reopen(cblc_file_t *file, int mode_char) |
| { |
| // This is a useful, although scary, little helper. The file must not be |
| // open. The file->filename must be valid, as must the file_name_quoted_e flag. |
| // You can see it used in __gg__file_open. You can also see it in |
| // __gg__sort_workfile, where the workfile is passed as open for read, and |
| // which needs to be closed and reopened first in "r" and then "w" modes. |
| |
| // Note that when closing for reopening, you must simply use fclose() and not |
| // __gg__file_close(). The second one does bookkeeping and cleanup that is |
| // not appropriate here. |
| |
| bool the_file_exists; |
| bool random_access_mode; |
| char achMode[3]; |
| |
| // Stash the mode_char for later analysis during READ and WRITE operations |
| file->mode_char = mode_char; |
| char *trimmed_name; |
| trimmed_name = get_filename(file, !!(file->flags & file_name_quoted_e)); |
| if( !trimmed_name[0] ) |
| { |
| bool all_spaces = true; |
| for(size_t i=0; i<strlen(file->filename); i++) |
| { |
| if( file->filename[i] != internal_space ) |
| { |
| all_spaces = false; |
| } |
| break; |
| } |
| if( all_spaces ) |
| { |
| warnx("Warning: %s specified with a filename that is all spaces", |
| file->name); |
| file->io_status = FsNameError; // "31" |
| goto done; |
| } |
| |
| static size_t fname_size = MINIMUM_ALLOCATION_SIZE; |
| static char *fname = (char *)malloc(fname_size); |
| internal_to_console(&fname, |
| &fname_size, |
| file->filename, |
| strlen(file->filename)); |
| warnx( "%s(): There is no environment variable named \"%s\"\n", |
| __func__, |
| fname); |
| file->io_status = FsNoFile; // "35" |
| goto done; |
| } |
| |
| // achMode is the mode string that gets passed down below to fopen(). |
| random_access_mode = ( file->access == file_access_rnd_e |
| || file->access == file_access_dyn_e); |
| the_file_exists = access(trimmed_name, F_OK) == 0; |
| file->flags |= the_file_exists ? file_flag_existed_e : file_flag_none_e ; |
| |
| // We have four operations: INPUT (r) OUTPUT (w) I-O (+) and EXTEND (a) |
| // INPUT and I-O and EXTEND have different results based on is_optional |
| // and whether or not the file exists. |
| // Various modification take place if random_access_mode is true |
| |
| if( the_file_exists ) |
| { |
| switch(mode_char) |
| { |
| case 'r': |
| // OPEN INPUT |
| // We need a vanilla read-only file: |
| strcpy(achMode, "r"); |
| break; |
| |
| case 'w': |
| // OPEN OUTPUT |
| // This syntax means create a new file, or overwrite an existing |
| // one. For files with random access mode, we need to be |
| // able to read as well as write, because we have to be able |
| // to ascertain that a record slot is empty in the event that |
| // the programmer tries to write to the same slot twice: |
| if( random_access_mode ) |
| { |
| strcpy(achMode, "w+"); |
| } |
| else |
| { |
| strcpy(achMode, "w"); |
| } |
| break; |
| |
| case 'a': |
| // EXTEND is for sequential files: |
| strcpy(achMode, "a"); |
| break; |
| |
| case '+': |
| // I-O |
| // We need to be able to read and write the existing file. |
| strcpy(achMode, "r+"); |
| break; |
| |
| default: |
| fprintf(stderr, |
| "%s(): We were given an unknown mode_char %d\n", |
| __func__, |
| mode_char); |
| exit(1); |
| break; |
| } |
| } |
| else |
| { |
| // The file *doesn't* exist |
| switch(mode_char) |
| { |
| case 'r': |
| // OPEN INPUT, but the file doesn't exist: |
| if( file->flags & file_flag_optional_e ) |
| { |
| // This is a weird condition. OPTIONAL means "flag it as sort of |
| // open but the first read causes AT END or INVALID KEY condition. |
| file->io_status = FsUnavail; // "05" |
| goto done; |
| } |
| else |
| { |
| report_open_failure("INPUT", file->name, trimmed_name); |
| file->io_status = FsNoFile; // "35" |
| goto done; |
| } |
| break; |
| |
| case 'w': |
| // OPEN OUTPUT |
| // This syntax means create a new file, or overwrite an existing |
| // one. For files with random access mode, we need to be |
| // able to read as well as write, because we have to be able |
| // to ascertain that a record slot is empty in the event that |
| // the programmer tries to write to the same slot twice: |
| if( random_access_mode ) |
| { |
| strcpy(achMode, "w+"); |
| } |
| else |
| { |
| strcpy(achMode, "w"); |
| } |
| break; |
| |
| case 'a': |
| // EXTEND |
| if( file->flags & file_flag_optional_e ) |
| { |
| if( random_access_mode ) |
| { |
| // For files that might be sequential or random: |
| strcpy(achMode, "a+"); |
| } |
| else |
| { |
| // For pure sequential files, we just do a straight "a" |
| strcpy(achMode, "a"); |
| } |
| file->io_status = FsUnavail; // "05" |
| } |
| else |
| { |
| // Trying to extend a non-optional non-existing file is against the rules |
| report_open_failure("EXTEND", file->name, trimmed_name); |
| file->io_status = FsNoFile; // "35" |
| goto done; |
| } |
| break; |
| |
| case '+': |
| // I-O |
| if( file->flags & file_flag_optional_e ) |
| { |
| // We need to be able to read and write a new file. |
| strcpy(achMode, "w+"); |
| file->io_status = FsUnavail; // "05" |
| } |
| else |
| { |
| report_open_failure("I-O", file->name, trimmed_name); |
| file->io_status = FsNoFile; // "35" |
| goto done; |
| } |
| break; |
| |
| default: |
| fprintf(stderr, |
| "%s(): We were given an unknown mode_char %d\n", |
| __func__, |
| mode_char); |
| exit(1); |
| break; |
| } |
| } |
| |
| file->file_pointer = fopen(trimmed_name, achMode); |
| if( file->file_pointer == NULL ) |
| { |
| file->errnum = errno; |
| // We were unable to open the file |
| switch(mode_char) |
| { |
| case 'r': |
| case 'a': |
| case '+': |
| file->io_status = FsNoFile; // "35" |
| goto done; |
| break; |
| |
| case 'w': |
| file->io_status = FsOsError; // "30" |
| goto done; |
| break; |
| } |
| } |
| file->errnum = ferror(file->file_pointer); |
| |
| // If this was a OPEN EXTEND, we want the file positioned at the |
| // the very end (which it won't be when achMode is "r+" |
| if( mode_char == 'a' ) |
| { |
| fseek(file->file_pointer, 0, SEEK_END); |
| if( handle_ferror(file, __func__, "fseek() after fopen() failed") ) |
| { |
| goto done; |
| } |
| } |
| if( file->org == file_indexed_e ) |
| { |
| file_indexed_open(file); |
| } |
| file->recent_char = '\0'; |
| |
| done: |
| file->prior_op = file_op_open; |
| } |
| |
| static void |
| __io__file_open(cblc_file_t *file, |
| char *filename, |
| int mode_char, |
| int is_quoted) |
| { |
| // Filename is a pointer to a malloc() buffer. |
| |
| // The complication: A filename can be literal text, it can be from a COBOL |
| // alphanumeric variable, or it can be the name of an environment variable |
| // that contains the actual name of the file. The consequence is that if |
| // you want to call __gg__file_open from anywhere except the parser_file_open |
| // routine, then you had best really know what you are doing. |
| |
| file->errnum = 0; |
| file->io_status = FsErrno; |
| if( file->file_pointer ) |
| { |
| // The file is already open: |
| file->io_status = FsIsOpen; // "41" 14.9.26.3 Paragraph 1 |
| } |
| else |
| { |
| // filename is the result of a strdup or malloc. We will free() it at |
| // file close time. |
| file->filename = filename; |
| file->flags &= ~file_name_quoted_e; |
| file->flags |= is_quoted ? file_name_quoted_e : file_flag_none_e; |
| |
| __gg__file_reopen(file, mode_char); |
| } |
| establish_status(file, -1); |
| file->prior_op = file_op_open; |
| } |
| |
| static void |
| __io__file_close( cblc_file_t *file, int how ) |
| { |
| // We are forcing line-sequential files to end with a newline: |
| |
| // if( file->org == file_line_sequential_e |
| // && ( file->mode_char == 'w' || file->mode_char == 'a' ) |
| // && file->recent_char != internal_newline ) |
| // { |
| // int ch = internal_newline; |
| // fputc(ch, file->file_pointer); |
| // if( handle_ferror(file, __func__, "fputc() error [6]") ) |
| // { |
| // goto done; |
| // } |
| // file->recent_char = ch; |
| // } |
| |
| errno = 0; |
| file->io_status = FsErrno; |
| long fpos = -1; |
| if( !file->file_pointer ) |
| { |
| // Attempting to close a file that isn't open: |
| file->io_status = FsCloseNotOpen; // "42" 14.9.6.3 Paragraph 1 |
| goto done; |
| } |
| |
| if( how == file_close_reel_unit_e ) |
| { |
| // Closing a REEL unit. Leave the file open, and return "07" |
| file->io_status = FsNotaTape; // "07" |
| |
| // We have to leave the file position alone: |
| fpos = ftell(file->file_pointer); |
| goto done; |
| } |
| |
| if( fclose(file->file_pointer) != 0 ) |
| { |
| handle_ferror(file, __func__, "fclose() error"); |
| } |
| file->file_pointer = NULL; |
| |
| if( file->org == file_indexed_e ) |
| { |
| file_indexed_close(file); |
| } |
| |
| // The filename can be from a COBOL alphanumeric variable, which means it can |
| // between a file_close and a subsequent file_open. So, we get rid of it |
| // here |
| free(file->filename); |
| file->filename = NULL; |
| |
| done: |
| establish_status(file, fpos); |
| file->prior_op = file_op_close; |
| } |
| |
| static cblc_file_t *stashed; |
| |
| cblc_file_t * |
| __gg__file_stashed() |
| { |
| return stashed; |
| } |
| |
| extern "C" |
| void |
| __gg__file_stash( cblc_file_t *file ) |
| { |
| ::stashed = file; |
| } |
| |
| /* |
| * The following constitutes a proposal for a public API to gcobol I/O. |
| * |
| * The class gcobol_io_t would be in a public header file that |
| * would be used by any library that provides an I/O |
| * implementation. Additionally, that header would be where |
| * cblc_file_t is defined, and where the valid file status values |
| * (and relops) are enumerated. |
| * |
| * The library contructs the class, providing its own pointers, |
| * and supplies a known function, gcobol_fileops, to return |
| * it. gcobol-compiled programs call the functions, or others |
| * supplied by a different implementation, through the pointers. |
| * |
| * This design makes it possible to run the same code, unaltered, |
| * simply by relinking. |
| * |
| * This design is commonly used, and is similar to the one used in |
| * GnuCOBOL. One difference is that the file status is captured |
| * in the cblc_file_t, whereas in GnuCOBOL it is the return value. |
| * |
| * There is no provision for using more than one implemetation at |
| * a time in the same program, as would be needed for CODE-SET |
| * support. To achieve that in C++ without dynamic linking, there |
| * would have to be a set of known implementations, each with its |
| * own namespace, in which gcobol_fileops would be defined. |
| */ |
| |
| class gcobol_io_t { |
| public: |
| static const char constexpr marquee[64] = "libgcobol: gfileio.cc"; |
| |
| typedef void (open_t)( cblc_file_t *file, |
| char *filename, |
| int mode_char, |
| int is_quoted ); |
| typedef void (close_t)( cblc_file_t *file, |
| int how ); |
| typedef void (start_t)( cblc_file_t *file, |
| int relop, // needs enum |
| int first_last_key, |
| size_t length ); |
| typedef void (read_t)( cblc_file_t *file, |
| int where ); |
| typedef void (write_t)( cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| int after, |
| int lines, |
| int is_random ); |
| typedef void (rewrite_t)( cblc_file_t *file, |
| size_t length, bool is_random ); |
| typedef void (delete_t)( cblc_file_t *file, |
| bool is_random ); |
| |
| open_t *Open; |
| close_t *Close; |
| start_t *Start; |
| read_t *Read; |
| write_t *Write; |
| rewrite_t *Rewrite; |
| delete_t *Delete; |
| |
| gcobol_io_t() |
| : Open(NULL) |
| , Close(NULL) |
| , Start(NULL) |
| , Read(NULL) |
| , Write(NULL) |
| , Rewrite(NULL) |
| , Delete(NULL) |
| {} |
| |
| gcobol_io_t( open_t *Open, |
| close_t *Close, |
| start_t *Start, |
| read_t *Read, |
| write_t *Write, |
| rewrite_t *Rewrite, |
| delete_t *Delete ) |
| : Open(Open) |
| , Close(Close) |
| , Start(Start) |
| , Read(Read) |
| , Write(Write) |
| , Rewrite(Rewrite) |
| , Delete(Delete) |
| {} |
| |
| #if FILE_IO_IMPLEMENTED |
| int read_next(); |
| int fildelete(); |
| void ioinit(); |
| void ioexit(); |
| int iofork(); |
| int iosync(); |
| int commit(); |
| int rollback(); |
| int iounlock(); |
| char * ioversion(); |
| #endif |
| }; |
| |
| |
| // Our implementation returns this populated structure |
| |
| gcobol_io_t* |
| gcobol_fileops() { |
| return new gcobol_io_t( __io__file_open, |
| __io__file_close, |
| __io__file_start, |
| __io__file_read, |
| __io__file_write, |
| __io__file_rewrite, |
| __io__file_delete ); |
| } |
| |
| /* |
| * To use this structure, whether our gcobolio.so or another, initialize with: |
| * |
| * gcobol_io_t * gfile = gcobol_filops(file); |
| * |
| * That means every gcobol binary expects to be linked to a library |
| * that supplies gcobol_fileops(). By default, we link to ours. |
| * |
| * Then, in libgcobol, replace direct calls with calls through fileops. |
| * That is, instead of |
| * |
| * __gg__file_open("foo", "r", false ); |
| * use |
| * gfile->Open("foo", "r", false ); |
| * |
| * You'll probably want some kind of trampoline to avoid the need to |
| * generate the Gimple to call through a pointer to a structure: |
| */ |
| |
| /* |
| * I/O via interface |
| */ |
| static gcobol_io_t * gcobol_io = NULL; |
| |
| static gcobol_io_t * |
| gcobol_io_funcs() { |
| if( ! gcobol_io ) |
| { |
| gcobol_io = gcobol_fileops(); |
| if( !gcobol_io ) |
| { |
| __gg__abort("gcobol_io_funcs(): gcobol_io is NULL"); |
| } |
| } |
| return gcobol_io; |
| } |
| |
| extern "C" |
| void |
| __gg__file_open(cblc_file_t *file, |
| char *filename, |
| int mode_char, |
| int is_quoted) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Open(file, filename, mode_char, is_quoted); |
| } |
| |
| extern "C" |
| void |
| __gg__file_close( cblc_file_t *file, int how ) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Close(file, how); |
| } |
| |
| extern "C" |
| void |
| __gg__file_start( cblc_file_t *file, |
| int relop, |
| int first_last_key, |
| size_t length) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Start(file, relop, first_last_key, length); |
| } |
| |
| extern "C" |
| void |
| __gg__file_read(cblc_file_t *file, |
| int where) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Read(file, where); |
| } |
| |
| extern "C" |
| void |
| __gg__file_write( cblc_file_t *file, |
| unsigned char *location, |
| size_t length, |
| int after, |
| int lines, |
| int is_random ) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Write(file, location, length, after, lines, is_random); |
| } |
| |
| extern "C" |
| void |
| __gg__file_rewrite(cblc_file_t *file, size_t length, bool is_random) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Rewrite(file, length, is_random); |
| } |
| |
| extern "C" |
| void |
| __gg__file_delete(cblc_file_t *file, bool is_random) |
| { |
| gcobol_io_t *functions = gcobol_io_funcs(); |
| functions->Delete(file, is_random); |
| } |
| |
| /* end interface functions */ |
| |