aboutsummaryrefslogtreecommitdiff
path: root/kernel/fs/pipe.c
blob: 7c41561e029696c5b5ba59d6d869d290cebd10fc (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/*
 *  FILE: pipe.c
 *  AUTH: eric
 *  DESC: Implementation of pipe(2) system call.
 *  DATE: Thu Dec 26 17:08:34 2013
 */

#include "errno.h"
#include "globals.h"

#include "fs/file.h"
#include "fs/pipe.h"
#include "fs/stat.h"
#include "fs/vfs.h"
#include "fs/vfs_syscall.h"
#include "fs/vnode.h"

#include "mm/kmalloc.h"
#include "mm/slab.h"

#include "util/debug.h"
#include "util/string.h"

#define PIPE_BUF_SIZE 4096

static void pipe_read_vnode(fs_t *fs, vnode_t *vnode);

static void pipe_delete_vnode(fs_t *fs, vnode_t *vnode);

static fs_ops_t pipe_fsops = {.read_vnode = pipe_read_vnode,
                              .delete_vnode = pipe_delete_vnode,
                              .umount = NULL};

static fs_t pipe_fs = {.fs_dev = "pipe",
                       .fs_type = "pipe",
                       .fs_ops = &pipe_fsops,
                       .fs_root = NULL,
                       .fs_i = NULL};

static long pipe_read(vnode_t *vnode, size_t pos, void *buf, size_t count);

static long pipe_write(vnode_t *vnode, size_t pos, const void *buf,
                       size_t count);

static long pipe_stat(vnode_t *vnode, stat_t *ss);

static long pipe_acquire(vnode_t *vnode, file_t *file);

static long pipe_release(vnode_t *vnode, file_t *file);

static vnode_ops_t pipe_vops = {
    .read = pipe_read,
    .write = pipe_write,
    .mmap = NULL,
    .mknod = NULL,
    .lookup = NULL,
    .link = NULL,
    .unlink = NULL,
    .mkdir = NULL,
    .rmdir = NULL,
    .readdir = NULL,
    .stat = pipe_stat,
    .acquire = pipe_acquire,
    .release = pipe_release,
    .get_pframe = NULL,
    .fill_pframe = NULL,
    .flush_pframe = NULL,
};

/* struct pipe defines some data specific to pipes. One of these
   should be present in the vn_i field of each pipe vnode. */
typedef struct pipe
{
    /* Buffer for data in the pipe, which has been written but not yet read. */
    char *pv_buf;
    /*
     * Position of the head and number of characters in the buffer. You can
     * write in characters at position head so long as size does not grow beyond
     * the pipe buffer size.
     */
    off_t pv_head;
    size_t pv_size;
    /* Number of file descriptors using this pipe for read and write. */
    int pv_readers;
    int pv_writers;
    /*
     * Mutexes for reading and writing. Without these, readers might get non-
     * contiguous reads in a single call (for example, if they empty the buffer
     * but still have more to read, then the writer continues writing, waking up
     * a different thread first) and similarly for writers.
     */
    kmutex_t pv_rdlock;
    kmutex_t pv_wrlock;
    /*
     * Waitqueues for threads attempting to read from an empty buffer, or
     * write to a full buffer. When the pipe becomes non-empty (or non-full)
     * then the corresponding waitq should be broadcasted on to make sure all
     * of the threads get a chance to go.
     */
    ktqueue_t pv_read_waitq;
    ktqueue_t pv_write_waitq;
} pipe_t;

#define VNODE_TO_PIPE(vn) ((pipe_t *)((vn)->vn_i))

static slab_allocator_t *pipe_allocator = NULL;
static int next_pno = 0;

void pipe_init(void)
{
    pipe_allocator = slab_allocator_create("pipe", sizeof(pipe_t));
    KASSERT(pipe_allocator);
}

/*
 * Create a pipe struct here. You are going to need to allocate all
 * of the necessary structs and buffers, and then initialize all of
 * the necessary fields (head, size, readers, writers, and the locks
 * and queues.)
 */
static pipe_t *pipe_create(void)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_create");
    return NULL;
}

/*
 * Free all necessary memory.
 */
static void pipe_destroy(pipe_t *pipe)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_destroy");
}

/* pipefs vnode operations */
static void pipe_read_vnode(fs_t *fs, vnode_t *vnode)
{
    vnode->vn_ops = &pipe_vops;
    vnode->vn_mode = S_IFIFO;
    vnode->vn_len = 0;
    vnode->vn_i = NULL;
}

static void pipe_delete_vnode(fs_t *fs, vnode_t *vnode)
{
    pipe_t *p = VNODE_TO_PIPE(vnode);
    if (p)
    {
        pipe_destroy(p);
    }
}

/*
 * Gets a new vnode representing a pipe. The reason
 * why we don't just do this setup in pipe_read_vnode
 * is that the creation of the pipe data might fail, since
 * there is memory allocation going on in there. Thus,
 * we split it into two steps, the first of which relies on
 * pipe_read_vnode to do some setup, and then the pipe_create
 * call, at which point we can safely vput the allocated
 * vnode if pipe_create fails.
 */
static vnode_t *pget(void)
{
    NOT_YET_IMPLEMENTED("PIPES: pget");
    return NULL;
}

/*
 * An implementation of the pipe(2) system call. You really
 * only have to worry about a few things:
 *   o Running out of memory when allocating the vnode, at which
 *     point you should fail with ENOMEM;
 *   o Running out of file descriptors, in which case you should
 *     fail with EMFILE.
 * Once all of the structures are set up, just put the read-end
 * file descriptor of the pipe into pipefd[0], and the write-end
 * descriptor into pipefd[1].
 */
int do_pipe(int pipefd[2])
{
    NOT_YET_IMPLEMENTED("PIPES: do_pipe");
    return -ENOTSUP;
}

/*
 * When reading from a pipe, you should make sure there are enough characters in
 * the buffer to read. If there are, grab them and move up the tail by
 * subtracting from size. offset is ignored. Also, remember to take the reader
 * lock to prevent other threads from reading while you are waiting for more
 * characters.
 *
 * This might block, e.g. if there are no or not enough characters to read.
 * It might be the case that there are no more writers and we aren't done
 * reading. However, in situations like this, there is no way to open the pipe
 * for writing again so no more writers will ever put characters in the pipe.
 * The reader should just take as much as it needs (or barring that, as much as
 * it can get) and return with a partial buffer.
 */
static long pipe_read(vnode_t *vnode, size_t pos, void *buf, size_t count)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_read");
    return -EINVAL;
}

/*
 * Writing to a pipe is the dual of reading: if there is room, we can write our
 * data and go, but if not, we have to wait until there is more room and alert
 * any potential readers. Like above, you should take the writer lock to make
 * sure your write is contiguous.
 *
 * If there are no more readers, we have a broken pipe, and should fail with
 * the EPIPE error number.
 */
static long pipe_write(vnode_t *vnode, size_t pos, const void *buf,
                       size_t count)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_write");
    return -EINVAL;
}

/*
 * It's still possible to stat a pipe using the fstat call, which takes a file
 * descriptor. Pipes don't have too much information, though. The only ones that
 * matter here are st_mode and st_ino, though you want to zero out some of the
 * others.
 */
static long pipe_stat(vnode_t *vnode, stat_t *ss)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_stat");
    return -EINVAL;
}

/*
 * If someone is opening the read end of the pipe, we need to increment
 * the reader count, and the same for the writer count if a file open
 * for writing is acquiring this vnode. This count needs to be accurate
 * for correct reading and writing behavior.
 */
static long pipe_acquire(vnode_t *vnode, file_t *file)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_acquire");
    return 0;
}

/*
 * Subtract from the reader or writer count as necessary here. If either
 * count hits zero, you are going to need to wake up the other group of
 * threads so they can either return with their partial read or notice
 * the broken pipe.
 */
static long pipe_release(vnode_t *vnode, file_t *file)
{
    NOT_YET_IMPLEMENTED("PIPES: pipe_release");
    return 0;
}