1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <errno.h>
8#include <getopt.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13#include <unistd.h>
14
15#include <DiskDevice.h>
16#include <DiskDeviceRoster.h>
17#include <Path.h>
18
19
20extern const char* __progname;
21const char* kCommandName = __progname;
22
23
24static const char* kUsage =
25	"Usage: %s register <file>\n"
26	"       %s unregister ( <file> | <device path> | <device ID> )\n"
27	"       %s list\n"
28	"Registers a regular file as disk device, unregisters an already "
29		"registered\n"
30	"one, or lists all file disk devices.\n"
31	"\n"
32	"Options:\n"
33	"  -h, --help   - Print this usage info.\n"
34;
35
36
37static void
38print_usage_and_exit(bool error)
39{
40    fprintf(error ? stderr : stdout, kUsage, kCommandName, kCommandName,
41		kCommandName);
42    exit(error ? 1 : 0);
43}
44
45
46static status_t
47list_file_disk_devices()
48{
49	printf("    ID  File                               Device\n");
50	printf("------------------------------------------------------------------"
51		"-------------\n");
52
53	BDiskDeviceRoster roster;
54	BDiskDevice device;
55	while (roster.GetNextDevice(&device) == B_OK) {
56		if (!device.IsFile())
57			continue;
58
59		// ID
60		printf("%6" B_PRId32 "  ", device.ID());
61
62		// file path
63		BPath path;
64		printf("%-35s",
65			device.GetFilePath(&path) == B_OK ? path.Path() : "???");
66
67		// device path
68		printf("%s", device.GetPath(&path) == B_OK ? path.Path() : "???");
69		printf("\n");
70	}
71
72	return B_OK;
73}
74
75
76static status_t
77register_file_disk_device(const char* fileName)
78{
79	// stat() the file to verify that it's a regular file
80	struct stat st;
81	if (lstat(fileName, &st) != 0) {
82		status_t error = errno;
83		fprintf(stderr, "Error: Failed to stat() \"%s\": %s\n", fileName,
84			strerror(error));
85		return error;
86	}
87
88	if (!S_ISREG(st.st_mode)) {
89		fprintf(stderr, "Error: \"%s\" is not a regular file.\n", fileName);
90		return B_BAD_VALUE;
91	}
92
93	// register the file
94	BDiskDeviceRoster roster;
95	partition_id id = roster.RegisterFileDevice(fileName);
96	if (id < 0) {
97		fprintf(stderr, "Error: Failed to register file disk device: %s\n",
98			strerror(id));
99		return id;
100	}
101
102	// print the success message (get the device path)
103	BDiskDevice device;
104	BPath path;
105	if (roster.GetDeviceWithID(id, &device) == B_OK
106		&& device.GetPath(&path) == B_OK) {
107		printf("Registered file as disk device \"%s\" with ID %" B_PRId32 ".\n",
108			path.Path(), id);
109	} else {
110		printf("Registered file as disk device with ID %" B_PRId32 ", "
111			"but failed to get the device path.\n", id);
112	}
113
114	return B_OK;
115}
116
117
118static status_t
119unregister_file_disk_device(const char* fileNameOrID)
120{
121	// try to parse the parameter as ID
122	char* numberEnd;
123	partition_id id = strtol(fileNameOrID, &numberEnd, 0);
124	BDiskDeviceRoster roster;
125	if (id >= 0 && numberEnd != fileNameOrID && *numberEnd == '\0') {
126		BDiskDevice device;
127		if (roster.GetDeviceWithID(id, &device) == B_OK && device.IsFile()) {
128			status_t error = roster.UnregisterFileDevice(id);
129			if (error != B_OK) {
130				fprintf(stderr, "Error: Failed to unregister file disk device "
131					"with ID %" B_PRId32 ": %s\n", id, strerror(error));
132				return error;
133			}
134
135			printf("Unregistered file disk device with ID %" B_PRId32 ".\n",
136				id);
137			return B_OK;
138		} else {
139			fprintf(stderr, "No file disk device with ID %" B_PRId32 ","
140				"trying file \"%s\"\n", id, fileNameOrID);
141		}
142	}
143
144	// the parameter must be a file name -- stat() it
145	struct stat st;
146	if (lstat(fileNameOrID, &st) != 0) {
147		status_t error = errno;
148		fprintf(stderr, "Error: Failed to stat() \"%s\": %s\n", fileNameOrID,
149			strerror(error));
150		return error;
151	}
152
153	// remember the volume and node ID, so we can identify the file
154	// NOTE: There's a race condition -- we would need to open the file and
155	// keep it open to avoid it.
156	dev_t volumeID = st.st_dev;
157	ino_t nodeID = st.st_ino;
158
159	// iterate through all file disk devices and try to find a match
160	BDiskDevice device;
161	while (roster.GetNextDevice(&device) == B_OK) {
162		if (!device.IsFile())
163			continue;
164
165		// get file path and stat it, same for the device path
166		BPath path;
167		bool isFilePath = true;
168		if ((device.GetFilePath(&path) == B_OK && lstat(path.Path(), &st) == 0
169				&& volumeID == st.st_dev && nodeID == st.st_ino)
170			|| (isFilePath = false, false)
171			|| (device.GetPath(&path) == B_OK && lstat(path.Path(), &st) == 0
172				&& volumeID == st.st_dev && nodeID == st.st_ino)) {
173			status_t error = roster.UnregisterFileDevice(device.ID());
174			if (error != B_OK) {
175				fprintf(stderr, "Error: Failed to unregister file disk device"
176					"%s \"%s\" (ID: %" B_PRId32 "): %s\n",
177					isFilePath ? " for file" : "", fileNameOrID, device.ID(),
178					strerror(error));
179				return error;
180			}
181
182			printf("Unregistered file disk device%s \"%s\" "
183				"(ID: %" B_PRId32 ")\n", isFilePath ? " for file" : "",
184				fileNameOrID, device.ID());
185
186			return B_OK;
187		}
188	}
189
190	fprintf(stderr, "Error: \"%s\" does not refer to a file disk device.\n",
191		fileNameOrID);
192	return B_BAD_VALUE;
193}
194
195
196int
197main(int argc, const char* const* argv)
198{
199	while (true) {
200		static struct option sLongOptions[] = {
201			{ "help", no_argument, 0, 'h' },
202			{ 0, 0, 0, 0 }
203		};
204
205		opterr = 0; // don't print errors
206		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
207		if (c == -1)
208			break;
209
210		switch (c) {
211			case 'h':
212				print_usage_and_exit(false);
213				break;
214
215			default:
216				print_usage_and_exit(true);
217				break;
218		}
219	}
220
221	// Of the remaining arguments the next one should be the command.
222	if (optind >= argc)
223		print_usage_and_exit(true);
224
225	status_t error = B_OK;
226	const char* command = argv[optind++];
227
228	if (strcmp(command, "list") == 0) {
229		if (optind < argc)
230			print_usage_and_exit(true);
231
232		list_file_disk_devices();
233	} else if (strcmp(command, "register") == 0) {
234		if (optind + 1 != argc)
235			print_usage_and_exit(true);
236
237		const char* fileName = argv[optind++];
238		register_file_disk_device(fileName);
239	} else if (strcmp(command, "unregister") == 0) {
240		if (optind + 1 != argc)
241			print_usage_and_exit(true);
242
243		const char* fileName = argv[optind++];
244		unregister_file_disk_device(fileName);
245	} else
246		print_usage_and_exit(true);
247
248	return error == B_OK ? 0 : 1;
249}
250