-
Notifications
You must be signed in to change notification settings - Fork 2k
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
gcoap: Add file server #14397
gcoap: Add file server #14397
Conversation
There is now support for ETag and block-wise transfer. (Not on directory listings yet, though). Example-wise, I'll abandon piggy-backing on gcoap and instead add a few lines (sic: 2 lines header, 17 lines static structs, 2 lines code to main) to the filesystem example, conditionally included. Creating good example directories and mouting/formatting is way too much hassle otherwise. |
👍 I'd like to see the ETag mechanism done in gcoap, so it can be reused for different scenarios (not only serving files). We can generalize it later, if you want. I'll take a deeper look at the provided implementation here. |
Is this still a draft? |
(Sorry, wrong button) |
Is this still a draft?
Yes; one of the reasons is that it'd be a bit stricter on the file
system API than that caters for -- for example, it struggles with the
different file systems's directory listing responses of one returning
"/entry" and the other "entry". URIs need a bit more precision there
than paths: "//entry" may work on the file system, but means something
vastly different in a URI.
(Also, I've been on vacation for the last two-and-a-half weeks and at
the IETF meeting right now, so I'll really only be back at RIOT issues
around the end of the week).
|
That sounds like a bug in the FS abstraction. What file systems are affected? |
That sounds like a bug in the FS abstraction. What file systems are
affected?
In the file system example on master, I see (with fatfs configured and a
prepared image):
ls /const
/hello-world
/hello-riot
total 2 files
mount
ls /sda
FOO
EMPTY
FOLDER
GARBAGE
total 4 files
while on the default littlefs config, I see:
ls /const
/hello-world
/hello-riot
total 2 files
format
mount
tee /sda/foo hello
ls /sda
/.
/..
/foo
total 3 files
(and carelessly destroyed my fatfs image :-/ )
|
This is starting point for a file server. It works but does not do block-wise (truncating large files or directory listings), ETag or write support, and fails to handle read errors (after a successful open) properly.
The note on partially cached versions is not applicable yet (for lack of block-wise support), but will become relevant.
The filesystem example is ideal to demonstrate the fileserver as it has a usable default set of demo files, and different file systems can be mounted for experimentation. The impact on the filesystem example outside the coapfileserver context is minimal: Only a few lines of includes (2), static configuration (17 including comments and whitespace) and startup code (2) are added under ifdef guards that clearly mark them as optional and Gcoap related. They are only enabled if NETWORK=1 is set.
With the "resolved agains the anchor" reading obsolete, link targets can easily be expressed relatively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice contribution! I made some initial comments, I'll be testing this shortly.
|
||
/* Normalizing fields whose value can change without affecting the ETag */ | ||
stattag->stat.st_nlink = 0; | ||
memset(&stattag->stat.st_atim, 0, sizeof(stattag->stat.st_atim)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting errors on this member access when compiling for cortex.
memset(&stattag->stat.st_atim, 0, sizeof(stattag->stat.st_atim)); | |
memset(&stattag->stat.st_atime, 0, sizeof(stattag->stat.st_atime)); |
#include <net/gcoap.h> | ||
#include <vfs.h> | ||
#include <fcntl.h> | ||
#include <error.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also getting errors with this inclusion, is it needed?
#include <fcntl.h> | ||
#include <error.h> | ||
|
||
#define ENABLE_DEBUG 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#define ENABLE_DEBUG 1 | |
#define ENABLE_DEBUG 0 |
|
||
SRC = coapfileserver.c | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed, the file should be included automatically:
SRC = coapfileserver.c |
#include "debug.h" | ||
|
||
/** Maximum length of an expressible path, including the trailing 0 character. */ | ||
#define COAPFILESERVER_PATH_MAX (64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this value be configured?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be made, but I'm hoping to get rid of the need to assemble any paths by using something openat-like (issue or PR TBD).
* As usual, GET operations are used to read files<!-- WRITESUPPORT, and PUT writes to files. | ||
* In the current implementation, PUTs are expressed as random-access, meaning | ||
* that files are not updated atomically -->. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are there some parts commented out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I had write support planned to go in from the start, had the documentation already written in the first step, and don't want to have to write it all again later on. It'll better if I move them over to a nascent write-support-for-fileserver branch instead.
* ``` | ||
* | ||
* The allowed methods dictate whether it's read-only (``COAP_GET``) or (in the | ||
* future<!-- WRITESUPPORT -->) read-write (``1COAP_GET | COAP_PUT | COAP_DELETE``). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* future<!-- WRITESUPPORT -->) read-write (``1COAP_GET | COAP_PUT | COAP_DELETE``). | |
* future<!-- WRITESUPPORT -->) read-write (``COAP_GET | COAP_PUT | COAP_DELETE``). |
* # About | ||
* | ||
* This maps files in the local file system onto a resources in CoAP. In that, | ||
* is is what is called a static web server in the unconstrained web. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* is is what is called a static web server in the unconstrained web. | |
* it is what is called a static web server in the unconstrained web. |
static const struct coapfileserver_entry vfs = { "", 1 }; | ||
|
||
/* CoAP resources. Must be sorted by path (ASCII order). */ | ||
static const coap_resource_t _resources[] = { | ||
{ "/vfs", COAP_GET | COAP_MATCH_SUBTREE, coapfileserver_handler, (void*)&vfs }, | ||
}; | ||
|
||
static gcoap_listener_t _listener = { | ||
&_resources[0], | ||
ARRAY_SIZE(_resources), | ||
NULL, | ||
NULL | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to move this to an auto-init function? Is there a reason why this needs to be registered from the application?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only if it were to be made configurable as to the file system entry point and to the coap entry point -- we really shouldn't pick resource names on users' CoAP servers. (And there'd still need to allow manual management for things like ACLs, or when different mount points need different treatment on the CoAP side, eg. w/rt writability).
AFAIK we currently don't do any automatically inserted CoAP resources (other than /.well-known/core). Doing that efficiently would mean putting sorted entries into an array, AFAICT that can't be done even with XFA (#15002) and'd thus need pre-sorting by wherethroughever the configured paths are set. (For example, if coapfileserver did this and so did a hypothetical resourcedirectory, they'd need to be put as ["/.well-known/core", "/coapfileserver", "/rd"] with the one set of names but as ["/.well-known/core", "/cord-server", "/files"] with others).
I'll give it a try, but it may result in an "I'd rather do this as a next step".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only if it were to be made configurable as to the file system entry point and to the coap entry point -- we really shouldn't pick resource names on users' CoAP servers. (And there'd still need to allow manual management for things like ACLs, or when different mount points need different treatment on the CoAP side, eg. w/rt writability).
Agreed, I was thinking as well on making the entry point configurable.
Doing that efficiently would mean putting sorted entries into an array
Yeah, that's true. Maybe if we could adapt the path matching function on nanocoap we could get rid of the alphabetical ordering constraint? In any case, if that's needed it would be work for a follow up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe if we could adapt the path matching function on nanocoap
Sorry, meant the gcoap matching, that's the one that enforces this.
Thanks for the review; most points can be trivially addressed with the next batch (which may take a few days though). The st_atim vs. st_atime and include parts are a bit icky as the otherwise RIOT-style vfs calls fan out to whatever the currently used libc decides to do about POSIX stat properties, but I'll manage and try building for some more platforms. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you want me to ignore this issue, please mark it with the "State: don't stale" label. Thank you for your contributions. |
Is this one something that could be revived @chrysn? |
Hm, there is now a file server in the rust-gcoap example. Is there much purpose in duplicating that?
(There are some features in here, which are not in there yet; these should be ported over.)
|
Is the rust server usable from C applications? How big is the overhead compared to the C implementation? |
For me this is still useful mainly because don't want to depend on rust: I still have not picked up rust, and I work with people that do not code in rust. But I can understand if you have no interest in pushing this. |
It will be once #16785 (similar to the above #14397 (comment)) and #16833 are done (maybe even only the latter, if it registers somewhere in-tree).
That is one reason I wrote the Rust one, but I'm not at feature parity (and then will have to strip down the Rust one again as it can already do some things the C one can't). Also, it'll be a nice comparison for gcoap and nanocoap; I didn't wrap nanocoap yet, but it's on my rough agenda. (Plus there's a Rust CoAP implementation competing).
That shouldn't be a problem once the above things are closed (any more than not speaking C++ is if a pkg uses that). However, platform support for MIPS, AVR and the pre-RISC-V ESPs.
It should be usable as it is already, just not at the quality level I expect from RIOT components. |
This allows quick revalidation: "I have a representation, but its Max-Age (default 60s) expires. It had tag 1234, so send anything new, or tell me it's still good." -- "2.03 Valid, ETag 1234 is good (for the next 60s)"
|
See also #17888 for the client-side :-) |
I rebased this and applied the suggested fixes to my branch but I noticed something off:
|
That is fine -- at least that was theresolution of #15291. The Rust based server, on directory access, looks both at the directory and at the mount point list, and serves entries from either. Of course, that's more complex on the client side than if just VFS added mount points magically, but then again, the code here is only used for relatively high-level applications (special purpose applications, even when using a file server, might not span mount points in the first place). Moreover, it's kind of practical in the file server to treat mount points special, as they can have (The nvm0 is something different, you found that already). |
request.namebuf[namelength] = '/'; | ||
memcpy(&request.namebuf[namelength] + 1, value, optlen); | ||
namelength = newlength; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there really no way in GCoap to provide the handler with the request URL, so that the handler doesn't have to parse the coap packet itself?
The request URL is a very complex beast, why would you want that? You can't pluck the URL (or its path component) into the FS any better than the options, plus it's a lot of string processing.
If anything it'd make sense to access it as a CRI (cf <#13827>), but that'd be very similar in the end to what is here now.
|
I was thinking of something like char uri[CONFIG_NANOCOAP_URI_MAX];
coap_get_uri_path(pkt, uri);
[…]
char *sub_path = &uri[strlen(resource->path)]);
resource->handler(pkt, resp_buf, resp_buf_len, sub_path, resource->context); That would allow us to get rid of the uneconomic |
Dealing in URIs would mean a great lot of reserved character and percent encoding shenanigans, I wouldn't want to have that where it's perfectly avoidable.
A pointer to CRI components starting at the first unprocessed one sounds more practical. (Note that the server may still need access to the processed ones too, eg if it is to serve link-format listings, as these practically don't have <./file> style local references, only </path/deep/file> ones.
|
Hm wouldn't it still help if the handler had knowledge of the resource path that it's supposed to handle? So resource->handler(pkt, resp_buf, resp_buf_len, resource->path, resource->context); maybe? |
That's what I mean by the last unprocessed element.
[edit: Wrong reading, see next comment]
|
Ah, sorry, I misunderstood your comment. Yes, passing the entry to the handler as a whole seems like a viable option; might even just pass resource in rather than ->path and ->context. (For example, that'd allow the handler to tell whether or not it is configured for POST and DELETE, if it'd want to know whether or not to advertise these featuers). |
Contribution description
This ties in the Gcoap module with the VFS module in the style of a static web server.
This is quite minimal (no write support, no blockwise, no ETag), but already fully documented. The example commit is marked WIP as it's not sure yet where to best put the example.
Testing procedure
Currently: Run the gcoap example, and
GET /vfs/const/
orGET /vfs/const/hello-world
, which should show a directory listing or the file content, respectively.