diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:23:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:23:09 +0000 |
commit | 30d479c28c831a0d4f1fdb54a9e346b0fc176be1 (patch) | |
tree | aa35d7414ce9f1326abf6f723f6dfa5b0aa08b1d /file_io/unix/readwrite.c | |
parent | Initial commit. (diff) | |
download | apr-upstream/1.7.2.tar.xz apr-upstream/1.7.2.zip |
Adding upstream version 1.7.2.upstream/1.7.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'file_io/unix/readwrite.c')
-rw-r--r-- | file_io/unix/readwrite.c | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/file_io/unix/readwrite.c b/file_io/unix/readwrite.c new file mode 100644 index 0000000..866acb8 --- /dev/null +++ b/file_io/unix/readwrite.c @@ -0,0 +1,538 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr_arch_file_io.h" +#include "apr_strings.h" +#include "apr_thread_mutex.h" +#include "apr_support.h" + +/* The only case where we don't use wait_for_io_or_timeout is on + * pre-BONE BeOS, so this check should be sufficient and simpler */ +#if !defined(BEOS_R5) +#define USE_WAIT_FOR_IO +#endif + +static apr_status_t file_read_buffered(apr_file_t *thefile, void *buf, + apr_size_t *nbytes) +{ + apr_ssize_t rv; + char *pos = (char *)buf; + apr_uint64_t blocksize; + apr_uint64_t size = *nbytes; + + if (thefile->direction == 1) { + rv = apr_file_flush_locked(thefile); + if (rv) { + return rv; + } + thefile->bufpos = 0; + thefile->direction = 0; + thefile->dataRead = 0; + } + + rv = 0; + if (thefile->ungetchar != -1) { + *pos = (char)thefile->ungetchar; + ++pos; + --size; + thefile->ungetchar = -1; + } + while (rv == 0 && size > 0) { + if (thefile->bufpos >= thefile->dataRead) { + int bytesread = read(thefile->filedes, thefile->buffer, + thefile->bufsize); + if (bytesread == 0) { + thefile->eof_hit = TRUE; + rv = APR_EOF; + break; + } + else if (bytesread == -1) { + rv = errno; + break; + } + thefile->dataRead = bytesread; + thefile->filePtr += thefile->dataRead; + thefile->bufpos = 0; + } + + blocksize = size > thefile->dataRead - thefile->bufpos ? thefile->dataRead - thefile->bufpos : size; + memcpy(pos, thefile->buffer + thefile->bufpos, blocksize); + thefile->bufpos += blocksize; + pos += blocksize; + size -= blocksize; + } + + *nbytes = pos - (char *)buf; + if (*nbytes) { + rv = 0; + } + return rv; +} + +APR_DECLARE(apr_status_t) apr_file_read(apr_file_t *thefile, void *buf, apr_size_t *nbytes) +{ + apr_ssize_t rv; + apr_size_t bytes_read; + + if (*nbytes <= 0) { + *nbytes = 0; + return APR_SUCCESS; + } + + if (thefile->buffered) { + file_lock(thefile); + rv = file_read_buffered(thefile, buf, nbytes); + file_unlock(thefile); + return rv; + } + else { + bytes_read = 0; + if (thefile->ungetchar != -1) { + bytes_read = 1; + *(char *)buf = (char)thefile->ungetchar; + buf = (char *)buf + 1; + (*nbytes)--; + thefile->ungetchar = -1; + if (*nbytes == 0) { + *nbytes = bytes_read; + return APR_SUCCESS; + } + } + + do { + rv = read(thefile->filedes, buf, *nbytes); + } while (rv == -1 && errno == EINTR); +#ifdef USE_WAIT_FOR_IO + if (rv == -1 && + (errno == EAGAIN || errno == EWOULDBLOCK) && + thefile->timeout != 0) { + apr_status_t arv = apr_wait_for_io_or_timeout(thefile, NULL, 1); + if (arv != APR_SUCCESS) { + *nbytes = bytes_read; + return arv; + } + else { + do { + rv = read(thefile->filedes, buf, *nbytes); + } while (rv == -1 && errno == EINTR); + } + } +#endif + *nbytes = bytes_read; + if (rv == 0) { + thefile->eof_hit = TRUE; + return APR_EOF; + } + if (rv > 0) { + *nbytes += rv; + return APR_SUCCESS; + } + return errno; + } +} + +APR_DECLARE(apr_status_t) apr_file_write(apr_file_t *thefile, const void *buf, apr_size_t *nbytes) +{ + apr_size_t rv = APR_SUCCESS; + + if (thefile->buffered) { + char *pos = (char *)buf; + int blocksize; + int size = *nbytes; + + file_lock(thefile); + + if ( thefile->direction == 0 ) { + /* Position file pointer for writing at the offset we are + * logically reading from + */ + apr_int64_t offset = thefile->filePtr - thefile->dataRead + thefile->bufpos; + if (offset != thefile->filePtr) { + thefile->filePtr = lseek(thefile->filedes, offset, SEEK_SET); + if (thefile->filePtr == -1) rv = errno; + } + thefile->bufpos = thefile->dataRead = 0; + thefile->direction = 1; + } + + while (rv == 0 && size > 0) { + if (thefile->bufpos == thefile->bufsize) /* write buffer is full*/ + rv = apr_file_flush_locked(thefile); + + blocksize = size > thefile->bufsize - thefile->bufpos ? + thefile->bufsize - thefile->bufpos : size; + memcpy(thefile->buffer + thefile->bufpos, pos, blocksize); + thefile->bufpos += blocksize; + pos += blocksize; + size -= blocksize; + } + + file_unlock(thefile); + + return rv; + } + else { + do { + rv = write(thefile->filedes, buf, *nbytes); + } while (rv == (apr_size_t)-1 && errno == EINTR); +#ifdef USE_WAIT_FOR_IO + if (rv == (apr_size_t)-1 && + (errno == EAGAIN || errno == EWOULDBLOCK) && + thefile->timeout != 0) { + apr_status_t arv = apr_wait_for_io_or_timeout(thefile, NULL, 0); + if (arv != APR_SUCCESS) { + *nbytes = 0; + return arv; + } + else { + do { + do { + rv = write(thefile->filedes, buf, *nbytes); + } while (rv == (apr_size_t)-1 && errno == EINTR); + if (rv == (apr_size_t)-1 && + (errno == EAGAIN || errno == EWOULDBLOCK)) { + *nbytes /= 2; /* yes, we'll loop if kernel lied + * and we can't even write 1 byte + */ + } + else { + break; + } + } while (1); + } + } +#endif + if (rv == (apr_size_t)-1) { + (*nbytes) = 0; + return errno; + } + *nbytes = rv; + return APR_SUCCESS; + } +} + +APR_DECLARE(apr_status_t) apr_file_writev(apr_file_t *thefile, const struct iovec *vec, + apr_size_t nvec, apr_size_t *nbytes) +{ +#ifdef HAVE_WRITEV + apr_status_t rv; + apr_ssize_t bytes; + + if (thefile->buffered) { + file_lock(thefile); + + rv = apr_file_flush_locked(thefile); + if (rv != APR_SUCCESS) { + file_unlock(thefile); + return rv; + } + if (thefile->direction == 0) { + /* Position file pointer for writing at the offset we are + * logically reading from + */ + apr_int64_t offset = thefile->filePtr - thefile->dataRead + + thefile->bufpos; + if (offset != thefile->filePtr) { + thefile->filePtr = lseek(thefile->filedes, offset, SEEK_SET); + if (thefile->filePtr == -1) rv = errno; + } + thefile->bufpos = thefile->dataRead = 0; + } + + file_unlock(thefile); + if (rv) return rv; + } + + if ((bytes = writev(thefile->filedes, vec, nvec)) < 0) { + *nbytes = 0; + rv = errno; + } + else { + *nbytes = bytes; + rv = APR_SUCCESS; + } + return rv; +#else + /** + * The problem with trying to output the entire iovec is that we cannot + * maintain the behaviour that a real writev would have. If we iterate + * over the iovec one at a time, we lose the atomic properties of + * writev(). The other option is to combine the entire iovec into one + * buffer that we could then send in one call to write(). This is not + * reasonable since we do not know how much data an iovec could contain. + * + * The only reasonable option, that maintains the semantics of a real + * writev(), is to only write the first iovec. Callers of file_writev() + * must deal with partial writes as they normally would. If you want to + * ensure an entire iovec is written, use apr_file_writev_full(). + */ + + *nbytes = vec[0].iov_len; + return apr_file_write(thefile, vec[0].iov_base, nbytes); +#endif +} + +APR_DECLARE(apr_status_t) apr_file_putc(char ch, apr_file_t *thefile) +{ + apr_size_t nbytes = 1; + + return apr_file_write(thefile, &ch, &nbytes); +} + +APR_DECLARE(apr_status_t) apr_file_ungetc(char ch, apr_file_t *thefile) +{ + thefile->ungetchar = (unsigned char)ch; + return APR_SUCCESS; +} + +APR_DECLARE(apr_status_t) apr_file_getc(char *ch, apr_file_t *thefile) +{ + apr_size_t nbytes = 1; + + return apr_file_read(thefile, ch, &nbytes); +} + +APR_DECLARE(apr_status_t) apr_file_puts(const char *str, apr_file_t *thefile) +{ + return apr_file_write_full(thefile, str, strlen(str), NULL); +} + +apr_status_t apr_file_flush_locked(apr_file_t *thefile) +{ + apr_status_t rv = APR_SUCCESS; + + if (thefile->direction == 1 && thefile->bufpos) { + apr_ssize_t written = 0, ret; + + do { + ret = write(thefile->filedes, thefile->buffer + written, + thefile->bufpos - written); + if (ret > 0) + written += ret; + } while (written < thefile->bufpos && + (ret > 0 || (ret == -1 && errno == EINTR))); + if (ret == -1) { + rv = errno; + } else { + thefile->filePtr += written; + thefile->bufpos = 0; + } + } + + return rv; +} + +APR_DECLARE(apr_status_t) apr_file_flush(apr_file_t *thefile) +{ + apr_status_t rv = APR_SUCCESS; + + if (thefile->buffered) { + file_lock(thefile); + rv = apr_file_flush_locked(thefile); + file_unlock(thefile); + } + /* There isn't anything to do if we aren't buffering the output + * so just return success. + */ + return rv; +} + +APR_DECLARE(apr_status_t) apr_file_sync(apr_file_t *thefile) +{ + apr_status_t rv = APR_SUCCESS; + + file_lock(thefile); + + if (thefile->buffered) { + rv = apr_file_flush_locked(thefile); + + if (rv != APR_SUCCESS) { + file_unlock(thefile); + return rv; + } + } + + if (fsync(thefile->filedes)) { + rv = apr_get_os_error(); + } + + file_unlock(thefile); + + return rv; +} + +APR_DECLARE(apr_status_t) apr_file_datasync(apr_file_t *thefile) +{ + apr_status_t rv = APR_SUCCESS; + int os_status = 0; + + file_lock(thefile); + + if (thefile->buffered) { + rv = apr_file_flush_locked(thefile); + + if (rv != APR_SUCCESS) { + file_unlock(thefile); + return rv; + } + } + +#ifdef HAVE_FDATASYNC + os_status = fdatasync(thefile->filedes); +#elif defined(F_FULLFSYNC) + os_status = fcntl(thefile->filedes, F_FULLFSYNC); + if (os_status) { + /* Fall back to fsync() if the device doesn't support F_FULLFSYNC. */ + os_status = fsync(thefile->filedes); + } +#else + os_status = fsync(thefile->filedes); +#endif + if (os_status) { + rv = apr_get_os_error(); + } + + file_unlock(thefile); + + return rv; +} + +APR_DECLARE(apr_status_t) apr_file_gets(char *str, int len, apr_file_t *thefile) +{ + apr_status_t rv = APR_SUCCESS; /* get rid of gcc warning */ + apr_size_t nbytes; + const char *str_start = str; + char *final = str + len - 1; + + if (len <= 1) { + /* sort of like fgets(), which returns NULL and stores no bytes + */ + return APR_SUCCESS; + } + + /* If we have an underlying buffer, we can be *much* more efficient + * and skip over the apr_file_read calls. + */ + if (thefile->buffered) { + file_lock(thefile); + + if (thefile->direction == 1) { + rv = apr_file_flush_locked(thefile); + if (rv) { + file_unlock(thefile); + return rv; + } + + thefile->direction = 0; + thefile->bufpos = 0; + thefile->dataRead = 0; + } + + while (str < final) { /* leave room for trailing '\0' */ + /* Force ungetc leftover to call apr_file_read. */ + if (thefile->bufpos < thefile->dataRead && + thefile->ungetchar == -1) { + *str = thefile->buffer[thefile->bufpos++]; + } + else { + nbytes = 1; + rv = file_read_buffered(thefile, str, &nbytes); + if (rv != APR_SUCCESS) { + break; + } + } + if (*str == '\n') { + ++str; + break; + } + ++str; + } + file_unlock(thefile); + } + else { + while (str < final) { /* leave room for trailing '\0' */ + nbytes = 1; + rv = apr_file_read(thefile, str, &nbytes); + if (rv != APR_SUCCESS) { + break; + } + if (*str == '\n') { + ++str; + break; + } + ++str; + } + } + + /* We must store a terminating '\0' if we've stored any chars. We can + * get away with storing it if we hit an error first. + */ + *str = '\0'; + if (str > str_start) { + /* we stored chars; don't report EOF or any other errors; + * the app will find out about that on the next call + */ + return APR_SUCCESS; + } + return rv; +} + +struct apr_file_printf_data { + apr_vformatter_buff_t vbuff; + apr_file_t *fptr; + char *buf; +}; + +static int file_printf_flush(apr_vformatter_buff_t *buff) +{ + struct apr_file_printf_data *data = (struct apr_file_printf_data *)buff; + + if (apr_file_write_full(data->fptr, data->buf, + data->vbuff.curpos - data->buf, NULL)) { + return -1; + } + + data->vbuff.curpos = data->buf; + return 0; +} + +APR_DECLARE_NONSTD(int) apr_file_printf(apr_file_t *fptr, + const char *format, ...) +{ + struct apr_file_printf_data data; + va_list ap; + int count; + + /* don't really need a HUGE_STRING_LEN anymore */ + data.buf = malloc(HUGE_STRING_LEN); + if (data.buf == NULL) { + return -1; + } + data.vbuff.curpos = data.buf; + data.vbuff.endpos = data.buf + HUGE_STRING_LEN; + data.fptr = fptr; + va_start(ap, format); + count = apr_vformatter(file_printf_flush, + (apr_vformatter_buff_t *)&data, format, ap); + /* apr_vformatter does not call flush for the last bits */ + if (count >= 0) file_printf_flush((apr_vformatter_buff_t *)&data); + + va_end(ap); + + free(data.buf); + + return count; +} |