diff --git a/xfs/xfs_sysfs.c b/xfs/xfs_sysfs.c new file mode 100644 index 0000000..cbb0ed2 --- /dev/null +++ b/xfs/xfs_sysfs.c @@ -0,0 +1,339 @@ +/* + * A sysfs interface for triggering events like "Nearly Out Of Space" + * Krzysztof Blaszkowski 2009 + * + * + * each mounted block device will be associted with a directory under + * /sys/fs/xfs-oos/ where there is a file "trigger" which sets + * a free space trigger point and if crossed over then there will be raised + * an uevent like this: + * UEVENT[1240929288.150050] change /fs/xfs-oos/dm-18 (xfs-oos) + * ACTION=change + * DEVPATH=/fs/xfs-oos/dm-18 + * SUBSYSTEM=xfs-oos + * XFSDEV=dm-18 + * XFSLEFT=191111168 + * + * syntax: + * echo "1G" > /sys/fs/xfs-oos/ the_block_device_name /trigger + * echo "400M" > /sys/fs/xfs-oos/ some_other_bdev /trigger + * echo "20000000" > /sys/fs/xfs-oos/ yet_another_bdev /trigger + * + * the module polls each mount point every fixed time (5 sec) + * and if necessary raises events. it utilizes hysteresis loop of 1/8 + * of trigger value to avoid sending excessive events on small change. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifdef CONFIG_SYSFS + +#include +#include +#include +#include +#include +#include + +#include "xfs.h" +#include "xfs_fs.h" +#include "xfs_types.h" +#include "xfs_bit.h" +#include "xfs_log.h" +#include "xfs_inum.h" +#include "xfs_trans.h" +#include "xfs_sb.h" +#include "xfs_ag.h" +#include "xfs_dir2.h" +#include "xfs_dmapi.h" +#include "xfs_mount.h" + + +static struct xfs_event_ctrl { + struct kset *kset; + struct timer_list poll; + int poll_interval; + spinlock_t kset_lock; + /* because kset is acessed in different contextes then + kobject locking (kset->lock) is not appropriate this time */ +} xfs_event_ctrl; + + + +struct xfs_event_s { + struct kobject kobj; + __uint64_t trigger; + __uint64_t left_bytes; + struct xfs_mount *mp; + int triggered:1; + int fire_event:1; +}; + +struct xfs_event_attribute { + struct attribute attr; + ssize_t (*show)(struct xfs_event_s *, struct xfs_event_attribute *, char *); + ssize_t (*store)(struct xfs_event_s *, struct xfs_event_attribute *, const char *, ssize_t); +}; + + +static ssize_t xfs_event_kobj_attr_show(struct kobject *_kobj, + struct attribute *a, + char *buf) +{ + struct xfs_event_attribute *attribute = container_of(a, struct xfs_event_attribute, attr); + struct xfs_event_s *event = container_of(_kobj, struct xfs_event_s, kobj); + + if (!attribute->show) + return -EIO; + + return attribute->show(event, attribute, buf); +} + +static ssize_t xfs_event_kobj_attr_store(struct kobject *_kobj, + struct attribute *a, + const char *buf, size_t len) +{ + struct xfs_event_attribute *attribute = container_of(a, struct xfs_event_attribute, attr); + struct xfs_event_s *event = container_of(_kobj, struct xfs_event_s, kobj); + + if (!attribute->store) + return -EIO; + + return attribute->store(event, attribute, buf, len); +} + +static void xfs_event_kobj_release(struct kobject *_kobj) +{ + struct xfs_event_s *event = container_of(_kobj, struct xfs_event_s, kobj); + kfree(event); +} + + +static const struct sysfs_ops xfs_event_sysfs_ops = { + .show = xfs_event_kobj_attr_show, + .store = xfs_event_kobj_attr_store, +}; + +static const struct attribute *xfs_events_attrs[]; +static const struct kobj_type xfs_event_ktype = { + .sysfs_ops = (void *)&xfs_event_sysfs_ops, + .release = xfs_event_kobj_release, + .default_attrs = (void *)xfs_events_attrs, +}; + + + + + +static ssize_t xfs_trigger_get(struct xfs_event_s *event, + struct xfs_event_attribute *obj_attr, + char *buf) +{ + __uint64_t bs = event->mp->m_sb.sb_blocksize; + + return sprintf(buf, "%lld %lld %c", event->trigger * bs, event->left_bytes, event->triggered ? '1' : '0'); +} + +static ssize_t xfs_trigger_set(struct xfs_event_s *event, + struct xfs_event_attribute *obj_attr, + const char *buf, ssize_t count) +{ + int rc; + char unit; + + spin_lock_bh(&xfs_event_ctrl.kset_lock); + rc = sscanf(buf, "%lld%c", &event->trigger, &unit); + if (rc == 2) { + switch(unit) { + case 'T': + case 't': + event->trigger <<= 10; + case 'G': + case 'g': + event->trigger <<= 10; + case 'M': + case 'm': + event->trigger <<= 20; + } + } + event->trigger /= event->mp->m_sb.sb_blocksize; + spin_unlock_bh(&xfs_event_ctrl.kset_lock); + return count; +} + + +static const struct xfs_event_attribute xfs_event_trigger = + __ATTR(trigger, S_IFREG | S_IRUGO | S_IWUSR, + xfs_trigger_get, + xfs_trigger_set); + +static const struct attribute *xfs_events_attrs[] = { + &xfs_event_trigger.attr, + NULL +}; + + +extern __uint64_t xfs_icsb_get_fdblocks_lazy(xfs_mount_t *mp); + +static void xfs_event_fdblocks_poll(unsigned long arg) +{ + struct xfs_event_ctrl *ctrl = (struct xfs_event_ctrl *)arg; + struct kset *kset = ctrl->kset; + struct kobject *k, *tmp; + + struct list_head events_to_fire; + + INIT_LIST_HEAD(&events_to_fire); + mod_timer(&ctrl->poll, jiffies + ctrl->poll_interval); + spin_lock_bh(&ctrl->kset_lock); + /* no need to use kset->lock */ + list_for_each_entry_safe(k, tmp, &kset->list, entry) { + struct xfs_event_s *event = container_of(k, struct xfs_event_s, kobj); + __uint64_t space_left = xfs_icsb_get_fdblocks_lazy(event->mp); + + if (space_left < event->trigger && !event->triggered) { + event->triggered = 1; + kobject_get(&event->kobj); + event->left_bytes = space_left * event->mp->m_sb.sb_blocksize; + list_del(&event->kobj.entry); + list_add(&event->kobj.entry, &events_to_fire); + } + + if (space_left > (event->trigger + (event->trigger >> 3)) + && event->triggered) { + event->triggered = 0; + } + + } + spin_unlock_bh(&ctrl->kset_lock); + + if (!list_empty(&events_to_fire)) { + char buffer[256]; + char *envp[3]; + + envp[0] = buffer; + envp[2] = NULL; + list_for_each_entry(k, &events_to_fire, entry) { + struct xfs_event_s *event = container_of(k, struct xfs_event_s, kobj); + + envp[1] = envp[0] + snprintf(envp[0], 250, "XFSDEV=%s", k->name) + 1; + snprintf(envp[1], 250 - (envp[1] - envp[0]), "XFSLEFT=%lld", event->left_bytes); + +// printk("%s:%d firing [%s] [%s] [%s]\n", __FUNCTION__, __LINE__, k->name, envp[0], envp[1]); + kobject_uevent_env(k, KOBJ_CHANGE, envp); + } + + spin_lock_bh(&ctrl->kset_lock); + list_for_each_entry_safe(k, tmp, &events_to_fire, entry) { + struct xfs_event_s *event = container_of(k, struct xfs_event_s, kobj); + + list_del(&event->kobj.entry); + list_add(&event->kobj.entry, &kset->list); + kobject_put(&event->kobj); + } + spin_unlock_bh(&ctrl->kset_lock); + } +} + + +/* api */ + +void xfs_sysfs_init(void) +{ + memset(&xfs_event_ctrl, 0, sizeof(struct xfs_event_ctrl)); + + xfs_event_ctrl.poll_interval = HZ * 5; + xfs_event_ctrl.kset = kset_create_and_add("xfs-oos", NULL, fs_kobj); + spin_lock_init(&xfs_event_ctrl.kset_lock); + if (!xfs_event_ctrl.kset) { + cmn_err(CE_ALERT, "kset_create failed"); + return; + } +} + +void xfs_sysfs_done(void) +{ + if (xfs_event_ctrl.kset) { + BUG_ON(!list_empty(&xfs_event_ctrl.kset->list)); + kset_unregister(xfs_event_ctrl.kset); + xfs_event_ctrl.kset = NULL; + } +} + +/* stuff for xfs_fs_fill_super, xfs_fs_put_super */ + +int xfs_event_alloc(struct xfs_mount *mnt) +{ + struct xfs_event_s * ev; + int rc = -1; + int empty; + + if (!xfs_event_ctrl.kset) + return rc; + + ev = kzalloc(sizeof(struct xfs_event_s), GFP_KERNEL); + + spin_lock_bh(&xfs_event_ctrl.kset_lock); + empty = list_empty(&xfs_event_ctrl.kset->list); + + if (ev) { + ev->kobj.kset = xfs_event_ctrl.kset; + ev->mp = mnt; + + rc = kobject_init_and_add(&ev->kobj, (void *)&xfs_event_ktype, NULL, "%s", mnt->m_fsname); + if (!rc) { + mnt->event_onlow = ev; + kobject_uevent(&ev->kobj, KOBJ_ADD); + } else { + kobject_put(&ev->kobj); // ev will be freed too. + } + } + spin_unlock_bh(&xfs_event_ctrl.kset_lock); + + if (empty) { + init_timer(&xfs_event_ctrl.poll); + xfs_event_ctrl.poll.function = xfs_event_fdblocks_poll; + xfs_event_ctrl.poll.data = (unsigned long)&xfs_event_ctrl; + xfs_event_ctrl.poll.expires = jiffies + xfs_event_ctrl.poll_interval; + add_timer(&xfs_event_ctrl.poll); + } + + return rc; +} + +int xfs_event_release(struct xfs_mount *mnt) +{ + struct xfs_event_s * ev = (struct xfs_event_s *)mnt->event_onlow; + int empty; + + if (!xfs_event_ctrl.kset) + return -1; + + spin_lock_bh(&xfs_event_ctrl.kset_lock); + if (ev) + kobject_put(&ev->kobj); + + empty = list_empty(&xfs_event_ctrl.kset->list); + spin_unlock_bh(&xfs_event_ctrl.kset_lock); + + if (empty) { + del_timer_sync(&xfs_event_ctrl.poll); + } + + return 0; +} + +#endif +