xref: /illumos-gate/usr/src/cmd/fstyp/fstyp.c (revision 2bbdd445a21f9d61f4a0ca0faf05d5ceb2bd91f3)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <libintl.h>
33 #include <locale.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <errno.h>
37 #include <dirent.h>
38 #include <dlfcn.h>
39 #include <sys/wait.h>
40 #include <sys/fstyp.h>
41 #include <sys/dkio.h>
42 #include <sys/param.h>
43 #include <libfstyp.h>
44 #include <sys/dktp/fdisk.h>
45 #include <sys/fs/pc_label.h>
46 
47 #include "libadm.h"
48 
49 #define	FSTYP_LIBFS_DIR	"/usr/lib/fs"
50 
51 static const char *getmodfsname();
52 static char *getexecpathname();
53 static boolean_t dos_to_dev(char *path, char **devpath, int *num);
54 static boolean_t find_dos_drive(int fd, int num, off_t *offset);
55 static void run_legacy_cmds(int fd, char *device, int vflag);
56 static int run_cmd(char *path, char *arg0, char *arg1, char *arg2);
57 
58 
59 static void
60 usage(void)
61 {
62 	(void) fprintf(stderr, gettext("Usage: fstyp [-av] <device>\n"));
63 	exit(1);
64 }
65 
66 int
67 main(int argc, char **argv)
68 {
69 	int		fd = -1;
70 	int		c;
71 	int		aflag = 0;
72 	int		vflag = 0;
73 	int		indent = 0;
74 	char		*devpath;
75 	boolean_t	is_dos;
76 	int		dos_num;
77 	off_t		offset = 0;
78 	nvlist_t	*attr = NULL;
79 	fstyp_handle_t	h = NULL;
80 	const char	*modfsname;
81 	const char	*fsname;
82 	int		error = FSTYP_ERR_NO_MATCH;
83 
84 	(void) setlocale(LC_ALL, "");
85 
86 #if !defined(TEXT_DOMAIN)
87 #define	TEXT_DOMAIN "SYS_TEST"
88 #endif
89 	(void) textdomain(TEXT_DOMAIN);
90 
91 	while ((c = getopt(argc, argv, "av")) != -1) {
92 		switch (c) {
93 		case 'a':
94 			aflag = 1;
95 			break;
96 		case 'v':
97 			vflag = 1;
98 			break;
99 		default:
100 			usage();
101 			break;
102 		}
103 	}
104 
105 	argv += optind;
106 	argc -= optind;
107 
108 	if (argc != 1) {
109 		usage();
110 	}
111 
112 	modfsname = getmodfsname();
113 
114 	/*
115 	 * Open device, find partition offset if requested
116 	 */
117 	if (!(is_dos = dos_to_dev(argv[0], &devpath, &dos_num))) {
118 		devpath = argv[0];
119 	}
120 	if ((fd = open(devpath, O_RDONLY)) < 0) {
121 		error = FSTYP_ERR_DEV_OPEN;
122 		goto out;
123 	}
124 	if (is_dos) {
125 		if (!find_dos_drive(fd, dos_num, &offset)) {
126 			error = FSTYP_ERR_NO_PARTITION;
127 			goto out;
128 		}
129 	}
130 
131 	/*
132 	 * Use libfstyp to identify filesystem
133 	 */
134 	if ((error = fstyp_init(fd, offset, NULL, &h)) != 0) {
135 		goto out;
136 	}
137 	if ((error = fstyp_ident(h, modfsname, &fsname)) != 0) {
138 		fstyp_fini(h);
139 		h = NULL;
140 
141 		run_legacy_cmds(fd, argv[0], vflag);
142 
143 		goto out;
144 	}
145 
146 	(void) printf("%s\n", fsname);
147 
148 	/*
149 	 * Output additional info if requested
150 	 */
151 	if (vflag) {
152 		error = fstyp_dump(h, stdout, stderr);
153 	}
154 	if (aflag || (vflag && (error == FSTYP_ERR_NOP))) {
155 		if ((error = fstyp_get_attr(h, &attr)) != 0) {
156 			goto out;
157 		}
158 		dump_nvlist(attr, indent);
159 	}
160 
161 out:
162 	if (error != 0) {
163 		(void) fprintf(stderr, gettext("unknown_fstyp (%s)\n"),
164 		    fstyp_strerror(h, error));
165 	}
166 	if (h != NULL) {
167 		fstyp_fini(h);
168 	}
169 	if (fd >= 0) {
170 		(void) close(fd);
171 	}
172 	if (devpath != argv[0]) {
173 		free(devpath);
174 	}
175 	return (error);
176 
177 }
178 
179 /*
180  * If the executable is a fs-specific hardlink, /usr/lib/fs/<fsname>/fstyp,
181  * return that fsname; otherwise return NULL.
182  */
183 static const char *
184 getmodfsname()
185 {
186 	static char fsname_buf[FSTYPSZ + 1];
187 	char	*fsname = NULL;
188 	char	*path;
189 	char	*p;
190 	int	len;
191 
192 	if ((path = getexecpathname()) == NULL) {
193 		return (NULL);
194 	}
195 	if ((p = strrchr(path, '/')) != NULL) {
196 		*p = '\0';
197 		if ((p = strrchr(path, '/')) != NULL) {
198 			*p++ = '\0';
199 			len = strlen(p);
200 			if ((strcmp(path, FSTYP_LIBFS_DIR) == 0) &&
201 			    (len > 0) && (len < sizeof (fsname_buf))) {
202 				(void) strlcpy(fsname_buf, p,
203 				    sizeof (fsname_buf));
204 				fsname = fsname_buf;
205 			}
206 		}
207 	}
208 	free(path);
209 	return (fsname);
210 }
211 
212 /*
213  * Return executable's absolute pathname
214  */
215 static char *
216 getexecpathname()
217 {
218 	size_t		size;
219 	const char	*execname;
220 	char		*cwd;
221 	char		*path;
222 	char		*rpath;
223 
224 	size = pathconf(".", _PC_PATH_MAX) + 1;
225 	path = malloc(size);
226 	rpath = malloc(size);
227 	cwd = getcwd(NULL, size);
228 	if ((path == NULL) || (rpath == NULL) || (cwd == NULL)) {
229 		goto out;
230 	}
231 	execname = getexecname();
232 
233 	if (execname[0] == '/') {
234 		(void) snprintf(path, size, "%s", execname);
235 	} else {
236 		(void) snprintf(path, size, "%s/%s", cwd, execname);
237 	}
238 	if (realpath(path, rpath) == NULL) {
239 		free(rpath);
240 		rpath = NULL;
241 	}
242 
243 out:
244 	if (path != NULL) {
245 		free(path);
246 	}
247 	if (cwd != NULL) {
248 		free(cwd);
249 	}
250 	return (rpath);
251 }
252 
253 /*
254  * Separates dos notation device spec into device and drive number
255  */
256 static boolean_t
257 dos_to_dev(char *path, char **devpath, int *num)
258 {
259 	char *p;
260 
261 	if ((p = strrchr(path, ':')) == NULL) {
262 		return (B_FALSE);
263 	}
264 	if ((*num = atoi(p + 1)) == 0) {
265 		return (B_FALSE);
266 	}
267 	p[0] = '\0';
268 	*devpath = getfullrawname(path);
269 	p[0] = ':';
270 	if (*devpath != NULL && **devpath == '\0') {
271 		free(*devpath);
272 		*devpath = NULL;
273 	}
274 	return (*devpath != NULL);
275 }
276 
277 static boolean_t
278 is_dos_drive(uchar_t type)
279 {
280 	return ((type == DOSOS12) || (type == DOSOS16) ||
281 	    (type == DOSHUGE) || (type == FDISK_WINDOWS) ||
282 	    (type == FDISK_EXT_WIN) || (type == FDISK_FAT95) ||
283 	    (type == DIAGPART));
284 }
285 
286 static boolean_t
287 is_dos_extended(uchar_t id)
288 {
289 	return ((id == EXTDOS) || (id == FDISK_EXTLBA));
290 }
291 
292 struct part_find_s {
293 	int	num;
294 	int	count;
295 	int	systid;
296 	int	r_systid;
297 	uint32_t	r_relsect;
298 	uint32_t	r_numsect;
299 };
300 
301 enum { WALK_CONTINUE, WALK_TERMINATE };
302 
303 /*
304  * Walk partition tables and invoke a callback for each.
305  */
306 static void
307 walk_partitions(int fd, uint32_t startsec, off_t secsz,
308     int (*f)(void *, int, uint32_t, uint32_t), void *arg)
309 {
310 	uint32_t buf[1024/4];
311 	int bufsize = 1024;
312 	struct mboot *mboot = (struct mboot *)&buf[0];
313 	struct ipart ipart[FD_NUMPART];
314 	uint32_t sec = startsec;
315 	uint32_t lastsec = sec + 1;
316 	uint32_t relsect;
317 	int ext = 0;
318 	int systid;
319 	boolean_t valid;
320 	int i;
321 
322 	while (sec != lastsec) {
323 		if (pread(fd, buf, bufsize, (off_t)sec * secsz) != bufsize) {
324 			break;
325 		}
326 		lastsec = sec;
327 		if (ltohs(mboot->signature) != MBB_MAGIC) {
328 			break;
329 		}
330 		bcopy(mboot->parts, ipart, FD_NUMPART * sizeof (struct ipart));
331 
332 		for (i = 0; i < FD_NUMPART; i++) {
333 			systid = ipart[i].systid;
334 			relsect = sec + ltohi(ipart[i].relsect);
335 			if (systid == 0) {
336 				continue;
337 			}
338 			valid = B_TRUE;
339 			if (is_dos_extended(systid) && (sec == lastsec)) {
340 				sec = startsec + ltohi(ipart[i].relsect);
341 				if (ext++ == 0) {
342 					relsect = startsec = sec;
343 				} else {
344 					valid = B_FALSE;
345 				}
346 			}
347 			if (valid && f(arg, ipart[i].systid, relsect,
348 			    ltohi(ipart[i].numsect)) == WALK_TERMINATE) {
349 				return;
350 			}
351 		}
352 	}
353 }
354 
355 static int
356 find_dos_drive_cb(void *arg, int systid, uint32_t relsect, uint32_t numsect)
357 {
358 	struct part_find_s *p = arg;
359 
360 	if (is_dos_drive(systid)) {
361 		if (++p->count == p->num) {
362 			p->r_relsect = relsect;
363 			p->r_numsect = numsect;
364 			p->r_systid = systid;
365 			return (WALK_TERMINATE);
366 		}
367 	}
368 
369 	return (WALK_CONTINUE);
370 }
371 
372 /*
373  * Given a dos drive number, return its relative offset in the drive.
374  */
375 static boolean_t
376 find_dos_drive(int fd, int num, off_t *offset)
377 {
378 	struct dk_minfo mi;
379 	off_t secsz;
380 	struct part_find_s p = { 0, 0, 0, 0, 0, 0 };
381 
382 	p.num = num;
383 
384 	/*
385 	 * It is possible that the media we are dealing with can have different
386 	 * sector size than the default 512 bytes. Query the driver and check
387 	 * whether the media has different sector size.
388 	 */
389 	if (ioctl(fd, DKIOCGMEDIAINFO, &mi) < 0)
390 		secsz = DEV_BSIZE;
391 	else
392 		secsz = mi.dki_lbsize;
393 
394 	if (num > 0) {
395 		walk_partitions(fd, 0, secsz, find_dos_drive_cb, &p);
396 		if (p.count == num) {
397 			*offset = secsz * (off_t)p.r_relsect;
398 			return (B_TRUE);
399 		}
400 	}
401 
402 	return (B_FALSE);
403 }
404 
405 /*
406  * libfstyp identification failed: as a last resort, try to
407  * find and run legacy /usr/lib/fs/<fsname>/fstyp commands.
408  */
409 static void
410 run_legacy_cmds(int fd, char *device, int vflag)
411 {
412 	char		*lib_dir = FSTYP_LIBFS_DIR;
413 	char		*path;
414 	long		name_max;
415 	DIR		*dirp;
416 	struct dirent	*dp_mem, *dp;
417 	struct stat	st;
418 	fstyp_handle_t	h;
419 	int		error;
420 	char		*arg1, *arg2;
421 
422 	if (vflag) {
423 		arg1 = "-v";
424 		arg2 = device;
425 	} else {
426 		arg1 = device;
427 		arg2 = NULL;
428 	}
429 
430 	if ((dirp = opendir(lib_dir)) == NULL) {
431 		return;
432 	}
433 
434 	name_max = pathconf(lib_dir, _PC_NAME_MAX);
435 	path = calloc(1, name_max + 1);
436 	dp = dp_mem = calloc(1, sizeof (struct dirent) + name_max + 1);
437 	if ((path == NULL) || (dp_mem == NULL)) {
438 		goto out;
439 	}
440 
441 	while ((readdir_r(dirp, dp, &dp) == 0) && (dp != NULL)) {
442 		if (dp->d_name[0] == '.') {
443 			continue;
444 		}
445 		(void) snprintf(path, name_max, "%s/%s", lib_dir, dp->d_name);
446 
447 		/* it's legacy if there's no libfstyp module for it */
448 		error = fstyp_init(fd, 0, path, &h);
449 		if (error != FSTYP_ERR_MOD_NOT_FOUND) {
450 			if (error == 0) {
451 				fstyp_fini(h);
452 			}
453 			continue;
454 		}
455 
456 		/* file must exist and be executable */
457 		(void) snprintf(path, name_max,
458 		    "%s/%s/fstyp", lib_dir, dp->d_name);
459 		if ((stat(path, &st) < 0) ||
460 		    ((st.st_mode & S_IXUSR) == 0)) {
461 			continue;
462 		}
463 
464 		if ((error = run_cmd(path, "fstyp", arg1, arg2)) == 0) {
465 			exit(0);
466 		}
467 	}
468 
469 out:
470 	if (dp_mem != NULL) {
471 		free(dp_mem);
472 	}
473 	if (path != NULL) {
474 		free(path);
475 	}
476 	(void) closedir(dirp);
477 }
478 
479 static int
480 run_cmd(char *path, char *arg0, char *arg1, char *arg2)
481 {
482 	pid_t	pid;
483 	int	status = 1;
484 
485 	pid = fork();
486 	if (pid < 0) {
487 		return (1);
488 	} else if (pid == 0) {
489 		/* child */
490 		(void) execl(path, arg0, arg1, arg2, 0);
491 		exit(1);
492 	}
493 	/* parent */
494 	(void) wait(&status);
495 	return (status);
496 }
497