| /* 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" |
| #include "apr_time.h" |
| #include "apr_file_info.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; |
| } |
| } |
| |
| static apr_status_t do_rotating_check(apr_file_t *thefile, apr_time_t now) |
| { |
| apr_size_t rv = APR_SUCCESS; |
| |
| if ((now - thefile->rotating->lastcheck) >= thefile->rotating->timeout) { |
| apr_finfo_t new_finfo; |
| apr_pool_t *tmp_pool; |
| |
| apr_pool_create(&tmp_pool, thefile->pool); |
| |
| rv = apr_stat(&new_finfo, thefile->fname, |
| APR_FINFO_DEV|APR_FINFO_INODE, tmp_pool); |
| |
| if (rv != APR_SUCCESS || |
| new_finfo.inode != thefile->rotating->finfo.inode || |
| new_finfo.device != thefile->rotating->finfo.device) { |
| |
| if (thefile->buffered) { |
| apr_file_flush(thefile); |
| } |
| |
| close(thefile->filedes); |
| thefile->filedes = -1; |
| |
| if (thefile->rotating->perm == APR_FPROT_OS_DEFAULT) { |
| thefile->filedes = open(thefile->fname, |
| thefile->rotating->oflags, |
| 0666); |
| } |
| else { |
| thefile->filedes = open(thefile->fname, |
| thefile->rotating->oflags, |
| apr_unix_perms2mode(thefile->rotating->perm)); |
| } |
| |
| if (thefile->filedes < 0) { |
| rv = errno; |
| } |
| else { |
| rv = apr_file_info_get(&thefile->rotating->finfo, |
| APR_FINFO_DEV|APR_FINFO_INODE, |
| thefile); |
| } |
| } |
| |
| apr_pool_destroy(tmp_pool); |
| thefile->rotating->lastcheck = now; |
| } |
| return rv; |
| } |
| |
| static apr_status_t file_rotating_check(apr_file_t *thefile) |
| { |
| if (thefile->rotating && thefile->rotating->manual == 0) { |
| apr_time_t now = apr_time_sec(apr_time_now()); |
| return do_rotating_check(thefile, now); |
| } |
| return APR_SUCCESS; |
| } |
| |
| APR_DECLARE(apr_status_t) apr_file_rotating_check(apr_file_t *thefile) |
| { |
| if (thefile->rotating) { |
| apr_time_t now = apr_time_sec(apr_time_now()); |
| return do_rotating_check(thefile, now); |
| } |
| return APR_SUCCESS; |
| } |
| |
| APR_DECLARE(apr_status_t) apr_file_rotating_manual_check(apr_file_t *thefile, apr_time_t n) |
| { |
| if (thefile->rotating) { |
| return do_rotating_check(thefile, n); |
| } |
| return APR_SUCCESS; |
| } |
| |
| APR_DECLARE(apr_status_t) apr_file_write(apr_file_t *thefile, const void *buf, apr_size_t *nbytes) |
| { |
| apr_size_t rv; |
| |
| rv = file_rotating_check(thefile); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| |
| 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) |
| lseek(thefile->filedes, offset, SEEK_SET); |
| thefile->bufpos = thefile->dataRead = 0; |
| thefile->direction = 1; |
| } |
| |
| rv = 0; |
| 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) |
| lseek(thefile->filedes, offset, SEEK_SET); |
| thefile->bufpos = thefile->dataRead = 0; |
| } |
| |
| file_unlock(thefile); |
| } |
| |
| rv = file_rotating_check(thefile); |
| if (rv != APR_SUCCESS) { |
| 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; |
| |
| file_lock(thefile); |
| |
| if (thefile->buffered) { |
| rv = apr_file_flush_locked(thefile); |
| |
| if (rv != APR_SUCCESS) { |
| file_unlock(thefile); |
| return rv; |
| } |
| } |
| |
| #ifdef HAVE_FDATASYNC |
| if (fdatasync(thefile->filedes)) { |
| #else |
| if (fsync(thefile->filedes)) { |
| #endif |
| 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; |
| } |
| |
| |
| |
| APR_DECLARE(apr_status_t) apr_file_pipe_wait(apr_file_t *thepipe, apr_wait_type_t direction) |
| { |
| return apr_wait_for_io_or_timeout(thepipe, NULL, direction == APR_WAIT_READ); |
| } |