| /* | 
 |  * 2007+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> | 
 |  * All rights reserved. | 
 |  * | 
 |  * 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; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  * | 
 |  * This program is distributed in the hope that it will 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. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/connector.h> | 
 | #include <linux/crypto.h> | 
 | #include <linux/list.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/string.h> | 
 | #include <linux/in.h> | 
 | #include <linux/slab.h> | 
 |  | 
 | #include "netfs.h" | 
 |  | 
 | /* | 
 |  * Global configuration list. | 
 |  * Each client can be asked to get one of them. | 
 |  * | 
 |  * Allows to provide remote server address (ipv4/v6/whatever), port | 
 |  * and so on via kernel connector. | 
 |  */ | 
 |  | 
 | static struct cb_id pohmelfs_cn_id = {.idx = POHMELFS_CN_IDX, .val = POHMELFS_CN_VAL}; | 
 | static LIST_HEAD(pohmelfs_config_list); | 
 | static DEFINE_MUTEX(pohmelfs_config_lock); | 
 |  | 
 | static inline int pohmelfs_config_eql(struct pohmelfs_ctl *sc, struct pohmelfs_ctl *ctl) | 
 | { | 
 | 	if (sc->idx == ctl->idx && sc->type == ctl->type && | 
 | 			sc->proto == ctl->proto && | 
 | 			sc->addrlen == ctl->addrlen && | 
 | 			!memcmp(&sc->addr, &ctl->addr, ctl->addrlen)) | 
 | 		return 1; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct pohmelfs_config_group *pohmelfs_find_config_group(unsigned int idx) | 
 | { | 
 | 	struct pohmelfs_config_group *g, *group = NULL; | 
 |  | 
 | 	list_for_each_entry(g, &pohmelfs_config_list, group_entry) { | 
 | 		if (g->idx == idx) { | 
 | 			group = g; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return group; | 
 | } | 
 |  | 
 | static struct pohmelfs_config_group *pohmelfs_find_create_config_group(unsigned int idx) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 |  | 
 | 	g = pohmelfs_find_config_group(idx); | 
 | 	if (g) | 
 | 		return g; | 
 |  | 
 | 	g = kzalloc(sizeof(struct pohmelfs_config_group), GFP_KERNEL); | 
 | 	if (!g) | 
 | 		return NULL; | 
 |  | 
 | 	INIT_LIST_HEAD(&g->config_list); | 
 | 	g->idx = idx; | 
 | 	g->num_entry = 0; | 
 |  | 
 | 	list_add_tail(&g->group_entry, &pohmelfs_config_list); | 
 |  | 
 | 	return g; | 
 | } | 
 |  | 
 | static inline void pohmelfs_insert_config_entry(struct pohmelfs_sb *psb, struct pohmelfs_config *dst) | 
 | { | 
 | 	struct pohmelfs_config *tmp; | 
 |  | 
 | 	INIT_LIST_HEAD(&dst->config_entry); | 
 |  | 
 | 	list_for_each_entry(tmp, &psb->state_list, config_entry) { | 
 | 		if (dst->state.ctl.prio > tmp->state.ctl.prio) | 
 | 			list_add_tail(&dst->config_entry, &tmp->config_entry); | 
 | 	} | 
 | 	if (list_empty(&dst->config_entry)) | 
 | 		list_add_tail(&dst->config_entry, &psb->state_list); | 
 | } | 
 |  | 
 | static int pohmelfs_move_config_entry(struct pohmelfs_sb *psb, | 
 | 		struct pohmelfs_config *dst, struct pohmelfs_config *new) | 
 | { | 
 | 	if ((dst->state.ctl.prio == new->state.ctl.prio) && | 
 | 		(dst->state.ctl.perm == new->state.ctl.perm)) | 
 | 		return 0; | 
 |  | 
 | 	dprintk("%s: dst: prio: %d, perm: %x, new: prio: %d, perm: %d.\n", | 
 | 			__func__, dst->state.ctl.prio, dst->state.ctl.perm, | 
 | 			new->state.ctl.prio, new->state.ctl.perm); | 
 | 	dst->state.ctl.prio = new->state.ctl.prio; | 
 | 	dst->state.ctl.perm = new->state.ctl.perm; | 
 |  | 
 | 	list_del_init(&dst->config_entry); | 
 | 	pohmelfs_insert_config_entry(psb, dst); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * pohmelfs_copy_config() is used to copy new state configs from the | 
 |  * config group (controlled by the netlink messages) into the superblock. | 
 |  * This happens either at startup time where no transactions can access | 
 |  * the list of the configs (and thus list of the network states), or at | 
 |  * run-time, where it is protected by the psb->state_lock. | 
 |  */ | 
 | int pohmelfs_copy_config(struct pohmelfs_sb *psb) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	struct pohmelfs_config *c, *dst; | 
 | 	int err = -ENODEV; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	g = pohmelfs_find_config_group(psb->idx); | 
 | 	if (!g) | 
 | 		goto out_unlock; | 
 |  | 
 | 	/* | 
 | 	 * Run over all entries in given config group and try to crate and | 
 | 	 * initialize those, which do not exist in superblock list. | 
 | 	 * Skip all existing entries. | 
 | 	 */ | 
 |  | 
 | 	list_for_each_entry(c, &g->config_list, config_entry) { | 
 | 		err = 0; | 
 | 		list_for_each_entry(dst, &psb->state_list, config_entry) { | 
 | 			if (pohmelfs_config_eql(&dst->state.ctl, &c->state.ctl)) { | 
 | 				err = pohmelfs_move_config_entry(psb, dst, c); | 
 | 				if (!err) | 
 | 					err = -EEXIST; | 
 | 				break; | 
 | 			} | 
 | 		} | 
 |  | 
 | 		if (err) | 
 | 			continue; | 
 |  | 
 | 		dst = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL); | 
 | 		if (!dst) { | 
 | 			err = -ENOMEM; | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		memcpy(&dst->state.ctl, &c->state.ctl, sizeof(struct pohmelfs_ctl)); | 
 |  | 
 | 		pohmelfs_insert_config_entry(psb, dst); | 
 |  | 
 | 		err = pohmelfs_state_init_one(psb, dst); | 
 | 		if (err) { | 
 | 			list_del(&dst->config_entry); | 
 | 			kfree(dst); | 
 | 		} | 
 |  | 
 | 		err = 0; | 
 | 	} | 
 |  | 
 | out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | int pohmelfs_copy_crypto(struct pohmelfs_sb *psb) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	int err = -ENOENT; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 | 	g = pohmelfs_find_config_group(psb->idx); | 
 | 	if (!g) | 
 | 		goto err_out_exit; | 
 |  | 
 | 	if (g->hash_string) { | 
 | 		err = -ENOMEM; | 
 | 		psb->hash_string = kstrdup(g->hash_string, GFP_KERNEL); | 
 | 		if (!psb->hash_string) | 
 | 			goto err_out_exit; | 
 | 		psb->hash_strlen = g->hash_strlen; | 
 | 	} | 
 |  | 
 | 	if (g->cipher_string) { | 
 | 		psb->cipher_string = kstrdup(g->cipher_string, GFP_KERNEL); | 
 | 		if (!psb->cipher_string) | 
 | 			goto err_out_free_hash_string; | 
 | 		psb->cipher_strlen = g->cipher_strlen; | 
 | 	} | 
 |  | 
 | 	if (g->hash_keysize) { | 
 | 		psb->hash_key = kmemdup(g->hash_key, g->hash_keysize, | 
 | 					GFP_KERNEL); | 
 | 		if (!psb->hash_key) | 
 | 			goto err_out_free_cipher_string; | 
 | 		psb->hash_keysize = g->hash_keysize; | 
 | 	} | 
 |  | 
 | 	if (g->cipher_keysize) { | 
 | 		psb->cipher_key = kmemdup(g->cipher_key, g->cipher_keysize, | 
 | 					  GFP_KERNEL); | 
 | 		if (!psb->cipher_key) | 
 | 			goto err_out_free_hash; | 
 | 		psb->cipher_keysize = g->cipher_keysize; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_out_free_hash: | 
 | 	kfree(psb->hash_key); | 
 | err_out_free_cipher_string: | 
 | 	kfree(psb->cipher_string); | 
 | err_out_free_hash_string: | 
 | 	kfree(psb->hash_string); | 
 | err_out_exit: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int pohmelfs_send_reply(int err, int msg_num, int action, struct cn_msg *msg, struct pohmelfs_ctl *ctl) | 
 | { | 
 | 	struct pohmelfs_cn_ack *ack; | 
 |  | 
 | 	ack = kzalloc(sizeof(struct pohmelfs_cn_ack), GFP_KERNEL); | 
 | 	if (!ack) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	memcpy(&ack->msg, msg, sizeof(struct cn_msg)); | 
 |  | 
 | 	if (action == POHMELFS_CTLINFO_ACK) | 
 | 		memcpy(&ack->ctl, ctl, sizeof(struct pohmelfs_ctl)); | 
 |  | 
 | 	ack->msg.len = sizeof(struct pohmelfs_cn_ack) - sizeof(struct cn_msg); | 
 | 	ack->msg.ack = msg->ack + 1; | 
 | 	ack->error = err; | 
 | 	ack->msg_num = msg_num; | 
 |  | 
 | 	cn_netlink_send(&ack->msg, 0, GFP_KERNEL); | 
 | 	kfree(ack); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pohmelfs_cn_disp(struct cn_msg *msg) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; | 
 | 	struct pohmelfs_config *c, *tmp; | 
 | 	int err = 0, i = 1; | 
 |  | 
 | 	if (msg->len != sizeof(struct pohmelfs_ctl)) | 
 | 		return -EBADMSG; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	g = pohmelfs_find_config_group(ctl->idx); | 
 | 	if (!g) { | 
 | 		pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL); | 
 | 		goto out_unlock; | 
 | 	} | 
 |  | 
 | 	list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { | 
 | 		struct pohmelfs_ctl *sc = &c->state.ctl; | 
 | 		if (pohmelfs_send_reply(err, g->num_entry - i, POHMELFS_CTLINFO_ACK, msg, sc)) { | 
 | 			err = -ENOMEM; | 
 | 			goto out_unlock; | 
 | 		} | 
 | 		i += 1; | 
 | 	} | 
 |  | 
 |  out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int pohmelfs_cn_dump(struct cn_msg *msg) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	struct pohmelfs_config *c, *tmp; | 
 | 	int err = 0, i = 1; | 
 | 	int total_msg = 0; | 
 |  | 
 | 	if (msg->len != sizeof(struct pohmelfs_ctl)) | 
 | 		return -EBADMSG; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	list_for_each_entry(g, &pohmelfs_config_list, group_entry) | 
 | 		total_msg += g->num_entry; | 
 | 	if (total_msg == 0) { | 
 | 		if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) | 
 | 			err = -ENOMEM; | 
 | 		goto out_unlock; | 
 | 	} | 
 |  | 
 | 	list_for_each_entry(g, &pohmelfs_config_list, group_entry) { | 
 | 		list_for_each_entry_safe(c, tmp, &g->config_list, | 
 | 					 config_entry) { | 
 | 			struct pohmelfs_ctl *sc = &c->state.ctl; | 
 | 			if (pohmelfs_send_reply(err, total_msg - i, | 
 | 						POHMELFS_CTLINFO_ACK, msg, | 
 | 						sc)) { | 
 | 				err = -ENOMEM; | 
 | 				goto out_unlock; | 
 | 			} | 
 | 			i += 1; | 
 | 		} | 
 | 	} | 
 |  | 
 | out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	return err; | 
 | } | 
 |  | 
 | static int pohmelfs_cn_flush(struct cn_msg *msg) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; | 
 | 	struct pohmelfs_config *c, *tmp; | 
 | 	int err = 0; | 
 |  | 
 | 	if (msg->len != sizeof(struct pohmelfs_ctl)) | 
 | 		return -EBADMSG; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	if (ctl->idx != POHMELFS_NULL_IDX) { | 
 | 		g = pohmelfs_find_config_group(ctl->idx); | 
 |  | 
 | 		if (!g) | 
 | 			goto out_unlock; | 
 |  | 
 | 		list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { | 
 | 			list_del(&c->config_entry); | 
 | 			g->num_entry--; | 
 | 			kfree(c); | 
 | 		} | 
 | 	} else { | 
 | 		list_for_each_entry(g, &pohmelfs_config_list, group_entry) { | 
 | 			list_for_each_entry_safe(c, tmp, &g->config_list, | 
 | 						 config_entry) { | 
 | 				list_del(&c->config_entry); | 
 | 				g->num_entry--; | 
 | 				kfree(c); | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	pohmelfs_cn_dump(msg); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int pohmelfs_modify_config(struct pohmelfs_ctl *old, struct pohmelfs_ctl *new) | 
 | { | 
 | 	old->perm = new->perm; | 
 | 	old->prio = new->prio; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pohmelfs_cn_ctl(struct cn_msg *msg, int action) | 
 | { | 
 | 	struct pohmelfs_config_group *g; | 
 | 	struct pohmelfs_ctl *ctl = (struct pohmelfs_ctl *)msg->data; | 
 | 	struct pohmelfs_config *c, *tmp; | 
 | 	int err = 0; | 
 |  | 
 | 	if (msg->len != sizeof(struct pohmelfs_ctl)) | 
 | 		return -EBADMSG; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	g = pohmelfs_find_create_config_group(ctl->idx); | 
 | 	if (!g) { | 
 | 		err = -ENOMEM; | 
 | 		goto out_unlock; | 
 | 	} | 
 |  | 
 | 	list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { | 
 | 		struct pohmelfs_ctl *sc = &c->state.ctl; | 
 |  | 
 | 		if (pohmelfs_config_eql(sc, ctl)) { | 
 | 			if (action == POHMELFS_FLAGS_ADD) { | 
 | 				err = -EEXIST; | 
 | 				goto out_unlock; | 
 | 			} else if (action == POHMELFS_FLAGS_DEL) { | 
 | 				list_del(&c->config_entry); | 
 | 				g->num_entry--; | 
 | 				kfree(c); | 
 | 				goto out_unlock; | 
 | 			} else if (action == POHMELFS_FLAGS_MODIFY) { | 
 | 				err = pohmelfs_modify_config(sc, ctl); | 
 | 				goto out_unlock; | 
 | 			} else { | 
 | 				err = -EEXIST; | 
 | 				goto out_unlock; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	if (action == POHMELFS_FLAGS_DEL) { | 
 | 		err = -EBADMSG; | 
 | 		goto out_unlock; | 
 | 	} | 
 |  | 
 | 	c = kzalloc(sizeof(struct pohmelfs_config), GFP_KERNEL); | 
 | 	if (!c) { | 
 | 		err = -ENOMEM; | 
 | 		goto out_unlock; | 
 | 	} | 
 | 	memcpy(&c->state.ctl, ctl, sizeof(struct pohmelfs_ctl)); | 
 | 	g->num_entry++; | 
 |  | 
 | 	list_add_tail(&c->config_entry, &g->config_list); | 
 |  | 
 |  out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) | 
 | 		err = -ENOMEM; | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int pohmelfs_crypto_hash_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c) | 
 | { | 
 | 	char *algo = (char *)c->data; | 
 | 	u8 *key = (u8 *)(algo + c->strlen); | 
 |  | 
 | 	if (g->hash_string) | 
 | 		return -EEXIST; | 
 |  | 
 | 	g->hash_string = kstrdup(algo, GFP_KERNEL); | 
 | 	if (!g->hash_string) | 
 | 		return -ENOMEM; | 
 | 	g->hash_strlen = c->strlen; | 
 | 	g->hash_keysize = c->keysize; | 
 |  | 
 | 	g->hash_key = kmemdup(key, c->keysize, GFP_KERNEL); | 
 | 	if (!g->hash_key) { | 
 | 		kfree(g->hash_string); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pohmelfs_crypto_cipher_init(struct pohmelfs_config_group *g, struct pohmelfs_crypto *c) | 
 | { | 
 | 	char *algo = (char *)c->data; | 
 | 	u8 *key = (u8 *)(algo + c->strlen); | 
 |  | 
 | 	if (g->cipher_string) | 
 | 		return -EEXIST; | 
 |  | 
 | 	g->cipher_string = kstrdup(algo, GFP_KERNEL); | 
 | 	if (!g->cipher_string) | 
 | 		return -ENOMEM; | 
 | 	g->cipher_strlen = c->strlen; | 
 | 	g->cipher_keysize = c->keysize; | 
 |  | 
 | 	g->cipher_key = kmemdup(key, c->keysize, GFP_KERNEL); | 
 | 	if (!g->cipher_key) { | 
 | 		kfree(g->cipher_string); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int pohmelfs_cn_crypto(struct cn_msg *msg) | 
 | { | 
 | 	struct pohmelfs_crypto *crypto = (struct pohmelfs_crypto *)msg->data; | 
 | 	struct pohmelfs_config_group *g; | 
 | 	int err = 0; | 
 |  | 
 | 	dprintk("%s: idx: %u, strlen: %u, type: %u, keysize: %u, algo: %s.\n", | 
 | 			__func__, crypto->idx, crypto->strlen, crypto->type, | 
 | 			crypto->keysize, (char *)crypto->data); | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 | 	g = pohmelfs_find_create_config_group(crypto->idx); | 
 | 	if (!g) { | 
 | 		err = -ENOMEM; | 
 | 		goto out_unlock; | 
 | 	} | 
 |  | 
 | 	switch (crypto->type) { | 
 | 	case POHMELFS_CRYPTO_HASH: | 
 | 			err = pohmelfs_crypto_hash_init(g, crypto); | 
 | 			break; | 
 | 	case POHMELFS_CRYPTO_CIPHER: | 
 | 			err = pohmelfs_crypto_cipher_init(g, crypto); | 
 | 			break; | 
 | 	default: | 
 | 			err = -ENOTSUPP; | 
 | 			break; | 
 | 	} | 
 |  | 
 | out_unlock: | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | 	if (pohmelfs_send_reply(err, 0, POHMELFS_NOINFO_ACK, msg, NULL)) | 
 | 		err = -ENOMEM; | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static void pohmelfs_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	if (!cap_raised(current_cap(), CAP_SYS_ADMIN)) | 
 | 		return; | 
 |  | 
 | 	switch (msg->flags) { | 
 | 	case POHMELFS_FLAGS_ADD: | 
 | 	case POHMELFS_FLAGS_DEL: | 
 | 	case POHMELFS_FLAGS_MODIFY: | 
 | 			err = pohmelfs_cn_ctl(msg, msg->flags); | 
 | 			break; | 
 | 	case POHMELFS_FLAGS_FLUSH: | 
 | 			err = pohmelfs_cn_flush(msg); | 
 | 			break; | 
 | 	case POHMELFS_FLAGS_SHOW: | 
 | 			err = pohmelfs_cn_disp(msg); | 
 | 			break; | 
 | 	case POHMELFS_FLAGS_DUMP: | 
 | 			err = pohmelfs_cn_dump(msg); | 
 | 			break; | 
 | 	case POHMELFS_FLAGS_CRYPTO: | 
 | 			err = pohmelfs_cn_crypto(msg); | 
 | 			break; | 
 | 	default: | 
 | 			err = -ENOSYS; | 
 | 			break; | 
 | 	} | 
 | } | 
 |  | 
 | int pohmelfs_config_check(struct pohmelfs_config *config, int idx) | 
 | { | 
 | 	struct pohmelfs_ctl *ctl = &config->state.ctl; | 
 | 	struct pohmelfs_config *tmp; | 
 | 	int err = -ENOENT; | 
 | 	struct pohmelfs_ctl *sc; | 
 | 	struct pohmelfs_config_group *g; | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 |  | 
 | 	g = pohmelfs_find_config_group(ctl->idx); | 
 | 	if (g) { | 
 | 		list_for_each_entry(tmp, &g->config_list, config_entry) { | 
 | 			sc = &tmp->state.ctl; | 
 |  | 
 | 			if (pohmelfs_config_eql(sc, ctl)) { | 
 | 				err = 0; | 
 | 				break; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | int __init pohmelfs_config_init(void) | 
 | { | 
 | 	/* XXX remove (void *) cast when vanilla connector got synced */ | 
 | 	return cn_add_callback(&pohmelfs_cn_id, "pohmelfs", (void *)pohmelfs_cn_callback); | 
 | } | 
 |  | 
 | void pohmelfs_config_exit(void) | 
 | { | 
 | 	struct pohmelfs_config *c, *tmp; | 
 | 	struct pohmelfs_config_group *g, *gtmp; | 
 |  | 
 | 	cn_del_callback(&pohmelfs_cn_id); | 
 |  | 
 | 	mutex_lock(&pohmelfs_config_lock); | 
 | 	list_for_each_entry_safe(g, gtmp, &pohmelfs_config_list, group_entry) { | 
 | 		list_for_each_entry_safe(c, tmp, &g->config_list, config_entry) { | 
 | 			list_del(&c->config_entry); | 
 | 			kfree(c); | 
 | 		} | 
 |  | 
 | 		list_del(&g->group_entry); | 
 |  | 
 | 		if (g->hash_string) | 
 | 			kfree(g->hash_string); | 
 |  | 
 | 		if (g->cipher_string) | 
 | 			kfree(g->cipher_string); | 
 |  | 
 | 		kfree(g); | 
 | 	} | 
 | 	mutex_unlock(&pohmelfs_config_lock); | 
 | } |