/* * Add wedges for a valid NetBSD disklabel. */ static void addwedges(const mbr_args_t *a, const struct disklabel *lp) { int error, i; for (i = 0; i < lp->d_npartitions; i++) { struct dkwedge_info dkw; const struct partition *p; const char *ptype; p = &lp->d_partitions[i]; if (p->p_fstype == FS_UNUSED) continue; if ((ptype = bsdlabel_fstype_to_str(p->p_fstype)) == NULL) { /* * XXX Should probably just add these... * XXX maybe just have an empty ptype? */ aprint_verbose("%s: skipping partition %d, type %d\n", a->pdk->dk_name, i, p->p_fstype); continue; } strcpy(dkw.dkw_ptype, ptype); strcpy(dkw.dkw_parent, a->pdk->dk_name); dkw.dkw_offset = p->p_offset; dkw.dkw_size = p->p_size; /* * These get historical disk naming style * wedge names. */ snprintf((char *)&dkw.dkw_wname, sizeof(dkw.dkw_wname), "%s%c", a->pdk->dk_name, 'a' + i); error = dkwedge_add(&dkw); if (error == EEXIST) aprint_error("%s: wedge named '%s' already " "exists, manual intervention required\n", a->pdk->dk_name, dkw.dkw_wname); else if (error) aprint_error("%s: error %d adding partition " "%d type %d\n", a->pdk->dk_name, error, i, p->p_fstype); } }
/* * disk_ioctl -- * Generic disk ioctl handling. */ int disk_ioctl(struct disk *dk, dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct dkwedge_info *dkw; struct partinfo *pi; struct partition *dp; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif switch (cmd) { case DIOCGDISKINFO: if (dk->dk_info == NULL) return ENOTSUP; return prop_dictionary_copyout_ioctl(data, cmd, dk->dk_info); case DIOCGSECTORSIZE: *(u_int *)data = dk->dk_geom.dg_secsize; return 0; case DIOCGMEDIASIZE: *(off_t *)data = (off_t)dk->dk_geom.dg_secsize * dk->dk_geom.dg_secperunit; return 0; default: break; } if (dev == NODEV) return EPASSTHROUGH; /* The following should be moved to dk_ioctl */ switch (cmd) { case DIOCGDINFO: if (dk->dk_label == NULL) return EBUSY; memcpy(data, dk->dk_label, sizeof (*dk->dk_label)); return 0; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: if (dk->dk_label == NULL) return EBUSY; memcpy(&newlabel, dk->dk_label, sizeof(newlabel)); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof(struct olddisklabel)); return 0; #endif case DIOCGPARTINFO: pi = data; memset(pi, 0, sizeof(*pi)); pi->pi_secsize = dk->dk_geom.dg_secsize; pi->pi_bsize = BLKDEV_IOSIZE; if (DISKPART(dev) == RAW_PART) { pi->pi_size = dk->dk_geom.dg_secperunit; return 0; } if (dk->dk_label == NULL) return EBUSY; dp = &dk->dk_label->d_partitions[DISKPART(dev)]; pi->pi_offset = dp->p_offset; pi->pi_size = dp->p_size; pi->pi_fstype = dp->p_fstype; pi->pi_frag = dp->p_frag; pi->pi_fsize = dp->p_fsize; pi->pi_cpg = dp->p_cpg; /* * dholland 20130616: XXX this logic should not be * here. It is here because the old buffer cache * demands that all accesses to the same blocks need * to be the same size; but it only works for FFS and * nowadays I think it'll fail silently if the size * info in the disklabel is wrong. (Or missing.) The * buffer cache needs to be smarter; or failing that * we need a reliable way here to get the right block * size; or a reliable way to guarantee that (a) the * fs is not mounted when we get here and (b) any * buffers generated here will get purged when the fs * does get mounted. */ if (dp->p_fstype == FS_BSDFFS && dp->p_frag != 0 && dp->p_fsize != 0) pi->pi_bsize = dp->p_frag * dp->p_fsize; return 0; case DIOCAWEDGE: if ((flag & FWRITE) == 0) return EBADF; dkw = data; strlcpy(dkw->dkw_parent, dk->dk_name, sizeof(dkw->dkw_parent)); return dkwedge_add(dkw); case DIOCDWEDGE: if ((flag & FWRITE) == 0) return EBADF; dkw = data; strlcpy(dkw->dkw_parent, dk->dk_name, sizeof(dkw->dkw_parent)); return dkwedge_del(dkw); case DIOCLWEDGES: return dkwedge_list(dk, data, l); case DIOCMWEDGES: if ((flag & FWRITE) == 0) return EBADF; dkwedge_discover(dk); return 0; default: return EPASSTHROUGH; } }
static int dkwedge_discover_apple(struct disk *pdk, struct vnode *vp) { size_t i; int error; void *buf; uint32_t blocksize, offset, rsize; struct apple_drvr_map *am; struct apple_part_map_entry *ae; struct apple_blockzeroblock ab; const char *ptype; buf = DKW_MALLOC(ASIZE); if ((error = dkwedge_read(pdk, vp, 0, buf, ASIZE)) != 0) { DPRINTF("%s: read @%u %d\n", __func__, 0, error); goto out; } am = buf; swap_apple_drvr_map(am); error = ESRCH; if (am->sbSig != APPLE_DRVR_MAP_MAGIC) { DPRINTF("%s: drvr magic %x != %x\n", __func__, am->sbSig, APPLE_DRVR_MAP_MAGIC); goto out; } blocksize = am->sbBlockSize; rsize = 1 << (ilog2(MAX(sizeof(*ae), blocksize) - 1) + 1); if (ASIZE < rsize) { DPRINTF("%s: buffer too small %u < %u\n", __func__, ASIZE, rsize); goto out; } ae = buf; for (offset = blocksize;; offset += rsize) { DPRINTF("%s: offset %x rsize %x\n", __func__, offset, rsize); if ((error = dkwedge_read(pdk, vp, offset / DEV_BSIZE, buf, rsize)) != 0) { DPRINTF("%s: read @%u %d\n", __func__, offset, error); goto out; } swap_apple_part_map_entry(ae); if (ae->pmSig != APPLE_PART_MAP_ENTRY_MAGIC) { DPRINTF("%s: part magic %x != %x\n", __func__, ae->pmSig, APPLE_PART_MAP_ENTRY_MAGIC); break; } for (i = 0; i < __arraycount(map); i++) if (strcasecmp(map[i].name, ae->pmPartType) == 0) break; DPRINTF("%s: %s/%s PH=%u/%u LG=%u/%u\n", __func__, ae->pmPartName, ae->pmPartType, ae->pmPyPartStart, ae->pmPartBlkCnt, ae->pmLgDataStart, ae->pmDataCnt); if (i == __arraycount(map)) continue; ptype = map[i].type; memcpy(&ab, ae->pmBootArgs, sizeof(ab)); swap_apple_blockzeroblock(&ab); if (ab.bzbMagic == APPLE_BZB_MAGIC) { if (ab.bzbType == APPLE_BZB_TYPESWAP) ptype = DKW_PTYPE_SWAP; } struct dkwedge_info dkw; strcpy(dkw.dkw_ptype, ptype); strcpy(dkw.dkw_parent, pdk->dk_name); dkw.dkw_offset = ae->pmPyPartStart; dkw.dkw_size = ae->pmPartBlkCnt; strlcpy(dkw.dkw_wname, ae->pmPartName, sizeof(dkw.dkw_wname)); error = dkwedge_add(&dkw); if (error == EEXIST) aprint_error("%s: wedge named '%s' already " "exists, manual intervention required\n", pdk->dk_name, dkw.dkw_wname); else if (error) aprint_error("%s: error %d adding partition " "%s type %s\n", pdk->dk_name, error, ae->pmPartType, dkw.dkw_ptype); } out: DKW_FREE(buf); DPRINTF("%s: return %d\n", __func__, error); return error; }
int dk_ioctl(struct dk_intf *di, struct dk_softc *dksc, dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct disklabel *lp; struct disk *dk; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif int error = 0; DPRINTF_FOLLOW(("dk_ioctl(%s, %p, 0x%"PRIx64", 0x%lx)\n", di->di_dkname, dksc, dev, cmd)); /* ensure that the pseudo disk is open for writes for these commands */ switch (cmd) { case DIOCSDINFO: case DIOCWDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCSDINFO: case ODIOCWDINFO: #endif case DIOCWLABEL: case DIOCAWEDGE: case DIOCDWEDGE: if ((flag & FWRITE) == 0) return EBADF; } /* ensure that the pseudo-disk is initialized for these */ switch (cmd) { #ifdef DIOCGSECTORSIZE case DIOCGSECTORSIZE: case DIOCGMEDIASIZE: #endif case DIOCGDINFO: case DIOCSDINFO: case DIOCWDINFO: case DIOCGPART: case DIOCWLABEL: case DIOCGDEFLABEL: case DIOCAWEDGE: case DIOCDWEDGE: case DIOCLWEDGES: case DIOCCACHESYNC: #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: case ODIOCSDINFO: case ODIOCWDINFO: case ODIOCGDEFLABEL: #endif if ((dksc->sc_flags & DKF_INITED) == 0) return ENXIO; } switch (cmd) { #ifdef DIOCGSECTORSIZE case DIOCGSECTORSIZE: *(u_int *)data = dksc->sc_dkdev.dk_geom.dg_secsize; return 0; case DIOCGMEDIASIZE: *(off_t *)data = (off_t)dksc->sc_dkdev.dk_geom.dg_secsize * dksc->sc_dkdev.dk_geom.dg_nsectors; return 0; #endif case DIOCGDINFO: *(struct disklabel *)data = *(dksc->sc_dkdev.dk_label); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: newlabel = *(dksc->sc_dkdev.dk_label); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); break; #endif case DIOCGPART: ((struct partinfo *)data)->disklab = dksc->sc_dkdev.dk_label; ((struct partinfo *)data)->part = &dksc->sc_dkdev.dk_label->d_partitions[DISKPART(dev)]; break; case DIOCWDINFO: case DIOCSDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCWDINFO: case ODIOCSDINFO: #endif #ifdef __HAVE_OLD_DISKLABEL if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { memset(&newlabel, 0, sizeof newlabel); memcpy(&newlabel, data, sizeof (struct olddisklabel)); lp = &newlabel; } else #endif lp = (struct disklabel *)data; dk = &dksc->sc_dkdev; mutex_enter(&dk->dk_openlock); dksc->sc_flags |= DKF_LABELLING; error = setdisklabel(dksc->sc_dkdev.dk_label, lp, 0, dksc->sc_dkdev.dk_cpulabel); if (error == 0) { if (cmd == DIOCWDINFO #ifdef __HAVE_OLD_DISKLABEL || cmd == ODIOCWDINFO #endif ) error = writedisklabel(DKLABELDEV(dev), di->di_strategy, dksc->sc_dkdev.dk_label, dksc->sc_dkdev.dk_cpulabel); } dksc->sc_flags &= ~DKF_LABELLING; mutex_exit(&dk->dk_openlock); break; case DIOCWLABEL: if (*(int *)data != 0) dksc->sc_flags |= DKF_WLABEL; else dksc->sc_flags &= ~DKF_WLABEL; break; case DIOCGDEFLABEL: dk_getdefaultlabel(di, dksc, (struct disklabel *)data); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDEFLABEL: dk_getdefaultlabel(di, dksc, &newlabel); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); break; #endif case DIOCAWEDGE: { struct dkwedge_info *dkw = (void *)data; if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name); return (dkwedge_add(dkw)); } case DIOCDWEDGE: { struct dkwedge_info *dkw = (void *)data; if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strcpy(dkw->dkw_parent, dksc->sc_dkdev.dk_name); return (dkwedge_del(dkw)); } case DIOCLWEDGES: { struct dkwedge_list *dkwl = (void *)data; return (dkwedge_list(&dksc->sc_dkdev, dkwl, l)); } case DIOCGSTRATEGY: { struct disk_strategy *dks = (void *)data; int s; s = splbio(); strlcpy(dks->dks_name, bufq_getstrategyname(dksc->sc_bufq), sizeof(dks->dks_name)); splx(s); dks->dks_paramlen = 0; return 0; } case DIOCSSTRATEGY: { struct disk_strategy *dks = (void *)data; struct bufq_state *new; struct bufq_state *old; int s; if ((flag & FWRITE) == 0) { return EBADF; } if (dks->dks_param != NULL) { return EINVAL; } dks->dks_name[sizeof(dks->dks_name) - 1] = 0; /* ensure term */ error = bufq_alloc(&new, dks->dks_name, BUFQ_EXACT|BUFQ_SORT_RAWBLOCK); if (error) { return error; } s = splbio(); old = dksc->sc_bufq; bufq_move(new, old); dksc->sc_bufq = new; splx(s); bufq_free(old); return 0; } default: error = ENOTTY; } return error; }
static void getparts(mbr_args_t *a, uint32_t off, uint32_t extoff) { struct dkwedge_info dkw; struct mbr_partition *dp; struct mbr_sector *mbr; const char *ptype; int i, error; error = dkwedge_read(a->pdk, a->vp, off, a->buf, a->secsize); if (error) { aprint_error("%s: unable to read MBR @ %u/%u, " "error = %d\n", a->pdk->dk_name, off, a->secsize, a->error); a->error = error; return; } mbr = a->buf; if (mbr->mbr_magic != htole16(MBR_MAGIC)) return; dp = mbr->mbr_parts; for (i = 0; i < MBR_PART_COUNT; i++) { /* Extended partitions are handled below. */ if (dp[i].mbrp_type == 0 || MBR_IS_EXTENDED(dp[i].mbrp_type)) continue; if ((ptype = mbr_ptype_to_str(dp[i].mbrp_type)) == NULL) { /* * XXX Should probably just add these... * XXX maybe just have an empty ptype? */ aprint_verbose("%s: skipping partition %d, " "type 0x%02x\n", a->pdk->dk_name, i, dp[i].mbrp_type); continue; } strcpy(dkw.dkw_ptype, ptype); strcpy(dkw.dkw_parent, a->pdk->dk_name); dkw.dkw_offset = le32toh(dp[i].mbrp_start); dkw.dkw_size = le32toh(dp[i].mbrp_size); /* * These get historical disk naming style * wedge names. We start at 'e', and reserve * 4 slots for each MBR we parse. * * XXX For FAT, we should extract the FAT volume * XXX name. */ snprintf(dkw.dkw_wname, sizeof(dkw.dkw_wname), "%s%c", a->pdk->dk_name, 'e' + (a->mbr_count * MBR_PART_COUNT) + i); error = dkwedge_add(&dkw); if (error == EEXIST) aprint_error("%s: wedge named '%s' already " "exists, manual intervention required\n", a->pdk->dk_name, dkw.dkw_wname); else if (error) aprint_error("%s: error %d adding partition " "%d type 0x%02x\n", a->pdk->dk_name, error, (a->mbr_count * MBR_PART_COUNT) + i, dp[i].mbrp_type); } /* We've parsed one MBR. */ a->mbr_count++; /* Recursively scan extended partitions. */ for (i = 0; i < MBR_PART_COUNT; i++) { uint32_t poff; if (MBR_IS_EXTENDED(dp[i].mbrp_type)) { poff = le32toh(dp[i].mbrp_start) + extoff; getparts(a, poff, extoff ? extoff : poff); } } }
static int dkwedge_discover_gpt(struct disk *pdk, struct vnode *vp) { static const struct uuid ent_type_unused = GPT_ENT_TYPE_UNUSED; static const char gpt_hdr_sig[] = GPT_HDR_SIG; struct dkwedge_info dkw; void *buf; uint32_t secsize; struct gpt_hdr *hdr; struct gpt_ent *ent; uint32_t entries, entsz; daddr_t lba_start, lba_end, lba_table; uint32_t gpe_crc; int error; u_int i; size_t r, n; uint8_t *c; secsize = DEV_BSIZE << pdk->dk_blkshift; buf = malloc(secsize, M_DEVBUF, M_WAITOK); /* * Note: We don't bother with a Legacy or Protective MBR * here. If a GPT is found, then the search stops, and * the GPT is authoritative. */ /* Read in the GPT Header. */ error = dkwedge_read(pdk, vp, GPT_HDR_BLKNO << pdk->dk_blkshift, buf, secsize); if (error) goto out; hdr = buf; /* Validate it. */ if (memcmp(gpt_hdr_sig, hdr->hdr_sig, sizeof(hdr->hdr_sig)) != 0) { /* XXX Should check at end-of-disk. */ error = ESRCH; goto out; } if (hdr->hdr_revision != htole32(GPT_HDR_REVISION)) { /* XXX Should check at end-of-disk. */ error = ESRCH; goto out; } if (le32toh(hdr->hdr_size) > secsize) { /* XXX Should check at end-of-disk. */ error = ESRCH; goto out; } if (gpt_verify_header_crc(hdr) == 0) { /* XXX Should check at end-of-disk. */ error = ESRCH; goto out; } /* XXX Now that we found it, should we validate the backup? */ { struct uuid disk_guid; char guid_str[UUID_STR_LEN]; uuid_dec_le(hdr->hdr_guid, &disk_guid); uuid_snprintf(guid_str, sizeof(guid_str), &disk_guid); aprint_verbose("%s: GPT GUID: %s\n", pdk->dk_name, guid_str); } entries = le32toh(hdr->hdr_entries); entsz = roundup(le32toh(hdr->hdr_entsz), 8); if (entsz > roundup(sizeof(struct gpt_ent), 8)) { aprint_error("%s: bogus GPT entry size: %u\n", pdk->dk_name, le32toh(hdr->hdr_entsz)); error = EINVAL; goto out; } gpe_crc = le32toh(hdr->hdr_crc_table); /* XXX Clamp entries at 128 for now. */ if (entries > 128) { aprint_error("%s: WARNING: clamping number of GPT entries to " "128 (was %u)\n", pdk->dk_name, entries); entries = 128; } lba_start = le64toh(hdr->hdr_lba_start); lba_end = le64toh(hdr->hdr_lba_end); lba_table = le64toh(hdr->hdr_lba_table); if (lba_start < 0 || lba_end < 0 || lba_table < 0) { aprint_error("%s: GPT block numbers out of range\n", pdk->dk_name); error = EINVAL; goto out; } free(buf, M_DEVBUF); buf = malloc(roundup(entries * entsz, secsize), M_DEVBUF, M_WAITOK); error = dkwedge_read(pdk, vp, lba_table << pdk->dk_blkshift, buf, roundup(entries * entsz, secsize)); if (error) { /* XXX Should check alternate location. */ aprint_error("%s: unable to read GPT partition array, " "error = %d\n", pdk->dk_name, error); goto out; } if (crc32(0, buf, entries * entsz) != gpe_crc) { /* XXX Should check alternate location. */ aprint_error("%s: bad GPT partition array CRC\n", pdk->dk_name); error = EINVAL; goto out; } /* * Walk the partitions, adding a wedge for each type we know about. */ for (i = 0; i < entries; i++) { struct uuid ptype_guid, ent_guid; const char *ptype; int j; char ptype_guid_str[UUID_STR_LEN], ent_guid_str[UUID_STR_LEN]; ent = (struct gpt_ent *)((char *)buf + (i * entsz)); uuid_dec_le(ent->ent_type, &ptype_guid); if (memcmp(&ptype_guid, &ent_type_unused, sizeof(ptype_guid)) == 0) continue; uuid_dec_le(ent->ent_guid, &ent_guid); uuid_snprintf(ptype_guid_str, sizeof(ptype_guid_str), &ptype_guid); uuid_snprintf(ent_guid_str, sizeof(ent_guid_str), &ent_guid); /* figure out the type */ ptype = gpt_ptype_guid_to_str(&ptype_guid); strcpy(dkw.dkw_ptype, ptype); strcpy(dkw.dkw_parent, pdk->dk_name); dkw.dkw_offset = le64toh(ent->ent_lba_start); dkw.dkw_size = le64toh(ent->ent_lba_end) - dkw.dkw_offset + 1; /* XXX Make sure it falls within the disk's data area. */ if (ent->ent_name[0] == 0x0000) strcpy(dkw.dkw_wname, ent_guid_str); else { c = dkw.dkw_wname; r = sizeof(dkw.dkw_wname) - 1; for (j = 0; ent->ent_name[j] != 0x0000; j++) { n = wput_utf8(c, r, le16toh(ent->ent_name[j])); if (n == 0) break; c += n; r -= n; } *c = '\0'; } /* * Try with the partition name first. If that fails, * use the GUID string. If that fails, punt. */ if ((error = dkwedge_add(&dkw)) == EEXIST && strcmp(dkw.dkw_wname, ent_guid_str) != 0) { strcpy(dkw.dkw_wname, ent_guid_str); error = dkwedge_add(&dkw); if (!error) aprint_error("%s: wedge named '%s' already " "existed, using '%s'\n", pdk->dk_name, dkw.dkw_wname, /* XXX Unicode */ ent_guid_str); } if (error == EEXIST) aprint_error("%s: wedge named '%s' already exists, " "manual intervention required\n", pdk->dk_name, dkw.dkw_wname); else if (error) aprint_error("%s: error %d adding entry %u (%s), " "type %s\n", pdk->dk_name, error, i, ent_guid_str, ptype_guid_str); } error = 0; out: free(buf, M_DEVBUF); return (error); }
int ofdisk_ioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct ofdisk_softc *of = device_lookup_private(&ofdisk_cd, DISKUNIT(dev)); int error; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif switch (cmd) { case DIOCGDINFO: *(struct disklabel *)data = *of->sc_dk.dk_label; return 0; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: newlabel = *of->sc_dk.dk_label; if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); return 0; #endif case DIOCGPART: ((struct partinfo *)data)->disklab = of->sc_dk.dk_label; ((struct partinfo *)data)->part = &of->sc_dk.dk_label->d_partitions[DISKPART(dev)]; return 0; case DIOCWDINFO: case DIOCSDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCWDINFO: case ODIOCSDINFO: #endif { struct disklabel *lp; #ifdef __HAVE_OLD_DISKLABEL if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { memset(&newlabel, 0, sizeof newlabel); memcpy(&newlabel, data, sizeof (struct olddisklabel)); lp = &newlabel; } else #endif lp = (struct disklabel *)data; if ((flag & FWRITE) == 0) return EBADF; mutex_enter(&of->sc_dk.dk_openlock); error = setdisklabel(of->sc_dk.dk_label, lp, /*of->sc_dk.dk_openmask */0, of->sc_dk.dk_cpulabel); if (error == 0 && cmd == DIOCWDINFO #ifdef __HAVE_OLD_DISKLABEL || xfer == ODIOCWDINFO #endif ) error = writedisklabel(MAKEDISKDEV(major(dev), DISKUNIT(dev), RAW_PART), ofdisk_strategy, of->sc_dk.dk_label, of->sc_dk.dk_cpulabel); mutex_exit(&of->sc_dk.dk_openlock); return error; } case DIOCGDEFLABEL: ofdisk_getdefaultlabel(of, (struct disklabel *)data); return 0; #ifdef __HAVE_OLD_DISKLABEL case DIOCGDEFLABEL: ofdisk_getdefaultlabel(of, &newlabel); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); return 0; #endif case DIOCAWEDGE: { struct dkwedge_info *dkw = (void *) data; if (OFDISK_FLOPPY_P(of)) return (ENOTTY); if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(of->sc_dev), sizeof(dkw->dkw_parent)); return (dkwedge_add(dkw)); } case DIOCDWEDGE: { struct dkwedge_info *dkw = (void *) data; if (OFDISK_FLOPPY_P(of)) return (ENOTTY); if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(of->sc_dev), sizeof(dkw->dkw_parent)); return (dkwedge_del(dkw)); } case DIOCLWEDGES: { struct dkwedge_list *dkwl = (void *) data; if (OFDISK_FLOPPY_P(of)) return (ENOTTY); return (dkwedge_list(&of->sc_dk, dkwl, l)); } default: return ENOTTY; } }
/* * I/O controls. */ int raioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct disklabel *lp, *tp; struct ra_softc *ra = mscp_device_lookup(dev); int error = 0; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif lp = ra->ra_disk.dk_label; switch (cmd) { case DIOCGDINFO: memcpy(data, lp, sizeof (struct disklabel)); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: memcpy(&newlabel, lp, sizeof newlabel); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); break; #endif case DIOCGPART: ((struct partinfo *)data)->disklab = lp; ((struct partinfo *)data)->part = &lp->d_partitions[DISKPART(dev)]; break; case DIOCWDINFO: case DIOCSDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCWDINFO: case ODIOCSDINFO: if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { memset(&newlabel, 0, sizeof newlabel); memcpy(&newlabel, data, sizeof (struct olddisklabel)); tp = &newlabel; } else #endif tp = (struct disklabel *)data; if ((flag & FWRITE) == 0) error = EBADF; else { mutex_enter(&ra->ra_disk.dk_openlock); error = setdisklabel(lp, tp, 0, 0); if ((error == 0) && (cmd == DIOCWDINFO #ifdef __HAVE_OLD_DISKLABEL || cmd == ODIOCWDINFO #endif )) { ra->ra_wlabel = 1; error = writedisklabel(dev, rastrategy, lp,0); ra->ra_wlabel = 0; } mutex_exit(&ra->ra_disk.dk_openlock); } break; case DIOCWLABEL: if ((flag & FWRITE) == 0) error = EBADF; else ra->ra_wlabel = 1; break; case DIOCGDEFLABEL: #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDEFLABEL: if (cmd == ODIOCGDEFLABEL) tp = &newlabel; else #endif tp = (struct disklabel *)data; memset(tp, 0, sizeof(struct disklabel)); tp->d_secsize = lp->d_secsize; tp->d_nsectors = lp->d_nsectors; tp->d_ntracks = lp->d_ntracks; tp->d_ncylinders = lp->d_ncylinders; tp->d_secpercyl = lp->d_secpercyl; tp->d_secperunit = lp->d_secperunit; tp->d_type = DTYPE_MSCP; tp->d_rpm = 3600; rrmakelabel(tp, ra->ra_mediaid); #ifdef __HAVE_OLD_DISKLABEL if (cmd == ODIOCGDEFLABEL) { if (tp->d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, tp, sizeof (struct olddisklabel)); } #endif break; case DIOCAWEDGE: { struct dkwedge_info *dkw = (void *) data; if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(ra->ra_dev), sizeof(dkw->dkw_parent)); return (dkwedge_add(dkw)); } case DIOCDWEDGE: { struct dkwedge_info *dkw = (void *) data; if ((flag & FWRITE) == 0) return (EBADF); /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(ra->ra_dev), sizeof(dkw->dkw_parent)); return (dkwedge_del(dkw)); } case DIOCLWEDGES: { struct dkwedge_list *dkwl = (void *) data; return (dkwedge_list(&ra->ra_disk, dkwl, l)); } default: error = ENOTTY; break; } return (error); }
/* ARGSUSED */ static int vndioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { bool force; int unit = vndunit(dev); struct vnd_softc *vnd; struct vnd_ioctl *vio; struct vattr vattr; struct pathbuf *pb; struct nameidata nd; int error, part, pmask; uint64_t geomsize; int fflags; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif struct dkwedge_info *dkw; struct dkwedge_list *dkwl; #ifdef DEBUG if (vnddebug & VDB_FOLLOW) printf("vndioctl(0x%"PRIx64", 0x%lx, %p, 0x%x, %p): unit %d\n", dev, cmd, data, flag, l->l_proc, unit); #endif vnd = device_lookup_private(&vnd_cd, unit); if (vnd == NULL && #ifdef COMPAT_30 cmd != VNDIOCGET30 && #endif #ifdef COMPAT_50 cmd != VNDIOCGET50 && #endif cmd != VNDIOCGET) return ENXIO; vio = (struct vnd_ioctl *)data; /* Must be open for writes for these commands... */ switch (cmd) { case VNDIOCSET: case VNDIOCCLR: #ifdef COMPAT_50 case VNDIOCSET50: case VNDIOCCLR50: #endif case DIOCSDINFO: case DIOCWDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCSDINFO: case ODIOCWDINFO: #endif case DIOCKLABEL: case DIOCWLABEL: if ((flag & FWRITE) == 0) return EBADF; } /* Must be initialized for these... */ switch (cmd) { case VNDIOCCLR: #ifdef VNDIOCCLR50 case VNDIOCCLR50: #endif case DIOCGDINFO: case DIOCSDINFO: case DIOCWDINFO: case DIOCGPART: case DIOCKLABEL: case DIOCWLABEL: case DIOCGDEFLABEL: case DIOCCACHESYNC: #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: case ODIOCSDINFO: case ODIOCWDINFO: case ODIOCGDEFLABEL: #endif if ((vnd->sc_flags & VNF_INITED) == 0) return ENXIO; } switch (cmd) { #ifdef VNDIOCSET50 case VNDIOCSET50: #endif case VNDIOCSET: if (vnd->sc_flags & VNF_INITED) return EBUSY; if ((error = vndlock(vnd)) != 0) return error; fflags = FREAD; if ((vio->vnd_flags & VNDIOF_READONLY) == 0) fflags |= FWRITE; error = pathbuf_copyin(vio->vnd_file, &pb); if (error) { goto unlock_and_exit; } NDINIT(&nd, LOOKUP, FOLLOW, pb); if ((error = vn_open(&nd, fflags, 0)) != 0) { pathbuf_destroy(pb); goto unlock_and_exit; } KASSERT(l); error = VOP_GETATTR(nd.ni_vp, &vattr, l->l_cred); if (!error && nd.ni_vp->v_type != VREG) error = EOPNOTSUPP; if (!error && vattr.va_bytes < vattr.va_size) /* File is definitely sparse, use vn_rdwr() */ vnd->sc_flags |= VNF_USE_VN_RDWR; if (error) { VOP_UNLOCK(nd.ni_vp); goto close_and_exit; } /* If using a compressed file, initialize its info */ /* (or abort with an error if kernel has no compression) */ if (vio->vnd_flags & VNF_COMP) { #ifdef VND_COMPRESSION struct vnd_comp_header *ch; int i; u_int32_t comp_size; u_int32_t comp_maxsize; /* allocate space for compresed file header */ ch = malloc(sizeof(struct vnd_comp_header), M_TEMP, M_WAITOK); /* read compressed file header */ error = vn_rdwr(UIO_READ, nd.ni_vp, (void *)ch, sizeof(struct vnd_comp_header), 0, UIO_SYSSPACE, IO_UNIT|IO_NODELOCKED, l->l_cred, NULL, NULL); if (error) { free(ch, M_TEMP); VOP_UNLOCK(nd.ni_vp); goto close_and_exit; } /* save some header info */ vnd->sc_comp_blksz = ntohl(ch->block_size); /* note last offset is the file byte size */ vnd->sc_comp_numoffs = ntohl(ch->num_blocks)+1; free(ch, M_TEMP); if (vnd->sc_comp_blksz == 0 || vnd->sc_comp_blksz % DEV_BSIZE !=0) { VOP_UNLOCK(nd.ni_vp); error = EINVAL; goto close_and_exit; } if (sizeof(struct vnd_comp_header) + sizeof(u_int64_t) * vnd->sc_comp_numoffs > vattr.va_size) { VOP_UNLOCK(nd.ni_vp); error = EINVAL; goto close_and_exit; } /* set decompressed file size */ vattr.va_size = ((u_quad_t)vnd->sc_comp_numoffs - 1) * (u_quad_t)vnd->sc_comp_blksz; /* allocate space for all the compressed offsets */ vnd->sc_comp_offsets = malloc(sizeof(u_int64_t) * vnd->sc_comp_numoffs, M_DEVBUF, M_WAITOK); /* read in the offsets */ error = vn_rdwr(UIO_READ, nd.ni_vp, (void *)vnd->sc_comp_offsets, sizeof(u_int64_t) * vnd->sc_comp_numoffs, sizeof(struct vnd_comp_header), UIO_SYSSPACE, IO_UNIT|IO_NODELOCKED, l->l_cred, NULL, NULL); if (error) { VOP_UNLOCK(nd.ni_vp); goto close_and_exit; } /* * find largest block size (used for allocation limit). * Also convert offset to native byte order. */ comp_maxsize = 0; for (i = 0; i < vnd->sc_comp_numoffs - 1; i++) { vnd->sc_comp_offsets[i] = be64toh(vnd->sc_comp_offsets[i]); comp_size = be64toh(vnd->sc_comp_offsets[i + 1]) - vnd->sc_comp_offsets[i]; if (comp_size > comp_maxsize) comp_maxsize = comp_size; } vnd->sc_comp_offsets[vnd->sc_comp_numoffs - 1] = be64toh(vnd->sc_comp_offsets[vnd->sc_comp_numoffs - 1]); /* create compressed data buffer */ vnd->sc_comp_buff = malloc(comp_maxsize, M_DEVBUF, M_WAITOK); /* create decompressed buffer */ vnd->sc_comp_decombuf = malloc(vnd->sc_comp_blksz, M_DEVBUF, M_WAITOK); vnd->sc_comp_buffblk = -1; /* Initialize decompress stream */ memset(&vnd->sc_comp_stream, 0, sizeof(z_stream)); vnd->sc_comp_stream.zalloc = vnd_alloc; vnd->sc_comp_stream.zfree = vnd_free; error = inflateInit2(&vnd->sc_comp_stream, MAX_WBITS); if (error) { if (vnd->sc_comp_stream.msg) printf("vnd%d: compressed file, %s\n", unit, vnd->sc_comp_stream.msg); VOP_UNLOCK(nd.ni_vp); error = EINVAL; goto close_and_exit; } vnd->sc_flags |= VNF_COMP | VNF_READONLY; #else /* !VND_COMPRESSION */ VOP_UNLOCK(nd.ni_vp); error = EOPNOTSUPP; goto close_and_exit; #endif /* VND_COMPRESSION */ } VOP_UNLOCK(nd.ni_vp); vnd->sc_vp = nd.ni_vp; vnd->sc_size = btodb(vattr.va_size); /* note truncation */ /* * Use pseudo-geometry specified. If none was provided, * use "standard" Adaptec fictitious geometry. */ if (vio->vnd_flags & VNDIOF_HASGEOM) { memcpy(&vnd->sc_geom, &vio->vnd_geom, sizeof(vio->vnd_geom)); /* * Sanity-check the sector size. * XXX Don't allow secsize < DEV_BSIZE. Should * XXX we? */ if (vnd->sc_geom.vng_secsize < DEV_BSIZE || (vnd->sc_geom.vng_secsize % DEV_BSIZE) != 0 || vnd->sc_geom.vng_ncylinders == 0 || (vnd->sc_geom.vng_ntracks * vnd->sc_geom.vng_nsectors) == 0) { error = EINVAL; goto close_and_exit; } /* * Compute the size (in DEV_BSIZE blocks) specified * by the geometry. */ geomsize = (vnd->sc_geom.vng_nsectors * vnd->sc_geom.vng_ntracks * vnd->sc_geom.vng_ncylinders) * (vnd->sc_geom.vng_secsize / DEV_BSIZE); /* * Sanity-check the size against the specified * geometry. */ if (vnd->sc_size < geomsize) { error = EINVAL; goto close_and_exit; } } else if (vnd->sc_size >= (32 * 64)) { /* * Size must be at least 2048 DEV_BSIZE blocks * (1M) in order to use this geometry. */ vnd->sc_geom.vng_secsize = DEV_BSIZE; vnd->sc_geom.vng_nsectors = 32; vnd->sc_geom.vng_ntracks = 64; vnd->sc_geom.vng_ncylinders = vnd->sc_size / (64 * 32); } else { vnd->sc_geom.vng_secsize = DEV_BSIZE; vnd->sc_geom.vng_nsectors = 1; vnd->sc_geom.vng_ntracks = 1; vnd->sc_geom.vng_ncylinders = vnd->sc_size; } vnd_set_geometry(vnd); if (vio->vnd_flags & VNDIOF_READONLY) { vnd->sc_flags |= VNF_READONLY; } if ((error = vndsetcred(vnd, l->l_cred)) != 0) goto close_and_exit; vndthrottle(vnd, vnd->sc_vp); vio->vnd_osize = dbtob(vnd->sc_size); #ifdef VNDIOCSET50 if (cmd != VNDIOCSET50) #endif vio->vnd_size = dbtob(vnd->sc_size); vnd->sc_flags |= VNF_INITED; /* create the kernel thread, wait for it to be up */ error = kthread_create(PRI_NONE, 0, NULL, vndthread, vnd, &vnd->sc_kthread, "%s", device_xname(vnd->sc_dev)); if (error) goto close_and_exit; while ((vnd->sc_flags & VNF_KTHREAD) == 0) { tsleep(&vnd->sc_kthread, PRIBIO, "vndthr", 0); } #ifdef DEBUG if (vnddebug & VDB_INIT) printf("vndioctl: SET vp %p size 0x%lx %d/%d/%d/%d\n", vnd->sc_vp, (unsigned long) vnd->sc_size, vnd->sc_geom.vng_secsize, vnd->sc_geom.vng_nsectors, vnd->sc_geom.vng_ntracks, vnd->sc_geom.vng_ncylinders); #endif /* Attach the disk. */ disk_attach(&vnd->sc_dkdev); disk_blocksize(&vnd->sc_dkdev, vnd->sc_geom.vng_secsize); /* Initialize the xfer and buffer pools. */ pool_init(&vnd->sc_vxpool, sizeof(struct vndxfer), 0, 0, 0, "vndxpl", NULL, IPL_BIO); vndunlock(vnd); pathbuf_destroy(pb); /* Discover wedges on this disk */ dkwedge_discover(&vnd->sc_dkdev); break; close_and_exit: (void) vn_close(nd.ni_vp, fflags, l->l_cred); pathbuf_destroy(pb); unlock_and_exit: #ifdef VND_COMPRESSION /* free any allocated memory (for compressed file) */ if (vnd->sc_comp_offsets) { free(vnd->sc_comp_offsets, M_DEVBUF); vnd->sc_comp_offsets = NULL; } if (vnd->sc_comp_buff) { free(vnd->sc_comp_buff, M_DEVBUF); vnd->sc_comp_buff = NULL; } if (vnd->sc_comp_decombuf) { free(vnd->sc_comp_decombuf, M_DEVBUF); vnd->sc_comp_decombuf = NULL; } #endif /* VND_COMPRESSION */ vndunlock(vnd); return error; #ifdef VNDIOCCLR50 case VNDIOCCLR50: #endif case VNDIOCCLR: part = DISKPART(dev); pmask = (1 << part); force = (vio->vnd_flags & VNDIOF_FORCE) != 0; if ((error = vnddoclear(vnd, pmask, minor(dev), force)) != 0) return error; break; #ifdef COMPAT_30 case VNDIOCGET30: { struct vnd_user30 *vnu; struct vattr va; vnu = (struct vnd_user30 *)data; KASSERT(l); switch (error = vnd_cget(l, unit, &vnu->vnu_unit, &va)) { case 0: vnu->vnu_dev = va.va_fsid; vnu->vnu_ino = va.va_fileid; break; case -1: /* unused is not an error */ vnu->vnu_dev = 0; vnu->vnu_ino = 0; break; default: return error; } break; } #endif #ifdef COMPAT_50 case VNDIOCGET50: { struct vnd_user50 *vnu; struct vattr va; vnu = (struct vnd_user50 *)data; KASSERT(l); switch (error = vnd_cget(l, unit, &vnu->vnu_unit, &va)) { case 0: vnu->vnu_dev = va.va_fsid; vnu->vnu_ino = va.va_fileid; break; case -1: /* unused is not an error */ vnu->vnu_dev = 0; vnu->vnu_ino = 0; break; default: return error; } break; } #endif case VNDIOCGET: { struct vnd_user *vnu; struct vattr va; vnu = (struct vnd_user *)data; KASSERT(l); switch (error = vnd_cget(l, unit, &vnu->vnu_unit, &va)) { case 0: vnu->vnu_dev = va.va_fsid; vnu->vnu_ino = va.va_fileid; break; case -1: /* unused is not an error */ vnu->vnu_dev = 0; vnu->vnu_ino = 0; break; default: return error; } break; } case DIOCGDINFO: *(struct disklabel *)data = *(vnd->sc_dkdev.dk_label); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: newlabel = *(vnd->sc_dkdev.dk_label); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); break; #endif case DIOCGPART: ((struct partinfo *)data)->disklab = vnd->sc_dkdev.dk_label; ((struct partinfo *)data)->part = &vnd->sc_dkdev.dk_label->d_partitions[DISKPART(dev)]; break; case DIOCWDINFO: case DIOCSDINFO: #ifdef __HAVE_OLD_DISKLABEL case ODIOCWDINFO: case ODIOCSDINFO: #endif { struct disklabel *lp; if ((error = vndlock(vnd)) != 0) return error; vnd->sc_flags |= VNF_LABELLING; #ifdef __HAVE_OLD_DISKLABEL if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) { memset(&newlabel, 0, sizeof newlabel); memcpy(&newlabel, data, sizeof (struct olddisklabel)); lp = &newlabel; } else #endif lp = (struct disklabel *)data; error = setdisklabel(vnd->sc_dkdev.dk_label, lp, 0, vnd->sc_dkdev.dk_cpulabel); if (error == 0) { if (cmd == DIOCWDINFO #ifdef __HAVE_OLD_DISKLABEL || cmd == ODIOCWDINFO #endif ) error = writedisklabel(VNDLABELDEV(dev), vndstrategy, vnd->sc_dkdev.dk_label, vnd->sc_dkdev.dk_cpulabel); } vnd->sc_flags &= ~VNF_LABELLING; vndunlock(vnd); if (error) return error; break; } case DIOCKLABEL: if (*(int *)data != 0) vnd->sc_flags |= VNF_KLABEL; else vnd->sc_flags &= ~VNF_KLABEL; break; case DIOCWLABEL: if (*(int *)data != 0) vnd->sc_flags |= VNF_WLABEL; else vnd->sc_flags &= ~VNF_WLABEL; break; case DIOCGDEFLABEL: vndgetdefaultlabel(vnd, (struct disklabel *)data); break; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDEFLABEL: vndgetdefaultlabel(vnd, &newlabel); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof (struct olddisklabel)); break; #endif case DIOCCACHESYNC: vn_lock(vnd->sc_vp, LK_EXCLUSIVE | LK_RETRY); error = VOP_FSYNC(vnd->sc_vp, vnd->sc_cred, FSYNC_WAIT | FSYNC_DATAONLY | FSYNC_CACHE, 0, 0); VOP_UNLOCK(vnd->sc_vp); return error; case DIOCAWEDGE: dkw = (void *) data; if ((flag & FWRITE) == 0) return EBADF; /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(vnd->sc_dev), sizeof(dkw->dkw_parent)); return dkwedge_add(dkw); case DIOCDWEDGE: dkw = (void *) data; if ((flag & FWRITE) == 0) return EBADF; /* If the ioctl happens here, the parent is us. */ strlcpy(dkw->dkw_parent, device_xname(vnd->sc_dev), sizeof(dkw->dkw_parent)); return dkwedge_del(dkw); case DIOCLWEDGES: dkwl = (void *) data; return dkwedge_list(&vnd->sc_dkdev, dkwl, l); default: return ENOTTY; } return 0; }
/* * disk_ioctl -- * Generic disk ioctl handling. */ int disk_ioctl(struct disk *dk, dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct dkwedge_info *dkw; struct partinfo *pt; #ifdef __HAVE_OLD_DISKLABEL struct disklabel newlabel; #endif switch (cmd) { case DIOCGDISKINFO: if (dk->dk_info == NULL) return ENOTSUP; return prop_dictionary_copyout_ioctl(data, cmd, dk->dk_info); case DIOCGSECTORSIZE: *(u_int *)data = dk->dk_geom.dg_secsize; return 0; case DIOCGMEDIASIZE: *(off_t *)data = (off_t)dk->dk_geom.dg_secsize * dk->dk_geom.dg_secperunit; return 0; default: break; } if (dev == NODEV) return EPASSTHROUGH; /* The following should be moved to dk_ioctl */ switch (cmd) { case DIOCGDINFO: memcpy(data, dk->dk_label, sizeof (*dk->dk_label)); return 0; #ifdef __HAVE_OLD_DISKLABEL case ODIOCGDINFO: memcpy(&newlabel, dk->dk_label, sizeof(newlabel)); if (newlabel.d_npartitions > OLDMAXPARTITIONS) return ENOTTY; memcpy(data, &newlabel, sizeof(struct olddisklabel)); return 0; #endif case DIOCGPART: if (dk->dk_label == NULL) return EBUSY; pt = data; pt->disklab = dk->dk_label; pt->part = &dk->dk_label->d_partitions[DISKPART(dev)]; return 0; case DIOCAWEDGE: if ((flag & FWRITE) == 0) return EBADF; dkw = data; strlcpy(dkw->dkw_parent, dk->dk_name, sizeof(dkw->dkw_parent)); return dkwedge_add(dkw); case DIOCDWEDGE: if ((flag & FWRITE) == 0) return EBADF; dkw = data; strlcpy(dkw->dkw_parent, dk->dk_name, sizeof(dkw->dkw_parent)); return dkwedge_del(dkw); case DIOCLWEDGES: return dkwedge_list(dk, data, l); case DIOCMWEDGES: if ((flag & FWRITE) == 0) return EBADF; dkwedge_discover(dk); return 0; default: return EPASSTHROUGH; } }