Add sbuf streaming mode to pseudofs(9), use in linprocfs(5)

Add a pseudofs node flag 'PFS_AUTODRAIN', which automatically emits sbuf
contents to the caller when the sbuf buffer fills.  This is only
permissible if the corresponding PFS node fill function can sleep
whenever it appends to the sbuf.

linprocfs' /proc/self/maps node happens to meet this requirement.
Streaming out the file as it is composed avoids truncating the output
and also avoids preallocating a very large buffer.

Reviewed by:	markj; earlier version: emaste, kib, trasz
Differential Revision:	https://reviews.freebsd.org/D27047
This commit is contained in:
Conrad Meyer 2020-11-05 06:48:51 +00:00
parent df69035d7f
commit 20172854ab
3 changed files with 83 additions and 13 deletions

View File

@ -1252,10 +1252,6 @@ linprocfs_doprocmaps(PFS_FILL_ARGS)
*name ? " " : " ",
name
);
if (error == -1) {
linux_msg(td, "cannot fill /proc/self/maps; "
"consider bumping PFS_MAXBUFSIZ");
}
if (freename)
free(freename, M_TEMP);
vm_map_lock_read(map);
@ -1890,7 +1886,7 @@ linprocfs_init(PFS_INIT_ARGS)
pfs_create_link(dir, "exe", &procfs_doprocfile,
NULL, &procfs_notsystem, NULL, 0);
pfs_create_file(dir, "maps", &linprocfs_doprocmaps,
NULL, NULL, NULL, PFS_RD);
NULL, NULL, NULL, PFS_RD | PFS_AUTODRAIN);
pfs_create_file(dir, "mem", &linprocfs_doprocmem,
procfs_attr_rw, &procfs_candebug, NULL, PFS_RDWR | PFS_RAW);
pfs_create_file(dir, "mounts", &linprocfs_domtab,

View File

@ -78,6 +78,7 @@ typedef enum {
#define PFS_RAW (PFS_RAWRD|PFS_RAWWR)
#define PFS_PROCDEP 0x0010 /* process-dependent */
#define PFS_NOWAIT 0x0020 /* allow malloc to fail */
#define PFS_AUTODRAIN 0x0040 /* sbuf_print can sleep to drain */
/*
* Data structures

View File

@ -623,6 +623,50 @@ pfs_open(struct vop_open_args *va)
PFS_RETURN (0);
}
struct sbuf_seek_helper {
off_t skip_bytes;
struct uio *uio;
};
static int
pfs_sbuf_uio_drain(void *arg, const char *data, int len)
{
struct sbuf_seek_helper *ssh;
struct uio *uio;
int error, skipped;
ssh = arg;
uio = ssh->uio;
skipped = 0;
/* Need to discard first uio_offset bytes. */
if (ssh->skip_bytes > 0) {
if (ssh->skip_bytes >= len) {
ssh->skip_bytes -= len;
return (len);
}
data += ssh->skip_bytes;
len -= ssh->skip_bytes;
skipped = ssh->skip_bytes;
ssh->skip_bytes = 0;
}
error = uiomove(__DECONST(void *, data), len, uio);
if (error != 0)
return (-error);
/*
* The fill function has more to emit, but the reader is finished.
* This is similar to the truncated read case for non-draining PFS
* sbufs, and should be handled appropriately in fill-routines.
*/
if (uio->uio_resid == 0)
return (-ENOBUFS);
return (skipped + len);
}
/*
* Read from a file
*/
@ -636,7 +680,8 @@ pfs_read(struct vop_read_args *va)
struct proc *proc;
struct sbuf *sb = NULL;
int error, locked;
off_t buflen;
off_t buflen, buflim;
struct sbuf_seek_helper ssh;
PFS_TRACE(("%s", pn->pn_name));
pfs_assert_not_owned(pn);
@ -678,16 +723,30 @@ pfs_read(struct vop_read_args *va)
error = EINVAL;
goto ret;
}
buflen = uio->uio_offset + uio->uio_resid;
if (buflen > PFS_MAXBUFSIZ)
buflen = PFS_MAXBUFSIZ;
buflen = uio->uio_offset + uio->uio_resid + 1;
if (pn->pn_flags & PFS_AUTODRAIN)
/*
* We can use a smaller buffer if we can stream output to the
* consumer.
*/
buflim = PAGE_SIZE;
else
buflim = PFS_MAXBUFSIZ;
if (buflen > buflim)
buflen = buflim;
sb = sbuf_new(sb, NULL, buflen + 1, 0);
sb = sbuf_new(sb, NULL, buflen, 0);
if (sb == NULL) {
error = EIO;
goto ret;
}
if (pn->pn_flags & PFS_AUTODRAIN) {
ssh.skip_bytes = uio->uio_offset;
ssh.uio = uio;
sbuf_set_drain(sb, pfs_sbuf_uio_drain, &ssh);
}
error = pn_fill(curthread, proc, pn, sb, uio);
if (error) {
@ -700,9 +759,23 @@ pfs_read(struct vop_read_args *va)
* the data length. Then just use the full length because an
* overflowed sbuf must be full.
*/
if (sbuf_finish(sb) == 0)
buflen = sbuf_len(sb);
error = uiomove_frombuf(sbuf_data(sb), buflen, uio);
error = sbuf_finish(sb);
if ((pn->pn_flags & PFS_AUTODRAIN)) {
/*
* ENOBUFS just indicates early termination of the fill
* function as the caller's buffer was already filled. Squash
* to zero.
*/
if (uio->uio_resid == 0 && error == ENOBUFS)
error = 0;
} else {
if (error == 0)
buflen = sbuf_len(sb);
else
/* The trailing byte is not valid. */
buflen--;
error = uiomove_frombuf(sbuf_data(sb), buflen, uio);
}
sbuf_delete(sb);
ret:
vn_lock(vn, locked | LK_RETRY);