summaryrefslogtreecommitdiff
path: root/kern/usermem.c
blob: 34b98b22bd867726c90298637930908a6b5efd99 (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
/** @file usermem.c
 *
 *  @brief userspace r/w functions.
 *
 *  @author Hanjie Wu (hanjiew)
 *  @bug No functional bugs
 */

#include <stdlib.h>
#include <string.h>

#include <common_kern.h>
#include <x86/asm.h>
#include <x86/seg.h>

#include <mm.h>
#include <paging.h>
#include <pts.h>
#include <sched.h>

/**
 * @brief try to read a byte from userspace
 * @param addr address
 * @param output output byte if successful
 * @return 0 on success, jump to usermem_fail on failure
 */
int try_read(va_t addr, char* output);
/**
 * @brief try to write a byte to userspace
 * @param addr address
 * @param c byte to write
 * @return 0 on success, jump to usermem_fail on failure
 */
int try_write(va_t addr, char c);

/**
 * @brief tell the calling function the r/w failed
 * @return -1
 */
int usermem_fail();

/**
 * @brief setup fault handler before r/w
 * @return old fault handler
 */
static reg_t usermem_setup() {
    thread_t* current = get_current();
    reg_t old_eip0 = current->eip0;
    current->eip0 = (reg_t)usermem_fail;
    if (current->process->pv != NULL) {
        set_gs(SEGSEL_PV_DS);
    } else {
        set_gs(SEGSEL_USER_DS);
    }
    return old_eip0;
}

/**
 * @brief restore old fault handler
 * @param old_eip0 old fault handler
 */
static void usermem_finish(reg_t old_eip0) {
    get_current()->eip0 = old_eip0;
}

int copy_from_user(va_t addr, int size, void* buf) {
    reg_t old_eip0 = usermem_setup();
    int i, result = 0;
    for (i = 0; i < size; i++) {
        if (try_read(addr + i, (char*)buf + i) != 0) {
            result = -1;
            break;
        }
    }
    usermem_finish(old_eip0);
    return result;
}

int copy_to_user(va_t addr, int size, void* buf) {
    reg_t old_eip0 = usermem_setup();
    int i, result = 0;
    for (i = 0; i < size; i++) {
        if (try_write(addr + i, *((char*)buf + i)) != 0) {
            result = -1;
            break;
        }
    }
    usermem_finish(old_eip0);
    return result;
}

char* copy_string_from_user(va_t addr, int maxlen) {
    reg_t old_eip0 = usermem_setup();
    int len = 0, buflen = 3 * sizeof(int);
    char* buf = malloc(buflen);
    char c;
    if (buf == NULL) {
        goto read_string_finished;
    }
    while (1) {
        if (len >= maxlen || try_read(addr + len, &c) != 0) {
            free(buf);
            buf = NULL;
            break;
        }

        buf[len++] = c;
        if (c == '\0') {
            break;
        }

        if (len >= buflen) {
            buflen <<= 1;
            char* result = realloc(buf, buflen);
            if (result == NULL) {
                free(buf);
                buf = NULL;
                break;
            }
            buf = result;
        }
    }
read_string_finished:
    usermem_finish(old_eip0);
    return buf;
}

int print_buf_from_user(pts_t* pts, va_t addr, int len) {
    reg_t old_eip0 = usermem_setup();
    int i, result = 0;
    char c;
    for (i = 0; i < len; i++) {
        if (try_read(addr + i, &c) != 0) {
            result = -1;
            break;
        }
        pts_putbyte(pts, c);
    }
    usermem_finish(old_eip0);
    return result;
}