/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains miscellaneous device validation routines. */ #include "global.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "checkdev.h" #include /* Function prototypes */ static struct swaptable *getswapentries(void); static void freeswapentries(struct swaptable *); static int getpartition(char *pathname); static int checkpartitions(int bm_mounted); static struct swaptable * getswapentries(void) { struct swaptable *st; struct swapent *swapent; int i, num; char fullpathname[MAXPATHLEN]; /* * get the number of swap entries */ if ((num = swapctl(SC_GETNSWP, NULL)) == -1) { err_print("swapctl error "); fullabort(); } if (num == 0) return (NULL); if ((st = (swaptbl_t *)malloc(num * sizeof (swapent_t) + sizeof (int))) == NULL) { err_print("getswapentries: malloc failed.\n"); fullabort(); } swapent = st->swt_ent; for (i = 0; i < num; i++, swapent++) { if ((swapent->ste_path = malloc(MAXPATHLEN)) == NULL) { err_print("getswapentries: malloc failed.\n"); fullabort(); } } st->swt_n = num; if ((num = swapctl(SC_LIST, (void *)st)) == -1) { err_print("swapctl error "); fullabort(); } swapent = st->swt_ent; for (i = 0; i < num; i++, swapent++) { if (*swapent->ste_path != '/') { (void) snprintf(fullpathname, sizeof (fullpathname), "/dev/%s", swapent->ste_path); (void) strcpy(swapent->ste_path, fullpathname); } } return (st); } static void freeswapentries(struct swaptable *st) { struct swapent *swapent; int i; swapent = st->swt_ent; for (i = 0; i < st->swt_n; i++, swapent++) free(swapent->ste_path); free(st); } /* * function getpartition: */ static int getpartition(char *pathname) { int mfd; struct dk_cinfo dkinfo; struct stat stbuf; char raw_device[MAXPATHLEN]; int found = -1; /* * Map the block device name to the raw device name. * If it doesn't appear to be a device name, skip it. */ if (match_substr(pathname, "/dev/") == 0) return (found); (void) strcpy(raw_device, "/dev/r"); (void) strcat(raw_device, pathname + strlen("/dev/")); /* * Determine if this appears to be a disk device. * First attempt to open the device. If if fails, skip it. */ if ((mfd = open(raw_device, O_RDWR | O_NDELAY)) < 0) { return (found); } /* * Must be a character device */ if (fstat(mfd, &stbuf) == -1 || !S_ISCHR(stbuf.st_mode)) { (void) close(mfd); return (found); } /* * Attempt to read the configuration info on the disk. */ if (ioctl(mfd, DKIOCINFO, &dkinfo) < 0) { (void) close(mfd); return (found); } /* * Finished with the opened device */ (void) close(mfd); /* * If it's not the disk we're interested in, it doesn't apply. */ if (cur_disk->disk_dkinfo.dki_ctype != dkinfo.dki_ctype || cur_disk->disk_dkinfo.dki_cnum != dkinfo.dki_cnum || cur_disk->disk_dkinfo.dki_unit != dkinfo.dki_unit || strcmp(cur_disk->disk_dkinfo.dki_dname, dkinfo.dki_dname) != 0) { return (found); } /* * Extract the partition that is mounted. */ return (PARTITION(stbuf.st_rdev)); } /* * This Routine checks to see if there are partitions used for swapping overlaps * a given portion of a disk. If the start parameter is < 0, it means * that the entire disk should be checked */ int checkswap(diskaddr_t start, diskaddr_t end) { struct swaptable *st; struct swapent *swapent; int i; int found = 0; struct dk_map32 *map; int part; /* * If we are only checking part of the disk, the disk must * have a partition map to check against. If it doesn't, * we hope for the best. */ if (cur_parts == NULL) return (0); /* * check for swap entries */ st = getswapentries(); /* * if there are no swap entries return. */ if (st == NULL) return (0); swapent = st->swt_ent; for (i = 0; i < st->swt_n; i++, swapent++) { if ((part = getpartition(swapent->ste_path)) != -1) { if (start == UINT_MAX64) { found = -1; break; } map = &cur_parts->pinfo_map[part]; if ((start >= (int)(map->dkl_cylno * spc() + map->dkl_nblk)) || (end < (int)(map->dkl_cylno * spc()))) { continue; } found = -1; break; }; } freeswapentries(st); /* * If we found trouble and we're running from a command file, * quit before doing something we really regret. */ if (found && option_f) { err_print( "Operation on disks being used for swapping must be interactive.\n"); cmdabort(SIGINT); } return (found); } /* * Determines if there are partitions that are a part of an SVM, VxVM, zpool * volume or a live upgrade device, overlapping a given portion of a disk. * Mounts and swap devices are checked in legacy format code. */ int checkdevinuse(char *cur_disk_path, diskaddr_t start, diskaddr_t end, int print, int check_label) { int error; int found = 0; int check = 0; int i; int bm_inuse = 0; int part = 0; uint64_t slice_start, slice_size; dm_descriptor_t *slices = NULL; nvlist_t *attrs = NULL; char *usage; char *name; /* * If the user does not want to do in use checking, return immediately. * Normally, this is handled in libdiskmgt. For format, there is more * processing required, so we want to bypass the in use checking * here. */ if (NOINUSE_SET) return (0); /* * Skip if it is not a real disk * * There could be two kinds of strings in cur_disk_path * One starts with c?t?d?, while the other is a absolute path of a * block device file. */ if (*cur_disk_path != 'c') { struct stat stbuf; char majorname[16]; major_t majornum; (void) stat(cur_disk_path, &stbuf); majornum = major(stbuf.st_rdev); (void) modctl(MODGETNAME, majorname, sizeof (majorname), &majornum); if (strcmp(majorname, "sd")) if (strcmp(majorname, "ssd")) if (strcmp(majorname, "cmdk")) return (0); } /* * Truncate the characters following "d*", such as "s*" or "p*" */ cur_disk_path = basename(cur_disk_path); name = strrchr(cur_disk_path, 'd'); if (name) { name++; for (; (*name <= '9') && (*name >= '0'); name++) { } *name = (char)0; } /* * For format, we get basic 'in use' details from libdiskmgt. After * that we must do the appropriate checking to see if the 'in use' * details require a bit of additional work. */ dm_get_slices(cur_disk_path, &slices, &error); if (error) { /* * If ENODEV, it actually means the device is not in use. * We will return 0 without displaying error. */ if (error != ENODEV) { err_print("Error occurred with device in use" "checking: %s\n", strerror(error)); return (found); } } if (slices == NULL) return (found); for (i = 0; slices[i] != 0; i++) { /* * If we are checking the whole disk * then any and all in use data is * relevant. */ if (start == UINT_MAX64) { name = dm_get_name(slices[i], &error); if (error != 0 || !name) { err_print("Error occurred with device " "in use checking: %s\n", strerror(error)); continue; } if (dm_inuse(name, &usage, DM_WHO_FORMAT, &error) || error) { if (error != 0) { dm_free_name(name); name = NULL; err_print("Error occurred with " "device in use checking: " "%s\n", strerror(error)); continue; } dm_free_name(name); name = NULL; /* * If this is a dump device, then it is * a failure. You cannot format a slice * that is a dedicated dump device. */ if (strstr(usage, DM_USE_DUMP)) { if (print) { err_print(usage); free(usage); } dm_free_descriptors(slices); return (1); } /* * We really found a device that is in use. * Set 'found' for the return value, and set * 'check' to indicate below that we must * get the partition number to set bm_inuse * in the event we are trying to label this * device. check_label is set when we are * checking modifications for in use slices * on the device. */ found ++; check = 1; if (print) { err_print(usage); free(usage); } } } else { /* * Before getting the in use data, verify that the * current slice is within the range we are checking. */ attrs = dm_get_attributes(slices[i], &error); if (error) { err_print("Error occurred with device in use " "checking: %s\n", strerror(error)); continue; } if (attrs == NULL) { continue; } (void) nvlist_lookup_uint64(attrs, DM_START, &slice_start); (void) nvlist_lookup_uint64(attrs, DM_SIZE, &slice_size); if (start >= (slice_start + slice_size) || (end < slice_start)) { nvlist_free(attrs); attrs = NULL; continue; } name = dm_get_name(slices[i], &error); if (error != 0 || !name) { err_print("Error occurred with device " "in use checking: %s\n", strerror(error)); nvlist_free(attrs); attrs = NULL; continue; } if (dm_inuse(name, &usage, DM_WHO_FORMAT, &error) || error) { if (error != 0) { dm_free_name(name); name = NULL; err_print("Error occurred with " "device in use checking: " "%s\n", strerror(error)); nvlist_free(attrs); attrs = NULL; continue; } dm_free_name(name); name = NULL; /* * If this is a dump device, then it is * a failure. You cannot format a slice * that is a dedicated dump device. */ if (strstr(usage, DM_USE_DUMP)) { if (print) { err_print(usage); free(usage); } dm_free_descriptors(slices); nvlist_free(attrs); return (1); } /* * We really found a device that is in use. * Set 'found' for the return value, and set * 'check' to indicate below that we must * get the partition number to set bm_inuse * in the event we are trying to label this * device. check_label is set when we are * checking modifications for in use slices * on the device. */ found ++; check = 1; if (print) { err_print(usage); free(usage); } } } /* * If check is set it means we found a slice(the current slice) * on this device in use in some way. We potentially want * to check this slice when labeling is * requested. We set bm_inuse with this partition value * for use later if check_label was set when called. */ if (check) { name = dm_get_name(slices[i], &error); if (error != 0 || !name) { err_print("Error occurred with device " "in use checking: %s\n", strerror(error)); nvlist_free(attrs); attrs = NULL; continue; } part = getpartition(name); dm_free_name(name); name = NULL; if (part != -1) { bm_inuse |= 1 << part; } check = 0; } /* * If we have attributes then we have successfully * found the slice we were looking for and we also * know this means we are not searching the whole * disk so break out of the loop * now. */ if (attrs) { nvlist_free(attrs); break; } } if (slices) { dm_free_descriptors(slices); } /* * The user is trying to label the disk. We have to do special * checking here to ensure they are not trying to modify a slice * that is in use in an incompatible way. */ if (check_label && bm_inuse) { /* * !0 indicates that we found a * problem. In this case, we have overloaded * the use of checkpartitions to work for * in use devices. bm_inuse is representative * of the slice that is in use, not that * is mounted as is in the case of the normal * use of checkpartitions. * * The call to checkpartitions will return !0 if * we are trying to shrink a device that we have found * to be in use above. */ return (checkpartitions(bm_inuse)); } return (found); } /* * This routine checks to see if there are mounted partitions overlapping * a given portion of a disk. If the start parameter is < 0, it means * that the entire disk should be checked. */ int checkmount(diskaddr_t start, diskaddr_t end) { FILE *fp; int found = 0; struct dk_map32 *map; int part; struct mnttab mnt_record; struct mnttab *mp = &mnt_record; /* * If we are only checking part of the disk, the disk must * have a partition map to check against. If it doesn't, * we hope for the best. */ if (cur_parts == NULL) return (0); /* * Lock out interrupts because of the mntent protocol. */ enter_critical(); /* * Open the mount table. */ fp = fopen(MNTTAB, "r"); if (fp == NULL) { err_print("Unable to open mount table.\n"); fullabort(); } /* * Loop through the mount table until we run out of entries. */ while ((getmntent(fp, mp)) != -1) { if ((part = getpartition(mp->mnt_special)) == -1) continue; /* * It's a mount on the disk we're checking. If we are * checking whole disk, then we found trouble. We can * quit searching. */ if (start == UINT_MAX64) { found = -1; break; } /* * If the partition overlaps the zone we're checking, * then we found trouble. We can quit searching. */ map = &cur_parts->pinfo_map[part]; if ((start >= (int)(map->dkl_cylno * spc() + map->dkl_nblk)) || (end < (int)(map->dkl_cylno * spc()))) { continue; } found = -1; break; } /* * Close down the mount table. */ (void) fclose(fp); exit_critical(); /* * If we found trouble and we're running from a command file, * quit before doing something we really regret. */ if (found && option_f) { err_print("Operation on mounted disks must be interactive.\n"); cmdabort(SIGINT); } /* * Return the result. */ return (found); } int check_label_with_swap(void) { int i; struct swaptable *st; struct swapent *swapent; int part; int bm_swap = 0; /* * If we are only checking part of the disk, the disk must * have a partition map to check against. If it doesn't, * we hope for the best. */ if (cur_parts == NULL) return (0); /* Will be checked later */ /* * Check for swap entries */ st = getswapentries(); /* * if there are no swap entries return. */ if (st == NULL) return (0); swapent = st->swt_ent; for (i = 0; i < st->swt_n; i++, swapent++) if ((part = getpartition(swapent->ste_path)) != -1) bm_swap |= (1 << part); freeswapentries(st); return (checkpartitions(bm_swap)); } /* * Check the new label with the existing label on the disk, * to make sure that any mounted partitions are not being * affected by writing the new label. */ int check_label_with_mount(void) { FILE *fp; int part; struct mnttab mnt_record; struct mnttab *mp = &mnt_record; int bm_mounted = 0; /* * If we are only checking part of the disk, the disk must * have a partition map to check against. If it doesn't, * we hope for the best. */ if (cur_parts == NULL) return (0); /* Will be checked later */ /* * Lock out interrupts because of the mntent protocol. */ enter_critical(); /* * Open the mount table. */ fp = fopen(MNTTAB, "r"); if (fp == NULL) { err_print("Unable to open mount table.\n"); fullabort(); } /* * Loop through the mount table until we run out of entries. */ while ((getmntent(fp, mp)) != -1) { if ((part = getpartition(mp->mnt_special)) != -1) bm_mounted |= (1 << part); } /* * Close down the mount table. */ (void) fclose(fp); exit_critical(); return (checkpartitions(bm_mounted)); } /* * This Routine checks if any partitions specified * are affected by writing the new label */ static int checkpartitions(int bm_mounted) { struct dk_map32 *n; struct dk_map *o; struct dk_allmap old_map; int i, found = 0; struct partition64 o_efi; /* * Now we need to check that the current partition list and the * previous partition list (which there must be if we actually * have partitions mounted) overlap in any way on the mounted * partitions */ /* * Check if the user wants to online-label an * existing EFI label. */ if (cur_label == L_TYPE_EFI) { for (i = 0; i < EFI_NUMPAR; i++) { if (bm_mounted & (1 << i)) { o_efi.p_partno = i; if (ioctl(cur_file, DKIOCPARTITION, &o_efi) == -1) { err_print("Unable to get information " "for EFI partition %d.\n", i); return (-1); } /* * Partition can grow or remain same. */ if (o_efi.p_start == cur_parts->etoc-> efi_parts[i].p_start && o_efi.p_size <= cur_parts->etoc->efi_parts[i].p_size) { continue; } found = -1; } if (found) break; } } else { /* * Get the "real" (on-disk) version of the partition table */ if (ioctl(cur_file, DKIOCGAPART, &old_map) == -1) { err_print("Unable to get current partition map.\n"); return (-1); } for (i = 0; i < NDKMAP; i++) { if (bm_mounted & (1 << i)) { /* * This partition is mounted */ o = &old_map.dka_map[i]; n = &cur_parts->pinfo_map[i]; #ifdef DEBUG fmt_print( "checkpartitions :checking partition '%c' \n", i + PARTITION_BASE); #endif /* * If partition is identical, we're fine. * If the partition grows, we're also fine, * because the routines in partition.c check * for overflow. It will (ultimately) be up * to the routines in partition.c to warn * about creation of overlapping partitions. */ if (o->dkl_cylno == n->dkl_cylno && o->dkl_nblk <= n->dkl_nblk) { #ifdef DEBUG if (o->dkl_nblk < n->dkl_nblk) { fmt_print( "- new partition larger by %d blocks", n->dkl_nblk-o->dkl_nblk); } fmt_print("\n"); #endif continue; } #ifdef DEBUG fmt_print("- changes; old (%d,%d)->new " "(%d,%d)\n", o->dkl_cylno, o->dkl_nblk, n->dkl_cylno, n->dkl_nblk); #endif found = -1; } if (found) break; } } /* * If we found trouble and we're running from a command file, * quit before doing something we really regret. */ if (found && option_f) { err_print("Operation on mounted disks or \ disks currently being used for swapping must be interactive.\n"); cmdabort(SIGINT); } /* * Return the result. */ return (found); }