summaryrefslogtreecommitdiffstats
path: root/libc-top-half/musl/src/stdio/fmemopen.c
blob: 3ee57b9ea5b4937449f9968df89164f84f26a205 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include "stdio_impl.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <inttypes.h>
#include "libc.h"

struct cookie {
	size_t pos, len, size;
	unsigned char *buf;
	int mode;
};

struct mem_FILE {
	FILE f;
	struct cookie c;
	unsigned char buf[UNGET+BUFSIZ], buf2[];
};

static off_t mseek(FILE *f, off_t off, int whence)
{
	ssize_t base;
	struct cookie *c = f->cookie;
	if (whence>2U) {
fail:
		errno = EINVAL;
		return -1;
	}
#ifdef __wasilibc_unmodified_upstream // WASI's SEEK_* constants have different values.
	base = (size_t [3]){0, c->pos, c->len}[whence];
#else
	base = (size_t [3]) {
            [SEEK_SET] = 0,
            [SEEK_CUR] = c->pos,
            [SEEK_END] = c->len
        }[whence];
#endif
	if (off < -base || off > (ssize_t)c->size-base) goto fail;
	return c->pos = base+off;
}

static size_t mread(FILE *f, unsigned char *buf, size_t len)
{
	struct cookie *c = f->cookie;
	size_t rem = c->len - c->pos;
	if (c->pos > c->len) rem = 0;
	if (len > rem) {
		len = rem;
		f->flags |= F_EOF;
	}
	memcpy(buf, c->buf+c->pos, len);
	c->pos += len;
	rem -= len;
	if (rem > f->buf_size) rem = f->buf_size;
	f->rpos = f->buf;
	f->rend = f->buf + rem;
	memcpy(f->rpos, c->buf+c->pos, rem);
	c->pos += rem;
	return len;
}

static size_t mwrite(FILE *f, const unsigned char *buf, size_t len)
{
	struct cookie *c = f->cookie;
	size_t rem;
	size_t len2 = f->wpos - f->wbase;
	if (len2) {
		f->wpos = f->wbase;
		if (mwrite(f, f->wpos, len2) < len2) return 0;
	}
	if (c->mode == 'a') c->pos = c->len;
	rem = c->size - c->pos;
	if (len > rem) len = rem;
	memcpy(c->buf+c->pos, buf, len);
	c->pos += len;
	if (c->pos > c->len) {
		c->len = c->pos;
		if (c->len < c->size) c->buf[c->len] = 0;
		else if ((f->flags&F_NORD) && c->size) c->buf[c->size-1] = 0;
	}
	return len;
}

static int mclose(FILE *m)
{
	return 0;
}

FILE *fmemopen(void *restrict buf, size_t size, const char *restrict mode)
{
	struct mem_FILE *f;
	int plus = !!strchr(mode, '+');
	
	if (!strchr("rwa", *mode)) {
		errno = EINVAL;
		return 0;
	}

	if (!buf && size > PTRDIFF_MAX) {
		errno = ENOMEM;
		return 0;
	}

	f = malloc(sizeof *f + (buf?0:size));
	if (!f) return 0;
	memset(f, 0, offsetof(struct mem_FILE, buf));
	f->f.cookie = &f->c;
	f->f.fd = -1;
	f->f.lbf = EOF;
	f->f.buf = f->buf + UNGET;
	f->f.buf_size = sizeof f->buf - UNGET;
	if (!buf) {
		buf = f->buf2;
		memset(buf, 0, size);
	}

	f->c.buf = buf;
	f->c.size = size;
	f->c.mode = *mode;
	
	if (!plus) f->f.flags = (*mode == 'r') ? F_NOWR : F_NORD;
	if (*mode == 'r') f->c.len = size;
	else if (*mode == 'a') f->c.len = f->c.pos = strnlen(buf, size);
	else if (plus) *f->c.buf = 0;

	f->f.read = mread;
	f->f.write = mwrite;
	f->f.seek = mseek;
	f->f.close = mclose;

#if defined(__wasilibc_unmodified_upstream) || defined(_REENTRANT)
	if (!libc.threaded) f->f.lock = -1;
#endif

	return __ofl_add(&f->f);
}