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