text: /*
   *	X.25 Packet Layer release 002
   *
   *	This is ALPHA test software. This code may break your machine,
   *	randomly fail to work with new releases, misbehave and/or generally
   *	screw up. It might even work.
   *
   *	This code REQUIRES 2.1.15 or higher
   *
   *	This module:
   *		This module 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.
   *
   *	History
   *	X.25 001	Jonathan Naylor	Started coding.
   */
  
  #include <linux/if_arp.h>
  #include <linux/init.h>
  #include <net/x25.h>
  
  struct list_head x25_route_list = LIST_HEAD_INIT(x25_route_list);
  DEFINE_RWLOCK(x25_route_list_lock);
  
  /*
   *	Add a new route.
   */
  static int x25_add_route(struct x25_address *address, unsigned int sigdigits,
  			 struct net_device *dev)
  {
  	struct x25_route *rt;
  	struct list_head *entry;
  	int rc = -EINVAL;
  
  	write_lock_bh(&x25_route_list_lock);
  
  	list_for_each(entry, &x25_route_list) {
  		rt = list_entry(entry, struct x25_route, node);
  
  		if (!memcmp(&rt->address, address, sigdigits) &&
  		    rt->sigdigits == sigdigits)
  			goto out;
  	}
  
  	rt = kmalloc(sizeof(*rt), GFP_ATOMIC);
  	rc = -ENOMEM;
  	if (!rt)
  		goto out;
  
  	strcpy(rt->address.x25_addr, "000000000000000");
  	memcpy(rt->address.x25_addr, address->x25_addr, sigdigits);
  
  	rt->sigdigits = sigdigits;
  	rt->dev       = dev;
  	atomic_set(&rt->refcnt, 1);
  
  	list_add(&rt->node, &x25_route_list);
  	rc = 0;
  out:
  	write_unlock_bh(&x25_route_list_lock);
  	return rc;
  }
  
  /**
   * __x25_remove_route - remove route from x25_route_list
   * @rt - route to remove
   *
   * Remove route from x25_route_list. If it was there.
   * Caller must hold x25_route_list_lock.
   */
  static void __x25_remove_route(struct x25_route *rt)
  {
  	if (rt->node.next) {
  		list_del(&rt->node);
  		x25_route_put(rt);
  	}
  }
  
  static int x25_del_route(struct x25_address *address, unsigned int sigdigits,
  			 struct net_device *dev)
  {
  	struct x25_route *rt;
  	struct list_head *entry;
  	int rc = -EINVAL;
  
  	write_lock_bh(&x25_route_list_lock);
  
  	list_for_each(entry, &x25_route_list) {
  		rt = list_entry(entry, struct x25_route, node);
  
  		if (!memcmp(&rt->address, address, sigdigits) &&
  		    rt->sigdigits == sigdigits && rt->dev == dev) {
  			__x25_remove_route(rt);
  			rc = 0;
  			break;
  		}
  	}
  
  	write_unlock_bh(&x25_route_list_lock);
  	return rc;
  }
  
  /*
   *	A device has been removed, remove its routes.
   */
  void x25_route_device_down(struct net_device *dev)
  {
  	struct x25_route *rt;
  	struct list_head *entry, *tmp;
  
  	write_lock_bh(&x25_route_list_lock);
  
  	list_for_each_safe(entry, tmp, &x25_route_list) {
  		rt = list_entry(entry, struct x25_route, node);
  
  		if (rt->dev == dev)
  			__x25_remove_route(rt);
  	}
  	write_unlock_bh(&x25_route_list_lock);
  
  	/* Remove any related forwarding */
  	x25_clear_forward_by_dev(dev);
  }
  
  /*
   *	Check that the device given is a valid X.25 interface that is "up".
   */
  struct net_device *x25_dev_get(char *devname)
  {
  	struct net_device *dev = dev_get_by_name(devname);
  
  	if (dev &&
  	    (!(dev->flags & IFF_UP) || (dev->type != ARPHRD_X25
  #if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
  					&& dev->type != ARPHRD_ETHER
  #endif
  					)))
  		dev_put(dev);
  
  	return dev;
  }
  
  /**
   * 	x25_get_route -	Find a route given an X.25 address.
   * 	@addr - address to find a route for
   *
   * 	Find a route given an X.25 address.
   */
  struct x25_route *x25_get_route(struct x25_address *addr)
  {
  	struct x25_route *rt, *use = NULL;
  	struct list_head *entry;
  
  	read_lock_bh(&x25_route_list_lock);
  
  	list_for_each(entry, &x25_route_list) {
  		rt = list_entry(entry, struct x25_route, node);
  
  		if (!memcmp(&rt->address, addr, rt->sigdigits)) {
  			if (!use)
  				use = rt;
  			else if (rt->sigdigits > use->sigdigits)
  				use = rt;
  		}
  	}
  
  	if (use)
  		x25_route_hold(use);
  
  	read_unlock_bh(&x25_route_list_lock);
  	return use;
  }
  
  /*
   *	Handle the ioctls that control the routing functions.
   */
  int x25_route_ioctl(unsigned int cmd, void __user *arg)
  {
  	struct x25_route_struct rt;
  	struct net_device *dev;
  	int rc = -EINVAL;
  
  	if (cmd != SIOCADDRT && cmd != SIOCDELRT)
  		goto out;
  
  	rc = -EFAULT;
  	if (copy_from_user(&rt, arg, sizeof(rt)))
  		goto out;
  
  	rc = -EINVAL;
  	if (rt.sigdigits < 0 || rt.sigdigits > 15)
  		goto out;
  
  	dev = x25_dev_get(rt.device);
  	if (!dev)
  		goto out;
  
  	if (cmd == SIOCADDRT)
  		rc = x25_add_route(&rt.address, rt.sigdigits, dev);
  	else
  		rc = x25_del_route(&rt.address, rt.sigdigits, dev);
  	dev_put(dev);
  out:
  	return rc;
  }
  
  /*
   *	Release all memory associated with X.25 routing structures.
   */
  void __exit x25_route_free(void)
  {
  	struct x25_route *rt;
  	struct list_head *entry, *tmp;
  
  	write_lock_bh(&x25_route_list_lock);
  	list_for_each_safe(entry, tmp, &x25_route_list) {
  		rt = list_entry(entry, struct x25_route, node);
  		__x25_remove_route(rt);
  	}
  	write_unlock_bh(&x25_route_list_lock);
  }
  