PHPLinux与后台开发归档

[@TODO PHP-SRC 5.3]fgets和fread函数

2019-07-16  本文已影响1人  _小老虎_

关键函数: _php_stream_read
关键源代码线索:
./ext/standard/file.c
./main/php_streams.h
./main/streams/streams.c

先看个结构体:

struct _php_stream  {
    php_stream_ops *ops;
    void *abstract;         /* convenience pointer for abstraction */

    php_stream_filter_chain readfilters, writefilters;

    php_stream_wrapper *wrapper; /* which wrapper was used to open the stream */
    void *wrapperthis;      /* convenience pointer for a instance of a wrapper */
    zval *wrapperdata;      /* fgetwrapperdata retrieves this */

    int fgetss_state;       /* for fgetss to handle multiline tags */
    int is_persistent;
    char mode[16];          /* "rwb" etc. ala stdio */
    int rsrc_id;            /* used for auto-cleanup */
    int in_free;            /* to prevent recursion during free */
    /* so we know how to clean it up correctly.  This should be set to
     * PHP_STREAM_FCLOSE_XXX as appropriate */
    int fclose_stdiocast;
    FILE *stdiocast;    /* cache this, otherwise we might leak! */
#if ZEND_DEBUG
    int __exposed;  /* non-zero if exposed as a zval somewhere */
#endif
    char *orig_path;

    php_stream_context *context;
    int flags;  /* PHP_STREAM_FLAG_XXX */

    /* buffer */
    off_t position; /* of underlying stream */
    unsigned char *readbuf;
    size_t readbuflen;
    off_t readpos;
    off_t writepos;

    /* how much data to read when filling buffer */
    size_t chunk_size;

    int eof;  // ps kimi: 这里的eof是 end of file  

#if ZEND_DEBUG
    char *open_filename;
    uint open_lineno;
#endif
}; /* php_stream */

关键源代码:

./ext/standard/file.c:
/* {{{ proto string fread(resource fp, int length)
   Binary-safe file read */
PHPAPI PHP_FUNCTION(fread)
{
    zval *arg1;
    long len;
    php_stream *stream;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &arg1, &len) == FAILURE) {
        RETURN_FALSE;
    }

    PHP_STREAM_TO_ZVAL(stream, &arg1);

    if (len <= 0) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length parameter must be greater than 0");
        RETURN_FALSE;
    }

    Z_STRVAL_P(return_value) = emalloc(len + 1);
    Z_STRLEN_P(return_value) = php_stream_read(stream, Z_STRVAL_P(return_value), len);

    /* needed because recv/read/gzread doesnt put a null at the end*/
    Z_STRVAL_P(return_value)[Z_STRLEN_P(return_value)] = 0;

    if (PG(magic_quotes_runtime)) {
        Z_STRVAL_P(return_value) = php_addslashes(Z_STRVAL_P(return_value),
                Z_STRLEN_P(return_value), &Z_STRLEN_P(return_value), 1 TSRMLS_CC);
    }
    Z_TYPE_P(return_value) = IS_STRING;
}
/* }}} */
./main/streams/streams.c:
PHPAPI size_t _php_stream_read(php_stream *stream, char *buf, size_t size TSRMLS_DC)
{
    size_t toread = 0, didread = 0;

    while (size > 0) {

        /* take from the read buffer first.
         * It is possible that a buffered stream was switched to non-buffered, so we
         * drain the remainder of the buffer before using the "raw" read mode for
         * the excess */
        if (stream->writepos > stream->readpos) {

            toread = stream->writepos - stream->readpos;
            if (toread > size) {
                toread = size;
            }

            memcpy(buf, stream->readbuf + stream->readpos, toread);
            stream->readpos += toread;
            size -= toread;
            buf += toread;
            didread += toread;
        }

        /* ignore eof here; the underlying state might have changed */
        if (size == 0) {
            break;
        }

        if (!stream->readfilters.head && (stream->flags & PHP_STREAM_FLAG_NO_BUFFER || stream->chunk_size == 1)) {
            toread = stream->ops->read(stream, buf, size TSRMLS_CC);
        } else {
            php_stream_fill_read_buffer(stream, size TSRMLS_CC);

            toread = stream->writepos - stream->readpos;
            if (toread > size) {
                toread = size;
            }

            if (toread > 0) {
                memcpy(buf, stream->readbuf + stream->readpos, toread);
                stream->readpos += toread;
            }
        }
        if (toread > 0) {
            didread += toread;
            buf += toread;
            size -= toread;
        } else {
            /* EOF, or temporary end of data (for non-blocking mode). */
            break;
        }

        /* just break anyway, to avoid greedy read */
        if (stream->wrapper != &php_plain_files_wrapper) {
            break;
        }
    }

    if (didread > 0) {
        stream->position += didread;
    }

    return didread;
}


关键函数: _php_stream_get_line
关键源代码线索:
./ext/standard/file.c
./main/php_streams.h
./main/streams/streams.c

关键源代码:

./ext/standard/file.c:
   Get a line from file pointer */
PHPAPI PHP_FUNCTION(fgets)
{
    zval *arg1;
    long len = 1024;
    char *buf = NULL;
    int argc = ZEND_NUM_ARGS();
    size_t line_len = 0;
    php_stream *stream;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l", &arg1, &len) == FAILURE) {
        RETURN_FALSE;
    }

    PHP_STREAM_TO_ZVAL(stream, &arg1);

    if (argc == 1) {
        /* ask streams to give us a buffer of an appropriate size */
        buf = php_stream_get_line(stream, NULL, 0, &line_len);
        if (buf == NULL) {
            goto exit_failed;
        }
    } else if (argc > 1) {
        if (len <= 0) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length parameter must be greater than 0");
            RETURN_FALSE;
        }

        buf = ecalloc(len + 1, sizeof(char));
        if (php_stream_get_line(stream, buf, len, &line_len) == NULL) {
            goto exit_failed;
        }
    }

    if (PG(magic_quotes_runtime)) {
        Z_STRVAL_P(return_value) = php_addslashes(buf, line_len, &Z_STRLEN_P(return_value), 1 TSRMLS_CC);
        Z_TYPE_P(return_value) = IS_STRING;
    } else {
        ZVAL_STRINGL(return_value, buf, line_len, 0);
        /* resize buffer if it's much larger than the result.
         * Only needed if the user requested a buffer size. */
        if (argc > 1 && Z_STRLEN_P(return_value) < len / 2) {
            Z_STRVAL_P(return_value) = erealloc(buf, line_len + 1);
        }
    }
    return;

exit_failed:
    RETVAL_FALSE;
    if (buf) {
        efree(buf);
    }
}
/* }}} */

和一个函数:

PHPAPI char *php_stream_locate_eol(php_stream *stream, char *buf, size_t buf_len TSRMLS_DC)
{
    size_t avail;
    char *cr, *lf, *eol = NULL; // ps kimi: 这里eol是 end of line
    char *readptr;

    if (!buf) {
        readptr = stream->readbuf + stream->readpos;
        avail = stream->writepos - stream->readpos;
    } else {
        readptr = buf;
        avail = buf_len;
    }

    /* Look for EOL */
    if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
        cr = memchr(readptr, '\r', avail);
        lf = memchr(readptr, '\n', avail);

        if (cr && lf != cr + 1 && !(lf && lf < cr)) {
            /* mac */
            stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
            stream->flags |= PHP_STREAM_FLAG_EOL_MAC;
            eol = cr;
        } else if ((cr && lf && cr == lf - 1) || (lf)) {
            /* dos or unix endings */
            stream->flags ^= PHP_STREAM_FLAG_DETECT_EOL;
            eol = lf;
        }
    } else if (stream->flags & PHP_STREAM_FLAG_EOL_MAC) {
        eol = memchr(readptr, '\r', avail);
    } else {
        /* unix (and dos) line endings */
        eol = memchr(readptr, '\n', avail);
    }

    return eol;
}

./main/streams/streams.c:
/* If buf == NULL, the buffer will be allocated automatically and will be of an
 * appropriate length to hold the line, regardless of the line length, memory
 * permitting */
PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
        size_t *returned_len TSRMLS_DC)
{
    size_t avail = 0;
    size_t current_buf_size = 0;
    size_t total_copied = 0;
    int grow_mode = 0;
    char *bufstart = buf;

    if (buf == NULL) {
        grow_mode = 1;
    } else if (maxlen == 0) {
        return NULL;
    }

    /*
     * If the underlying stream operations block when no new data is readable,
     * we need to take extra precautions.
     *
     * If there is buffered data available, we check for a EOL. If it exists,
     * we pass the data immediately back to the caller. This saves a call
     * to the read implementation and will not block where blocking
     * is not necessary at all.
     *
     * If the stream buffer contains more data than the caller requested,
     * we can also avoid that costly step and simply return that data.
     */

    for (;;) {
        avail = stream->writepos - stream->readpos;

        if (avail > 0) {
            size_t cpysz = 0;
            char *readptr;
            char *eol;
            int done = 0;

            readptr = stream->readbuf + stream->readpos;
            eol = php_stream_locate_eol(stream, NULL, 0 TSRMLS_CC);

            if (eol) {
                cpysz = eol - readptr + 1;
                done = 1;
            } else {
                cpysz = avail;
            }

            if (grow_mode) {
                /* allow room for a NUL. If this realloc is really a realloc
                 * (ie: second time around), we get an extra byte. In most
                 * cases, with the default chunk size of 8K, we will only
                 * incur that overhead once.  When people have lines longer
                 * than 8K, we waste 1 byte per additional 8K or so.
                 * That seems acceptable to me, to avoid making this code
                 * hard to follow */
                bufstart = erealloc(bufstart, current_buf_size + cpysz + 1);
                current_buf_size += cpysz + 1;
                buf = bufstart + total_copied;
            } else {
                if (cpysz >= maxlen - 1) {
                    cpysz = maxlen - 1;
                    done = 1;
                }
            }

            memcpy(buf, readptr, cpysz);

            stream->position += cpysz;
            stream->readpos += cpysz;
            buf += cpysz;
            maxlen -= cpysz;
            total_copied += cpysz;

            if (done) {
                break;
            }
        } else if (stream->eof) {
            break;
        } else {
            /* XXX: Should be fine to always read chunk_size */
            size_t toread;

            if (grow_mode) {
                toread = stream->chunk_size;
            } else {
                toread = maxlen - 1;
                if (toread > stream->chunk_size) {
                    toread = stream->chunk_size;
                }
            }

            php_stream_fill_read_buffer(stream, toread TSRMLS_CC);

            if (stream->writepos - stream->readpos == 0) {
                break;
            }
        }
    }

    if (total_copied == 0) {
        if (grow_mode) {
            assert(bufstart == NULL);
        }
        return NULL;
    }

    buf[0] = '\0';
    if (returned_len) {
        *returned_len = total_copied;
    }

    return bufstart;
}
上一篇 下一篇

猜你喜欢

热点阅读