|
@@ -56,6 +56,7 @@ static inline struct hashlimit_net *hashlimit_pernet(struct net *net)
|
|
|
}
|
|
|
|
|
|
/* need to declare this at the top */
|
|
|
+static const struct file_operations dl_file_ops_v2;
|
|
|
static const struct file_operations dl_file_ops_v1;
|
|
|
static const struct file_operations dl_file_ops;
|
|
|
|
|
@@ -87,8 +88,19 @@ struct dsthash_ent {
|
|
|
unsigned long expires; /* precalculated expiry time */
|
|
|
struct {
|
|
|
unsigned long prev; /* last modification */
|
|
|
- u_int64_t credit;
|
|
|
- u_int64_t credit_cap, cost;
|
|
|
+ union {
|
|
|
+ struct {
|
|
|
+ u_int64_t credit;
|
|
|
+ u_int64_t credit_cap;
|
|
|
+ u_int64_t cost;
|
|
|
+ };
|
|
|
+ struct {
|
|
|
+ u_int32_t interval, prev_window;
|
|
|
+ u_int64_t current_rate;
|
|
|
+ u_int64_t rate;
|
|
|
+ int64_t burst;
|
|
|
+ };
|
|
|
+ };
|
|
|
} rateinfo;
|
|
|
struct rcu_head rcu;
|
|
|
};
|
|
@@ -99,7 +111,7 @@ struct xt_hashlimit_htable {
|
|
|
u_int8_t family;
|
|
|
bool rnd_initialized;
|
|
|
|
|
|
- struct hashlimit_cfg2 cfg; /* config */
|
|
|
+ struct hashlimit_cfg3 cfg; /* config */
|
|
|
|
|
|
/* used internally */
|
|
|
spinlock_t lock; /* lock for list_head */
|
|
@@ -116,10 +128,10 @@ struct xt_hashlimit_htable {
|
|
|
};
|
|
|
|
|
|
static int
|
|
|
-cfg_copy(struct hashlimit_cfg2 *to, void *from, int revision)
|
|
|
+cfg_copy(struct hashlimit_cfg3 *to, const void *from, int revision)
|
|
|
{
|
|
|
if (revision == 1) {
|
|
|
- struct hashlimit_cfg1 *cfg = from;
|
|
|
+ struct hashlimit_cfg1 *cfg = (struct hashlimit_cfg1 *)from;
|
|
|
|
|
|
to->mode = cfg->mode;
|
|
|
to->avg = cfg->avg;
|
|
@@ -131,7 +143,19 @@ cfg_copy(struct hashlimit_cfg2 *to, void *from, int revision)
|
|
|
to->srcmask = cfg->srcmask;
|
|
|
to->dstmask = cfg->dstmask;
|
|
|
} else if (revision == 2) {
|
|
|
- memcpy(to, from, sizeof(struct hashlimit_cfg2));
|
|
|
+ struct hashlimit_cfg2 *cfg = (struct hashlimit_cfg2 *)from;
|
|
|
+
|
|
|
+ to->mode = cfg->mode;
|
|
|
+ to->avg = cfg->avg;
|
|
|
+ to->burst = cfg->burst;
|
|
|
+ to->size = cfg->size;
|
|
|
+ to->max = cfg->max;
|
|
|
+ to->gc_interval = cfg->gc_interval;
|
|
|
+ to->expire = cfg->expire;
|
|
|
+ to->srcmask = cfg->srcmask;
|
|
|
+ to->dstmask = cfg->dstmask;
|
|
|
+ } else if (revision == 3) {
|
|
|
+ memcpy(to, from, sizeof(struct hashlimit_cfg3));
|
|
|
} else {
|
|
|
return -EINVAL;
|
|
|
}
|
|
@@ -240,13 +264,14 @@ dsthash_free(struct xt_hashlimit_htable *ht, struct dsthash_ent *ent)
|
|
|
}
|
|
|
static void htable_gc(struct work_struct *work);
|
|
|
|
|
|
-static int htable_create(struct net *net, struct hashlimit_cfg2 *cfg,
|
|
|
+static int htable_create(struct net *net, struct hashlimit_cfg3 *cfg,
|
|
|
const char *name, u_int8_t family,
|
|
|
struct xt_hashlimit_htable **out_hinfo,
|
|
|
int revision)
|
|
|
{
|
|
|
struct hashlimit_net *hashlimit_net = hashlimit_pernet(net);
|
|
|
struct xt_hashlimit_htable *hinfo;
|
|
|
+ const struct file_operations *fops;
|
|
|
unsigned int size, i;
|
|
|
int ret;
|
|
|
|
|
@@ -268,7 +293,7 @@ static int htable_create(struct net *net, struct hashlimit_cfg2 *cfg,
|
|
|
*out_hinfo = hinfo;
|
|
|
|
|
|
/* copy match config into hashtable config */
|
|
|
- ret = cfg_copy(&hinfo->cfg, (void *)cfg, 2);
|
|
|
+ ret = cfg_copy(&hinfo->cfg, (void *)cfg, 3);
|
|
|
|
|
|
if (ret)
|
|
|
return ret;
|
|
@@ -293,11 +318,21 @@ static int htable_create(struct net *net, struct hashlimit_cfg2 *cfg,
|
|
|
}
|
|
|
spin_lock_init(&hinfo->lock);
|
|
|
|
|
|
+ switch (revision) {
|
|
|
+ case 1:
|
|
|
+ fops = &dl_file_ops_v1;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ fops = &dl_file_ops_v2;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ fops = &dl_file_ops;
|
|
|
+ }
|
|
|
+
|
|
|
hinfo->pde = proc_create_data(name, 0,
|
|
|
(family == NFPROTO_IPV4) ?
|
|
|
hashlimit_net->ipt_hashlimit : hashlimit_net->ip6t_hashlimit,
|
|
|
- (revision == 1) ? &dl_file_ops_v1 : &dl_file_ops,
|
|
|
- hinfo);
|
|
|
+ fops, hinfo);
|
|
|
if (hinfo->pde == NULL) {
|
|
|
kfree(hinfo->name);
|
|
|
vfree(hinfo);
|
|
@@ -482,6 +517,25 @@ static u32 user2credits_byte(u32 user)
|
|
|
return (u32) (us >> 32);
|
|
|
}
|
|
|
|
|
|
+static u64 user2rate(u64 user)
|
|
|
+{
|
|
|
+ if (user != 0) {
|
|
|
+ return div64_u64(XT_HASHLIMIT_SCALE_v2, user);
|
|
|
+ } else {
|
|
|
+ pr_warn("invalid rate from userspace: %llu\n", user);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static u64 user2rate_bytes(u64 user)
|
|
|
+{
|
|
|
+ u64 r;
|
|
|
+
|
|
|
+ r = user ? 0xFFFFFFFFULL / user : 0xFFFFFFFFULL;
|
|
|
+ r = (r - 1) << 4;
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
static void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now,
|
|
|
u32 mode, int revision)
|
|
|
{
|
|
@@ -491,6 +545,21 @@ static void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now,
|
|
|
if (delta == 0)
|
|
|
return;
|
|
|
|
|
|
+ if (revision >= 3 && mode & XT_HASHLIMIT_RATE_MATCH) {
|
|
|
+ u64 interval = dh->rateinfo.interval * HZ;
|
|
|
+
|
|
|
+ if (delta < interval)
|
|
|
+ return;
|
|
|
+
|
|
|
+ dh->rateinfo.prev = now;
|
|
|
+ dh->rateinfo.prev_window =
|
|
|
+ ((dh->rateinfo.current_rate * interval) >
|
|
|
+ (delta * dh->rateinfo.rate));
|
|
|
+ dh->rateinfo.current_rate = 0;
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
dh->rateinfo.prev = now;
|
|
|
|
|
|
if (mode & XT_HASHLIMIT_BYTES) {
|
|
@@ -515,7 +584,23 @@ static void rateinfo_init(struct dsthash_ent *dh,
|
|
|
struct xt_hashlimit_htable *hinfo, int revision)
|
|
|
{
|
|
|
dh->rateinfo.prev = jiffies;
|
|
|
- if (hinfo->cfg.mode & XT_HASHLIMIT_BYTES) {
|
|
|
+ if (revision >= 3 && hinfo->cfg.mode & XT_HASHLIMIT_RATE_MATCH) {
|
|
|
+ dh->rateinfo.prev_window = 0;
|
|
|
+ dh->rateinfo.current_rate = 0;
|
|
|
+ if (hinfo->cfg.mode & XT_HASHLIMIT_BYTES) {
|
|
|
+ dh->rateinfo.rate = user2rate_bytes(hinfo->cfg.avg);
|
|
|
+ if (hinfo->cfg.burst)
|
|
|
+ dh->rateinfo.burst =
|
|
|
+ hinfo->cfg.burst * dh->rateinfo.rate;
|
|
|
+ else
|
|
|
+ dh->rateinfo.burst = dh->rateinfo.rate;
|
|
|
+ } else {
|
|
|
+ dh->rateinfo.rate = user2rate(hinfo->cfg.avg);
|
|
|
+ dh->rateinfo.burst =
|
|
|
+ hinfo->cfg.burst + dh->rateinfo.rate;
|
|
|
+ }
|
|
|
+ dh->rateinfo.interval = hinfo->cfg.interval;
|
|
|
+ } else if (hinfo->cfg.mode & XT_HASHLIMIT_BYTES) {
|
|
|
dh->rateinfo.credit = CREDITS_PER_JIFFY_BYTES * HZ;
|
|
|
dh->rateinfo.cost = user2credits_byte(hinfo->cfg.avg);
|
|
|
dh->rateinfo.credit_cap = hinfo->cfg.burst;
|
|
@@ -648,7 +733,7 @@ static u32 hashlimit_byte_cost(unsigned int len, struct dsthash_ent *dh)
|
|
|
static bool
|
|
|
hashlimit_mt_common(const struct sk_buff *skb, struct xt_action_param *par,
|
|
|
struct xt_hashlimit_htable *hinfo,
|
|
|
- const struct hashlimit_cfg2 *cfg, int revision)
|
|
|
+ const struct hashlimit_cfg3 *cfg, int revision)
|
|
|
{
|
|
|
unsigned long now = jiffies;
|
|
|
struct dsthash_ent *dh;
|
|
@@ -680,6 +765,20 @@ hashlimit_mt_common(const struct sk_buff *skb, struct xt_action_param *par,
|
|
|
rateinfo_recalc(dh, now, hinfo->cfg.mode, revision);
|
|
|
}
|
|
|
|
|
|
+ if (cfg->mode & XT_HASHLIMIT_RATE_MATCH) {
|
|
|
+ cost = (cfg->mode & XT_HASHLIMIT_BYTES) ? skb->len : 1;
|
|
|
+ dh->rateinfo.current_rate += cost;
|
|
|
+
|
|
|
+ if (!dh->rateinfo.prev_window &&
|
|
|
+ (dh->rateinfo.current_rate <= dh->rateinfo.burst)) {
|
|
|
+ spin_unlock(&dh->lock);
|
|
|
+ rcu_read_unlock_bh();
|
|
|
+ return !(cfg->mode & XT_HASHLIMIT_INVERT);
|
|
|
+ } else {
|
|
|
+ goto overlimit;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (cfg->mode & XT_HASHLIMIT_BYTES)
|
|
|
cost = hashlimit_byte_cost(skb->len, dh);
|
|
|
else
|
|
@@ -693,6 +792,7 @@ hashlimit_mt_common(const struct sk_buff *skb, struct xt_action_param *par,
|
|
|
return !(cfg->mode & XT_HASHLIMIT_INVERT);
|
|
|
}
|
|
|
|
|
|
+overlimit:
|
|
|
spin_unlock(&dh->lock);
|
|
|
local_bh_enable();
|
|
|
/* default match is underlimit - so over the limit, we need to invert */
|
|
@@ -708,7 +808,7 @@ hashlimit_mt_v1(const struct sk_buff *skb, struct xt_action_param *par)
|
|
|
{
|
|
|
const struct xt_hashlimit_mtinfo1 *info = par->matchinfo;
|
|
|
struct xt_hashlimit_htable *hinfo = info->hinfo;
|
|
|
- struct hashlimit_cfg2 cfg = {};
|
|
|
+ struct hashlimit_cfg3 cfg = {};
|
|
|
int ret;
|
|
|
|
|
|
ret = cfg_copy(&cfg, (void *)&info->cfg, 1);
|
|
@@ -720,17 +820,33 @@ hashlimit_mt_v1(const struct sk_buff *skb, struct xt_action_param *par)
|
|
|
}
|
|
|
|
|
|
static bool
|
|
|
-hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)
|
|
|
+hashlimit_mt_v2(const struct sk_buff *skb, struct xt_action_param *par)
|
|
|
{
|
|
|
const struct xt_hashlimit_mtinfo2 *info = par->matchinfo;
|
|
|
struct xt_hashlimit_htable *hinfo = info->hinfo;
|
|
|
+ struct hashlimit_cfg3 cfg = {};
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = cfg_copy(&cfg, (void *)&info->cfg, 2);
|
|
|
+
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return hashlimit_mt_common(skb, par, hinfo, &cfg, 2);
|
|
|
+}
|
|
|
+
|
|
|
+static bool
|
|
|
+hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)
|
|
|
+{
|
|
|
+ const struct xt_hashlimit_mtinfo3 *info = par->matchinfo;
|
|
|
+ struct xt_hashlimit_htable *hinfo = info->hinfo;
|
|
|
|
|
|
- return hashlimit_mt_common(skb, par, hinfo, &info->cfg, 2);
|
|
|
+ return hashlimit_mt_common(skb, par, hinfo, &info->cfg, 3);
|
|
|
}
|
|
|
|
|
|
static int hashlimit_mt_check_common(const struct xt_mtchk_param *par,
|
|
|
struct xt_hashlimit_htable **hinfo,
|
|
|
- struct hashlimit_cfg2 *cfg,
|
|
|
+ struct hashlimit_cfg3 *cfg,
|
|
|
const char *name, int revision)
|
|
|
{
|
|
|
struct net *net = par->net;
|
|
@@ -753,7 +869,17 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par,
|
|
|
}
|
|
|
|
|
|
/* Check for overflow. */
|
|
|
- if (cfg->mode & XT_HASHLIMIT_BYTES) {
|
|
|
+ if (revision >= 3 && cfg->mode & XT_HASHLIMIT_RATE_MATCH) {
|
|
|
+ if (cfg->avg == 0) {
|
|
|
+ pr_info("hashlimit invalid rate\n");
|
|
|
+ return -ERANGE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cfg->interval == 0) {
|
|
|
+ pr_info("hashlimit invalid interval\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+ } else if (cfg->mode & XT_HASHLIMIT_BYTES) {
|
|
|
if (user2credits_byte(cfg->avg) == 0) {
|
|
|
pr_info("overflow, rate too high: %llu\n", cfg->avg);
|
|
|
return -EINVAL;
|
|
@@ -784,7 +910,7 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par,
|
|
|
static int hashlimit_mt_check_v1(const struct xt_mtchk_param *par)
|
|
|
{
|
|
|
struct xt_hashlimit_mtinfo1 *info = par->matchinfo;
|
|
|
- struct hashlimit_cfg2 cfg = {};
|
|
|
+ struct hashlimit_cfg3 cfg = {};
|
|
|
int ret;
|
|
|
|
|
|
if (info->name[sizeof(info->name) - 1] != '\0')
|
|
@@ -799,15 +925,40 @@ static int hashlimit_mt_check_v1(const struct xt_mtchk_param *par)
|
|
|
&cfg, info->name, 1);
|
|
|
}
|
|
|
|
|
|
-static int hashlimit_mt_check(const struct xt_mtchk_param *par)
|
|
|
+static int hashlimit_mt_check_v2(const struct xt_mtchk_param *par)
|
|
|
{
|
|
|
struct xt_hashlimit_mtinfo2 *info = par->matchinfo;
|
|
|
+ struct hashlimit_cfg3 cfg = {};
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (info->name[sizeof(info->name) - 1] != '\0')
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ret = cfg_copy(&cfg, (void *)&info->cfg, 2);
|
|
|
+
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return hashlimit_mt_check_common(par, &info->hinfo,
|
|
|
+ &cfg, info->name, 2);
|
|
|
+}
|
|
|
+
|
|
|
+static int hashlimit_mt_check(const struct xt_mtchk_param *par)
|
|
|
+{
|
|
|
+ struct xt_hashlimit_mtinfo3 *info = par->matchinfo;
|
|
|
|
|
|
if (info->name[sizeof(info->name) - 1] != '\0')
|
|
|
return -EINVAL;
|
|
|
|
|
|
return hashlimit_mt_check_common(par, &info->hinfo, &info->cfg,
|
|
|
- info->name, 2);
|
|
|
+ info->name, 3);
|
|
|
+}
|
|
|
+
|
|
|
+static void hashlimit_mt_destroy_v2(const struct xt_mtdtor_param *par)
|
|
|
+{
|
|
|
+ const struct xt_hashlimit_mtinfo2 *info = par->matchinfo;
|
|
|
+
|
|
|
+ htable_put(info->hinfo);
|
|
|
}
|
|
|
|
|
|
static void hashlimit_mt_destroy_v1(const struct xt_mtdtor_param *par)
|
|
@@ -819,7 +970,7 @@ static void hashlimit_mt_destroy_v1(const struct xt_mtdtor_param *par)
|
|
|
|
|
|
static void hashlimit_mt_destroy(const struct xt_mtdtor_param *par)
|
|
|
{
|
|
|
- const struct xt_hashlimit_mtinfo2 *info = par->matchinfo;
|
|
|
+ const struct xt_hashlimit_mtinfo3 *info = par->matchinfo;
|
|
|
|
|
|
htable_put(info->hinfo);
|
|
|
}
|
|
@@ -840,9 +991,20 @@ static struct xt_match hashlimit_mt_reg[] __read_mostly = {
|
|
|
.name = "hashlimit",
|
|
|
.revision = 2,
|
|
|
.family = NFPROTO_IPV4,
|
|
|
- .match = hashlimit_mt,
|
|
|
+ .match = hashlimit_mt_v2,
|
|
|
.matchsize = sizeof(struct xt_hashlimit_mtinfo2),
|
|
|
.usersize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
|
|
|
+ .checkentry = hashlimit_mt_check_v2,
|
|
|
+ .destroy = hashlimit_mt_destroy_v2,
|
|
|
+ .me = THIS_MODULE,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .name = "hashlimit",
|
|
|
+ .revision = 3,
|
|
|
+ .family = NFPROTO_IPV4,
|
|
|
+ .match = hashlimit_mt,
|
|
|
+ .matchsize = sizeof(struct xt_hashlimit_mtinfo3),
|
|
|
+ .usersize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
|
|
|
.checkentry = hashlimit_mt_check,
|
|
|
.destroy = hashlimit_mt_destroy,
|
|
|
.me = THIS_MODULE,
|
|
@@ -863,9 +1025,20 @@ static struct xt_match hashlimit_mt_reg[] __read_mostly = {
|
|
|
.name = "hashlimit",
|
|
|
.revision = 2,
|
|
|
.family = NFPROTO_IPV6,
|
|
|
- .match = hashlimit_mt,
|
|
|
+ .match = hashlimit_mt_v2,
|
|
|
.matchsize = sizeof(struct xt_hashlimit_mtinfo2),
|
|
|
.usersize = offsetof(struct xt_hashlimit_mtinfo2, hinfo),
|
|
|
+ .checkentry = hashlimit_mt_check_v2,
|
|
|
+ .destroy = hashlimit_mt_destroy_v2,
|
|
|
+ .me = THIS_MODULE,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .name = "hashlimit",
|
|
|
+ .revision = 3,
|
|
|
+ .family = NFPROTO_IPV6,
|
|
|
+ .match = hashlimit_mt,
|
|
|
+ .matchsize = sizeof(struct xt_hashlimit_mtinfo3),
|
|
|
+ .usersize = offsetof(struct xt_hashlimit_mtinfo3, hinfo),
|
|
|
.checkentry = hashlimit_mt_check,
|
|
|
.destroy = hashlimit_mt_destroy,
|
|
|
.me = THIS_MODULE,
|
|
@@ -947,6 +1120,21 @@ static void dl_seq_print(struct dsthash_ent *ent, u_int8_t family,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static int dl_seq_real_show_v2(struct dsthash_ent *ent, u_int8_t family,
|
|
|
+ struct seq_file *s)
|
|
|
+{
|
|
|
+ const struct xt_hashlimit_htable *ht = s->private;
|
|
|
+
|
|
|
+ spin_lock(&ent->lock);
|
|
|
+ /* recalculate to show accurate numbers */
|
|
|
+ rateinfo_recalc(ent, jiffies, ht->cfg.mode, 2);
|
|
|
+
|
|
|
+ dl_seq_print(ent, family, s);
|
|
|
+
|
|
|
+ spin_unlock(&ent->lock);
|
|
|
+ return seq_has_overflowed(s);
|
|
|
+}
|
|
|
+
|
|
|
static int dl_seq_real_show_v1(struct dsthash_ent *ent, u_int8_t family,
|
|
|
struct seq_file *s)
|
|
|
{
|
|
@@ -969,7 +1157,7 @@ static int dl_seq_real_show(struct dsthash_ent *ent, u_int8_t family,
|
|
|
|
|
|
spin_lock(&ent->lock);
|
|
|
/* recalculate to show accurate numbers */
|
|
|
- rateinfo_recalc(ent, jiffies, ht->cfg.mode, 2);
|
|
|
+ rateinfo_recalc(ent, jiffies, ht->cfg.mode, 3);
|
|
|
|
|
|
dl_seq_print(ent, family, s);
|
|
|
|
|
@@ -977,6 +1165,20 @@ static int dl_seq_real_show(struct dsthash_ent *ent, u_int8_t family,
|
|
|
return seq_has_overflowed(s);
|
|
|
}
|
|
|
|
|
|
+static int dl_seq_show_v2(struct seq_file *s, void *v)
|
|
|
+{
|
|
|
+ struct xt_hashlimit_htable *htable = s->private;
|
|
|
+ unsigned int *bucket = (unsigned int *)v;
|
|
|
+ struct dsthash_ent *ent;
|
|
|
+
|
|
|
+ if (!hlist_empty(&htable->hash[*bucket])) {
|
|
|
+ hlist_for_each_entry(ent, &htable->hash[*bucket], node)
|
|
|
+ if (dl_seq_real_show_v2(ent, htable->family, s))
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int dl_seq_show_v1(struct seq_file *s, void *v)
|
|
|
{
|
|
|
struct xt_hashlimit_htable *htable = s->private;
|
|
@@ -1012,6 +1214,13 @@ static const struct seq_operations dl_seq_ops_v1 = {
|
|
|
.show = dl_seq_show_v1
|
|
|
};
|
|
|
|
|
|
+static const struct seq_operations dl_seq_ops_v2 = {
|
|
|
+ .start = dl_seq_start,
|
|
|
+ .next = dl_seq_next,
|
|
|
+ .stop = dl_seq_stop,
|
|
|
+ .show = dl_seq_show_v2
|
|
|
+};
|
|
|
+
|
|
|
static const struct seq_operations dl_seq_ops = {
|
|
|
.start = dl_seq_start,
|
|
|
.next = dl_seq_next,
|
|
@@ -1019,6 +1228,18 @@ static const struct seq_operations dl_seq_ops = {
|
|
|
.show = dl_seq_show
|
|
|
};
|
|
|
|
|
|
+static int dl_proc_open_v2(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ int ret = seq_open(file, &dl_seq_ops_v2);
|
|
|
+
|
|
|
+ if (!ret) {
|
|
|
+ struct seq_file *sf = file->private_data;
|
|
|
+
|
|
|
+ sf->private = PDE_DATA(inode);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static int dl_proc_open_v1(struct inode *inode, struct file *file)
|
|
|
{
|
|
|
int ret = seq_open(file, &dl_seq_ops_v1);
|
|
@@ -1042,6 +1263,14 @@ static int dl_proc_open(struct inode *inode, struct file *file)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static const struct file_operations dl_file_ops_v2 = {
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .open = dl_proc_open_v2,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = seq_release
|
|
|
+};
|
|
|
+
|
|
|
static const struct file_operations dl_file_ops_v1 = {
|
|
|
.owner = THIS_MODULE,
|
|
|
.open = dl_proc_open_v1,
|