nuttx/libs/libc/stdio/lib_libfread_unlocked.c
Michal Lenc 0a107ca6d9 libc: add support for custom streams with fopencookie()
This commit adds support for custom stream via fopencookie function.
The function allows the programmer the create his own custom stream
for IO operations and hook his custom functions to it.

This is a non POSIX interface defined in Standard C library and implemented
according to it. The only difference is in usage of off_t instead of
off64_t. Programmer can use 64 bits offset if CONFIG_FS_LARGEFILE is
enabled. In that case off_t is defined as int64_t (int32_t otherwise).

Field fs_fd is removed from file_struct and fs_cookie is used instead
as a shared variable for file descriptor or user defined cookie.

The interface will be useful for future fmemopen implementation.

Signed-off-by: Michal Lenc <michallenc@seznam.cz>
2023-10-18 21:13:01 +08:00

359 lines
12 KiB
C

/****************************************************************************
* libs/libc/stdio/lib_libfread_unlocked.c
*
* 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/fs/fs.h>
#include "libc.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: lib_fread_unlocked
****************************************************************************/
ssize_t lib_fread_unlocked(FAR void *ptr, size_t count, FAR FILE *stream)
{
FAR char *dest = ptr;
ssize_t bytes_read;
size_t remaining = count;
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
size_t gulp_size;
int ret;
#endif
int fd;
/* Make sure that reading from this stream is allowed */
if (!stream)
{
_NX_SETERRNO(EBADF);
return ERROR;
}
else if ((stream->fs_oflags & O_RDOK) == 0)
{
stream->fs_flags |= __FS_FLAG_ERROR;
_NX_SETERRNO(EBADF);
return ERROR;
}
else
{
fd = (int)(intptr_t)stream->fs_cookie;
#if CONFIG_NUNGET_CHARS > 0
/* First, re-read any previously ungotten characters */
while (stream->fs_nungotten > 0 && remaining > 0)
{
/* Decrement the count of ungotten bytes to get an index */
stream->fs_nungotten--;
/* Return the last ungotten byte */
*dest++ = stream->fs_ungotten[stream->fs_nungotten];
/* That's one less byte that we have to read */
remaining--;
}
#endif
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
/* Is there an I/O buffer? */
if (stream->fs_bufstart != NULL)
{
/* If the buffer is currently being used for write access, then
* flush all of the buffered write data. We do not support
* concurrent buffered read/write access.
*/
ret = lib_wrflush_unlocked(stream);
if (ret < 0)
{
if (count - remaining > 0)
{
goto shortread;
}
else
{
_NX_SETERRNO(ret);
goto errout_with_errno;
}
}
/* Now get any other needed chars from the buffer or the file. */
while (remaining > 0)
{
/* Is there readable data in the buffer? */
gulp_size = stream->fs_bufread - stream->fs_bufpos;
/* Avoid empty buffers or read requests greater than the size
* buffer remaining
*/
if (gulp_size > 0)
{
if (gulp_size > remaining)
{
/* Clip the gulp size to the requested byte count */
gulp_size = remaining;
}
memcpy(dest, stream->fs_bufpos, gulp_size);
remaining -= gulp_size;
stream->fs_bufpos += gulp_size;
dest += gulp_size;
}
/* The buffer is empty OR we have already supplied the number
* of bytes requested in the read. Check if we need to read
* more from the file.
*/
if (remaining > 0)
{
size_t buffer_available;
/* We need to read more data into the buffer from the
* file
*/
/* Mark the buffer empty */
stream->fs_bufpos = stream->fs_bufread =
stream->fs_bufstart;
/* How much space is available in the buffer? */
buffer_available = stream->fs_bufend - stream->fs_bufread;
/* Will the number of bytes that we need to read fit into
* the buffer space that is available? If the read size is
* larger than the buffer, then read some of the data
* directly into the user's buffer.
*/
if (remaining > buffer_available)
{
if (stream->fs_iofunc.read != NULL)
{
bytes_read = stream->fs_iofunc.read(
stream->fs_cookie,
dest, remaining);
}
else
{
bytes_read = _NX_READ(fd, dest, remaining);
}
if (bytes_read < 0)
{
if (count - remaining > 0)
{
goto shortread;
}
else
{
/* An error occurred on the read. */
_NX_SETERRNO(bytes_read);
goto errout_with_errno;
}
}
else if (bytes_read == 0)
{
/* We are at the end of the file. But we may
* already have buffered data. In that case,
* we will report the EOF indication later.
*/
goto shortread;
}
else
{
/* Some (perhaps all) bytes were read. Adjust the
* dest pointer and remaining bytes to be read.
*/
DEBUGASSERT(bytes_read <= remaining);
dest += bytes_read;
remaining -= bytes_read;
}
}
else
{
/* The number of bytes required to satisfy the read
* is less than or equal to the size of the buffer
* space that we have left. Read as much as we can
* into the buffer.
*/
if (stream->fs_iofunc.read != NULL)
{
bytes_read = stream->fs_iofunc.read(
stream->fs_cookie,
(FAR char *)stream->fs_bufread,
buffer_available);
}
else
{
bytes_read = _NX_READ(fd, stream->fs_bufread,
buffer_available);
}
if (bytes_read < 0)
{
if (count - remaining > 0)
{
goto shortread;
}
else
{
/* An error occurred on the read. The error
* code is in the 'errno' variable.
*/
_NX_SETERRNO(bytes_read);
goto errout_with_errno;
}
}
else if (bytes_read == 0)
{
/* We are at the end of the file. But we may
* already have buffered data. In that case,
* we will report the EOF indication later.
*/
goto shortread;
}
else
{
/* Some (perhaps all) bytes were read */
stream->fs_bufread += bytes_read;
}
}
}
}
}
else
#endif
{
/* Now get any other needed chars from the file. */
while (remaining > 0)
{
if (stream->fs_iofunc.read != NULL)
{
bytes_read = stream->fs_iofunc.read(stream->fs_cookie,
dest,
remaining);
}
else
{
bytes_read = _NX_READ(fd, dest, remaining);
}
if (bytes_read < 0)
{
if (count - remaining > 0)
{
break;
}
else
{
/* An error occurred on the read. The error code is
* in the 'errno' variable.
*/
_NX_SETERRNO(bytes_read);
goto errout_with_errno;
}
}
else if (bytes_read == 0)
{
/* We are at the end of the file. But we may already
* have buffered data. In that case, we will report
* the EOF indication later.
*/
break;
}
else
{
DEBUGASSERT(bytes_read <= remaining);
dest += bytes_read;
remaining -= bytes_read;
}
}
}
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
/* Here after a successful (but perhaps short) read. A short read can
* only occur is read() returns 0 (end-of-file).
*/
shortread:
#endif
/* Set or clear the EOF indicator. If we get here because of a
* short read and the total number of bytes read is zero, then
* we must be at the end-of-file.
*/
if (remaining == 0)
{
stream->fs_flags &= ~__FS_FLAG_EOF;
}
else
{
stream->fs_flags |= __FS_FLAG_EOF;
}
return count - remaining;
}
/* Error exits */
errout_with_errno:
stream->fs_flags |= __FS_FLAG_ERROR;
return ERROR;
}