xref: /haiku/src/bin/ramdisk.cpp (revision 15fb7d88e971c4d6c787c6a3a5c159afb1ebf77b)
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 <StringForSize.h>
21 #include <TextTable.h>
22 
23 #include <file_systems/ram_disk/ram_disk.h>
24 
25 
26 extern const char* __progname;
27 static const char* kProgramName = __progname;
28 
29 static const char* const kUsage =
30 	"Usage: %s <command> [ <options> ]\n"
31 	"Controls RAM disk devices.\n"
32 	"\n"
33 	"Commands:\n"
34 	"  create (-s <size> | <path>)\n"
35 	"    Creates a new RAM disk.\n"
36 	"  delete <id>\n"
37 	"    Deletes an existing RAM disk.\n"
38 	"  flush <id>\n"
39 	"    Writes modified data of an existing RAM disk back to its file.\n"
40 	"  help\n"
41 	"    Print this usage info.\n"
42 	"  list\n"
43 	"    List all RAM disks.\n"
44 ;
45 
46 static const char* const kCreateUsage =
47 	"Usage: %s %s (-s <size> | <path>)\n"
48 	"Creates a new RAM disk device. If the <size> argument is specified, a\n"
49 	"new zeroed RAM disk with that size (in bytes, suffixes 'k', 'm', 'g' are\n"
50 	"interpreted as KiB, MiB, GiB) is registered.\n"
51 	"Alternatively a file path can be specified. In that case the RAM disk \n"
52 	"data are initially read from that file and at any later point the\n"
53 	"modified RAM disk data can be written back to the same file upon request\n"
54 	"(via the \"flush\" command). The size of the RAM disk is implied by that\n"
55 	"of the file.\n"
56 ;
57 
58 static const char* const kDeleteUsage =
59 	"Usage: %s %s <id>\n"
60 	"Deletes the existing RAM disk with ID <id>. All modified data will be\n"
61 	"lost.\n"
62 ;
63 
64 static const char* const kFlushUsage =
65 	"Usage: %s %s <id>\n"
66 	"Writes all modified data of the RAM disk with ID <id> back to the file\n"
67 	"specified when the RAM disk was created. Fails, if the RAM disk had been\n"
68 	"created without an associated file.\n"
69 ;
70 
71 static const char* const kListUsage =
72 	"Usage: %s %s\n"
73 	"Lists all existing RAM disks.\n"
74 ;
75 
76 static const char* const kRamDiskControlDevicePath
77 	= "/dev/" RAM_DISK_CONTROL_DEVICE_NAME;
78 static const char* const kRamDiskRawDeviceBasePath
79 	= "/dev/" RAM_DISK_RAW_DEVICE_BASE_NAME;
80 
81 static const char* sCommandName = NULL;
82 static const char* sCommandUsage = NULL;
83 
84 
85 static void
86 print_usage_and_exit(bool error)
87 {
88 	if (sCommandUsage != NULL) {
89 	    fprintf(error ? stderr : stdout, sCommandUsage, kProgramName,
90 			sCommandName);
91 	} else
92 	    fprintf(error ? stderr : stdout, kUsage, kProgramName);
93     exit(error ? 1 : 0);
94 }
95 
96 
97 static status_t
98 execute_control_device_ioctl(int operation, void* request)
99 {
100 	// open the ram disk control device
101 	int fd = open(kRamDiskControlDevicePath, O_RDONLY);
102 	if (fd < 0) {
103 		fprintf(stderr, "Error: Failed to open RAM disk control device \"%s\": "
104 			"%s\n", kRamDiskControlDevicePath, strerror(errno));
105 		return errno;
106 	}
107 	FileDescriptorCloser fdCloser(fd);
108 
109 	// issue the request
110 	if (ioctl(fd, 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 	int fd = open(path, O_RDONLY);
334 	if (fd < 0) {
335 		fprintf(stderr, "Error: Failed to open RAM disk device \"%s\"\n",
336 			path.String());
337 		return 1;
338 	}
339 	FileDescriptorCloser fdCloser(fd);
340 
341 	// issue the request
342 	if (ioctl(fd, RAM_DISK_IOCTL_FLUSH, NULL) < 0) {
343 		fprintf(stderr, "Error: Failed to flush RAM disk device: %s\n",
344 			strerror(errno));
345 		return 1;
346 	}
347 
348 	return 0;
349 }
350 
351 
352 static int
353 command_list(int argc, const char* const* argv)
354 {
355 	sCommandUsage = kListUsage;
356 
357 	while (true) {
358 		static struct option sLongOptions[] = {
359 			{ "help", no_argument, 0, 'h' },
360 			{ 0, 0, 0, 0 }
361 		};
362 
363 		opterr = 0; // don't print errors
364 		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
365 		if (c == -1)
366 			break;
367 
368 		switch (c) {
369 			case 'h':
370 				print_usage_and_exit(false);
371 				break;
372 
373 			default:
374 				print_usage_and_exit(true);
375 				break;
376 		}
377 	}
378 
379 	// There shouldn't be any remaining arguments.
380 	if (optind != argc)
381 		print_usage_and_exit(true);
382 
383 	// iterate through the RAM disk device directory and search for raw devices
384 	DIR* dir = opendir(kRamDiskRawDeviceBasePath);
385 	if (dir == NULL) {
386 		fprintf(stderr, "Error: Failed to open RAM disk device directory: %s\n",
387 			strerror(errno));
388 		return 1;
389 	}
390 	CObjectDeleter<DIR, int> dirCloser(dir, &closedir);
391 
392 	TextTable table;
393 	table.AddColumn("ID", B_ALIGN_RIGHT);
394 	table.AddColumn("Size", B_ALIGN_RIGHT);
395 	table.AddColumn("Associated file");
396 
397 	while (dirent* entry = readdir(dir)) {
398 		// check, if the entry name could be an ID
399 		const char* idString = entry->d_name;
400 		char* end;
401 		long long id = strtol(idString, &end, 0);
402 		if (end == idString || *end != '\0' || id < 0 || id > INT32_MAX)
403 			continue;
404 
405 		// open the raw device
406 		BString path;
407 		path.SetToFormat("%s/%s/raw", kRamDiskRawDeviceBasePath, idString);
408 		int fd = open(path, O_RDONLY);
409 		if (fd < 0)
410 			continue;
411 		FileDescriptorCloser fdCloser(fd);
412 
413 		// issue the request
414 		ram_disk_ioctl_info request;
415 		if (ioctl(fd, RAM_DISK_IOCTL_INFO, &request, sizeof(request)) < 0)
416 			continue;
417 
418 		int32 rowIndex = table.CountRows();
419 		table.SetTextAt(rowIndex, 0, BString() << request.id);
420 		table.SetTextAt(rowIndex, 1, BString() << request.size);
421 		table.SetTextAt(rowIndex, 2, request.path);
422 	}
423 
424 	if (table.CountRows() > 0)
425 		table.Print(INT32_MAX);
426 	else
427 		printf("No RAM disks.\n");
428 
429 	return 0;
430 }
431 
432 
433 int
434 main(int argc, const char* const* argv)
435 {
436 	if (argc < 2)
437 		print_usage_and_exit(true);
438 
439 	if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0
440 		|| strcmp(argv[1], "-h") == 0) {
441 		print_usage_and_exit(false);
442 	}
443 
444 	sCommandName = argv[1];
445 
446 	if (strcmp(sCommandName, "create") == 0)
447 		return command_register(argc - 1, argv + 1);
448 	if (strcmp(sCommandName, "delete") == 0)
449 		return command_unregister(argc - 1, argv + 1);
450 	if (strcmp(sCommandName, "flush") == 0)
451 		return command_flush(argc - 1, argv + 1);
452 	if (strcmp(sCommandName, "list") == 0)
453 		return command_list(argc - 1, argv + 1);
454 
455 	print_usage_and_exit(true);
456 }
457