#include <sys/modctl.h> #include <sys/ddi.h> #include <sys/sunddi.h> #include <sys/conf.h> #include <sys/devops.h> #include <sys/stat.h> #include <sys/zfs_znode.h> #include <sys/time.h> #include <sys/sa.h> #include <sys/zap.h> #include <sys/time.h> #include <sys/dsl_dataset.h> #include <sys/zfs_vfsops.h> #include <sys/dmu.h> #include <sys/dmu_objset.h> #include <sys/dsl_dir.h> #include <getgen/getgen.h> /* per-instance context */ typedef struct gg_state { kmutex_t mutex; dev_info_t *dip; boolean_t busy; } gg_state_t; static void *statep; static kmutex_t gg_mutex; static int gg_instances = 0; int gg_ioc_get_gen(intptr_t arg, int mode) { gg_ioctl_get_generation_t gg; file_t *fp; uint64_t gen; uint64_t crtime[2]; int ret = 0; zfsvfs_t *zfsvfs = NULL; objset_t *osp; sa_attr_type_t *sa_table; sa_handle_t *hdl; dmu_buf_t *db; sa_bulk_attr_t bulk[2]; int count = 0; dmu_object_info_t doi; timestruc_t crtime_s; znode_t *zp; if (ddi_copyin((void *)arg, &gg, sizeof(gg), mode) != 0) return EFAULT; fp = getf(gg.fd); if (fp == NULL) return EBADF; if (fp->f_vnode->v_vfsp->vfs_fstype != zfsfstype) { ret = EINVAL; goto out; } zp = (znode_t *)fp->f_vnode->v_data; zfsvfs = zp->z_zfsvfs; /* modified version of ZFS_ENTER() macro - we need to clean up fp */ zfsvfs = zp->z_zfsvfs; rrm_enter_read(&zfsvfs->z_teardown_lock, FTAG); if (zp->z_zfsvfs->z_unmounted) { ret = EIO; goto out; } /* modified version of ZFS_VERIFY_ZP() macro */ if (zp->z_sa_hdl == NULL) { ret = EIO; goto out; } /* get dataset name */ dsl_dataset_name(zfsvfs->z_os->os_dsl_dataset, gg.dataset); /* get guid */ gg.guid = dsl_dataset_phys(zfsvfs->z_os->os_dsl_dataset)->ds_guid; /* get generation and crtime */ osp = zfsvfs->z_os; ret = sa_setup(osp, gg.inode, zfs_attr_table, ZPL_END, &sa_table); if (ret) goto out; ret = sa_buf_hold(osp, gg.inode, FTAG, &db); if (ret) goto out; dmu_object_info_from_db(db, &doi); if ((doi.doi_bonus_type != DMU_OT_SA && doi.doi_bonus_type != DMU_OT_ZNODE) || doi.doi_bonus_type == DMU_OT_ZNODE && doi.doi_bonus_size < sizeof (znode_phys_t)) { sa_buf_rele(db, FTAG); ret = ENOTSUP; goto out; } ret = sa_handle_get(osp, gg.inode, NULL, SA_HDL_PRIVATE, &hdl); if (ret) { sa_buf_rele(db, FTAG); goto out; } SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GEN], NULL, &gen, sizeof(gen)); SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_CRTIME], NULL, &crtime, sizeof(crtime)); ret = sa_bulk_lookup(hdl, bulk, count); sa_handle_destroy(hdl); sa_buf_rele(db, FTAG); if (ret) goto out; ZFS_TIME_DECODE(&crtime_s, crtime); gg.generation = gen; gg.crtime = crtime_s.tv_sec; ddi_copyout(&gg, (void *)arg, sizeof(gg), mode); out: if (zfsvfs) ZFS_EXIT(zfsvfs); releasef(gg.fd); return ret; } /* ARGSUSED */ static int gg_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { int instance; instance = getminor(dev); if (ddi_get_soft_state(statep, instance) == NULL) return (ENXIO); /* * all structures passed between kernel and userspace * are compatible between 64 and 32 bit. Model * conversion can be ignored. */ switch (cmd) { case GG_IOC_GET_GENERATION: return gg_ioc_get_gen(arg, mode); default: /* generic "ioctl unknown" error */ return ENOTTY; } return (0); } /* gg_close() is called when the final fd/reference is removed. */ /* ARGSUSED */ static int gg_close(dev_t dev, int flag, int otyp, cred_t *crepd) { gg_state_t *sp; int instance; instance = getminor(dev); if ((sp = ddi_get_soft_state(statep, instance)) == NULL) return (ENXIO); if (otyp != OTYP_CHR) return (EINVAL); mutex_enter(&sp->mutex); if (sp->busy != B_TRUE) { mutex_exit(&sp->mutex); return (EINVAL); } sp->busy = B_FALSE; mutex_exit(&sp->mutex); return (0); } /* gg_open() is called every time the device is opened/mounted/duped. */ /* ARGSUSED */ static int gg_open(dev_t *devp, int flag, int otyp, cred_t *credp) { gg_state_t *sp; int instance; instance = getminor(*devp); if ((sp = ddi_get_soft_state(statep, instance)) == NULL) return (ENXIO); if (otyp != OTYP_CHR) return (EINVAL); if (drv_priv(credp) != 0) return (EPERM); mutex_enter(&sp->mutex); if ((flag & FEXCL) && (sp->busy == B_TRUE)) { mutex_exit(&sp->mutex); return (EBUSY); } sp->busy = B_TRUE; mutex_exit(&sp->mutex); return (0); } static struct cb_ops gg_cb_ops = { gg_open, /* open */ gg_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ gg_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* chpoll */ ddi_prop_op, /* prop_op */ NULL, /* streamtab */ D_MP | D_64BIT, /* cb_flag */ CB_REV, /* cb_rev */ nodev, /* aread */ nodev, /* awrite */ }; static int gg_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { int instance; gg_state_t *sp; /* called once per instance with DDI_DETACH, may be called to suspend */ switch (cmd) { case DDI_DETACH: /* instance busy? */ instance = ddi_get_instance(dip); if ((sp = ddi_get_soft_state(statep, instance)) == NULL) return (DDI_FAILURE); mutex_enter(&sp->mutex); if (sp->busy == B_TRUE) { mutex_exit(&sp->mutex); return (DDI_FAILURE); } mutex_exit(&sp->mutex); /* free resources allocated for this instance */ mutex_destroy(&sp->mutex); ddi_remove_minor_node(dip, NULL); ddi_soft_state_free(statep, instance); mutex_enter(&gg_mutex); gg_instances--; mutex_exit(&gg_mutex); return (DDI_SUCCESS); case DDI_SUSPEND: return (DDI_FAILURE); default: return (DDI_FAILURE); } } static int gg_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { /* called once per instance with DDI_ATTACH, may be called to resume */ int instance; gg_state_t *sp; switch (cmd) { case DDI_ATTACH: instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(statep, instance) != DDI_SUCCESS) { return (DDI_FAILURE); } sp = ddi_get_soft_state(statep, instance); ddi_set_driver_private(dip, sp); sp->dip = dip; sp->busy = B_FALSE; if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, instance, DDI_PSEUDO, 0) == DDI_FAILURE) { ddi_soft_state_free(statep, instance); return (DDI_FAILURE); } mutex_init(&sp->mutex, NULL, MUTEX_DRIVER, NULL); ddi_report_dev(dip); mutex_enter(&gg_mutex); gg_instances++; mutex_exit(&gg_mutex); return (DDI_SUCCESS); case DDI_RESUME: return (DDI_SUCCESS); default: return (DDI_FAILURE); } } /* ARGSUSED */ static int gg_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **resultp) { int instance; gg_state_t *sp; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: /* arg is dev_t */ instance = getminor((dev_t)arg); if ((sp = ddi_get_soft_state(statep, instance)) != NULL) { mutex_enter(&sp->mutex); *resultp = sp->dip; mutex_exit(&sp->mutex); return (DDI_SUCCESS); } *resultp = NULL; return (DDI_FAILURE); case DDI_INFO_DEVT2INSTANCE: /* arg is dev_t */ instance = getminor((dev_t)arg); *resultp = (void *)(uintptr_t)instance; return (DDI_FAILURE); } return (DDI_FAILURE); } static struct dev_ops gg_dev_ops = { DEVO_REV, /* driver build revision */ 0, /* driver reference count */ gg_getinfo, /* getinfo */ nulldev, /* identify (obsolete) */ nulldev, /* probe (search for devices) */ gg_attach, /* attach */ gg_detach, /* detach */ nodev, /* reset (obsolete, use quiesce) */ &gg_cb_ops, /* character and block device ops */ NULL, /* bus driver ops */ NULL, /* power management, not needed */ ddi_quiesce_not_needed, /* quiesce */ }; static struct modldrv gg_modldrv = { &mod_driverops, /* all loadable modules use this */ "getgen - znode info, v1.0", /* driver name and version info */ &gg_dev_ops /* ops method pointers */ }; static struct modlinkage gg_modlinkage = { MODREV_1, /* fixed value */ { &gg_modldrv, /* driver linkage structure */ NULL /* list terminator */ } }; int _init(void) { int error; if ((error = ddi_soft_state_init(&statep, sizeof(gg_state_t), 1)) != 0) return (error); gg_instances = 0; mutex_init(&gg_mutex, NULL, MUTEX_DRIVER, NULL); if ((error = mod_install(&gg_modlinkage)) != 0) { cmn_err(CE_WARN, "getgen: could not install module"); mutex_destroy(&gg_mutex); ddi_soft_state_fini(&statep); return (error); } return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&gg_modlinkage, modinfop)); } int _fini(void) { int error = 0; mutex_enter(&gg_mutex); if (gg_instances > 0) { mutex_exit(&gg_mutex); return (SET_ERROR(EBUSY)); } mutex_exit(&gg_mutex); if ((error = mod_remove(&gg_modlinkage)) != 0) { cmn_err(CE_WARN, "mod_remove failed: %d", error); return (error); } /* free resources */ ddi_soft_state_fini(&statep); mutex_destroy(&gg_mutex); return (0); }