xref: /haiku/src/bin/ramdisk.cpp (revision 3af8011358bd4c624a0979336d48dabb466171ed)
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 	FileDescriptorCloser fd(open(kRamDiskControlDevicePath, O_RDONLY));
103 	if (!fd.IsSet()) {
104 		fprintf(stderr, "Error: Failed to open RAM disk control device \"%s\": "
105 			"%s\n", kRamDiskControlDevicePath, strerror(errno));
106 		return errno;
107 	}
108 
109 	// issue the request
110 	if (ioctl(fd.Get(), operation, request) < 0)
111 		return errno;
112 
113 	return B_OK;
114 }
115 
116 
117 static int
118 command_register(int argc, const char* const* argv)
119 {
120 	sCommandUsage = kCreateUsage;
121 
122 	int64 deviceSize = -1;
123 
124 	while (true) {
125 		static struct option sLongOptions[] = {
126 			{ "size", required_argument, 0, 's' },
127 			{ "help", no_argument, 0, 'h' },
128 			{ 0, 0, 0, 0 }
129 		};
130 
131 		opterr = 0; // don't print errors
132 		int c = getopt_long(argc, (char**)argv, "+s:h", sLongOptions, NULL);
133 		if (c == -1)
134 			break;
135 
136 		switch (c) {
137 			case 'h':
138 				print_usage_and_exit(false);
139 				break;
140 
141 			case 's':
142 			{
143 				const char* sizeString = optarg;
144 				deviceSize = parse_size(sizeString);
145 
146 				if (deviceSize <= 0) {
147 					fprintf(stderr, "Error: Invalid size argument: \"%s\"\n",
148 						sizeString);
149 					return 1;
150 				}
151 
152 				// check maximum size
153 				system_info info;
154 				get_system_info(&info);
155 				if (deviceSize / B_PAGE_SIZE > (int64)info.max_pages * 2 / 3) {
156 					fprintf(stderr, "Error: Given RAM disk size too large.\n");
157 					return 1;
158 				}
159 
160 				break;
161 			}
162 
163 			default:
164 				print_usage_and_exit(true);
165 				break;
166 		}
167 	}
168 
169 	// The remaining optional argument is the file path. It may only be
170 	// specified, if no size has been specified.
171 	const char* path = optind < argc ? argv[optind++] : NULL;
172 	if (optind < argc || (deviceSize >= 0) == (path != NULL))
173 		print_usage_and_exit(true);
174 
175 	// prepare the request
176 	ram_disk_ioctl_register request;
177 	request.size = (uint64)deviceSize;
178 	request.path[0] = '\0';
179 	request.id = -1;
180 
181 	if (path != NULL) {
182 		// verify the path
183 		BEntry entry;
184 		status_t error = entry.SetTo(path, true);
185 		if (error == B_OK && !entry.Exists())
186 			error = B_ENTRY_NOT_FOUND;
187 		if (error != B_OK) {
188 			fprintf(stderr, "Error: Failed to resolve path \"%s\": %s\n",
189 				path, strerror(error));
190 			return 1;
191 		}
192 
193 		if (!entry.IsFile()) {
194 			fprintf(stderr, "Error: \"%s\" is not a file.\n", path);
195 			return 1;
196 		}
197 
198 		BPath normalizedPath;
199 		error = entry.GetPath(&normalizedPath);
200 		if (error != B_OK) {
201 			fprintf(stderr, "Error: Failed to normalize path \"%s\": %s\n",
202 				path, strerror(error));
203 			return 1;
204 		}
205 
206 		if (strlcpy(request.path, normalizedPath.Path(), sizeof(request.path))
207 				>= sizeof(request.path)) {
208 			fprintf(stderr, "Error: Normalized path too long.\n");
209 			return 1;
210 		}
211 	}
212 
213 	status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_REGISTER,
214 		&request);
215 	if (error != B_OK) {
216 		fprintf(stderr, "Error: Failed to create RAM disk device: %s\n",
217 			strerror(error));
218 		return 1;
219 	}
220 
221 	printf("RAM disk device created as \"%s/%" B_PRId32 "/raw\"\n",
222 		kRamDiskRawDeviceBasePath, request.id);
223 	return 0;
224 }
225 
226 
227 static int
228 command_unregister(int argc, const char* const* argv)
229 {
230 	sCommandUsage = kDeleteUsage;
231 
232 	while (true) {
233 		static struct option sLongOptions[] = {
234 			{ "help", no_argument, 0, 'h' },
235 			{ 0, 0, 0, 0 }
236 		};
237 
238 		opterr = 0; // don't print errors
239 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
240 		if (c == -1)
241 			break;
242 
243 		switch (c) {
244 			case 'h':
245 				print_usage_and_exit(false);
246 				break;
247 
248 			default:
249 				print_usage_and_exit(true);
250 				break;
251 		}
252 	}
253 
254 	// The remaining argument is the device ID.
255 	if (optind + 1 != argc)
256 		print_usage_and_exit(true);
257 
258 	const char* idString = argv[optind++];
259 	char* end;
260 	long long id = strtol(idString, &end, 0);
261 	if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) {
262 		fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString);
263 		return 1;
264 	}
265 
266 	// check whether the raw device for that ID exists
267 	BString path;
268 	path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
269 	struct stat st;
270 	if (lstat(path, &st) != 0) {
271 		fprintf(stderr, "Error: No RAM disk with ID %s.\n", idString);
272 		return 1;
273 	}
274 
275 	// issue the request
276 	ram_disk_ioctl_unregister request;
277 	request.id = (int32)id;
278 
279 	status_t error = execute_control_device_ioctl(RAM_DISK_IOCTL_UNREGISTER,
280 		&request);
281 	if (error != B_OK) {
282 		fprintf(stderr, "Error: Failed to delete RAM disk device: %s\n",
283 			strerror(error));
284 		return 1;
285 	}
286 
287 	return 0;
288 }
289 
290 
291 static int
292 command_flush(int argc, const char* const* argv)
293 {
294 	sCommandUsage = kFlushUsage;
295 
296 	while (true) {
297 		static struct option sLongOptions[] = {
298 			{ "help", no_argument, 0, 'h' },
299 			{ 0, 0, 0, 0 }
300 		};
301 
302 		opterr = 0; // don't print errors
303 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
304 		if (c == -1)
305 			break;
306 
307 		switch (c) {
308 			case 'h':
309 				print_usage_and_exit(false);
310 				break;
311 
312 			default:
313 				print_usage_and_exit(true);
314 				break;
315 		}
316 	}
317 
318 	// The remaining argument is the device ID.
319 	if (optind + 1 != argc)
320 		print_usage_and_exit(true);
321 
322 	const char* idString = argv[optind++];
323 	char* end;
324 	long long id = strtol(idString, &end, 0);
325 	if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX) {
326 		fprintf(stderr, "Error: Invalid ID \"%s\".\n", idString);
327 		return 1;
328 	}
329 
330 	// open the raw device
331 	BString path;
332 	path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
333 	FileDescriptorCloser fd(open(path, O_RDONLY));
334 	if (!fd.IsSet()) {
335 		fprintf(stderr, "Error: Failed to open RAM disk device \"%s\"\n",
336 			path.String());
337 		return 1;
338 	}
339 
340 	// issue the request
341 	if (ioctl(fd.Get(), RAM_DISK_IOCTL_FLUSH, NULL) < 0) {
342 		fprintf(stderr, "Error: Failed to flush RAM disk device: %s\n",
343 			strerror(errno));
344 		return 1;
345 	}
346 
347 	return 0;
348 }
349 
350 
351 static int
352 command_list(int argc, const char* const* argv)
353 {
354 	sCommandUsage = kListUsage;
355 
356 	while (true) {
357 		static struct option sLongOptions[] = {
358 			{ "help", no_argument, 0, 'h' },
359 			{ 0, 0, 0, 0 }
360 		};
361 
362 		opterr = 0; // don't print errors
363 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
364 		if (c == -1)
365 			break;
366 
367 		switch (c) {
368 			case 'h':
369 				print_usage_and_exit(false);
370 				break;
371 
372 			default:
373 				print_usage_and_exit(true);
374 				break;
375 		}
376 	}
377 
378 	// There shouldn't be any remaining arguments.
379 	if (optind != argc)
380 		print_usage_and_exit(true);
381 
382 	// iterate through the RAM disk device directory and search for raw devices
383 	DirCloser dir(opendir(kRamDiskRawDeviceBasePath));
384 	if (!dir.IsSet()) {
385 		fprintf(stderr, "Error: Failed to open RAM disk device directory: %s\n",
386 			strerror(errno));
387 		return 1;
388 	}
389 
390 	TextTable table;
391 	table.AddColumn("ID", B_ALIGN_RIGHT);
392 	table.AddColumn("Size", B_ALIGN_RIGHT);
393 	table.AddColumn("Associated file");
394 
395 	while (dirent* entry = readdir(dir.Get())) {
396 		// check, if the entry name could be an ID
397 		const char* idString = entry->d_name;
398 		char* end;
399 		long long id = strtol(idString, &end, 0);
400 		if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX)
401 			continue;
402 
403 		// open the raw device
404 		BString path;
405 		path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
406 		FileDescriptorCloser fd(open(path, O_RDONLY));
407 		if (!fd.IsSet())
408 			continue;
409 
410 		// issue the request
411 		ram_disk_ioctl_info request;
412 		if (ioctl(fd.Get(), RAM_DISK_IOCTL_INFO, &request, sizeof(request))
413 			< 0)
414 			continue;
415 
416 		int32 rowIndex = table.CountRows();
417 		table.SetTextAt(rowIndex, 0, BString() << request.id);
418 		table.SetTextAt(rowIndex, 1, BString() << request.size);
419 		table.SetTextAt(rowIndex, 2, request.path);
420 	}
421 
422 	if (table.CountRows() > 0)
423 		table.Print(INT32_MAX);
424 	else
425 		printf("No RAM disks.\n");
426 
427 	return 0;
428 }
429 
430 
431 int
432 main(int argc, const char* const* argv)
433 {
434 	if (argc < 2)
435 		print_usage_and_exit(true);
436 
437 	if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0
438 		|| strcmp(argv[1], "-h") == 0) {
439 		print_usage_and_exit(false);
440 	}
441 
442 	sCommandName = argv[1];
443 
444 	if (strcmp(sCommandName, "create") == 0)
445 		return command_register(argc - 1, argv + 1);
446 	if (strcmp(sCommandName, "delete") == 0)
447 		return command_unregister(argc - 1, argv + 1);
448 	if (strcmp(sCommandName, "flush") == 0)
449 		return command_flush(argc - 1, argv + 1);
450 	if (strcmp(sCommandName, "list") == 0)
451 		return command_list(argc - 1, argv + 1);
452 
453 	print_usage_and_exit(true);
454 }
455