xref: /haiku/src/bin/ramdisk.cpp (revision 002f37b0cca92e4cf72857c72ac95db5a8b09615)
1 /*
2  * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <dirent.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <getopt.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include <Entry.h>
16 #include <Path.h>
17 #include <String.h>
18 
19 #include <AutoDeleter.h>
20 #include <TextTable.h>
21 
22 #include <file_systems/ram_disk/ram_disk.h>
23 
24 
25 extern const char* __progname;
26 static const char* kProgramName = __progname;
27 
28 static const char* const kUsage =
29 	"Usage: %s <command> [ <options> ]\n"
30 	"Controls RAM disk devices.\n"
31 	"\n"
32 	"Commands:\n"
33 	"  create (-s <size> | <path>)\n"
34 	"    Creates a new RAM disk.\n"
35 	"  delete <id>\n"
36 	"    Deletes an existing RAM disk.\n"
37 	"  flush <id>\n"
38 	"    Writes modified data of an existing RAM disk back to its file.\n"
39 	"  help\n"
40 	"    Print this usage info.\n"
41 	"  list\n"
42 	"    List all RAM disks.\n"
43 ;
44 
45 static const char* const kCreateUsage =
46 	"Usage: %s %s (-s <size> | <path>)\n"
47 	"Creates a new RAM disk device. If the <size> argument is specified, a\n"
48 	"new zeroed RAM disk with that size (in bytes, suffixes 'k', 'm', 'g' are\n"
49 	"interpreted as KiB, MiB, GiB) is registered.\n"
50 	"Alternatively a file path can be specified. In that case the RAM disk \n"
51 	"data are initially read from that file and at any later point the\n"
52 	"modified RAM disk data can be written back to the same file upon request\n"
53 	"(via the \"flush\" command). The size of the RAM disk is implied by that\n"
54 	"of the file.\n"
55 ;
56 
57 static const char* const kDeleteUsage =
58 	"Usage: %s %s <id>\n"
59 	"Deletes the existing RAM disk with ID <id>. All modified data will be\n"
60 	"lost.\n"
61 ;
62 
63 static const char* const kFlushUsage =
64 	"Usage: %s %s <id>\n"
65 	"Writes all modified data of the RAM disk with ID <id> back to the file\n"
66 	"specified when the RAM disk was created. Fails, if the RAM disk had been\n"
67 	"created without an associated file.\n"
68 ;
69 
70 static const char* const kListUsage =
71 	"Usage: %s %s\n"
72 	"Lists all existing RAM disks.\n"
73 ;
74 
75 static const char* const kRamDiskControlDevicePath
76 	= "/dev/" RAM_DISK_CONTROL_DEVICE_NAME;
77 static const char* const kRamDiskRawDeviceBasePath
78 	= "/dev/" RAM_DISK_RAW_DEVICE_BASE_NAME;
79 
80 static const char* sCommandName = NULL;
81 static const char* sCommandUsage = NULL;
82 
83 
84 static void
85 print_usage_and_exit(bool error)
86 {
87 	if (sCommandUsage != NULL) {
88 	    fprintf(error ? stderr : stdout, sCommandUsage, kProgramName,
89 			sCommandName);
90 	} else
91 	    fprintf(error ? stderr : stdout, kUsage, kProgramName);
92     exit(error ? 1 : 0);
93 }
94 
95 
96 static status_t
97 execute_control_device_ioctl(int operation, void* request)
98 {
99 	// open the ram disk control device
100 	int fd = open(kRamDiskControlDevicePath, O_RDONLY);
101 	if (fd < 0) {
102 		fprintf(stderr, "Error: Failed to open RAM disk control device \"%s\": "
103 			"%s\n", kRamDiskControlDevicePath, strerror(errno));
104 		return errno;
105 	}
106 	FileDescriptorCloser fdCloser(fd);
107 
108 	// issue the request
109 	if (ioctl(fd, operation, request) < 0)
110 		return errno;
111 
112 	return B_OK;
113 }
114 
115 
116 static int
117 command_register(int argc, const char* const* argv)
118 {
119 	sCommandUsage = kCreateUsage;
120 
121 	int64 deviceSize = -1;
122 
123 	while (true) {
124 		static struct option sLongOptions[] = {
125 			{ "size", required_argument, 0, 's' },
126 			{ "help", no_argument, 0, 'h' },
127 			{ 0, 0, 0, 0 }
128 		};
129 
130 		opterr = 0; // don't print errors
131 		int c = getopt_long(argc, (char**)argv, "+s:h", sLongOptions, NULL);
132 		if (c == -1)
133 			break;
134 
135 		switch (c) {
136 			case 'h':
137 				print_usage_and_exit(false);
138 				break;
139 
140 			case 's':
141 			{
142 				const char* sizeString = optarg;
143 				char* end;
144 				deviceSize = strtoll(sizeString, &end, 0);
145 				if (end != sizeString && deviceSize > 0) {
146 					int64 originalDeviceSize = deviceSize;
147 					switch (*end) {
148 						case 'g':
149 							deviceSize *= 1024;
150 						case 'm':
151 							deviceSize *= 1024;
152 						case 'k':
153 							deviceSize *= 1024;
154 							end++;
155 							break;
156 						case '\0':
157 							break;
158 						default:
159 							deviceSize = -1;
160 							break;
161 					}
162 
163 					if (deviceSize > 0 && originalDeviceSize > deviceSize)
164 						deviceSize = -1;
165 				}
166 
167 				if (deviceSize <= 0) {
168 					fprintf(stderr, "Error: Invalid size argument: \"%s\"\n",
169 						sizeString);
170 					return 1;
171 				}
172 
173 				// check maximum size
174 				system_info info;
175 				get_system_info(&info);
176 				if (deviceSize / B_PAGE_SIZE > (int64)info.max_pages * 2 / 3) {
177 					fprintf(stderr, "Error: Given RAM disk size too large.\n");
178 					return 1;
179 				}
180 
181 				break;
182 			}
183 
184 			default:
185 				print_usage_and_exit(true);
186 				break;
187 		}
188 	}
189 
190 	// The remaining optional argument is the file path. It may only be
191 	// specified, if no size has been specified.
192 	const char* path = optind < argc ? argv[optind++] : NULL;
193 	if (optind < argc || (deviceSize >= 0) == (path != NULL))
194 		print_usage_and_exit(true);
195 
196 	// prepare the request
197 	ram_disk_ioctl_register request;
198 	request.size = (uint64)deviceSize;
199 	request.path[0] = '\0';
200 	request.id = -1;
201 
202 	if (path != NULL) {
203 		// verify the path
204 		BEntry entry;
205 		status_t error = entry.SetTo(path, true);
206 		if (error == B_OK && !entry.Exists())
207 			error = B_ENTRY_NOT_FOUND;
208 		if (error != B_OK) {
209 			fprintf(stderr, "Error: Failed to resolve path \"%s\": %s\n",
210 				path, strerror(error));
211 			return 1;
212 		}
213 
214 		if (!entry.IsFile()) {
215 			fprintf(stderr, "Error: \"%s\" is not a file.\n", path);
216 			return 1;
217 		}
218 
219 		BPath normalizedPath;
220 		error = entry.GetPath(&normalizedPath);
221 		if (error != B_OK) {
222 			fprintf(stderr, "Error: Failed to normalize path \"%s\": %s\n",
223 				path, strerror(error));
224 			return 1;
225 		}
226 
227 		if (strlcpy(request.path, normalizedPath.Path(), sizeof(request.path))
228 				>= sizeof(request.path)) {
229 			fprintf(stderr, "Error: Normalized path too long.\n");
230 			return 1;
231 		}
232 	}
233 
234 	status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_REGISTER,
235 		&request);
236 	if (error != B_OK) {
237 		fprintf(stderr, "Error: Failed to create RAM disk device: %s\n",
238 			strerror(error));
239 		return 1;
240 	}
241 
242 	printf("RAM disk device created as \"%s/%" B_PRId32 "/raw\"\n",
243 		kRamDiskRawDeviceBasePath, request.id);
244 	return 0;
245 }
246 
247 
248 static int
249 command_unregister(int argc, const char* const* argv)
250 {
251 	sCommandUsage = kDeleteUsage;
252 
253 	while (true) {
254 		static struct option sLongOptions[] = {
255 			{ "help", no_argument, 0, 'h' },
256 			{ 0, 0, 0, 0 }
257 		};
258 
259 		opterr = 0; // don't print errors
260 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
261 		if (c == -1)
262 			break;
263 
264 		switch (c) {
265 			case 'h':
266 				print_usage_and_exit(false);
267 				break;
268 
269 			default:
270 				print_usage_and_exit(true);
271 				break;
272 		}
273 	}
274 
275 	// The remaining argument is the device ID.
276 	if (optind + 1 != argc)
277 		print_usage_and_exit(true);
278 
279 	const char* idString = argv[optind++];
280 	char* end;
281 	long long id = strtol(idString, &end, 0);
282 	if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) {
283 		fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString);
284 		return 1;
285 	}
286 
287 	// check whether the raw device for that ID exists
288 	BString path;
289 	path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
290 	struct stat st;
291 	if (lstat(path, &st) != 0) {
292 		fprintf(stderr, "Error: No RAM disk with ID %s.\n", idString);
293 		return 1;
294 	}
295 
296 	// issue the request
297 	ram_disk_ioctl_unregister request;
298 	request.id = (int32)id;
299 
300 	status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_UNREGISTER,
301 		&request);
302 	if (error != B_OK) {
303 		fprintf(stderr, "Error: Failed to delete RAM disk device: %s\n",
304 			strerror(error));
305 		return 1;
306 	}
307 
308 	return 0;
309 }
310 
311 
312 static int
313 command_flush(int argc, const char* const* argv)
314 {
315 	sCommandUsage = kFlushUsage;
316 
317 	while (true) {
318 		static struct option sLongOptions[] = {
319 			{ "help", no_argument, 0, 'h' },
320 			{ 0, 0, 0, 0 }
321 		};
322 
323 		opterr = 0; // don't print errors
324 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
325 		if (c == -1)
326 			break;
327 
328 		switch (c) {
329 			case 'h':
330 				print_usage_and_exit(false);
331 				break;
332 
333 			default:
334 				print_usage_and_exit(true);
335 				break;
336 		}
337 	}
338 
339 	// The remaining argument is the device ID.
340 	if (optind + 1 != argc)
341 		print_usage_and_exit(true);
342 
343 	const char* idString = argv[optind++];
344 	char* end;
345 	long long id = strtol(idString, &end, 0);
346 	if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) {
347 		fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString);
348 		return 1;
349 	}
350 
351 	// open the raw device
352 	BString path;
353 	path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
354 	int fd = open(path, O_RDONLY);
355 	if (fd < 0) {
356 		fprintf(stderr, "Error: Failed to open RAM disk device \"%s\"\n",
357 			path.String());
358 		return 1;
359 	}
360 	FileDescriptorCloser fdCloser(fd);
361 
362 	// issue the request
363 	if (ioctl(fd, RAM_DISK_IOCTL_FLUSH, NULL) < 0) {
364 		fprintf(stderr, "Error: Failed to flush RAM disk device: %s\n",
365 			strerror(errno));
366 		return 1;
367 	}
368 
369 	return 0;
370 }
371 
372 
373 static int
374 command_list(int argc, const char* const* argv)
375 {
376 	sCommandUsage = kListUsage;
377 
378 	while (true) {
379 		static struct option sLongOptions[] = {
380 			{ "help", no_argument, 0, 'h' },
381 			{ 0, 0, 0, 0 }
382 		};
383 
384 		opterr = 0; // don't print errors
385 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
386 		if (c == -1)
387 			break;
388 
389 		switch (c) {
390 			case 'h':
391 				print_usage_and_exit(false);
392 				break;
393 
394 			default:
395 				print_usage_and_exit(true);
396 				break;
397 		}
398 	}
399 
400 	// There shouldn't be any remaining arguments.
401 	if (optind != argc)
402 		print_usage_and_exit(true);
403 
404 	// iterate through the RAM disk device directory and search for raw devices
405 	DIR* dir = opendir(kRamDiskRawDeviceBasePath);
406 	if (dir == NULL) {
407 		fprintf(stderr, "Error: Failed to open RAM disk device directory: %s\n",
408 			strerror(errno));
409 		return 1;
410 	}
411 	CObjectDeleter<DIR, int> dirCloser(dir, &closedir);
412 
413 	TextTable table;
414 	table.AddColumn("ID", B_ALIGN_RIGHT);
415 	table.AddColumn("Size", B_ALIGN_RIGHT);
416 	table.AddColumn("Associated file");
417 
418 	while (dirent* entry = readdir(dir)) {
419 		// check, if the entry name could be an ID
420 		const char* idString = entry->d_name;
421 		char* end;
422 		long long id = strtol(idString, &end, 0);
423 		if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX)
424 			continue;
425 
426 		// open the raw device
427 		BString path;
428 		path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
429 		int fd = open(path, O_RDONLY);
430 		if (fd < 0)
431 			continue;
432 		FileDescriptorCloser fdCloser(fd);
433 
434 		// issue the request
435 		ram_disk_ioctl_info request;
436 		if (ioctl(fd, RAM_DISK_IOCTL_INFO, &request) < 0)
437 			continue;
438 
439 		int32 rowIndex = table.CountRows();
440 		table.SetTextAt(rowIndex, 0, BString() << request.id);
441 		table.SetTextAt(rowIndex, 1, BString() << request.size);
442 		table.SetTextAt(rowIndex, 2, request.path);
443 	}
444 
445 	if (table.CountRows() > 0)
446 		table.Print(INT32_MAX);
447 	else
448 		printf("No RAM disks.\n");
449 
450 	return 0;
451 }
452 
453 
454 int
455 main(int argc, const char* const* argv)
456 {
457 	if (argc < 2)
458 		print_usage_and_exit(true);
459 
460 	if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0
461 		|| strcmp(argv[1], "-h") == 0) {
462 		print_usage_and_exit(false);
463 	}
464 
465 	sCommandName = argv[1];
466 
467 	if (strcmp(sCommandName, "create") == 0)
468 		return command_register(argc - 1, argv + 1);
469 	if (strcmp(sCommandName, "delete") == 0)
470 		return command_unregister(argc - 1, argv + 1);
471 	if (strcmp(sCommandName, "flush") == 0)
472 		return command_flush(argc - 1, argv + 1);
473 	if (strcmp(sCommandName, "list") == 0)
474 		return command_list(argc - 1, argv + 1);
475 
476 	print_usage_and_exit(true);
477 }
478