1 /* Copyright  (C) 2010-2020 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (nbio_linux.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <file/nbio.h>
24 
25 #if defined(__linux__)
26 
27 #ifndef _GNU_SOURCE
28 #define _GNU_SOURCE
29 #endif
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdint.h>
33 #include <string.h>
34 #include <time.h>
35 
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <sys/syscall.h>
39 #include <linux/aio_abi.h>
40 
41 struct nbio_linux_t
42 {
43    void* ptr;
44    aio_context_t ctx;
45    struct iocb cb;
46    size_t len;
47    int fd;
48    bool busy;
49 };
50 
51 /* there's also a Unix AIO thingy, but it's not in glibc
52  * and we don't want more dependencies */
53 
io_setup(unsigned nr,aio_context_t * ctxp)54 static int io_setup(unsigned nr, aio_context_t * ctxp)
55 {
56    return syscall(__NR_io_setup, nr, ctxp);
57 }
58 
io_destroy(aio_context_t ctx)59 static int io_destroy(aio_context_t ctx)
60 {
61    return syscall(__NR_io_destroy, ctx);
62 }
63 
io_submit(aio_context_t ctx,long nr,struct iocb ** cbp)64 static int io_submit(aio_context_t ctx, long nr, struct iocb ** cbp)
65 {
66    return syscall(__NR_io_submit, ctx, nr, cbp);
67 }
68 
io_cancel(aio_context_t ctx,struct iocb * iocb,struct io_event * result)69 static int io_cancel(aio_context_t ctx, struct iocb * iocb, struct io_event * result)
70 {
71    return syscall(__NR_io_cancel, ctx, iocb, result);
72 }
73 
io_getevents(aio_context_t ctx,long min_nr,long nr,struct io_event * events,struct timespec * timeout)74 static int io_getevents(aio_context_t ctx, long min_nr, long nr,
75       struct io_event * events, struct timespec * timeout)
76 {
77    return syscall(__NR_io_getevents, ctx, min_nr, nr, events, timeout);
78 }
79 
nbio_begin_op(struct nbio_linux_t * handle,uint16_t op)80 static void nbio_begin_op(struct nbio_linux_t* handle, uint16_t op)
81 {
82    struct iocb * cbp         = &handle->cb;
83 
84    memset(&handle->cb, 0, sizeof(handle->cb));
85 
86    handle->cb.aio_fildes     = handle->fd;
87    handle->cb.aio_lio_opcode = op;
88 
89    handle->cb.aio_buf        = (uint64_t)(uintptr_t)handle->ptr;
90    handle->cb.aio_offset     = 0;
91    handle->cb.aio_nbytes     = handle->len;
92 
93    if (io_submit(handle->ctx, 1, &cbp) != 1)
94       abort();
95 
96    handle->busy = true;
97 }
98 
nbio_linux_open(const char * filename,unsigned mode)99 static void *nbio_linux_open(const char * filename, unsigned mode)
100 {
101    static const int o_flags[]  =   { O_RDONLY, O_RDWR|O_CREAT|O_TRUNC, O_RDWR, O_RDONLY, O_RDWR|O_CREAT|O_TRUNC };
102 
103    aio_context_t ctx           = 0;
104    struct nbio_linux_t* handle = NULL;
105    int fd                      = open(filename, o_flags[mode]|O_CLOEXEC, 0644);
106    if (fd < 0)
107       return NULL;
108 
109    if (io_setup(128, &ctx) < 0)
110    {
111       close(fd);
112       return NULL;
113    }
114 
115    handle       = (struct nbio_linux_t*)malloc(sizeof(struct nbio_linux_t));
116    handle->fd   = fd;
117    handle->ctx  = ctx;
118    handle->len  = lseek(fd, 0, SEEK_END);
119    handle->ptr  = malloc(handle->len);
120    handle->busy = false;
121 
122    return handle;
123 }
124 
nbio_linux_begin_read(void * data)125 static void nbio_linux_begin_read(void *data)
126 {
127    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
128    if (handle)
129       nbio_begin_op(handle, IOCB_CMD_PREAD);
130 }
131 
nbio_linux_begin_write(void * data)132 static void nbio_linux_begin_write(void *data)
133 {
134    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
135    if (handle)
136       nbio_begin_op(handle, IOCB_CMD_PWRITE);
137 }
138 
nbio_linux_iterate(void * data)139 static bool nbio_linux_iterate(void *data)
140 {
141    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
142    if (!handle)
143       return false;
144    if (handle->busy)
145    {
146       struct io_event ev;
147       if (io_getevents(handle->ctx, 0, 1, &ev, NULL) == 1)
148          handle->busy = false;
149    }
150    return !handle->busy;
151 }
152 
nbio_linux_resize(void * data,size_t len)153 static void nbio_linux_resize(void *data, size_t len)
154 {
155    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
156    if (!handle)
157       return;
158 
159    /* This works perfectly fine if this check is removed, but it
160     * won't work on other nbio implementations */
161    /* therefore, it's blocked so nobody accidentally relies on it */
162    if (len < handle->len)
163       abort();
164 
165    if (ftruncate(handle->fd, len) != 0)
166       abort(); /* this one returns void and I can't find any other way
167                   for it to report failure */
168 
169    handle->ptr = realloc(handle->ptr, len);
170    handle->len = len;
171 }
172 
nbio_linux_get_ptr(void * data,size_t * len)173 static void *nbio_linux_get_ptr(void *data, size_t* len)
174 {
175    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
176    if (!handle)
177       return NULL;
178    if (len)
179       *len = handle->len;
180    if (!handle->busy)
181       return handle->ptr;
182    return NULL;
183 }
184 
nbio_linux_cancel(void * data)185 static void nbio_linux_cancel(void *data)
186 {
187    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
188    if (!handle)
189       return;
190 
191    if (handle->busy)
192    {
193       struct io_event ev;
194       io_cancel(handle->ctx, &handle->cb, &ev);
195       handle->busy = false;
196    }
197 }
198 
nbio_linux_free(void * data)199 static void nbio_linux_free(void *data)
200 {
201    struct nbio_linux_t* handle = (struct nbio_linux_t*)data;
202    if (!handle)
203       return;
204 
205    io_destroy(handle->ctx);
206    close(handle->fd);
207    free(handle->ptr);
208    free(handle);
209 }
210 
211 nbio_intf_t nbio_linux = {
212    nbio_linux_open,
213    nbio_linux_begin_read,
214    nbio_linux_begin_write,
215    nbio_linux_iterate,
216    nbio_linux_resize,
217    nbio_linux_get_ptr,
218    nbio_linux_cancel,
219    nbio_linux_free,
220    "nbio_linux",
221 };
222 #else
223 nbio_intf_t nbio_linux = {
224    NULL,
225    NULL,
226    NULL,
227    NULL,
228    NULL,
229    NULL,
230    NULL,
231    NULL,
232    "nbio_linux",
233 };
234 
235 #endif
236