1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "netsvc.h"
6
7#include <errno.h>
8#include <fcntl.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#include <sys/stat.h>
14
15#include <inet6/inet6.h>
16#include <inet6/netifc.h>
17
18#include <zircon/processargs.h>
19#include <zircon/syscalls.h>
20
21#include <zircon/boot/netboot.h>
22
23#define TMP_SUFFIX ".netsvc.tmp"
24
25netfile_state netfile = {
26    .fd = -1,
27    .needs_rename = false,
28};
29
30static int netfile_mkdir(const char* filename) {
31    const char* ptr = filename[0] == '/' ? filename + 1 : filename;
32    struct stat st;
33    char tmp[1024];
34    for (;;) {
35        ptr = strchr(ptr, '/');
36        if (!ptr) {
37            return 0;
38        }
39        memcpy(tmp, filename, ptr - filename);
40        tmp[ptr - filename] = '\0';
41        ptr += 1;
42        if (stat(tmp, &st) < 0) {
43            if (errno == ENOENT) {
44                if (mkdir(tmp, 0755) < 0) {
45                    return -1;
46                }
47            } else {
48                return -1;
49            }
50        }
51    }
52}
53
54int netfile_open(const char *filename, uint32_t arg, size_t* file_size) {
55    if (netfile.fd >= 0) {
56        printf("netsvc: closing still-open '%s', replacing with '%s'\n", netfile.filename, filename);
57        close(netfile.fd);
58        netfile.fd = -1;
59    }
60    size_t len = strlen(filename);
61    strlcpy(netfile.filename, filename, sizeof(netfile.filename));
62
63    struct stat st;
64again: // label here to catch filename=/path/to/new/directory/
65    if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) {
66        errno = EISDIR;
67        goto err;
68    }
69
70    switch (arg) {
71    case O_RDONLY:
72        netfile.needs_rename = false;
73        netfile.fd = open(filename, O_RDONLY);
74        if (file_size) {
75            *file_size = st.st_size;
76        }
77        break;
78    case O_WRONLY: {
79        // If we're writing a file, actually write to "filename + TMP_SUFFIX",
80        // and rename to the final destination when we would close. This makes
81        // written files appear to atomically update.
82        if (len + strlen(TMP_SUFFIX) + 1 > PATH_MAX) {
83            errno = ENAMETOOLONG;
84            goto err;
85        }
86        strcat(netfile.filename, TMP_SUFFIX);
87        netfile.needs_rename = true;
88        netfile.fd = open(netfile.filename, O_WRONLY|O_CREAT|O_TRUNC);
89        netfile.filename[len] = '\0';
90        if (netfile.fd < 0 && errno == ENOENT) {
91            if (netfile_mkdir(filename) == 0) {
92                goto again;
93            }
94        }
95        break;
96    }
97    default:
98        printf("netsvc: open '%s' with invalid mode %d\n", filename, arg);
99        errno = EINVAL;
100    }
101    if (netfile.fd < 0) {
102        goto err;
103    } else {
104        strlcpy(netfile.filename, filename, sizeof(netfile.filename));
105        netfile.offset = 0;
106    }
107
108    return 0;
109err:
110    netfile.filename[0] = '\0';
111    return -errno;
112}
113
114int netfile_offset_read(void* data_out, off_t offset, size_t max_len) {
115    if (netfile.fd < 0) {
116        printf("netsvc: read, but no open file\n");
117        return -EBADF;
118    }
119    if (offset != netfile.offset) {
120        if (lseek(netfile.fd, offset, SEEK_SET) != offset) {
121            return -errno;
122        }
123        netfile.offset = offset;
124    }
125    return netfile_read(data_out, max_len);
126}
127
128int netfile_read(void *data_out, size_t data_sz) {
129    if (netfile.fd < 0) {
130        printf("netsvc: read, but no open file\n");
131        return -EBADF;
132    }
133    ssize_t n = read(netfile.fd, data_out, data_sz);
134    if (n < 0) {
135        printf("netsvc: error reading '%s': %d\n", netfile.filename, errno);
136        int result = (errno == 0) ? -EIO : -errno;
137        close(netfile.fd);
138        netfile.fd = -1;
139        return result;
140    }
141    netfile.offset += n;
142    return n;
143}
144
145int netfile_offset_write(const char* data, off_t offset, size_t length) {
146    if (netfile.fd < 0) {
147        printf("netsvc: write, but no open file\n");
148        return -EBADF;
149    }
150    if (offset != netfile.offset) {
151        if (lseek(netfile.fd, offset, SEEK_SET) != offset) {
152            return -errno;
153        }
154        netfile.offset = offset;
155    }
156    return netfile_write(data, length);
157}
158
159int netfile_write(const char* data, size_t len) {
160    if (netfile.fd < 0) {
161        printf("netsvc: write, but no open file\n");
162        return -EBADF;
163    }
164    ssize_t n = write(netfile.fd, data, len);
165    if (n != (ssize_t)len) {
166        printf("netsvc: error writing %s: %d\n", netfile.filename, errno);
167        int result = (errno == 0) ? -EIO : -errno;
168        close(netfile.fd);
169        netfile.fd = -1;
170        return result;
171    }
172    netfile.offset += len;
173    return len;
174}
175
176int netfile_close(void) {
177    int result = 0;
178    if (netfile.fd < 0) {
179        printf("netsvc: close, but no open file\n");
180    } else {
181        if (netfile.needs_rename) {
182            char src[PATH_MAX];
183            strlcpy(src, netfile.filename, sizeof(src));
184            strlcat(src, TMP_SUFFIX, sizeof(src));
185            if (rename(src, netfile.filename)) {
186                printf("netsvc: failed to rename temporary file: %s\n", strerror(errno));
187            }
188        }
189        if (close(netfile.fd)) {
190            result = (errno == 0) ? -EIO : -errno;
191        }
192        netfile.fd = -1;
193    }
194    return result;
195}
196
197// Clean up if we abort before finishing a write. Close out and unlink it, rather than
198// leaving an incomplete file.
199void netfile_abort_write(void) {
200    if (netfile.fd < 0) {
201        return;
202    }
203    close(netfile.fd);
204    netfile.fd = -1;
205    char tmp[PATH_MAX];
206    const char* filename;
207    if (netfile.needs_rename) {
208        strlcpy(tmp, netfile.filename, sizeof(tmp));
209        strlcat(tmp, TMP_SUFFIX, sizeof(tmp));
210        filename = tmp;
211    } else {
212        filename = netfile.filename;
213    }
214    if (unlink(filename) != 0) {
215        printf("netsvc: failed to unlink aborted file %s\n", filename);
216    }
217}
218