Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libc: add support for custom streams with fopencookie() #10602

Merged
merged 2 commits into from
Oct 18, 2023

Conversation

michallenc
Copy link
Contributor

Summary

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).

The interface will be useful for future fmemopen implementation.

Impact

New interface.

Testing

Tested with example application on man page on SAMv7 MCU.

libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/stdio.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
@gregory-nutt
Copy link
Contributor

I personally think you are going down the wrong path with this ad hoc logic. This is very high risk because I don't think either of you are considering all of the issues and complexities. You apparently seem focused on avoiding a clean context switch (which I believe is the correct, clean, flexible solution).

I believe we need to at least insist on a good test for this code to give people like me confidence. A good test would include:

  • All build modes: FLAT, PROTECTED, and KERNEL (we would need some testing help).
  • Assure that user code never executes in kernel mode in the PROTECTED build
  • Test with client and server in same task/process; Client and server in different tasks/processes and different address spaces (in KERNEL mde).

If it could pass a pretty rigorous test then I would become a believer. That, I think is a requirement for merging. I am not a believer now.

@mu578
Copy link

mu578 commented Sep 14, 2023

I once had a look at that to handle a fmemopen and alike properly, it would require that LIBC struct FILE not to be a direct alias to internal FS struct, as indeed you need to keep a reference to fs operation callbacks and permission flags, so it would require an indirection where struct FILE holds a private pointer to internal. Meanwhile, I think LIBC stdio would benefit at term that indirection to be made.

struct libc_stdio_file
{
  // ... stdio specifics;
  libc_stdio_fread_t  read;
  libc_stdio_fwrite_t write;
  libc_stdio_fseek_t  seek;
  libc_stdio_fclose_t close;
  libc_stdio_cookie_t cookie;

  file_struct *       internal;
}

typedef libc_stdio_file FILE;

So open_memstream and fmemopen (which are standard), could be implemented using a fopencookie backend mechanism(1) or flattened to FILE struct.

  • 1 I guess that is the main reason fopencookie has been implemented in the first place.

If few folks are interested in architecting a new nuttx/io_vfs.h and then a stdio_v2.h, I am in, I don't think you should and can just hot patch current ; the implications with stream system calls are too wild to be taken lightly and not deeply thought prior.

Other option: we dig up actual nuttx/fs.h as is and provide private hooks to deal with a user given memory which will allow open_memstream and fmemopen LIBC implementations in user-space mode only and then give up on exposing any fopencookie like frontend.

Note: nuttx/fs.h being already an in-memory design.

@michallenc
Copy link
Contributor Author

You apparently seem focused on avoiding a clean context switch (which I believe is the correct, clean, flexible solution).

@patacongo My main argument againts hooks you described above (various notifications, events, signal handlers etc.) is that it would lead to additional requirements for user application (different from how fopencookie is used in GNU/Linux). I havent had time to go through all code you suggested (graphics etc) so maybe I am missing something. But I think we should keep it in simple fseak, fputs etc operations.

@mu578 I like the structure you proposed, that would solve a lot of issues with current file_struct and as @xiaoxiang781216 mentioned it should not be that difficult (mostly renaming etc.). We could still keep pointers to read/write/seek at file in fs/vfs as it is now for drivers and just add the possibility for custom streams if required.

I would do this primarily for fopencookie and than build fmemopen and open_memstream on that if the community would agree as I think it is still beneficial to have fopencookie implemented (and its presence in Linux should justify that in my opinion).

I also think we should probably move this discussion to mailing list now.

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Sep 14, 2023

I once had a look at that to handle a fomempen and alike properly, it would require that LIBC struct FILE not to be a direct alias to internal FS struct, as indeed you need to keep a reference to fs operation callbacks and permission flags, so it would require an indirection where struct FILE holds a private pointer to internal. Meanwhile, I think LIBC stdio would benefit at term that indirection to be made.

struct libc_stdio_file
{
  // ... stdio specifics;
  libc_stdio_fread_t  read;
  libc_stdio_fwrite_t write;
  libc_stdio_fseek_t  seek;
  libc_stdio_fclose_t close;
  libc_stdio_cookie_t cookie;

  file_struct *       internal;
}

typedef libc_stdio_file FILE;

we don't need add a new struct libc_stdio_file. Here is the current definition:

struct file_struct
{
  FAR struct file_struct *fs_next;      /* Pointer to next file stream */
  rmutex_t                fs_lock;      /* Recursive lock */
  int                     fs_fd;        /* File descriptor associated with stream */
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
  FAR unsigned char      *fs_bufstart;  /* Pointer to start of buffer */
  FAR unsigned char      *fs_bufend;    /* Pointer to 1 past end of buffer */
  FAR unsigned char      *fs_bufpos;    /* Current position in buffer */
  FAR unsigned char      *fs_bufread;   /* Pointer to 1 past last buffered read char. */
#  if CONFIG_STDIO_BUFFER_SIZE > 0
  unsigned char           fs_buffer[CONFIG_STDIO_BUFFER_SIZE];
#  endif
#endif
  uint16_t                fs_oflags;    /* Open mode flags */
  uint8_t                 fs_flags;     /* Stream flags */
#if CONFIG_NUNGET_CHARS > 0
  uint8_t                 fs_nungotten; /* The number of characters buffered for ungetc */
  unsigned char           fs_ungotten[CONFIG_NUNGET_CHARS];
#endif
};

typedef struct file_struct FILE;

We can make a simple change to it:

struct file_struct
{
  ...
-  int                     fs_fd;        /* File descriptor associated with stream */
+  cookie_io_functions_t   fs_iofunc;
+  FAR void               *fs_cookie;
  ...
};

and replace all read/write/seek/close with stream->iofunc.read/write/seek/close, fs_fd with fs_cookie.
The remaining work is adding the following wrappers:

static ssize_t read_fd(void *cookie, char *buf, size_t size)
{
  int fd = (int)(intptr_t)cookie;
  return read(fd, buf, size);
}

static ssize_t write_fd(void *cookie, const char *buf, size_t size)
{
  int fd = (int)(intptr_t)cookie;
  return write(fd, buf, size);
}

static int seek_fd(void *cookie, off_t *offset, int whence)
{
  int fd = (int)(intptr_t)cookie;
  off_t ret = seek(fd, *offset, whence);
  if (ret >= 0)
    {
      *offset = ret;
      ret = 0;
    }

  return ret;
}

static int close_fd(void *cookie)
{
  int fd = (int)(intptr_t)cookie;
  return close(fd);
}

and initialize fs_iofunc and fs_cookie in fopen like this:

  stream->fs_iofunc.read = read_fd;
  stream->fs_iofunc.write = write_fd;
  stream->fs_iofunc.seek = seek_fd;
  stream->fs_iofunc.close = close_fd;
  stream->fs_cookie = (FAR void *)(intptr_t)fd;

@gregory-nutt
Copy link
Contributor

You apparently seem focused on avoiding a clean context switch (which I believe is the correct, clean, flexible solution).

@patacongo My main argument againts hooks you described above (various notifications, events, signal handlers etc.) is that it would lead to additional requirements for user application (different from how fopencookie is used in GNU/Linux). I havent had time to go through all code you suggested (graphics etc) so maybe I am missing something. But I think we should keep it in simple fseak, fputs etc operations.

The marshaled data should be indisguishable from a direct function (provided an intermediate marshaling/de-marshaling,.

My main argument against this approach is that I have not heard any solution that will actually work in all conditions and build modes.

@xiaoxiang781216
Copy link
Contributor

You apparently seem focused on avoiding a clean context switch (which I believe is the correct, clean, flexible solution).

@patacongo My main argument againts hooks you described above (various notifications, events, signal handlers etc.) is that it would lead to additional requirements for user application (different from how fopencookie is used in GNU/Linux). I havent had time to go through all code you suggested (graphics etc) so maybe I am missing something. But I think we should keep it in simple fseak, fputs etc operations.

The marshaled data should be indisguishable from a direct function (provided an intermediate marshaling/de-marshaling,.

My main argument against this approach is that I have not heard any solution that will actually work in all conditions and build modes.

@patacongo I think @michallenc initial design may confuse you. stdio is a pure userspace library, the implementation of fopencookie or fmemopen doesn't need involve any kernel stuff: it's enough to add the callback(read/write/seek/close) to struct file_struct(FILE).
So, I would suggest you read the manual from:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html
https://man7.org/linux/man-pages/man3/fopencookie.3.html

@mu578
Copy link

mu578 commented Sep 14, 2023

I once had a look at that to handle a fomempen and alike properly, it would require that LIBC struct FILE not to be a direct alias to internal FS struct, as indeed you need to keep a reference to fs operation callbacks and permission flags, so it would require an indirection where struct FILE holds a private pointer to internal. Meanwhile, I think LIBC stdio would benefit at term that indirection to be made.

struct libc_stdio_file
{
  // ... stdio specifics;
  libc_stdio_fread_t  read;
  libc_stdio_fwrite_t write;
  libc_stdio_fseek_t  seek;
  libc_stdio_fclose_t close;
  libc_stdio_cookie_t cookie;

  file_struct *       internal;
}

typedef libc_stdio_file FILE;

we don't need add a new struct libc_stdio_file. Here is the current definition:

struct file_struct
{
  FAR struct file_struct *fs_next;      /* Pointer to next file stream */
  rmutex_t                fs_lock;      /* Recursive lock */
  int                     fs_fd;        /* File descriptor associated with stream */
#ifndef CONFIG_STDIO_DISABLE_BUFFERING
  FAR unsigned char      *fs_bufstart;  /* Pointer to start of buffer */
  FAR unsigned char      *fs_bufend;    /* Pointer to 1 past end of buffer */
  FAR unsigned char      *fs_bufpos;    /* Current position in buffer */
  FAR unsigned char      *fs_bufread;   /* Pointer to 1 past last buffered read char. */
#  if CONFIG_STDIO_BUFFER_SIZE > 0
  unsigned char           fs_buffer[CONFIG_STDIO_BUFFER_SIZE];
#  endif
#endif
  uint16_t                fs_oflags;    /* Open mode flags */
  uint8_t                 fs_flags;     /* Stream flags */
#if CONFIG_NUNGET_CHARS > 0
  uint8_t                 fs_nungotten; /* The number of characters buffered for ungetc */
  unsigned char           fs_ungotten[CONFIG_NUNGET_CHARS];
#endif
};

typedef struct file_struct FILE;

Here is the simple change to the struct:

struct file_struct
{
  ...
-  int                     fs_fd;        /* File descriptor associated with stream */
+  cookie_io_functions_t fs_iofunc;
+  FAR void *            fs_cookie;
  ...
};

and replace all read/write/seek/close with stream->iofunc.read/write/seek/close. The remaining work is adding the simple wrapper for fopen:

static ssize_t read_fd(void *cookie, char *buf, size_t size)
{
  int fd = (int)(intptr_t)cookie;
  return read(fd, buf, size);
}

static ssize_t write_fd(void *cookie, const char *buf, size_t size)
{
  int fd = (int)(intptr_t)cookie;
  return write(fd, buf, size);
}

static int seek_fd(void *cookie, off_t *offset, int whence)
{
  int fd = (int)(intptr_t)cookie;
  off_t ret = seek(fd, *offset, whence);
  if (ret >= 0)
    {
      *offset = ret;
      ret = 0;
    }

  return ret;
}

you would if you want to keep separated internal implementation to actual in-memory Os layout and a libc stdio frontend concern.

Indeed nutxx/fs.h is one possible core-stream handling implementation, you don't want to mixup ; theoretically, you could swap to any OS vfs implementation without changing any of your LIBC frontend.

fopencookie is a solution/vilain patch which only regards end-user and a LIBC frontend to address the implementation of open_memstream and fmemopen, nuttx_file_struct should remain agnostic to such concerns, now, I don't say it couldn't be smarter by holding default callback pointers, the problem: the ones defined by fopencookie do not match exactly signature.

But again do not hot patch such.

@xiaoxiang781216
Copy link
Contributor

you would if you want to keep separated internal implementation to actual in-memory Os layout and a libc stdio frontend concern.

Indeed nutxx/fs.h is one possible core-stream handling implementation, you don't want to mixup ;

If you don't like file_struct is contained in nuttx/fs.h, the right fix is moving it to stdio.h, but don't create a new libc_stdio_file since libc_stdio_file lack many fields which is required to implement buffer I/O and fungetc etc.

theoretically, you could swap to any OS vfs implementation without changing any of your LIBC frontend.

struct file_struct is designed to work with stdio, struct file is designed to work with vfs. Please don't confuse them.

@patacongo
Copy link
Contributor

patacongo commented Sep 14, 2023

@patacongo I think @michallenc initial design may confuse you. stdio is a pure userspace library, the implementation of fopencookie or fmemopen doesn't need involve any kernel stuff: it's enough to add the callback(read/write/seek/close) to struct file_struct(FILE).
So, I would suggest you read the manual from:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html
https://man7.org/linux/man-pages/man3/fopencookie.3.html

AFAIK that is not true. The user/supervisor mode is only one of several issues. I have enumerated many others below that this design pure user-space design will not handle. The bottom line: You cannot call from one task/process address space into another. In the FLAT build, that might work in some very, very simple cases. But it will not work in general. It will never work at all in the KERNEL build if the client and server lie in different process address spaces. The virtual user address spaces overlap and it you try calling into it you will certainly crash.

In order to that successfully and reliably, a context switch is required.

I don't see any reason for me to participate in this any further. I've tried to explain all of this as carefully as I can but I still see you guys marching ahead to fall off the cliff. I have unsubscribed from this PR. Please be sure to test all of these cases before merging an incorrect design.

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Sep 14, 2023

@patacongo I think @michallenc initial design may confuse you. stdio is a pure userspace library, the implementation of fopencookie or fmemopen doesn't need involve any kernel stuff: it's enough to add the callback(read/write/seek/close) to struct file_struct(FILE).
So, I would suggest you read the manual from:
https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html
https://man7.org/linux/man-pages/man3/fopencookie.3.html

AFAIK that is not true. The user/supervisor mode is only one of several issues. I have enumerated many others below that this design pure user-space design will not handle. The bottom line: You cannot call from one task/process address space into another.
In the FLAT build, that might work in some very, very simple cases. But it will not work in general. It will never work at all in the KERNEL build if the client and server lie in different process address spaces. The virtual user address spaces overlap and it you try calling into it you will certainly crash.

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

@patacongo
Copy link
Contributor

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

I don't see the relevance of the FILE*. I was more concerned at the io_funcs and cookie. But looking at the prototype, it seems impossible that the io_funcs could like in a different address space. from the caller of fopencookie().

@mu578
Copy link

mu578 commented Sep 14, 2023

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

I don't see the relevance of the FILE*. I was more concerned at the io_funcs and cookie. But looking at the prototype, it seems impossible that the io_funcs could like in a different address space. from the caller of fopencookie().

Aggreed, it would require to pound defined and only allowing such APIs in usermode. However, it could be doable by making a new vfs interface handling such but it would require a whole revamp and further thoughts to make it possible, as is : it is not hot patchable -> so my proposal to support indirection for user-mode build only, at least it can be undone and evolve without breaking.

That is impossible to callback from two distinct regions ; it would require some kind of trampoline jump with copies back and forth. E.g if function pointers were actually just address pointers pointing to an actual somewhere we would not have this conversation, but the reality is different.

Myself, I would go to support standard only: open_memstream and fmemopen ; there is no reason to expose a non standard fopencookie call ; the two requires some memory mapping that's it, no callback exposure. It is all about to be able to use FILE api on a user in-memory buffer instead of on a file descriptor/ino ; which in case of nuttx is kif-kif bourricot a mere detail.

@xiaoxiang781216
Copy link
Contributor

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

I don't see the relevance of the FILE*. I was more concerned at the io_funcs and cookie. But looking at the prototype, it seems impossible that the io_funcs could like in a different address space. from the caller of fopencookie().

Yes, this is the key point: both io_funcs and cookie can only be used in the same proccess(address space), which simplify the thing a lot if the implementation can avoid the involve any kernel space stuff.

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Sep 15, 2023

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

I don't see the relevance of the FILE*. I was more concerned at the io_funcs and cookie. But looking at the prototype, it seems impossible that the io_funcs could like in a different address space. from the caller of fopencookie().

Aggreed, it would require to pound defined and only allowing such APIs in usermode.

Of course, all FILE functions can only be called inside the usermode, NuttX kernel forbid kernel code to use any FILE functions.

However, it could be doable by making a new vfs interface handling such but it would require a whole revamp and further thoughts to make it possible, as is : it is not hot patchable -> so my proposal to support indirection for user-mode build only, at least it can be undone and evolve without breaking.

@mu578 you may misunderstand NuttX file system:

  • nuttx/fs/vfs is the kernel VFS infrastructure, which is part of kernel, it's hard and bad to call the callback provided by userspace
  • nuttx/libc/stdio is userspace library, which is very simple and common to call the callback provided by userspace

That is impossible to callback from two distinct regions ; it would require some kind of trampoline jump with copies back and forth. E.g if function pointers were actually just address pointers pointing to an actual somewhere we would not have this conversation, but the reality is different.

Do you want to implement the userspace file system? NuttX already support it by socket:
https://github.com/apache/nuttx/blob/master/include/nuttx/fs/userfs.h

Myself, I would go to support standard only: open_memstream and fmemopen ; there is no reason to expose a non standard fopencookie call ; the two requires some memory mapping that's it, no callback exposure. It is all about to be able to use FILE api on a user in-memory buffer instead of on a file descriptor/ino ; which in case of nuttx is kif-kif bourricot a mere detail.

If we want to support all open_memstream/open_wmemstream/fmemopen, it's important to setup a callback infrastructure otherwise fread/fwrite need to add many if/else:

if (file handle)
  {
    ...
  }
else if (mem handle)
  {
    ...
  }
else if (xxx handle)
  {
    ...
  }

If we need to add the callback to FILE, why not follow fopencookie which exist in most libc implementation(glibc, newlib, *BSD), but add a NuttX specific function instead? I don't see any benefit.

@michallenc
Copy link
Contributor Author

michallenc commented Sep 15, 2023

To summarize the so far discussion a bit:

The problem discussed here is a that this implementation reuses file_operation_s infrastructure which is a part of a file system (thus kernel address space) which is my fault as I haven´t realize during the implementation that the reuse of current infrastructure would lead to this.

I quite like the proposition of reworked/new file system @mu578 suggested but as he mentioned, it is not a stuff for one patch and probably should be discussed on a mailing list first. I also do not agree with the statement

Myself, I would go to support standard only: open_memstream and fmemopen ; there is no reason to expose a non standard fopencookie call ; the two requires some memory mapping that's it, no callback exposure.

as fopencookie can be beneficial for programmers (maybe even more than fmemopen). And while not being POSIX, we should keep in mind the NuttX compatibility with Linux imho. Also if done correctly, fopencookie can be used both as libc call available for programmers and internal hook for fopencookie and open_memstream.

Regarding kernel protected builds, address space (and also priorities to keep strict scheduling), I think it should be sufficient to ensure user calls read/write/seek/close do not enter kernel space i.e. fs/vfs when used with fopencookie. In that case all code would remain in stdio libc library and would be purely user space therefore none of the mentioned issues should occur. I would go with @xiaoxiang781216 suggested implementation for that and we will see where it leads.

We will also have to avoid calling fs_fdopen as it is in kernel space and initialize FILE *stream directly in fopencookie.

@mu578
Copy link

mu578 commented Sep 16, 2023

This case isn't suitable for stdio functions. How can you pass FILE * from one task/process to other task/process in kernel mode? So it's impossible to call the callback function cross the address space boundary since FILE* return from fopencookie can only be used by the caller's task/process and is always invalid and impossible to pass FILE* to another task/process at the first place.

I don't see the relevance of the FILE*. I was more concerned at the io_funcs and cookie. But looking at the prototype, it seems impossible that the io_funcs could like in a different address space. from the caller of fopencookie().

Aggreed, it would require to pound defined and only allowing such APIs in usermode.

Of course, all FILE functions can only be called inside the usermode, NuttX kernel forbid kernel code to use any FILE functions.

However, it could be doable by making a new vfs interface handling such but it would require a whole revamp and further thoughts to make it possible, as is : it is not hot patchable -> so my proposal to support indirection for user-mode build only, at least it can be undone and evolve without breaking.

@mu578 you may misunderstand NuttX file system:

  • nuttx/fs/vfs is the kernel VFS infrastructure, which is part of kernel, it's hard and bad to call the callback provided by userspace
  • nuttx/libc/stdio is userspace library, which is very simple and common to call the callback provided by userspace

That is impossible to callback from two distinct regions ; it would require some kind of trampoline jump with copies back and forth. E.g if function pointers were actually just address pointers pointing to an actual somewhere we would not have this conversation, but the reality is different.

Do you want to implement the userspace file system? NuttX already support it by socket: https://github.com/apache/nuttx/blob/master/include/nuttx/fs/userfs.h

Myself, I would go to support standard only: open_memstream and fmemopen ; there is no reason to expose a non standard fopencookie call ; the two requires some memory mapping that's it, no callback exposure. It is all about to be able to use FILE api on a user in-memory buffer instead of on a file descriptor/ino ; which in case of nuttx is kif-kif bourricot a mere detail.

If we want to support all open_memstream/open_wmemstream/fmemopen, it's important to setup a callback infrastructure otherwise fread/fwrite need to add many if/else:

if (file handle)
  {
    ...
  }
else if (mem handle)
  {
    ...
  }
else if (xxx handle)
  {
    ...
  }

If we need to add the callback to FILE, why not follow fopencookie which exist in most libc implementation(glibc, newlib, *BSD), but add a NuttX specific function instead? I don't see any benefit.

I fully understand vfs, no callback infrastructure is needed, that's a glibc patch, who cares? just deal with a flag about memory provider that's it.

@xiaoxiang781216
Copy link
Contributor

I fully understand vfs, no callback infrastructure is needed,

that's a glibc patch, who cares?

But I care where the implementation is elegance and extendable. The code like spaghetti should avoid as mention in:
https://github.com/apache/nuttx/blob/master/INVIOLABLES.md?plain=1#L36-L41
https://github.com/apache/nuttx/blob/master/INVIOLABLES.md?plain=1#L90-L94

just deal with a flag about memory provider that's it.

even for memory stream, there are at least two cases:

  1. fmemopen work on a fix size array(provided by caller, or allocated by self)
  2. open_memstream work on an allocate/reallocate extendable memory

The detail is evil, you have to add more flags/fields and many if/else in many common places to handle the different behavior required by spec. The callback is better design here to encapsulate the difference behind the differerent callback(fd vs. mem, fixed vs. dynamically).

@mu578
Copy link

mu578 commented Sep 16, 2023

  • provided by caller, or allocated by self)
  • open_memstream work on an allocate/reallocate extendable memor

yes make a context buffer |internal|user_malloc|user_dont_touch|


_io_buffer_grow_if_needed(stream_flag, ** buffer, ...)
{
  switch this type -> do that or not do
}

_io_buffer_free_if_needed(stream_flag, ** buffer, ...)
{
  switch this type -> do that or not do
}

@michallenc
Copy link
Contributor Author

I have updated the implementation with internal callbacks so now the code should entirely avoid kernel address space. How are we going to design some tests that would ensure the correctness?

@xiaoxiang781216
Copy link
Contributor

xiaoxiang781216 commented Sep 18, 2023

@michallenc
Copy link
Contributor Author

Some initial tests can be found here: michallenc/incubator-nuttx-apps@1cd88d5 I have only tested it in FLAT mode so far as I have not been able to compile NuttX in protected mode (regardless of fopencookie, I tried upstream NuttX without changes as well) on both samv7 and imxrt MCUs. SAMv7 fails on NVIC_CFAULTS_MUNSTKERR (MemManage fault on unstacking) and I could not get any output from imxrt. I used @acassis video on https://www.youtube.com/watch?v=p2aXmPNUnCg for configuration, so that stepts should be alright. There might be something wrong with MPU for ARMv7-M...

include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fclose.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fclose.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_stdio_cb.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_stdio_cb.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
@michallenc
Copy link
Contributor Author

michallenc commented Oct 16, 2023

@michallenc but, the ci still fail, could you take a look?

Yes, I fixed some on Saturday but I suppose the rest is somehow related to change to fs_fdopen which should be (at least partially) fixed by the update from your patch a remove of fs_fdopen. I will update the patch once yours is merged.

@xiaoxiang781216
Copy link
Contributor

Ok, let's wait the merging of #10913, so you can move the FILE allocation to fopencookie.

@xiaoxiang781216
Copy link
Contributor

@michallenc but, the ci still fail, could you take a look?

Yes, I fixed some on Saturday but I suppose the rest is somehow related to change to fs_fdopen which should be (at least partially) fixed by the update from your patch a remove of fs_fdopen. I will update the patch once yours is merged.

@michallenc #10913 was merged, you can rebase your patch on top of it now.

@michallenc
Copy link
Contributor Author

@michallenc #10913 was merged, you can rebase your patch on top of it now.

@xiaoxiang781216 Updated. Local CI runs were successful, lets see what GitHub CI does.

@michallenc
Copy link
Contributor Author

Hmm, sim-01 fails without actually logging any error.

include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fclose.c Outdated Show resolved Hide resolved
include/nuttx/fs/fs.h Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfflush.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfread_unlocked.c Outdated Show resolved Hide resolved
sched/tls/task_initinfo.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfwrite.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfread_unlocked.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfread_unlocked.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfgets.c Outdated Show resolved Hide resolved
@michallenc michallenc force-pushed the fopencookie branch 2 times, most recently from 7fe9d99 to f0e8304 Compare October 17, 2023 10:04
@xiaoxiang781216
Copy link
Contributor

please fix:

81a82
> CONFIG_TESTING_FOPENCOOKIE_TEST=y
87d87
< CONFIG_TESTING_FOPENCOOKIE_TEST=y
Saving the new configuration file
HEAD detached at pull/10602/merge
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   boards/sim/sim/sim/configs/citest/defconfig

@michallenc michallenc force-pushed the fopencookie branch 4 times, most recently from 1f1fad4 to 2e75156 Compare October 17, 2023 20:18
libs/libc/stdio/lib_fclose.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_fopencookie.c Show resolved Hide resolved
libs/libc/stdio/lib_ungetc.c Outdated Show resolved Hide resolved
libs/libc/stdio/lib_libfread_unlocked.c Outdated Show resolved Hide resolved
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 <[email protected]>
This adds fopencookie test for simulator CI.

Signed-off-by: Michal Lenc <[email protected]>
@xiaoxiang781216 xiaoxiang781216 merged commit 6e4ffb9 into apache:master Oct 18, 2023
26 checks passed
@michallenc michallenc deleted the fopencookie branch October 30, 2023 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants