Commit | Line | Data |
---|---|---|
b4d0d230 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
87563616 | 2 | /* Local endpoint object management |
17926a79 | 3 | * |
4f95dd78 | 4 | * Copyright (C) 2016 Red Hat, Inc. All Rights Reserved. |
17926a79 | 5 | * Written by David Howells (dhowells@redhat.com) |
17926a79 DH |
6 | */ |
7 | ||
9b6d5398 JP |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | ||
17926a79 DH |
10 | #include <linux/module.h> |
11 | #include <linux/net.h> | |
12 | #include <linux/skbuff.h> | |
5a0e3ad6 | 13 | #include <linux/slab.h> |
44ba0698 DH |
14 | #include <linux/udp.h> |
15 | #include <linux/ip.h> | |
4f95dd78 | 16 | #include <linux/hashtable.h> |
17926a79 | 17 | #include <net/sock.h> |
5271953c | 18 | #include <net/udp.h> |
5d30c626 | 19 | #include <net/udp_tunnel.h> |
17926a79 DH |
20 | #include <net/af_rxrpc.h> |
21 | #include "ar-internal.h" | |
22 | ||
4f95dd78 | 23 | static void rxrpc_local_rcu(struct rcu_head *); |
17926a79 | 24 | |
b6c66c43 DH |
25 | /* |
26 | * Handle an ICMP/ICMP6 error turning up at the tunnel. Push it through the | |
27 | * usual mechanism so that it gets parsed and presented through the UDP | |
28 | * socket's error_report(). | |
29 | */ | |
30 | static void rxrpc_encap_err_rcv(struct sock *sk, struct sk_buff *skb, int err, | |
31 | __be16 port, u32 info, u8 *payload) | |
32 | { | |
33 | if (ip_hdr(skb)->version == IPVERSION) | |
34 | return ip_icmp_error(sk, skb, err, port, info, payload); | |
41cf3a91 DH |
35 | if (IS_ENABLED(CONFIG_AF_RXRPC_IPV6)) |
36 | return ipv6_icmp_error(sk, skb, err, port, info, payload); | |
b6c66c43 DH |
37 | } |
38 | ||
17926a79 | 39 | /* |
4f95dd78 DH |
40 | * Compare a local to an address. Return -ve, 0 or +ve to indicate less than, |
41 | * same or greater than. | |
42 | * | |
43 | * We explicitly don't compare the RxRPC service ID as we want to reject | |
44 | * conflicting uses by differing services. Further, we don't want to share | |
45 | * addresses with different options (IPv6), so we don't compare those bits | |
46 | * either. | |
17926a79 | 47 | */ |
4f95dd78 DH |
48 | static long rxrpc_local_cmp_key(const struct rxrpc_local *local, |
49 | const struct sockaddr_rxrpc *srx) | |
50 | { | |
51 | long diff; | |
52 | ||
53 | diff = ((local->srx.transport_type - srx->transport_type) ?: | |
54 | (local->srx.transport_len - srx->transport_len) ?: | |
55 | (local->srx.transport.family - srx->transport.family)); | |
56 | if (diff != 0) | |
57 | return diff; | |
58 | ||
59 | switch (srx->transport.family) { | |
60 | case AF_INET: | |
61 | /* If the choice of UDP port is left up to the transport, then | |
62 | * the endpoint record doesn't match. | |
63 | */ | |
64 | return ((u16 __force)local->srx.transport.sin.sin_port - | |
65 | (u16 __force)srx->transport.sin.sin_port) ?: | |
66 | memcmp(&local->srx.transport.sin.sin_addr, | |
67 | &srx->transport.sin.sin_addr, | |
68 | sizeof(struct in_addr)); | |
d1912747 | 69 | #ifdef CONFIG_AF_RXRPC_IPV6 |
75b54cb5 DH |
70 | case AF_INET6: |
71 | /* If the choice of UDP6 port is left up to the transport, then | |
72 | * the endpoint record doesn't match. | |
73 | */ | |
74 | return ((u16 __force)local->srx.transport.sin6.sin6_port - | |
75 | (u16 __force)srx->transport.sin6.sin6_port) ?: | |
76 | memcmp(&local->srx.transport.sin6.sin6_addr, | |
77 | &srx->transport.sin6.sin6_addr, | |
78 | sizeof(struct in6_addr)); | |
d1912747 | 79 | #endif |
4f95dd78 DH |
80 | default: |
81 | BUG(); | |
82 | } | |
83 | } | |
84 | ||
0d6bf319 DH |
85 | static void rxrpc_client_conn_reap_timeout(struct timer_list *timer) |
86 | { | |
87 | struct rxrpc_local *local = | |
88 | container_of(timer, struct rxrpc_local, client_conn_reap_timer); | |
89 | ||
61e4a866 | 90 | if (!local->kill_all_client_conns && |
0d6bf319 DH |
91 | test_and_set_bit(RXRPC_CLIENT_CONN_REAP_TIMER, &local->client_conn_flags)) |
92 | rxrpc_wake_up_io_thread(local); | |
93 | } | |
94 | ||
4f95dd78 DH |
95 | /* |
96 | * Allocate a new local endpoint. | |
97 | */ | |
8a758d98 | 98 | static struct rxrpc_local *rxrpc_alloc_local(struct net *net, |
2baec2c3 | 99 | const struct sockaddr_rxrpc *srx) |
17926a79 DH |
100 | { |
101 | struct rxrpc_local *local; | |
f06cb291 | 102 | u32 tmp; |
17926a79 DH |
103 | |
104 | local = kzalloc(sizeof(struct rxrpc_local), GFP_KERNEL); | |
105 | if (local) { | |
a0575429 | 106 | refcount_set(&local->ref, 1); |
730c5fd4 | 107 | atomic_set(&local->active_users, 1); |
8a758d98 DH |
108 | local->net = net; |
109 | local->rxnet = rxrpc_net(net); | |
33912c26 | 110 | INIT_HLIST_NODE(&local->link); |
8fbcc833 | 111 | init_completion(&local->io_thread_ready); |
af094824 DH |
112 | #ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY |
113 | skb_queue_head_init(&local->rx_delay_queue); | |
114 | #endif | |
a275da62 | 115 | skb_queue_head_init(&local->rx_queue); |
f2cce89a | 116 | INIT_LIST_HEAD(&local->conn_attend_q); |
15f661dc | 117 | INIT_LIST_HEAD(&local->call_attend_q); |
0d6bf319 | 118 | |
245500d8 DH |
119 | local->client_bundles = RB_ROOT; |
120 | spin_lock_init(&local->client_bundles_lock); | |
0d6bf319 | 121 | local->kill_all_client_conns = false; |
0d6bf319 DH |
122 | INIT_LIST_HEAD(&local->idle_client_conns); |
123 | timer_setup(&local->client_conn_reap_timer, | |
124 | rxrpc_client_conn_reap_timeout, 0); | |
125 | ||
17926a79 DH |
126 | spin_lock_init(&local->lock); |
127 | rwlock_init(&local->services_lock); | |
17926a79 DH |
128 | local->debug_id = atomic_inc_return(&rxrpc_debug_id); |
129 | memcpy(&local->srx, srx, sizeof(*srx)); | |
28036f44 | 130 | local->srx.srx_service = 0; |
f06cb291 DH |
131 | idr_init(&local->conn_ids); |
132 | get_random_bytes(&tmp, sizeof(tmp)); | |
133 | tmp &= 0x3fffffff; | |
134 | if (tmp == 0) | |
135 | tmp = 1; | |
136 | idr_set_cursor(&local->conn_ids, tmp); | |
9d35d880 DH |
137 | INIT_LIST_HEAD(&local->new_client_calls); |
138 | spin_lock_init(&local->client_call_lock); | |
f06cb291 | 139 | |
0fde882f | 140 | trace_rxrpc_local(local->debug_id, rxrpc_local_new, 1, 1); |
17926a79 DH |
141 | } |
142 | ||
143 | _leave(" = %p", local); | |
144 | return local; | |
145 | } | |
146 | ||
147 | /* | |
148 | * create the local socket | |
4f95dd78 | 149 | * - must be called with rxrpc_local_mutex locked |
17926a79 | 150 | */ |
2baec2c3 | 151 | static int rxrpc_open_socket(struct rxrpc_local *local, struct net *net) |
17926a79 | 152 | { |
1a9b86c9 XL |
153 | struct udp_tunnel_sock_cfg tuncfg = {NULL}; |
154 | struct sockaddr_rxrpc *srx = &local->srx; | |
155 | struct udp_port_cfg udp_conf = {0}; | |
a275da62 | 156 | struct task_struct *io_thread; |
5271953c | 157 | struct sock *usk; |
fce93494 | 158 | int ret; |
17926a79 | 159 | |
75b54cb5 | 160 | _enter("%p{%d,%d}", |
1a9b86c9 XL |
161 | local, srx->transport_type, srx->transport.family); |
162 | ||
163 | udp_conf.family = srx->transport.family; | |
39cb9faa | 164 | udp_conf.use_udp_checksums = true; |
1a9b86c9 XL |
165 | if (udp_conf.family == AF_INET) { |
166 | udp_conf.local_ip = srx->transport.sin.sin_addr; | |
167 | udp_conf.local_udp_port = srx->transport.sin.sin_port; | |
295f830e | 168 | #if IS_ENABLED(CONFIG_AF_RXRPC_IPV6) |
1a9b86c9 XL |
169 | } else { |
170 | udp_conf.local_ip6 = srx->transport.sin6.sin6_addr; | |
171 | udp_conf.local_udp_port = srx->transport.sin6.sin6_port; | |
39cb9faa DH |
172 | udp_conf.use_udp6_tx_checksums = true; |
173 | udp_conf.use_udp6_rx_checksums = true; | |
295f830e | 174 | #endif |
1a9b86c9 XL |
175 | } |
176 | ret = udp_sock_create(net, &udp_conf, &local->socket); | |
17926a79 DH |
177 | if (ret < 0) { |
178 | _leave(" = %d [socket]", ret); | |
179 | return ret; | |
180 | } | |
181 | ||
1a9b86c9 | 182 | tuncfg.encap_type = UDP_ENCAP_RXRPC; |
446b3e14 | 183 | tuncfg.encap_rcv = rxrpc_encap_rcv; |
ac56a0b4 | 184 | tuncfg.encap_err_rcv = rxrpc_encap_err_rcv; |
1a9b86c9 XL |
185 | tuncfg.sk_user_data = local; |
186 | setup_udp_tunnel_sock(net, local->socket, &tuncfg); | |
187 | ||
2cfa2271 | 188 | /* set the socket up */ |
5271953c | 189 | usk = local->socket->sk; |
5271953c | 190 | usk->sk_error_report = rxrpc_error_report; |
2cfa2271 | 191 | |
1a9b86c9 | 192 | switch (srx->transport.family) { |
37a675e7 DH |
193 | case AF_INET6: |
194 | /* we want to receive ICMPv6 errors */ | |
1a9b86c9 | 195 | ip6_sock_set_recverr(usk); |
17926a79 | 196 | |
37a675e7 DH |
197 | /* Fall through and set IPv4 options too otherwise we don't get |
198 | * errors from IPv4 packets sent through the IPv6 socket. | |
199 | */ | |
df561f66 | 200 | fallthrough; |
37a675e7 | 201 | case AF_INET: |
f2aeed3a | 202 | /* we want to receive ICMP errors */ |
1a9b86c9 | 203 | ip_sock_set_recverr(usk); |
f2aeed3a DH |
204 | |
205 | /* we want to set the don't fragment bit */ | |
1a9b86c9 | 206 | ip_sock_set_mtu_discover(usk, IP_PMTUDISC_DO); |
b604dd98 DH |
207 | |
208 | /* We want receive timestamps. */ | |
1a9b86c9 | 209 | sock_enable_timestamps(usk); |
f2aeed3a DH |
210 | break; |
211 | ||
212 | default: | |
213 | BUG(); | |
17926a79 DH |
214 | } |
215 | ||
a275da62 DH |
216 | io_thread = kthread_run(rxrpc_io_thread, local, |
217 | "krxrpcio/%u", ntohs(udp_conf.local_udp_port)); | |
218 | if (IS_ERR(io_thread)) { | |
219 | ret = PTR_ERR(io_thread); | |
220 | goto error_sock; | |
221 | } | |
222 | ||
8fbcc833 | 223 | wait_for_completion(&local->io_thread_ready); |
a275da62 | 224 | local->io_thread = io_thread; |
17926a79 DH |
225 | _leave(" = 0"); |
226 | return 0; | |
a275da62 DH |
227 | |
228 | error_sock: | |
229 | kernel_sock_shutdown(local->socket, SHUT_RDWR); | |
230 | local->socket->sk->sk_user_data = NULL; | |
231 | sock_release(local->socket); | |
232 | local->socket = NULL; | |
233 | return ret; | |
17926a79 DH |
234 | } |
235 | ||
236 | /* | |
4f95dd78 | 237 | * Look up or create a new local endpoint using the specified local address. |
17926a79 | 238 | */ |
2baec2c3 DH |
239 | struct rxrpc_local *rxrpc_lookup_local(struct net *net, |
240 | const struct sockaddr_rxrpc *srx) | |
17926a79 DH |
241 | { |
242 | struct rxrpc_local *local; | |
2baec2c3 | 243 | struct rxrpc_net *rxnet = rxrpc_net(net); |
33912c26 | 244 | struct hlist_node *cursor; |
4f95dd78 | 245 | long diff; |
17926a79 DH |
246 | int ret; |
247 | ||
75b54cb5 DH |
248 | _enter("{%d,%d,%pISp}", |
249 | srx->transport_type, srx->transport.family, &srx->transport); | |
17926a79 | 250 | |
2baec2c3 | 251 | mutex_lock(&rxnet->local_mutex); |
17926a79 | 252 | |
33912c26 DH |
253 | hlist_for_each(cursor, &rxnet->local_endpoints) { |
254 | local = hlist_entry(cursor, struct rxrpc_local, link); | |
17926a79 | 255 | |
4f95dd78 | 256 | diff = rxrpc_local_cmp_key(local, srx); |
33912c26 | 257 | if (diff != 0) |
17926a79 | 258 | continue; |
4f95dd78 DH |
259 | |
260 | /* Services aren't allowed to share transport sockets, so | |
261 | * reject that here. It is possible that the object is dying - | |
262 | * but it may also still have the local transport address that | |
263 | * we want bound. | |
264 | */ | |
265 | if (srx->srx_service) { | |
266 | local = NULL; | |
267 | goto addr_in_use; | |
268 | } | |
17926a79 | 269 | |
33912c26 DH |
270 | /* Found a match. We want to replace a dying object. |
271 | * Attempting to bind the transport socket may still fail if | |
272 | * we're attempting to use a local address that the dying | |
273 | * object is still using. | |
4f95dd78 | 274 | */ |
0fde882f | 275 | if (!rxrpc_use_local(local, rxrpc_local_use_lookup)) |
4f95dd78 | 276 | break; |
17926a79 | 277 | |
4f95dd78 DH |
278 | goto found; |
279 | } | |
17926a79 | 280 | |
8a758d98 | 281 | local = rxrpc_alloc_local(net, srx); |
4f95dd78 DH |
282 | if (!local) |
283 | goto nomem; | |
17926a79 | 284 | |
2baec2c3 | 285 | ret = rxrpc_open_socket(local, net); |
4f95dd78 DH |
286 | if (ret < 0) |
287 | goto sock_error; | |
288 | ||
33912c26 DH |
289 | if (cursor) { |
290 | hlist_replace_rcu(cursor, &local->link); | |
291 | cursor->pprev = NULL; | |
292 | } else { | |
293 | hlist_add_head_rcu(&local->link, &rxnet->local_endpoints); | |
294 | } | |
17926a79 | 295 | |
4f95dd78 | 296 | found: |
2baec2c3 | 297 | mutex_unlock(&rxnet->local_mutex); |
4f95dd78 | 298 | _leave(" = %p", local); |
17926a79 DH |
299 | return local; |
300 | ||
4f95dd78 DH |
301 | nomem: |
302 | ret = -ENOMEM; | |
303 | sock_error: | |
2baec2c3 | 304 | mutex_unlock(&rxnet->local_mutex); |
032be5f1 ED |
305 | if (local) |
306 | call_rcu(&local->rcu, rxrpc_local_rcu); | |
4f95dd78 DH |
307 | _leave(" = %d", ret); |
308 | return ERR_PTR(ret); | |
17926a79 | 309 | |
4f95dd78 | 310 | addr_in_use: |
2baec2c3 | 311 | mutex_unlock(&rxnet->local_mutex); |
4f95dd78 DH |
312 | _leave(" = -EADDRINUSE"); |
313 | return ERR_PTR(-EADDRINUSE); | |
314 | } | |
17926a79 | 315 | |
09d2bf59 DH |
316 | /* |
317 | * Get a ref on a local endpoint. | |
318 | */ | |
0fde882f DH |
319 | struct rxrpc_local *rxrpc_get_local(struct rxrpc_local *local, |
320 | enum rxrpc_local_trace why) | |
09d2bf59 | 321 | { |
0fde882f | 322 | int r, u; |
09d2bf59 | 323 | |
0fde882f | 324 | u = atomic_read(&local->active_users); |
a0575429 | 325 | __refcount_inc(&local->ref, &r); |
0fde882f | 326 | trace_rxrpc_local(local->debug_id, why, r + 1, u); |
09d2bf59 DH |
327 | return local; |
328 | } | |
329 | ||
330 | /* | |
331 | * Get a ref on a local endpoint unless its usage has already reached 0. | |
332 | */ | |
0fde882f DH |
333 | struct rxrpc_local *rxrpc_get_local_maybe(struct rxrpc_local *local, |
334 | enum rxrpc_local_trace why) | |
09d2bf59 | 335 | { |
0fde882f | 336 | int r, u; |
09d2bf59 | 337 | |
0fde882f DH |
338 | if (local && __refcount_inc_not_zero(&local->ref, &r)) { |
339 | u = atomic_read(&local->active_users); | |
340 | trace_rxrpc_local(local->debug_id, why, r + 1, u); | |
341 | return local; | |
09d2bf59 | 342 | } |
0fde882f DH |
343 | |
344 | return NULL; | |
09d2bf59 DH |
345 | } |
346 | ||
09d2bf59 DH |
347 | /* |
348 | * Drop a ref on a local endpoint. | |
349 | */ | |
0fde882f | 350 | void rxrpc_put_local(struct rxrpc_local *local, enum rxrpc_local_trace why) |
09d2bf59 | 351 | { |
fac20b9e | 352 | unsigned int debug_id; |
a0575429 | 353 | bool dead; |
0fde882f | 354 | int r, u; |
09d2bf59 DH |
355 | |
356 | if (local) { | |
fac20b9e DH |
357 | debug_id = local->debug_id; |
358 | ||
0fde882f | 359 | u = atomic_read(&local->active_users); |
a0575429 | 360 | dead = __refcount_dec_and_test(&local->ref, &r); |
0fde882f | 361 | trace_rxrpc_local(debug_id, why, r, u); |
09d2bf59 | 362 | |
a0575429 | 363 | if (dead) |
730c5fd4 | 364 | call_rcu(&local->rcu, rxrpc_local_rcu); |
09d2bf59 DH |
365 | } |
366 | } | |
367 | ||
730c5fd4 DH |
368 | /* |
369 | * Start using a local endpoint. | |
370 | */ | |
0fde882f DH |
371 | struct rxrpc_local *rxrpc_use_local(struct rxrpc_local *local, |
372 | enum rxrpc_local_trace why) | |
730c5fd4 | 373 | { |
0fde882f | 374 | local = rxrpc_get_local_maybe(local, rxrpc_local_get_for_use); |
730c5fd4 DH |
375 | if (!local) |
376 | return NULL; | |
377 | ||
0fde882f DH |
378 | if (!__rxrpc_use_local(local, why)) { |
379 | rxrpc_put_local(local, rxrpc_local_put_for_use); | |
730c5fd4 DH |
380 | return NULL; |
381 | } | |
382 | ||
383 | return local; | |
384 | } | |
385 | ||
386 | /* | |
387 | * Cease using a local endpoint. Once the number of active users reaches 0, we | |
5e6ef4f1 | 388 | * start the closure of the transport in the I/O thread.. |
730c5fd4 | 389 | */ |
0fde882f | 390 | void rxrpc_unuse_local(struct rxrpc_local *local, enum rxrpc_local_trace why) |
730c5fd4 | 391 | { |
eaa02390 | 392 | unsigned int debug_id; |
a2cf3264 DH |
393 | int r, u; |
394 | ||
395 | if (local) { | |
eaa02390 | 396 | debug_id = local->debug_id; |
a2cf3264 DH |
397 | r = refcount_read(&local->ref); |
398 | u = atomic_dec_return(&local->active_users); | |
399 | trace_rxrpc_local(debug_id, why, r, u); | |
400 | if (u == 0) | |
401 | kthread_stop(local->io_thread); | |
402 | } | |
730c5fd4 DH |
403 | } |
404 | ||
17926a79 | 405 | /* |
4f95dd78 DH |
406 | * Destroy a local endpoint's socket and then hand the record to RCU to dispose |
407 | * of. | |
408 | * | |
409 | * Closing the socket cannot be done from bottom half context or RCU callback | |
410 | * context because it might sleep. | |
17926a79 | 411 | */ |
a275da62 | 412 | void rxrpc_destroy_local(struct rxrpc_local *local) |
17926a79 | 413 | { |
4f95dd78 | 414 | struct socket *socket = local->socket; |
2baec2c3 | 415 | struct rxrpc_net *rxnet = local->rxnet; |
17926a79 | 416 | |
4f95dd78 | 417 | _enter("%d", local->debug_id); |
17926a79 | 418 | |
d12040b6 DH |
419 | local->dead = true; |
420 | ||
2baec2c3 | 421 | mutex_lock(&rxnet->local_mutex); |
33912c26 | 422 | hlist_del_init_rcu(&local->link); |
2baec2c3 | 423 | mutex_unlock(&rxnet->local_mutex); |
4f95dd78 | 424 | |
d12040b6 DH |
425 | rxrpc_clean_up_local_conns(local); |
426 | rxrpc_service_connection_reaper(&rxnet->service_conn_reaper); | |
1e9e5c95 | 427 | ASSERT(!local->service); |
4f95dd78 DH |
428 | |
429 | if (socket) { | |
430 | local->socket = NULL; | |
431 | kernel_sock_shutdown(socket, SHUT_RDWR); | |
432 | socket->sk->sk_user_data = NULL; | |
433 | sock_release(socket); | |
434 | } | |
435 | ||
436 | /* At this point, there should be no more packets coming in to the | |
437 | * local endpoint. | |
438 | */ | |
af094824 DH |
439 | #ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY |
440 | rxrpc_purge_queue(&local->rx_delay_queue); | |
441 | #endif | |
a275da62 | 442 | rxrpc_purge_queue(&local->rx_queue); |
9d35d880 | 443 | rxrpc_purge_client_connections(local); |
17926a79 DH |
444 | } |
445 | ||
4f95dd78 DH |
446 | /* |
447 | * Destroy a local endpoint after the RCU grace period expires. | |
448 | */ | |
449 | static void rxrpc_local_rcu(struct rcu_head *rcu) | |
450 | { | |
451 | struct rxrpc_local *local = container_of(rcu, struct rxrpc_local, rcu); | |
17926a79 | 452 | |
0fde882f | 453 | rxrpc_see_local(local, rxrpc_local_free); |
17926a79 | 454 | kfree(local); |
17926a79 DH |
455 | } |
456 | ||
457 | /* | |
4f95dd78 | 458 | * Verify the local endpoint list is empty by this point. |
17926a79 | 459 | */ |
2baec2c3 | 460 | void rxrpc_destroy_all_locals(struct rxrpc_net *rxnet) |
17926a79 | 461 | { |
4f95dd78 | 462 | struct rxrpc_local *local; |
17926a79 DH |
463 | |
464 | _enter(""); | |
465 | ||
dee46364 | 466 | flush_workqueue(rxrpc_workqueue); |
17926a79 | 467 | |
33912c26 | 468 | if (!hlist_empty(&rxnet->local_endpoints)) { |
2baec2c3 | 469 | mutex_lock(&rxnet->local_mutex); |
33912c26 | 470 | hlist_for_each_entry(local, &rxnet->local_endpoints, link) { |
dee46364 | 471 | pr_err("AF_RXRPC: Leaked local %p {%d}\n", |
a0575429 | 472 | local, refcount_read(&local->ref)); |
dee46364 | 473 | } |
2baec2c3 | 474 | mutex_unlock(&rxnet->local_mutex); |
dee46364 | 475 | BUG(); |
17926a79 | 476 | } |
17926a79 | 477 | } |