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
27extern const char* __progname;
28static const char* kProgramName = __progname;
29
30static 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
47static 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
59static 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
65static 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
72static const char* const kListUsage =
73	"Usage: %s %s\n"
74	"Lists all existing RAM disks.\n"
75;
76
77static const char* const kRamDiskControlDevicePath
78	= "/dev/" RAM_DISK_CONTROL_DEVICE_NAME;
79static const char* const kRamDiskRawDeviceBasePath
80	= "/dev/" RAM_DISK_RAW_DEVICE_BASE_NAME;
81
82static const char* sCommandName = NULL;
83static const char* sCommandUsage = NULL;
84
85
86static void
87print_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
98static status_t
99execute_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
117static int
118command_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
227static int
228command_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
291static int
292command_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
351static int
352command_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
431int
432main(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