1/*
2 * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@vrfy.org>
3 *
4 *	This program is free software; you can redistribute it and/or modify it
5 *	under the terms of the GNU General Public License as published by the
6 *	Free Software Foundation version 2 of the License.
7 *
8 *	This program is distributed in the hope that it will be useful, but
9 *	WITHOUT ANY WARRANTY; without even the implied warranty of
10 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 *	General Public License for more details.
12 *
13 *	You should have received a copy of the GNU General Public License along
14 *	with this program; if not, write to the Free Software Foundation, Inc.,
15 *	51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 *
17 */
18
19#include <stdlib.h>
20#include <string.h>
21#include <stdio.h>
22#include <stddef.h>
23#include <ctype.h>
24#include <stdarg.h>
25#include <unistd.h>
26#include <dirent.h>
27#include <errno.h>
28#include <getopt.h>
29#include <sys/stat.h>
30#include <sys/types.h>
31
32#include "udev.h"
33
34
35#ifdef USE_LOG
36void log_message (int priority, const char *format, ...)
37{
38	va_list	args;
39
40	if (priority > udev_log_priority)
41		return;
42
43	va_start(args, format);
44	vsyslog(priority, format, args);
45	va_end(args);
46}
47#endif
48
49static void print_all_attributes(const char *devpath, const char *key)
50{
51	char path[PATH_SIZE];
52	DIR *dir;
53	struct dirent *dent;
54
55	strlcpy(path, sysfs_path, sizeof(path));
56	strlcat(path, devpath, sizeof(path));
57
58	dir = opendir(path);
59	if (dir != NULL) {
60		for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
61			struct stat statbuf;
62			char filename[PATH_SIZE];
63			char *attr_value;
64			char value[NAME_SIZE];
65			size_t len;
66
67			if (dent->d_name[0] == '.')
68				continue;
69
70			strlcpy(filename, path, sizeof(filename));
71			strlcat(filename, "/", sizeof(filename));
72			strlcat(filename, dent->d_name, sizeof(filename));
73			if (lstat(filename, &statbuf) != 0)
74				continue;
75			if (S_ISLNK(statbuf.st_mode))
76				continue;
77
78			attr_value = sysfs_attr_get_value(devpath, dent->d_name);
79			if (attr_value == NULL)
80				continue;
81			len = strlcpy(value, attr_value, sizeof(value));
82			dbg("attr '%s'='%s'(%zi)", dent->d_name, value, len);
83
84			/* remove trailing newlines */
85			while (len && value[len-1] == '\n')
86				value[--len] = '\0';
87
88			/* skip nonprintable attributes */
89			while (len && isprint(value[len-1]))
90				len--;
91			if (len) {
92				dbg("attribute value of '%s' non-printable, skip", dent->d_name);
93				continue;
94			}
95
96			replace_chars(value, ALLOWED_CHARS_INPUT);
97			printf("    %s{%s}==\"%s\"\n", key, dent->d_name, value);
98		}
99	}
100	printf("\n");
101}
102
103static int print_device_chain(const char *devpath)
104{
105	struct sysfs_device *dev;
106
107	dev = sysfs_device_get(devpath);
108	if (dev == NULL)
109		return -1;
110
111	printf("\n"
112	       "Udevinfo starts with the device specified by the devpath and then\n"
113	       "walks up the chain of parent devices. It prints for every device\n"
114	       "found, all possible attributes in the udev rules key format.\n"
115	       "A rule to match, can be composed by the attributes of the device\n"
116	       "and the attributes from one single parent device.\n"
117	       "\n");
118
119	printf("  looking at device '%s':\n", dev->devpath);
120	printf("    KERNEL==\"%s\"\n", dev->kernel);
121	printf("    SUBSYSTEM==\"%s\"\n", dev->subsystem);
122	printf("    DRIVER==\"%s\"\n", dev->driver);
123	print_all_attributes(dev->devpath, "ATTR");
124
125	/* walk up the chain of devices */
126	while (1) {
127		dev = sysfs_device_get_parent(dev);
128		if (dev == NULL)
129			break;
130		printf("  looking at parent device '%s':\n", dev->devpath);
131		printf("    KERNELS==\"%s\"\n", dev->kernel);
132		printf("    SUBSYSTEMS==\"%s\"\n", dev->subsystem);
133		printf("    DRIVERS==\"%s\"\n", dev->driver);
134
135		print_all_attributes(dev->devpath, "ATTRS");
136	}
137
138	return 0;
139}
140
141static void print_record(struct udevice *udev)
142{
143	struct name_entry *name_loop;
144
145	printf("P: %s\n", udev->dev->devpath);
146	printf("N: %s\n", udev->name);
147	list_for_each_entry(name_loop, &udev->symlink_list, node)
148		printf("S: %s\n", name_loop->name);
149	if (udev->link_priority != 0)
150		printf("L: %i\n", udev->link_priority);
151	if (udev->partitions != 0)
152		printf("A:%u\n", udev->partitions);
153	if (udev->ignore_remove)
154		printf("R:%u\n", udev->ignore_remove);
155	list_for_each_entry(name_loop, &udev->env_list, node)
156		printf("E: %s\n", name_loop->name);
157}
158
159static void export_db(void) {
160	LIST_HEAD(name_list);
161	struct name_entry *name_loop;
162
163	udev_db_get_all_entries(&name_list);
164	list_for_each_entry(name_loop, &name_list, node) {
165		struct udevice *udev_db;
166
167		udev_db = udev_device_init(NULL);
168		if (udev_db == NULL)
169			continue;
170		if (udev_db_get_device(udev_db, name_loop->name) == 0)
171			print_record(udev_db);
172			printf("\n");
173		udev_device_cleanup(udev_db);
174	}
175	name_list_cleanup(&name_list);
176}
177
178static int lookup_device_by_name(struct udevice *udev, const char *name)
179{
180	LIST_HEAD(name_list);
181	int count;
182	struct name_entry *device;
183	int rc  = -1;
184
185	count = udev_db_get_devices_by_name(name, &name_list);
186	if (count <= 0)
187		goto out;
188
189	info("found %i devices for '%s'", count, name);
190
191	/* select the device that seems to match */
192	list_for_each_entry(device, &name_list, node) {
193		char filename[PATH_SIZE];
194		struct stat statbuf;
195
196		udev_device_init(udev);
197		if (udev_db_get_device(udev, device->name) != 0)
198			continue;
199		info("found db entry '%s'", device->name);
200
201		/* make sure, we don't get a link of a differnt device */
202		strlcpy(filename, udev_root, sizeof(filename));
203		strlcat(filename, "/", sizeof(filename));
204		strlcat(filename, name, sizeof(filename));
205		if (stat(filename, &statbuf) != 0)
206			continue;
207		if (major(udev->devt) > 0 && udev->devt != statbuf.st_rdev) {
208			info("skip '%s', dev_t doesn't match", udev->name);
209			continue;
210		}
211		rc = 0;
212		break;
213	}
214out:
215	name_list_cleanup(&name_list);
216	return rc;
217}
218
219int main(int argc, char *argv[], char *envp[])
220{
221	int option;
222	struct udevice *udev;
223	int root = 0;
224
225	static const struct option options[] = {
226		{ "name", 1, NULL, 'n' },
227		{ "path", 1, NULL, 'p' },
228		{ "query", 1, NULL, 'q' },
229		{ "attribute-walk", 0, NULL, 'a' },
230		{ "export-db", 0, NULL, 'e' },
231		{ "root", 0, NULL, 'r' },
232		{ "version", 0, NULL, 1 }, /* -V outputs braindead format */
233		{ "help", 0, NULL, 'h' },
234		{}
235	};
236
237	enum action_type {
238		ACTION_NONE,
239		ACTION_QUERY,
240		ACTION_ATTRIBUTE_WALK,
241		ACTION_ROOT,
242	} action = ACTION_NONE;
243
244	enum query_type {
245		QUERY_NONE,
246		QUERY_NAME,
247		QUERY_PATH,
248		QUERY_SYMLINK,
249		QUERY_ENV,
250		QUERY_ALL,
251	} query = QUERY_NONE;
252
253	char path[PATH_SIZE] = "";
254	char name[PATH_SIZE] = "";
255	struct name_entry *name_loop;
256	int rc = 0;
257
258	logging_init("udevinfo");
259	udev_config_init();
260	sysfs_init();
261
262	udev = udev_device_init(NULL);
263	if (udev == NULL) {
264		rc = 1;
265		goto exit;
266	}
267
268	while (1) {
269		option = getopt_long(argc, argv, "aen:p:q:rVh", options, NULL);
270		if (option == -1)
271			break;
272
273		dbg("option '%c'", option);
274		switch (option) {
275		case 'n':
276			/* remove /dev if given */
277			if (strncmp(optarg, udev_root, strlen(udev_root)) == 0)
278				strlcpy(name, &optarg[strlen(udev_root)+1], sizeof(name));
279			else
280				strlcpy(name, optarg, sizeof(name));
281			dbg("name: %s", name);
282			break;
283		case 'p':
284			/* remove /sys if given */
285			if (strncmp(optarg, sysfs_path, strlen(sysfs_path)) == 0)
286				strlcpy(path, &optarg[strlen(sysfs_path)], sizeof(path));
287			else
288				strlcpy(path, optarg, sizeof(path));
289			dbg("path: %s", path);
290			break;
291		case 'q':
292			action = ACTION_QUERY;
293			if (strcmp(optarg, "name") == 0) {
294				query = QUERY_NAME;
295				break;
296			}
297			if (strcmp(optarg, "symlink") == 0) {
298				query = QUERY_SYMLINK;
299				break;
300			}
301			if (strcmp(optarg, "path") == 0) {
302				query = QUERY_PATH;
303				break;
304			}
305			if (strcmp(optarg, "env") == 0) {
306				query = QUERY_ENV;
307				break;
308			}
309			if (strcmp(optarg, "all") == 0) {
310				query = QUERY_ALL;
311				break;
312			}
313			fprintf(stderr, "unknown query type\n");
314			rc = 2;
315			goto exit;
316		case 'r':
317			if (action == ACTION_NONE)
318				action = ACTION_ROOT;
319			root = 1;
320			break;
321		case 'a':
322			action = ACTION_ATTRIBUTE_WALK;
323			break;
324		case 'e':
325			export_db();
326			goto exit;
327		case 1:
328			printf("%s\n", UDEV_VERSION);
329			goto exit;
330		case 'V':
331			printf("udevinfo, version %s\n", UDEV_VERSION);
332			goto exit;
333		case 'h':
334			printf("Usage: udevinfo OPTIONS\n"
335			       "  --query=<type>    query database for the specified value:\n"
336			       "    name            name of device node\n"
337			       "    symlink         pointing to node\n"
338			       "    path            sysfs device path\n"
339			       "    env             the device related imported environment\n"
340			       "    all             all values\n"
341			       "\n"
342			       "  --path=<devpath>  sysfs device path used for query or chain\n"
343			       "  --name=<name>     node or symlink name used for query\n"
344			       "\n"
345			       "  --root            prepend to query result or print udev_root\n"
346			       "  --attribute-walk  print all SYSFS_attributes along the device chain\n"
347			       "  --export-db       export the content of the udev database\n"
348			       "  --help            print this text\n"
349			       "\n");
350			goto exit;
351		default:
352			goto exit;
353		}
354	}
355
356	/* run action */
357	switch (action) {
358	case ACTION_QUERY:
359		/* needs devpath or node/symlink name for query */
360		if (path[0] != '\0') {
361			if (udev_db_get_device(udev, path) != 0) {
362				fprintf(stderr, "no record for '%s' in database\n", path);
363				rc = 3;
364				goto exit;
365			}
366		} else if (name[0] != '\0') {
367			if (lookup_device_by_name(udev, name) != 0) {
368				fprintf(stderr, "node name not found\n");
369				rc = 4;
370				goto exit;
371			}
372		} else {
373			fprintf(stderr, "query needs --path or node --name specified\n");
374			rc = 4;
375			goto exit;
376		}
377
378		switch(query) {
379		case QUERY_NAME:
380			if (root)
381				printf("%s/%s\n", udev_root, udev->name);
382			else
383				printf("%s\n", udev->name);
384			break;
385		case QUERY_SYMLINK:
386			if (list_empty(&udev->symlink_list))
387				goto exit;
388			if (root)
389				list_for_each_entry(name_loop, &udev->symlink_list, node)
390					printf("%s/%s ", udev_root, name_loop->name);
391			else
392				list_for_each_entry(name_loop, &udev->symlink_list, node)
393					printf("%s ", name_loop->name);
394			printf("\n");
395			break;
396		case QUERY_PATH:
397			printf("%s\n", udev->dev->devpath);
398			goto exit;
399		case QUERY_ENV:
400			list_for_each_entry(name_loop, &udev->env_list, node)
401				printf("%s\n", name_loop->name);
402			break;
403		case QUERY_ALL:
404			print_record(udev);
405			break;
406		default:
407			fprintf(stderr, "unknown query type\n");
408			break;
409		}
410		break;
411	case ACTION_ATTRIBUTE_WALK:
412		if (path[0] != '\0') {
413			if (print_device_chain(path) != 0) {
414				fprintf(stderr, "no valid sysfs device found\n");
415				rc = 4;
416				goto exit;
417			}
418		} else if (name[0] != '\0') {
419			if (lookup_device_by_name(udev, name) != 0) {
420				fprintf(stderr, "node name not found\n");
421				rc = 4;
422				goto exit;
423			}
424			if (print_device_chain(udev->dev->devpath) != 0) {
425				fprintf(stderr, "no valid sysfs device found\n");
426				rc = 4;
427				goto exit;
428			}
429		} else {
430			fprintf(stderr, "attribute walk needs --path or node --name specified\n");
431			rc = 5;
432			goto exit;
433		}
434		break;
435	case ACTION_ROOT:
436		printf("%s\n", udev_root);
437		break;
438	default:
439		fprintf(stderr, "missing option\n");
440		rc = 1;
441		break;
442	}
443
444exit:
445	udev_device_cleanup(udev);
446	sysfs_cleanup();
447	logging_close();
448	return rc;
449}
450