1 /**
2 * diskmanager.c
3 *
4 * Copyright (C) 2018 by David McNaughton
5 *
6 * History:
7 * 12-JUL-18 1.0 Initial Release
8 */
9
10 /**
11 * This diskmanager module provides an alternate interface for the array
12 *
13 * char *disks[];
14 *
15 * that is typically implemented in a disk controller simulators e.g.
16 * - imsai-fif.c
17 * - tarbell_fdc.c
18 *
19 * It persists the array in a file, typically "disk.map" [DISKMAP] in the
20 * provided path.
21 *
22 * The "disk.map" file is a simple text file with a line for each disk
23 * starting at 'A' upto [LAST_DISK], typically 'D'.
24 * - If a line is empty of starts with '#' the disk is "ejected"
25 * - If a disk image is "inserted" the line conatains only the file name
26 *
27 * The diskmanager provides functions to:
28 * - populate the array from the file
29 * - write the array to the file
30 * - insert a disk
31 * - stat() disk image files to validate them before inserting
32 * - reject inserting the same disk image in 2 disk drives
33 * - eject a disk
34 * - and some other support functions.
35 *
36 * TODO:
37 * - fully support paths to allow a disk libabry hierarchy
38 * - support more complex disk arrays that conatin structures e.g.
39 * * cpmsim::iosim.c
40 * * cromemco-fdc.c
41 */
42
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <errno.h>
47 #include <string.h>
48 #include <sys/stat.h>
49 #define LOCAL_LOG_LEVEL LOG_DEBUG
50 #include "log.h"
51 #include "sim.h"
52 #ifdef HAS_NETSERVER
53 #include "civetweb.h"
54 #include "netsrv.h"
55 #endif
56
57
58 #ifdef HAS_DISKMANAGER
59 #ifndef UNUSED
60 #define UNUSED(x) (void)(x)
61 #endif
62
63 #ifndef DISKMAP
64 #define DISKMAP "disk.map"
65 #endif
66
67 #define LAST_DISK 'D'
68 #define _MAX_DISK (LAST_DISK - '@')
69
70 static const char *TAG = "diskmanager";
71
72 char *disks[_MAX_DISK];
73 extern char *disks[];
74
75 static char path[MAX_LFN+1]; /* path/filename for disk image */
76 static char *file_start;
77 #define APPENDTOPATH(file) strncpy(file_start, file, MAX_LFN - strlen(path));
78
79 enum disk_err {
80 SUCCESS,
81 INVALID_DISK_NUM,
82 DRIVE_EMPTY,
83 DRIVE_NOT_EMPTY,
84 IMAGE_ALREADY_INSERTED,
85 IMAGE_NOT_VALID,
86 FAILURE
87 };
88
89 typedef enum disk_err disk_err_t;
90
findDiskImage(const char * image)91 int findDiskImage(const char *image) {
92 int i;
93
94 for (i = 0; i < _MAX_DISK; i++) {
95 if (disks[i] != NULL) {
96 if (strcmp(image, disks[i]) == 0) {
97 return 1;
98 }
99 }
100 }
101 return 0;
102 }
103
getDiskNumByID(const char * id)104 int getDiskNumByID(const char *id) {
105 int disk = -1;
106
107 /* id is in the form X:DSK: making it 6 characters long */
108 LOGD(TAG, "GET ID: %s, %ld", id, strlen(id));
109 if (id != NULL && strlen(id) == 6 && strcmp(id+1, ":DSK:") == 0) {
110 disk = *id - 'A';
111 if (disk < 0 || disk > (_MAX_DISK - 1)) {
112 LOGW(TAG, "BAD DISK ID: %c", *id);
113 disk = -1;
114 }
115 } else {
116 LOGW(TAG, "DISK ID required, got: %s", id);
117 disk = -1;
118 }
119 return disk;
120 }
121
insertDisk(int disk,const char * image)122 disk_err_t insertDisk(int disk, const char *image) {
123
124 char *name;
125 struct stat image_status;
126 int i;
127
128 if (disk >= 0 && disk < _MAX_DISK) {
129
130 if (disks[disk] != NULL) {
131 return DRIVE_NOT_EMPTY;
132 }
133
134 if (image != NULL && strlen(image) < MAX_LFN) {
135
136 for (i = 0; i < _MAX_DISK; i++) {
137 if (disks[i] != NULL && strcmp(image, disks[i]) == 0) {
138 return IMAGE_ALREADY_INSERTED;
139 }
140 }
141
142 APPENDTOPATH(image);
143
144 if (stat(path, &image_status) == 0) {
145 if (S_ISREG(image_status.st_mode)) {
146 name = strndup(image, MAX_LFN);
147 if (name == NULL) {
148 LOGW(TAG, "Failed to insert disk, not enough memory.");
149 return FAILURE;
150 } else {
151 /* Everything is OK, we can insert the disk */
152 disks[disk] = name;
153 return SUCCESS;
154 }
155 } else {
156 return IMAGE_NOT_VALID;
157 }
158 } else {
159 LOGW(TAG, "Failed to stat disk image: %s, error: %d", image, errno);
160 return IMAGE_NOT_VALID;
161 }
162 }
163
164 return IMAGE_NOT_VALID;
165 }
166
167 return INVALID_DISK_NUM;
168 }
169
ejectDisk(int disk)170 disk_err_t ejectDisk(int disk) {
171
172 char *name;
173
174 if (disk >= 0 || disk < _MAX_DISK) {
175
176 if (disks[disk] == NULL) {
177 return DRIVE_EMPTY;
178 }
179
180 name = disks[disk];
181 disks[disk] = NULL;
182 free(name);
183
184 return SUCCESS;
185 }
186
187 return INVALID_DISK_NUM;
188 }
189
writeDiskmap(void)190 void writeDiskmap(void) {
191 FILE *map;
192 int i;
193
194 if (*path == '\0') {
195 LOGW(TAG, "Path to disk map not set. Call readDiskmap(path) first.");
196 return;
197 }
198
199 APPENDTOPATH(DISKMAP);
200
201 map = fopen(path, "w");
202 if (map == NULL) {
203 LOGW(TAG, "Can't create disk map: %s", path);
204 return;
205 }
206
207 for (i = 0; i < _MAX_DISK; i++) {
208 fprintf(map, "%s\n", disks[i]==NULL?"#":disks[i]);
209 }
210 fclose(map);
211 }
212
readDiskmap(char * path_name)213 void readDiskmap(char *path_name) {
214 FILE *map;
215 char *line = NULL;
216 char *name;
217 size_t len;
218 ssize_t res;
219 int i;
220 disk_err_t insert;
221
222 for (i = 0; i < _MAX_DISK; i++) {
223 disks[i] = NULL;
224 }
225
226 strncpy(path, path_name, MAX_LFN);
227 strncat(path, "/", MAX_LFN - strlen(path));
228 file_start = path + strlen(path);
229
230 APPENDTOPATH(DISKMAP);
231 LOGD(TAG, "LIB: path: %s, diskmap: %s", path, file_start);
232
233 i = 0;
234 again:
235 map = fopen(path, "r");
236 if (map == NULL) {
237 if (i++ == 0) {
238 LOGW(TAG, "No disk map: %s, attempting to create file.", path);
239 writeDiskmap();
240 goto again;
241 }
242 return;
243 }
244
245 LOG(TAG, "DISK MAP: [%s]\r\n", path);
246
247 for (i = 0; i < _MAX_DISK; i++) {
248 line = NULL;
249 res = getline(&line, &len, map);
250
251 if (res != -1) {
252 if (line[res-1] == '\n')
253 line[res-1] = '\0';
254
255 /* empty lines or lines that begin with # are empty disks */
256 name = ((line[0]=='\0') || (line[0]=='#'))?NULL:line;
257 if (name != NULL) {
258 insert = insertDisk(i, name);
259
260 switch (insert) {
261 case SUCCESS :
262 LOG(TAG, "%c:DSK:='%s'\r\n", i+'A', disks[i]);
263 break;
264 case IMAGE_ALREADY_INSERTED :
265 LOGW(TAG, "%c:DSK: Image file '%s' already in use", i+'A', name);
266 break;
267 case IMAGE_NOT_VALID :
268 LOGW(TAG, "%c:DSK: Image file '%s' is invalid", i+'A', name);
269 break;
270 default:
271 LOGW(TAG, "%c:DSK: Failed to insert disk, error: %d", i+'A', insert)
272 break;
273 }
274 }
275 }
276 free(line);
277 }
278 fclose(map);
279 }
280
281 #ifdef HAS_NETSERVER
282 /**
283 * Web Server handlers for LIB: and X:DSK:
284 *
285 */
286 extern int DirectoryHandler(struct mg_connection *, void *);
287 extern int UploadHandler(struct mg_connection *, void *);
288
LibraryHandler(HttpdConnection_t * conn,void * unused)289 int LibraryHandler(HttpdConnection_t *conn, void *unused) {
290 request_t *req = get_request(conn);
291 int i = 0;
292 UNUSED(unused);
293
294 *file_start = '\0';
295
296 if (*path == '\0') {
297 LOGW(TAG, "Path to disk map not set. Call readDiskmap(path) first.");
298 return 0;
299 }
300
301 switch(req->method) {
302 case HTTP_GET:
303 DirectoryHandler(conn, path);
304 break;
305 case HTTP_PUT:
306 UploadHandler(conn, path);
307 LOGI(TAG, "PUT image: image uploaded.");
308 break;
309 case HTTP_DELETE:
310 if (req->len > 0) {
311 i = mg_read(conn, file_start, MAX_LFN - strlen(path));
312 *(file_start + i) = '\0';
313
314 LOGD(TAG, "DELETE image: %s, length: %lld", file_start, req->len);
315
316 if (findDiskImage(file_start)) {
317 LOGW(TAG, "DELETE image: %s, currently inserted in disks", file_start);
318 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
319 httpdEndHeaders(conn);
320 return 1;
321 }
322
323 if (unlink(path) < 0) {
324 LOGW(TAG, "DELETE image: %s, unlink failed [%d]", path, errno);
325 httpdStartResponse(conn, 410); /* http error code 'Gone' */
326 httpdEndHeaders(conn);
327 } else {
328 LOGI(TAG, "DELETE image: %s, deleted.", path);
329 httpdStartResponse(conn, 200);
330 httpdEndHeaders(conn);
331 httpdPrintf(conn, "Deleted");
332 };
333 }
334 break;
335 default:
336 httpdStartResponse(conn, 405); /* http error code 'Method Not Allowed' */
337 httpdEndHeaders(conn);
338 break;
339 }
340 return 1;
341 }
342
sendDisks(struct mg_connection * conn)343 static void sendDisks(struct mg_connection *conn) {
344
345 int i;
346
347 httpdStartResponse(conn, 200);
348 httpdHeader(conn, "Content-Type", "application/json");
349 httpdEndHeaders(conn);
350
351 httpdPrintf(conn, "{");
352
353 for (i = 0; i < _MAX_DISK; i++) {
354 httpdPrintf(conn, "\"%c\": \"%s\"", i+'A', disks[i]==NULL?"":disks[i]);
355 if (i < (_MAX_DISK - 1)) httpdPrintf(conn, ",");
356 }
357
358 httpdPrintf(conn, "}");
359 }
360
DiskHandler(HttpdConnection_t * conn,void * unused)361 int DiskHandler(HttpdConnection_t *conn, void *unused) {
362 request_t *req = get_request(conn);
363 int disk, i;
364 char image[MAX_LFN];
365 disk_err_t result;
366 UNUSED(unused);
367
368 switch(req->method) {
369 case HTTP_GET:
370 LOGD(TAG, "GET /disks");
371 sendDisks(conn);
372 break;
373 case HTTP_PUT:
374 LOGD(TAG, "PUT /disks: %s", req->args[0]);
375
376 disk = getDiskNumByID(req->args[0]);
377 image[0] = '\0';
378
379 if (req->len > 0) {
380 i = mg_read(conn, image, MAX_LFN);
381 image[i] = '\0';
382 };
383
384 LOGD(TAG, "PUT image length: %d [%s]", (int) req->len, image);
385
386 result = insertDisk(disk, image);
387
388 switch (result) {
389 case SUCCESS :
390 writeDiskmap();
391 sendDisks(conn);
392 break;
393 case DRIVE_NOT_EMPTY :
394 LOGW(TAG, "PUT /disks NOT EMPTY");
395 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
396 httpdEndHeaders(conn);
397 break;
398 case IMAGE_ALREADY_INSERTED :
399 LOGW(TAG, "PUT image: %s, already inserted in disks", image);
400 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
401 httpdEndHeaders(conn);
402 break;
403 default:
404 LOGW(TAG, "PUT image: %s, failed to insert disk: %d, error: %d", image, disk, result)
405 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
406 httpdEndHeaders(conn);
407 break;
408 }
409 break;
410 case HTTP_DELETE:
411 LOGD(TAG, "DELETE /disks: %s", req->args[0]);
412
413 disk = getDiskNumByID(req->args[0]);
414 result = ejectDisk(disk);
415
416 switch (result) {
417 case SUCCESS :
418 sendDisks(conn);
419 writeDiskmap();
420 break;
421 case DRIVE_EMPTY :
422 LOGW(TAG, "DELETE /disks ALREADY EMPTY");
423 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
424 httpdEndHeaders(conn);
425 break;
426 default:
427 LOGW(TAG, "DELETE /disks failed to eject disk: %d, error: %d", disk, result)
428 httpdStartResponse(conn, 404); /* http error code 'Not Found' */
429 httpdEndHeaders(conn);
430 break;
431 }
432 break;
433 default:
434 httpdStartResponse(conn, 405); /* http error code 'Method Not Allowed' */
435 httpdEndHeaders(conn);
436 }
437
438 return 1;
439 }
440 #endif
441 #endif
442