// unbounded_channels <= 0 means no, > 0 means yes // -1 means advance. void check_channel(char threadsafe, qio_chtype_t type, int64_t start, int64_t len, int64_t chunksz, qio_hint_t file_hints, qio_hint_t ch_hints, char unbounded_channels, char reopen) { qio_file_t* f; qio_channel_t* writing; qio_channel_t* reading; int64_t offset; int64_t usesz; int64_t end = start + len; int err; unsigned char* chunk; unsigned char* got_chunk; int64_t k; ssize_t amt_written; ssize_t amt_read; char* chhints; char* fhints; FILE* writefp = NULL; FILE* readfp = NULL; int memory; int64_t ch_end = end; char filename[128]; int fd = -1; strcpy(filename,"/tmp/qio_testXXXXXX"); if( unbounded_channels > 0 ) ch_end = INT64_MAX; ch_hints = (ch_hints & ~ QIO_CHTYPEMASK) | type; memory = 0; if( (file_hints & QIO_METHODMASK) == QIO_METHOD_MEMORY || (ch_hints & QIO_METHODMASK) == QIO_METHOD_MEMORY ) { memory = 1; } if( memory ) { file_hints = (file_hints & ~ QIO_METHODMASK ) | QIO_METHOD_MEMORY; ch_hints = (ch_hints & ~ QIO_METHODMASK ) | QIO_METHOD_MEMORY; } if( memory && type == QIO_CH_ALWAYS_UNBUFFERED ) return; if( memory && reopen ) return; if( (ch_hints & QIO_METHODMASK) == QIO_METHOD_FREADFWRITE ) { if( (file_hints & QIO_METHODMASK) != QIO_METHOD_FREADFWRITE ) return; } if( (ch_hints & QIO_METHODMASK) == QIO_METHOD_MMAP ) { if( (file_hints & QIO_METHODMASK) != QIO_METHOD_MMAP ) return; } fhints = qio_hints_to_string(file_hints); chhints = qio_hints_to_string(ch_hints); printf("check_channel(threadsafe=%i, type=%i, start=%lli, len=%lli, chunksz=%lli, file_hints=%s, ch_hints=%s, unbounded=%i, reopen=%i)\n", (int) threadsafe, (int) type, (long long int) start, (long long int) len, (long long int) chunksz, fhints, chhints, (int) unbounded_channels, (int) reopen ); free(fhints); free(chhints); chunk = malloc(chunksz); got_chunk = malloc(chunksz); assert(chunk); assert(got_chunk); if( memory ) { err = qio_file_open_mem_ext(&f, NULL, QIO_FDFLAG_READABLE|QIO_FDFLAG_WRITEABLE|QIO_FDFLAG_SEEKABLE, file_hints, NULL); assert(!err); } else { // Open a temporary file. if( reopen ) { fd = mkstemp(filename); close(fd); err = qio_file_open_access(&f, filename, "w", file_hints, NULL); assert(!err); } else { err = qio_file_open_tmp(&f, file_hints, NULL); assert(!err); } // Rewind the file if (f->fp ) { int got; got = fseek(f->fp, start, SEEK_SET); assert( got == 0 ); } else { off_t off; err = sys_lseek(f->fd, start, SEEK_SET, &off); assert(!err); } } // Create a "write to file" channel. err = qio_channel_create(&writing, f, ch_hints, 0, 1, start, ch_end, NULL); assert(!err); // Write stuff to the file. for( offset = start; offset < end; offset += usesz ) { usesz = chunksz; if( offset + usesz > end ) usesz = end - offset; // Fill chunk. fill_testdata(offset, usesz, chunk); // Write chunk. if( writefp ) { amt_written = fwrite(chunk, 1, usesz, writefp); } else { err = qio_channel_write(threadsafe, writing, chunk, usesz, &amt_written); assert(!err); } assert(amt_written == usesz); } // Attempt to write 1 more byte; we should get EEOF // if we've restricted the range of the channel. // Write chunk. if( unbounded_channels > 0) { // do nothing } else { if( writefp ) { int got; got = fflush(writefp); assert(got == 0); amt_written = fwrite(chunk, 1, 1, writefp); // fwrite might buffer on its own. if( amt_written != 0 ) { got = fflush(writefp); assert(got == EOF); } assert(errno == EEOF); } else { if( unbounded_channels < 0 ) { int times = -unbounded_channels; for( int z = 0; z < times; z++ ) { err = qio_channel_advance(threadsafe, writing, 1); assert( !err ); } } err = qio_channel_write(threadsafe, writing, chunk, 1, &amt_written); assert(amt_written == 0); assert( err == EEOF ); } } qio_channel_release(writing); // Reopen the file if we're doing reopen if( reopen ) { // Close the file. qio_file_release(f); err = qio_file_open_access(&f, filename, "r", file_hints, NULL); assert(!err); } // Check that the file is the right length. if( !memory ) { struct stat stats; err = sys_fstat(f->fd, &stats); assert(!err); assert(stats.st_size == end); } // That was fun. Now start at the beginning of the file // and read the data. // Rewind the file if( !memory ) { off_t off; sys_lseek(f->fd, start, SEEK_SET, &off); assert(!err); } // Read the data. //err = qio_channel_init_file(&reading, type, f, ch_hints, 1, 0, start, end); err = qio_channel_create(&reading, f, ch_hints, 1, 0, start, ch_end, NULL); assert(!err); // Read stuff from the file. for( offset = start; offset < end; offset += usesz ) { usesz = chunksz; if( offset + usesz > end ) usesz = end - offset; // Fill chunk. fill_testdata(offset, usesz, chunk); memset(got_chunk, 0xff, usesz); // Read chunk. if( readfp ) { amt_read = fread(got_chunk, 1, usesz, readfp); } else { err = qio_channel_read(threadsafe, reading, got_chunk, usesz, &amt_read); assert( err == EEOF || err == 0); } assert(amt_read == usesz); // Compare chunk. for( k = 0; k < usesz; k++ ) { assert(got_chunk[k] == chunk[k]); } } if( readfp ) { amt_read = fread(got_chunk, 1, 1, readfp); assert( amt_read == 0 ); assert( feof(readfp) ); } else { if( unbounded_channels < 0 ) { int times = -unbounded_channels; for( int z = 0; z < times; z++ ) { err = qio_channel_advance(threadsafe, reading, 1); assert( !err ); } err = qio_channel_advance(threadsafe, reading, -unbounded_channels); assert( !err ); } err = qio_channel_read(threadsafe, reading, got_chunk, 1, &amt_read); assert( err == EEOF ); } qio_channel_release(reading); //err = qio_channel_destroy(&reading); // Close the file. qio_file_release(f); if( reopen ) { unlink(filename); } free(chunk); free(got_chunk); }
void check_mark(char* writepattern, char* readpattern) { qio_file_t* f; qio_channel_t* writing; qio_channel_t* reading; int64_t offset; int32_t got; err_t err; int i; err = qio_file_open_tmp(&f, 0, NULL); assert(!err); err = qio_channel_create(&writing, f, 0, 0, 1, 0, INT64_MAX, NULL); assert(!err); err = qio_channel_offset(true, writing, &offset); assert(!err); assert(offset == 0); for( i = 0; writepattern[i]; i++ ) { switch (writepattern[i]) { case 'M': err = qio_channel_mark(true, writing); break; case 'R': err = qio_channel_revert(true, writing); break; case 'C': err = qio_channel_commit(true, writing); break; case 'A': err = qio_channel_advance(true, writing, 1); break; default: err = qio_channel_write_byte(true, writing, writepattern[i]); } assert(!err); } qio_channel_release(writing); // Read the data a byte at a time. err = qio_channel_create(&reading, f, 0, 1, 0, 0, INT64_MAX, NULL); assert(!err); for( i = 0; readpattern[i]; i++ ) { switch (readpattern[i]) { case 'M': err = qio_channel_mark(true, reading); break; case 'R': err = qio_channel_revert(true, reading); break; case 'C': err = qio_channel_commit(true, reading); break; case 'A': err = qio_channel_advance(true, reading, 1); break; default: got = qio_channel_read_byte(true, reading); if( got >= 0 ) assert(got == readpattern[i]); else err = -got; } assert(!err); } got = qio_channel_read_byte(true, reading); assert( got == - EEOF ); qio_channel_release(reading); // Close the file. qio_file_release(f); }
// commit input, sending any data to the subprocess. // once input is sent, close input channel and file. // While sending that data, read output and error channels, // buffering up the read data. // once output/error reads EOF, close output/error file (not channel // since output channel has the buffered data). qioerr qio_proc_communicate( const int threadsafe, qio_channel_t* input, qio_channel_t* output, qio_channel_t* error) { qioerr err = 0; int rc = 0; bool do_input; bool do_output; bool do_error; bool input_ready; bool output_ready; bool error_ready; fd_set rfds, wfds, efds; int nfds = 1; int input_fd = -1; int output_fd = -1; int error_fd = -1; if( threadsafe ) { // lock all three channels. // but unlock them immediately and set them to NULL // if they are already closed. if( input ) { err = qio_lock(&input->lock); if( err ) return err; if( qio_channel_isclosed(false, input) ) { qio_unlock(&input->lock); input = NULL; } } if( output ) { err = qio_lock(&output->lock); if( err ) { if( input ) qio_unlock(&input->lock); return err; } if( qio_channel_isclosed(false, output) ) { qio_unlock(&output->lock); output = NULL; } } if( error ) { err = qio_lock(&error->lock); if( err ) { if( input ) qio_unlock(&input->lock); if( output ) qio_unlock(&output->lock); return err; } if( qio_channel_isclosed(false, error) ) { qio_unlock(&error->lock); error = NULL; } } } if( input ) { input_fd = input->file->fd; if( nfds <= input_fd ) nfds = input_fd + 1; } if( output ) { output_fd = output->file->fd; if( nfds <= output_fd ) nfds = output_fd + 1; } if( error ) { error_fd = error->file->fd; if( nfds <= error_fd ) nfds = error_fd + 1; } // Adjust all three pipes to be non-blocking. if( input_fd != -1 ) { rc = fcntl(input_fd, F_SETFL, O_NONBLOCK); if( rc == -1 ) { err = qio_int_to_err(errno); } } if( output_fd != -1 ) { rc = fcntl(output_fd, F_SETFL, O_NONBLOCK); if( rc == -1 ) { err = qio_int_to_err(errno); } } if( error_fd != -1 ) { rc = fcntl(error_fd, F_SETFL, O_NONBLOCK); if( rc == -1 ) { err = qio_int_to_err(errno); } } // mark the output and error channels so that we // can just keep advancing to read while buffering // up all data. Before returning, we'll revert them // so that this buffered data can be read again and // update end_pos so that the channel knows not to // call read again. Then, we can close the file // descriptor. if( output ) { qio_channel_mark(false, output); } if( error ) { qio_channel_mark(false, error); } do_input = (input != NULL); do_output = (output != NULL); do_error = (error != NULL); while( do_input || do_output || do_error ) { // Now call select to wait for one of the descriptors to // become ready. FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); if( do_input && input_fd != -1 ) { FD_SET(input_fd, &wfds); FD_SET(input_fd, &efds); } if( do_output && output_fd != -1 ) { FD_SET(output_fd, &rfds); FD_SET(output_fd, &efds); } if( do_error && error_fd != -1 ) { FD_SET(error_fd, &rfds); FD_SET(error_fd, &efds); } input_ready = false; output_ready = false; error_ready = false; // Run select to wait for something if( do_input || do_output || do_error ) { // TODO -- use sys_select so threading can interact struct timeval t; t.tv_sec = 0; t.tv_usec = 10; rc = select(nfds, &rfds, &wfds, &efds, &t); if (rc > 0) { // read ready file descriptors input_ready = input_fd != -1 && FD_ISSET(input_fd, &wfds); output_ready = output_fd != -1 && FD_ISSET(output_fd, &rfds); error_ready = error_fd != -1 && FD_ISSET(error_fd, &rfds); } // Ignore EAGAIN and EINTR if (rc == EAGAIN || rc == EINTR) rc = 0; } if( rc == -1 ) { err = qio_int_to_err(errno); break; } if( do_input && input_ready ) { err = _qio_channel_flush_qio_unlocked(input); if( !err ) { do_input = false; // Close input channel. err = qio_channel_close(false, input); } if( qio_err_to_int(err) == EAGAIN ) err = 0; if( err ) break; } if( do_output && output_ready ) { // read some into our buffer. err = qio_channel_advance(false, output, qbytes_iobuf_size); if( qio_err_to_int(err) == EEOF ) { qio_file_t* output_file = qio_channel_get_file(output); do_output = false; // close the output file (not channel), in case closing output // causes the program to output on stderr, e.g. if( output_file ) err = qio_file_close(output_file); // Set the output channel maximum position // This prevents a read on output from trying to get // more data from the (now closed) file. output->end_pos = qio_channel_offset_unlocked(output); } if( qio_err_to_int(err) == EAGAIN ) err = 0; if( err ) break; } if( do_error && error_ready ) { // read some into our buffer. err = qio_channel_advance(false, error, qbytes_iobuf_size); if( qio_err_to_int(err) == EEOF ) { qio_file_t* error_file = qio_channel_get_file(error); do_error = false; // close the error file (not channel) if( error_file ) err = qio_file_close(error_file); // Set the error channel maximum position error->end_pos = qio_channel_offset_unlocked(error); } if( qio_err_to_int(err) == EAGAIN ) err = 0; if( err ) break; } chpl_task_yield(); } // we could close the file descriptors at this point, // but we don't because we don't want to modify // the file descriptor of the file since it's // constant (and not protected by file's lock). // The pipes will be closed when the channels are destroyed. // We marked the output and error channels so that we // can just keep advancing to read while buffering // up all data. Before returning, we'll revert them // so that this buffered data can be read again and // update end_pos so that the channel knows not to // call read again. Then, we can close the file // descriptor. // revert the output and error channels if( output) qio_channel_revert_unlocked(output); if( error ) qio_channel_revert_unlocked(error); if( threadsafe ) { // unlock all three channels. if( error ) qio_unlock(&error->lock); if( output ) qio_unlock(&output->lock); if( input ) qio_unlock(&input->lock); } return err; }