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 
25 netfile_state netfile = {
26     .fd = -1,
27     .needs_rename = false,
28 };
29 
netfile_mkdir(const char * filename)30 static 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 
netfile_open(const char * filename,uint32_t arg,size_t * file_size)54 int 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;
64 again: // 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;
109 err:
110     netfile.filename[0] = '\0';
111     return -errno;
112 }
113 
netfile_offset_read(void * data_out,off_t offset,size_t max_len)114 int 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 
netfile_read(void * data_out,size_t data_sz)128 int 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 
netfile_offset_write(const char * data,off_t offset,size_t length)145 int 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 
netfile_write(const char * data,size_t len)159 int 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 
netfile_close(void)176 int 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.
netfile_abort_write(void)199 void 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