xref: /titanic_41/usr/src/cmd/lvm/util/metaimport.c (revision 5a59a8b3d86e67dbe75588879c46e3629f40efec)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Utility to import SVM disksets into an active SVM configuration.
31  */
32 
33 #include <assert.h>
34 #include <strings.h>
35 #include <string.h>
36 #include <meta.h>
37 #include <sys/utsname.h>
38 #include <sys/lvm/md_mddb.h>
39 #include <sys/lvm/md_names.h>
40 #include <sdssc.h>
41 
42 static md_im_drive_info_t	*overlap_disks = NULL;
43 
44 static void
45 usage(mdsetname_t *sp, char *string)
46 {
47 	if ((string != NULL) && (*string != '\0'))
48 		md_eprintf("%s\n", string);
49 
50 	(void) fprintf(stderr,
51 	    "%s:\t%s -s setname [-n] [-f] [-v] [%s...]\n",
52 	    gettext("usage"), myname, gettext("disk"));
53 	(void) fprintf(stderr, "        %s -r [%s...]\n",
54 	    myname, gettext("disk"));
55 	(void) fprintf(stderr, "        %s -?\n", myname);
56 	(void) fprintf(stderr, "        %s -V\n", myname);
57 
58 	md_exit(sp, (string == NULL) ? 0 : 1);
59 }
60 
61 static void
62 print_version(mdsetname_t *sp)
63 {
64 	struct utsname curname;
65 
66 	if (uname(&curname) == -1) {
67 		md_eprintf("%s\n", strerror(errno));
68 		md_exit(sp, 1);
69 	}
70 
71 	(void) fprintf(stderr, "%s %s\n", myname, curname.version);
72 
73 	md_exit(sp, 0);
74 }
75 
76 /*
77  * Returns 0 if there is no overlap, 1 otherwise
78  */
79 static int
80 set_disk_overlap(md_im_set_desc_t *misp)
81 {
82 
83 	md_im_set_desc_t *next, *isp = misp;
84 	md_im_drive_info_t *set_dr, *next_set_dr, **chain;
85 	int	is_overlap = 0;
86 
87 	for (; isp != NULL; isp = isp->mis_next) {
88 	    for (next = isp->mis_next; next != NULL; next = next->mis_next) {
89 
90 		for (set_dr = isp->mis_drives; set_dr != NULL;
91 			set_dr = set_dr->mid_next) {
92 
93 			for (next_set_dr = next->mis_drives;
94 			    next_set_dr != NULL;
95 			    next_set_dr = next_set_dr->mid_next) {
96 			    if (strcmp(set_dr->mid_dnp->cname,
97 				next_set_dr->mid_dnp->cname) == 0) {
98 				/*
99 				 * Chain it, skip if already there
100 				 */
101 				if (overlap_disks == NULL) {
102 					set_dr->overlap = NULL;
103 					overlap_disks = set_dr;
104 				} else {
105 				    for (chain = &overlap_disks;
106 					*chain != NULL;
107 					chain = &(*chain)->overlap) {
108 					if (strcmp(set_dr->mid_dnp->cname,
109 					    (*chain)->mid_dnp->cname)
110 					    == 0)
111 						break;
112 				    }
113 
114 				    if (*chain == NULL) {
115 					*chain = set_dr;
116 					set_dr->overlap = NULL;
117 				    }
118 				}
119 				if (!is_overlap)
120 					is_overlap = 1;
121 			    }
122 			}
123 		}
124 	    }
125 	}
126 
127 	return (is_overlap);
128 }
129 
130 static void
131 report_overlap_recommendation()
132 {
133 	mddb_mb_t		*mbp;
134 	md_error_t		status = mdnullerror;
135 	md_error_t		*ep = &status;
136 	md_im_drive_info_t	*d;
137 
138 	(void) fprintf(stdout, "%s\n", gettext("Warning:  The following disks "
139 	    "have been detected in more than one set.\n"
140 	    "Import recommendation based upon set creation time.\n"
141 	    "Proceed with the import with caution."));
142 
143 	/*
144 	 * Look at all overlapping disks. Determine which slice
145 	 * would have a replica on it. i.e. either slice 7 or 6.
146 	 * Then read the master block. If the disk doesn't have a
147 	 * metadb on it, the master block is a dummy master block.
148 	 * Both dummy or normal master block contain the timestamp
149 	 * which is what we are after. Use this timestamp to issue
150 	 * the appropriate recommendation.
151 	 */
152 	mbp = Malloc(DEV_BSIZE);
153 	for (d = overlap_disks; d != NULL; d = d->overlap) {
154 		mdname_t	*rsp;
155 		uint_t		sliceno;
156 		int		fd = -1;
157 
158 		if (meta_replicaslice(d->mid_dnp, &sliceno, ep) != 0)
159 			continue;
160 
161 		if (d->mid_dnp->vtoc.parts[sliceno].size == 0)
162 			continue;
163 
164 		if ((rsp = metaslicename(d->mid_dnp, sliceno, ep)) == NULL)
165 			continue;
166 		if ((fd = open(rsp->rname, O_RDONLY| O_NDELAY)) < 0)
167 			continue;
168 		if (read_master_block(ep, fd, mbp, DEV_BSIZE) <= 0) {
169 			(void) close(fd);
170 			mdclrerror(ep);
171 			continue;
172 		}
173 		(void) close(fd);
174 		fprintf(stdout, "  %s ", d->mid_dnp->cname);
175 		    (void) fprintf(stdout, "%s: %s\n",
176 		    gettext(" - recommend importing with set "
177 		    "created at "), meta_print_time((md_timeval32_t *)
178 		    (&(mbp->mb_setcreatetime))));
179 	}
180 	Free(mbp);
181 }
182 
183 
184 int
185 main(int argc, char *argv[])
186 {
187 	char			c;
188 	md_error_t		status = mdnullerror;
189 	md_error_t		*ep = &status;
190 	mdsetname_t		*sp = NULL;
191 	char			*setname_new = NULL;
192 	int			report_only = 0;
193 	int			version = 0;
194 	bool_t			dry_run = 0;
195 	md_im_names_t		cnames = { 0, NULL };
196 	int			err_on_prune = 0;
197 	mddrivenamelist_t	*dnlp = NULL;
198 	mddrivenamelist_t	*dp;
199 	mddrivenamelist_t	*skiph = NULL;
200 	mddrivenamelist_t	**skipt = &skiph;
201 	int			rscount = 0;
202 	int			hasreplica;
203 	md_im_set_desc_t	*misp = NULL;
204 	md_im_set_desc_t	**mispp = &misp;
205 	mhd_mhiargs_t		mhiargs = defmhiargs;
206 	int			have_multiple_sets = 0;
207 	int			force = 0;
208 	int			overlap = 0;
209 	int			partial = 0;
210 	uint_t			imp_flags = 0;
211 	int			set_count = 0;
212 
213 	/*
214 	 * Get the locale set up before calling any other routines
215 	 * with messages to output.  Just in case we're not in a build
216 	 * environment, make sure that TEXT_DOMAIN gets set to
217 	 * something.
218 	 */
219 #if !defined(TEXT_DOMAIN)
220 #define	TEXT_DOMAIN "SYS_TEST"
221 #endif
222 	(void) setlocale(LC_ALL, "");
223 	(void) textdomain(TEXT_DOMAIN);
224 
225 	/*
226 	 * Check to see if the libsds_sc.so is bound on the
227 	 * current system. If it is, it means the system is
228 	 * part of a cluster.
229 	 *
230 	 * The import operation is currently not supported
231 	 * in a SunCluster environment.
232 	 */
233 	if (sdssc_bind_library() != SDSSC_NOT_BOUND) {
234 		printf(gettext(
235 		    "%s: Import operation not supported under SunCluster\n"),
236 		    argv[0]);
237 		exit(0);
238 	}
239 
240 	/* initialize */
241 	if (md_init(argc, argv, 0, 1, ep) != 0) {
242 		mde_perror(ep, "");
243 		md_exit(sp, 1);
244 	}
245 
246 	optind = 1;
247 	opterr = 1;
248 
249 	while ((c = getopt(argc, argv, "frns:vV?")) != -1) {
250 		switch (c) {
251 
252 		case 'f':
253 			force = 1;
254 			break;
255 
256 		case 'n':
257 			dry_run = 1;
258 			break;
259 
260 		case 'r':
261 			report_only = 1;
262 			imp_flags |= META_IMP_REPORT;
263 			break;
264 
265 		case 's':
266 			setname_new = optarg;
267 			break;
268 
269 		case 'v':
270 			imp_flags |= META_IMP_VERBOSE;
271 			break;
272 
273 		case 'V':
274 			version = 1;
275 			break;
276 
277 		case '?':
278 		default:
279 			usage(sp, NULL);
280 			break;
281 		}
282 	}
283 
284 	if (version == 1)
285 		print_version(sp);
286 
287 	/* Detect conflicting options */
288 	if ((dry_run != 0) && (report_only != 0))
289 		usage(sp, gettext("The -n and -r options conflict."));
290 
291 	if ((report_only != 0) && (setname_new != NULL))
292 		usage(sp, gettext("The -r and -s options conflict."));
293 
294 	if ((report_only == 0) && (setname_new == NULL))
295 		usage(sp, gettext("You must specify either -r or -s."));
296 
297 	/* Don't do any real work if we don't have root privilege */
298 	if (meta_check_root(ep) != 0) {
299 		mde_perror(ep, "");
300 		md_exit(sp, 1);
301 	}
302 
303 	if (meta_setup_db_locations(ep) != 0) {
304 		mde_perror(ep, "");
305 		if (mdismddberror(ep, MDE_DB_STALE))
306 			md_exit(sp, 66);
307 		if (! mdiserror(ep, MDE_MDDB_CKSUM))
308 			md_exit(sp, 1);
309 	}
310 
311 	/*
312 	 * Read remaining arguments into drive name list, otherwise
313 	 * call routine to list all drives in system.
314 	 */
315 	if (argc > optind) {
316 		int i;
317 
318 		/* For user specified disks, they MUST not be in use */
319 		err_on_prune = 1;
320 
321 		/* All remaining args should be disks */
322 		cnames.min_count = argc - optind;
323 		cnames.min_names = Malloc(cnames.min_count * sizeof (char *));
324 
325 		for (i = 0; i < cnames.min_count; i++, optind++) {
326 			mddrivename_t *dnp;
327 			dnp = metadrivename(&sp, argv[optind], ep);
328 			if (dnp == NULL) {
329 				mde_perror(ep, "");
330 				md_exit(sp, 1);
331 			} else {
332 				cnames.min_names[i] = dnp->rname;
333 			}
334 		}
335 	} else {
336 		if (meta_list_disks(ep, &cnames) != 0) {
337 			mde_perror(ep, "");
338 			md_exit(sp, 1);
339 		}
340 	}
341 
342 	/*
343 	 * If the user specified disks on the command line, min_count will be
344 	 * greater than zero.  If they didn't, it should be safe to assume that
345 	 * the system in question has at least one drive detected by the
346 	 * snapshot code, or we would have barfed earlier initializing the
347 	 * metadb.
348 	 */
349 	assert(cnames.min_count > 0);
350 
351 	/*
352 	 * Prune the list:
353 	 * - get rid of drives in current svm configuration
354 	 * - get rid of mounted drives
355 	 * - get rid of swap drives
356 	 * - get rid of drives in other sets
357 	 *
358 	 * If drives were specified on the command line, it should be
359 	 * an error to find in-use disks in the list.  (err_on_prune)
360 	 *
361 	 * On return from meta_prune_cnames call, dnlp
362 	 * will have candidate for replica scan.
363 	 */
364 	dnlp = meta_prune_cnames(ep, &cnames, err_on_prune);
365 
366 	/*
367 	 * Doctor the drive string in the error structure to list all of the
368 	 * unused disks, rather than just one.  The output will be done in the
369 	 * following !mdisok() block.
370 	 */
371 	if (mdisdserror(ep, MDE_DS_DRIVEINUSE)) {
372 		md_ds_error_t		*ip =
373 		    &ep->info.md_error_info_t_u.ds_error;
374 		char			*dlist;
375 		int			sizecnt = 0;
376 
377 		sizecnt += strlen(ip->drive);
378 		for (dp = dnlp->next; dp != NULL; dp = dp->next) {
379 			sizecnt += 2; /* for the ", " */
380 			sizecnt += strlen(dp->drivenamep->cname);
381 		}
382 
383 		dlist = Malloc(sizecnt);
384 
385 		strlcpy(dlist, ip->drive, sizecnt);
386 		Free(ip->drive);
387 
388 		dlist += strlen(ip->drive);
389 		for (dp = dnlp->next; dp != NULL; dp = dp->next) {
390 			strlcat(dlist, ", ", sizecnt);
391 			strlcat(dlist, dp->drivenamep->cname, sizecnt);
392 		}
393 
394 		ip->drive = Strdup(dlist);
395 	}
396 
397 	/* Don't continue if we're already hosed */
398 	if (!mdisok(ep)) {
399 		mde_perror(ep, "");
400 		md_exit(sp, 1);
401 	}
402 
403 	/* ...or if there's nothing to scan */
404 	if (dnlp == NULL) {
405 		md_eprintf("%s\n", gettext("no unused disks detected"));
406 		md_exit(sp, 0);
407 	}
408 
409 	/* Scan qualified disks */
410 	for (dp = dnlp; dp != NULL; dp = dp->next) {
411 		mddrivenamelist_t *slp;
412 
413 		/* is the current drive on the skip list? */
414 		for (slp = skiph; slp != NULL; slp = slp->next) {
415 		    if (dp->drivenamep == slp->drivenamep)
416 			    goto skipdisk;
417 		}
418 
419 		/*
420 		 * In addition to updating the misp list, either verbose or
421 		 * standard output will be generated.
422 		 *
423 		 */
424 		hasreplica = meta_get_and_report_set_info(dp, mispp, 0,
425 		    imp_flags, &set_count, ep);
426 
427 		/*
428 		 * If current disk is part of a partial diskset,
429 		 * meta_get_set_info returns an ENOTSUP for this disk.
430 		 * Import of partial disksets isn't supported yet,
431 		 * so do NOT put this disk onto any list being set up
432 		 * by metaimport. The partial diskset error message will
433 		 * only be printed once when the first partial diskset is
434 		 * detected. If the user is actually trying to import the
435 		 * partial diskset, print the error and exit; otherwise,
436 		 * print the error and continue.
437 		 */
438 		if (hasreplica == ENOTSUP) {
439 			if (report_only) {
440 			    if (!partial) {
441 				mde_perror(ep, "");
442 				partial = 1;
443 			    }
444 			    mdclrerror(ep);
445 			    goto skipdisk;
446 			} else {
447 			    mde_perror(ep, "");
448 			    md_exit(sp, 1);
449 			}
450 		}
451 
452 		if (hasreplica < 0) {
453 			mde_perror(ep, "");
454 			mdclrerror(ep);
455 		} else {
456 			md_im_set_desc_t	*p;
457 			md_im_drive_info_t	*d;
458 
459 			rscount += hasreplica;
460 
461 			/* Eliminate duplicate reporting */
462 			if (hasreplica > 0) {
463 				md_timeval32_t	firstdisktime;
464 
465 				/*
466 				 * Go to the tail for the current set
467 				 */
468 				for (p = misp; p->mis_next != NULL;
469 				    p = p->mis_next);
470 				firstdisktime =
471 				    p->mis_drives->mid_setcreatetimestamp;
472 				for (d = p->mis_drives;
473 				    d != NULL;
474 				    d = d->mid_next) {
475 					/*
476 					 * if the mb_setcreatetime for a disk
477 					 * is not the same as the first disk
478 					 * in the set, don't put it on the
479 					 * skip list. This disk probably
480 					 * doesn't really belong in this set
481 					 * and we'll want to look at it again
482 					 * to figure out where it does belong.
483 					 */
484 					if ((d->mid_setcreatetimestamp.tv_sec !=
485 					    firstdisktime.tv_sec) ||
486 					    (d->mid_setcreatetimestamp.tv_usec
487 					    != firstdisktime.tv_usec))
488 						continue;
489 					skipt =
490 					    meta_drivenamelist_append_wrapper(
491 						skipt, d->mid_dnp);
492 				}
493 			}
494 		}
495 
496 skipdisk:
497 		;
498 	}
499 
500 	/*
501 	 * Now have entire list of disks associated with diskset including
502 	 * disks listed in mddb locator blocks and namespace. Before importing
503 	 * diskset need to recheck that none of these disks is already in use.
504 	 * If a disk is found that is already in use, print error and exit.
505 	 */
506 	if (!report_only) {
507 		md_im_set_desc_t	*p;
508 		md_im_drive_info_t	*d;
509 		mddrivename_t		*dnp;
510 
511 		for (p = misp; p != NULL; p = p->mis_next) {
512 			for (d = p->mis_drives; d != NULL; d = d->mid_next) {
513 				dnp = d->mid_dnp;
514 				if (meta_imp_drvused(sp, dnp, ep)) {
515 					(void) mddserror(ep,
516 						MDE_DS_DRIVEINUSE, 0, NULL,
517 						dnp->cname, NULL);
518 					mde_perror(ep, "");
519 					md_exit(sp, 0);
520 				}
521 			}
522 		}
523 	}
524 
525 	/*
526 	 * If there are no unconfigured sets, then our work here is done.
527 	 * Hopefully this is friendlier than just not printing anything at all.
528 	 */
529 	if (rscount == 0) {
530 		/*
531 		 * If we've found partial disksets but no complete disksets,
532 		 * we don't want this to print.
533 		 */
534 		if (!partial) {
535 			md_eprintf("%s\n", gettext("no unconfigured sets "
536 			    "detected"));
537 		}
538 		md_exit(sp, 0);
539 	}
540 
541 	/*
542 	 * We'll need this info for both the report content and the import
543 	 * decision.  By the time we're here, misp should NOT be NULL (or we
544 	 * would have exited in the rscount == 0 test above).
545 	 */
546 	assert(misp != NULL);
547 	if (misp->mis_next != NULL) {
548 		have_multiple_sets = 1;
549 	}
550 	/*
551 	 * Generate the appropriate (verbose or not) report for all sets
552 	 * detected.  If we're planning on importing later, only include the
553 	 * "suggested import" command if multiple sets were detected.  (That
554 	 * way, when we error out later, we have still provided useful
555 	 * information.)
556 	 */
557 
558 	/*
559 	 * Now we should have all the unconfigured sets detected
560 	 * check for the overlapping
561 	 */
562 	if (have_multiple_sets) {
563 		/* Printing out how many candidate disksets we found. */
564 		if (imp_flags & META_IMP_REPORT) {
565 			(void) printf("%s: %i\n\n",
566 			    gettext("Number of disksets eligible for import"),
567 			    set_count);
568 		}
569 
570 		overlap = set_disk_overlap(misp);
571 		if (overlap) {
572 			report_overlap_recommendation();
573 		}
574 
575 		if (!report_only) {
576 			md_eprintf("%s\n\n", gettext("multiple unconfigured "
577 			    "sets detected.\nRerun the command with the "
578 			    "suggested options for the desired set."));
579 		}
580 	}
581 
582 
583 	/*
584 	 * If it's a report-only request, we're done.  If it's an import
585 	 * request, make sure that we only have one entry in the set list.
586 	 */
587 
588 	if (report_only) {
589 		md_exit(sp, 0);
590 	} else if (have_multiple_sets) {
591 		md_exit(sp, 1);
592 	}
593 
594 	if (setname_new == NULL) {
595 		usage(sp, gettext("You must specify a new set name."));
596 	}
597 
598 	(void) meta_imp_set(misp, setname_new, force, dry_run, ep);
599 
600 	if (dry_run) {
601 		md_exit(sp, 0);
602 	}
603 
604 	if (!mdisok(ep)) {
605 		mde_perror(ep, "");
606 		md_exit(sp, 1);
607 	}
608 
609 	if ((sp = metasetname(setname_new, ep)) == NULL) {
610 		mde_perror(ep, "");
611 		md_exit(sp, 1);
612 	}
613 
614 	if (meta_lock_nowait(sp, ep) != 0) {
615 		mde_perror(ep, "");
616 		md_exit(sp, 10);	/* special errcode */
617 	}
618 
619 	if (meta_set_take(sp, &mhiargs, 0, 0, &status)) {
620 		mde_perror(&status, "");
621 		md_exit(sp, 1);
622 	}
623 
624 	md_exit(sp, 0);
625 	/*NOTREACHED*/
626 	return (0);
627 }
628