]> git.proxmox.com Git - ceph.git/blame - ceph/src/common/pick_address.cc
buildsys: switch source download to quincy
[ceph.git] / ceph / src / common / pick_address.cc
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3/*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2004-2012 Inktank
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14
15#include "common/pick_address.h"
9f95a23c
TL
16
17#include <netdb.h>
522d829b 18#include <netinet/in.h>
9f95a23c
TL
19#include <string>
20#include <string.h>
21#include <vector>
22
522d829b 23#include <boost/algorithm/string/predicate.hpp>
9f95a23c
TL
24#include <fmt/format.h>
25
7c673cae
FG
26#include "include/ipaddr.h"
27#include "include/str_list.h"
11fdf7f2
TL
28#include "common/ceph_context.h"
29#ifndef WITH_SEASTAR
30#include "common/config.h"
31#include "common/config_obs.h"
32#endif
7c673cae
FG
33#include "common/debug.h"
34#include "common/errno.h"
11fdf7f2 35#include "common/numa.h"
7c673cae 36
522d829b
TL
37#ifndef HAVE_IN_ADDR_T
38typedef uint32_t in_addr_t;
39#endif
40
41#ifndef IN_LOOPBACKNET
42#define IN_LOOPBACKNET 127
43#endif
44
7c673cae
FG
45#define dout_subsys ceph_subsys_
46
f67539c2
TL
47using std::string;
48using std::vector;
49
522d829b
TL
50namespace {
51
52bool matches_with_name(const ifaddrs& ifa, const std::string& if_name)
53{
54 return if_name.compare(ifa.ifa_name) == 0;
55}
56
57static int is_loopback_addr(sockaddr* addr)
58{
59 if (addr->sa_family == AF_INET) {
60 const sockaddr_in* sin = (struct sockaddr_in *)(addr);
61 const in_addr_t net = ntohl(sin->sin_addr.s_addr) >> IN_CLASSA_NSHIFT;
62 return net == IN_LOOPBACKNET ? 1 : 0;
63 } else if (addr->sa_family == AF_INET6) {
64 sockaddr_in6* sin6 = (struct sockaddr_in6 *)(addr);
65 return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) ? 1 : 0;
66 } else {
67 return -1;
68 }
69}
70
71static int grade_addr(const ifaddrs& ifa)
72{
73 if (ifa.ifa_addr == nullptr) {
74 return -1;
75 }
76 int score = 0;
77 if (ifa.ifa_flags & IFF_UP) {
78 score += 4;
79 }
80 switch (is_loopback_addr(ifa.ifa_addr)) {
81 case 0:
82 // prefer non-loopback addresses
83 score += 2;
84 break;
85 case 1:
86 score += 0;
87 break;
88 default:
89 score = -1;
90 break;
91 }
92 return score;
93}
94
95bool matches_with_net(const ifaddrs& ifa,
96 const sockaddr* net,
97 unsigned int prefix_len,
98 unsigned ipv)
99{
100 switch (net->sa_family) {
101 case AF_INET:
102 if (ipv & CEPH_PICK_ADDRESS_IPV4) {
103 return matches_ipv4_in_subnet(ifa, (struct sockaddr_in*)net, prefix_len);
104 }
105 break;
106 case AF_INET6:
107 if (ipv & CEPH_PICK_ADDRESS_IPV6) {
108 return matches_ipv6_in_subnet(ifa, (struct sockaddr_in6*)net, prefix_len);
109 }
110 break;
111 }
112 return false;
113}
114
115bool matches_with_net(CephContext *cct,
116 const ifaddrs& ifa,
117 const std::string& s,
118 unsigned ipv)
119{
120 struct sockaddr_storage net;
121 unsigned int prefix_len;
122 if (!parse_network(s.c_str(), &net, &prefix_len)) {
123 lderr(cct) << "unable to parse network: " << s << dendl;
124 exit(1);
125 }
126 return matches_with_net(ifa, (sockaddr*)&net, prefix_len, ipv);
127}
128
129int grade_with_numa_node(const ifaddrs& ifa, int numa_node)
130{
131#if defined(WITH_SEASTAR) || defined(_WIN32)
132 return 0;
133#else
134 if (numa_node < 0) {
135 return 0;
136 }
137 int if_node = -1;
138 int r = get_iface_numa_node(ifa.ifa_name, &if_node);
139 if (r < 0) {
140 return 0;
141 }
142 return if_node == numa_node ? 1 : 0;
143#endif
144}
145}
146
3efd9988
FG
147const struct sockaddr *find_ip_in_subnet_list(
148 CephContext *cct,
149 const struct ifaddrs *ifa,
11fdf7f2 150 unsigned ipv,
3efd9988 151 const std::string &networks,
11fdf7f2
TL
152 const std::string &interfaces,
153 int numa_node)
7c673cae 154{
522d829b
TL
155 const auto ifs = get_str_list(interfaces);
156 const auto nets = get_str_list(networks);
157 if (!ifs.empty() && nets.empty()) {
3efd9988
FG
158 lderr(cct) << "interface names specified but not network names" << dendl;
159 exit(1);
3efd9988 160 }
7c673cae 161
522d829b
TL
162 int best_score = 0;
163 const sockaddr* best_addr = nullptr;
164 for (const auto* addr = ifa; addr != nullptr; addr = addr->ifa_next) {
165 if (!ifs.empty() &&
166 std::none_of(std::begin(ifs), std::end(ifs),
167 [&](const auto& if_name) {
168 return matches_with_name(*addr, if_name);
169 })) {
170 continue;
3efd9988 171 }
522d829b
TL
172 if (!nets.empty() &&
173 std::none_of(std::begin(nets), std::end(nets),
174 [&](const auto& net) {
175 return matches_with_net(cct, *addr, net, ipv);
176 })) {
177 continue;
11fdf7f2 178 }
522d829b
TL
179 int score = grade_addr(*addr);
180 if (score < 0) {
181 continue;
3efd9988 182 }
522d829b
TL
183 score += grade_with_numa_node(*addr, numa_node);
184 if (score > best_score) {
185 best_score = score;
186 best_addr = addr->ifa_addr;
7c673cae 187 }
3efd9988 188 }
522d829b 189 return best_addr;
7c673cae
FG
190}
191
11fdf7f2 192#ifndef WITH_SEASTAR
7c673cae
FG
193// observe this change
194struct Observer : public md_config_obs_t {
195 const char *keys[2];
196 explicit Observer(const char *c) {
197 keys[0] = c;
198 keys[1] = NULL;
199 }
200
201 const char** get_tracked_conf_keys() const override {
202 return (const char **)keys;
203 }
11fdf7f2 204 void handle_conf_change(const ConfigProxy& conf,
7c673cae
FG
205 const std::set <std::string> &changed) override {
206 // do nothing.
207 }
208};
209
210static void fill_in_one_address(CephContext *cct,
211 const struct ifaddrs *ifa,
522d829b
TL
212 const string &networks,
213 const string &interfaces,
11fdf7f2
TL
214 const char *conf_var,
215 int numa_node = -1)
7c673cae 216{
11fdf7f2
TL
217 const struct sockaddr *found = find_ip_in_subnet_list(
218 cct,
219 ifa,
220 CEPH_PICK_ADDRESS_IPV4|CEPH_PICK_ADDRESS_IPV6,
221 networks,
222 interfaces,
223 numa_node);
7c673cae 224 if (!found) {
3efd9988
FG
225 lderr(cct) << "unable to find any IP address in networks '" << networks
226 << "' interfaces '" << interfaces << "'" << dendl;
7c673cae
FG
227 exit(1);
228 }
229
230 char buf[INET6_ADDRSTRLEN];
231 int err;
232
233 err = getnameinfo(found,
234 (found->sa_family == AF_INET)
235 ? sizeof(struct sockaddr_in)
236 : sizeof(struct sockaddr_in6),
237
238 buf, sizeof(buf),
11fdf7f2 239 nullptr, 0,
7c673cae
FG
240 NI_NUMERICHOST);
241 if (err != 0) {
242 lderr(cct) << "unable to convert chosen address to string: " << gai_strerror(err) << dendl;
243 exit(1);
244 }
245
246 Observer obs(conf_var);
247
11fdf7f2 248 cct->_conf.add_observer(&obs);
7c673cae 249
11fdf7f2
TL
250 cct->_conf.set_val_or_die(conf_var, buf);
251 cct->_conf.apply_changes(nullptr);
7c673cae 252
11fdf7f2 253 cct->_conf.remove_observer(&obs);
7c673cae
FG
254}
255
256void pick_addresses(CephContext *cct, int needs)
257{
11fdf7f2
TL
258 auto public_addr = cct->_conf.get_val<entity_addr_t>("public_addr");
259 auto public_network = cct->_conf.get_val<std::string>("public_network");
260 auto public_network_interface =
261 cct->_conf.get_val<std::string>("public_network_interface");
262 auto cluster_addr = cct->_conf.get_val<entity_addr_t>("cluster_addr");
263 auto cluster_network = cct->_conf.get_val<std::string>("cluster_network");
264 auto cluster_network_interface =
265 cct->_conf.get_val<std::string>("cluster_network_interface");
266
522d829b
TL
267 struct ifaddrs *ifa;
268 int r = getifaddrs(&ifa);
11fdf7f2 269 if (r < 0) {
7c673cae
FG
270 string err = cpp_strerror(errno);
271 lderr(cct) << "unable to fetch interfaces and addresses: " << err << dendl;
272 exit(1);
273 }
522d829b 274 auto free_ifa = make_scope_guard([ifa] { freeifaddrs(ifa); });
11fdf7f2
TL
275 if ((needs & CEPH_PICK_ADDRESS_PUBLIC) &&
276 public_addr.is_blank_ip() && !public_network.empty()) {
277 fill_in_one_address(cct, ifa, public_network, public_network_interface,
522d829b 278 "public_addr");
7c673cae
FG
279 }
280
11fdf7f2
TL
281 if ((needs & CEPH_PICK_ADDRESS_CLUSTER) && cluster_addr.is_blank_ip()) {
282 if (!cluster_network.empty()) {
283 fill_in_one_address(cct, ifa, cluster_network, cluster_network_interface,
522d829b 284 "cluster_addr");
7c673cae 285 } else {
11fdf7f2 286 if (!public_network.empty()) {
7c673cae
FG
287 lderr(cct) << "Public network was set, but cluster network was not set " << dendl;
288 lderr(cct) << " Using public network also for cluster network" << dendl;
11fdf7f2 289 fill_in_one_address(cct, ifa, public_network, public_network_interface,
522d829b 290 "cluster_addr");
7c673cae
FG
291 }
292 }
293 }
7c673cae 294}
11fdf7f2
TL
295#endif // !WITH_SEASTAR
296
297static int fill_in_one_address(
298 CephContext *cct,
299 const struct ifaddrs *ifa,
300 unsigned ipv,
522d829b
TL
301 const string &networks,
302 const string &interfaces,
11fdf7f2
TL
303 entity_addrvec_t *addrs,
304 int numa_node = -1)
305{
522d829b
TL
306 const struct sockaddr *found = find_ip_in_subnet_list(cct, ifa, ipv,
307 networks,
308 interfaces,
309 numa_node);
11fdf7f2
TL
310 if (!found) {
311 std::string ip_type = "";
312 if ((ipv & CEPH_PICK_ADDRESS_IPV4) && (ipv & CEPH_PICK_ADDRESS_IPV6)) {
313 ip_type = "IPv4 or IPv6";
314 } else if (ipv & CEPH_PICK_ADDRESS_IPV4) {
315 ip_type = "IPv4";
316 } else {
317 ip_type = "IPv6";
318 }
319 lderr(cct) << "unable to find any " << ip_type << " address in networks '"
320 << networks << "' interfaces '" << interfaces << "'" << dendl;
321 return -1;
322 }
323
324 char buf[INET6_ADDRSTRLEN];
325 int err;
7c673cae 326
11fdf7f2
TL
327 err = getnameinfo(found,
328 (found->sa_family == AF_INET)
329 ? sizeof(struct sockaddr_in)
330 : sizeof(struct sockaddr_in6),
331
332 buf, sizeof(buf),
333 nullptr, 0,
334 NI_NUMERICHOST);
335 if (err != 0) {
336 lderr(cct) << "unable to convert chosen address to string: " << gai_strerror(err) << dendl;
337 return -1;
338 }
339
340 entity_addr_t addr;
341 const char *end = 0;
342 bool r = addr.parse(buf, &end);
343 if (!r) {
344 return -1;
345 }
346 addrs->v.push_back(addr);
347 return 0;
348}
349
350int pick_addresses(
351 CephContext *cct,
352 unsigned flags,
353 struct ifaddrs *ifa,
354 entity_addrvec_t *addrs,
355 int preferred_numa_node)
356{
357 addrs->v.clear();
358
359 unsigned addrt = (flags & (CEPH_PICK_ADDRESS_PUBLIC |
360 CEPH_PICK_ADDRESS_CLUSTER));
361 if (addrt == 0 ||
362 addrt == (CEPH_PICK_ADDRESS_PUBLIC |
363 CEPH_PICK_ADDRESS_CLUSTER)) {
364 return -EINVAL;
365 }
366 unsigned msgrv = flags & (CEPH_PICK_ADDRESS_MSGR1 |
367 CEPH_PICK_ADDRESS_MSGR2);
368 if (msgrv == 0) {
369 if (cct->_conf.get_val<bool>("ms_bind_msgr1")) {
370 msgrv |= CEPH_PICK_ADDRESS_MSGR1;
371 }
372 if (cct->_conf.get_val<bool>("ms_bind_msgr2")) {
373 msgrv |= CEPH_PICK_ADDRESS_MSGR2;
374 }
375 if (msgrv == 0) {
376 return -EINVAL;
377 }
378 }
379 unsigned ipv = flags & (CEPH_PICK_ADDRESS_IPV4 |
380 CEPH_PICK_ADDRESS_IPV6);
381 if (ipv == 0) {
382 if (cct->_conf.get_val<bool>("ms_bind_ipv4")) {
383 ipv |= CEPH_PICK_ADDRESS_IPV4;
384 }
385 if (cct->_conf.get_val<bool>("ms_bind_ipv6")) {
386 ipv |= CEPH_PICK_ADDRESS_IPV6;
387 }
388 if (ipv == 0) {
389 return -EINVAL;
390 }
391 if (cct->_conf.get_val<bool>("ms_bind_prefer_ipv4")) {
392 flags |= CEPH_PICK_ADDRESS_PREFER_IPV4;
393 } else {
394 flags &= ~CEPH_PICK_ADDRESS_PREFER_IPV4;
395 }
396 }
397
398 entity_addr_t addr;
399 string networks;
400 string interfaces;
401 if (addrt & CEPH_PICK_ADDRESS_PUBLIC) {
402 addr = cct->_conf.get_val<entity_addr_t>("public_addr");
403 networks = cct->_conf.get_val<std::string>("public_network");
404 interfaces =
405 cct->_conf.get_val<std::string>("public_network_interface");
406 } else {
407 addr = cct->_conf.get_val<entity_addr_t>("cluster_addr");
408 networks = cct->_conf.get_val<std::string>("cluster_network");
409 interfaces =
410 cct->_conf.get_val<std::string>("cluster_network_interface");
411 if (networks.empty()) {
412 lderr(cct) << "Falling back to public interface" << dendl;
413 // fall back to public_ network and interface if cluster is not set
414 networks = cct->_conf.get_val<std::string>("public_network");
415 interfaces =
416 cct->_conf.get_val<std::string>("public_network_interface");
417 }
418 }
419 if (addr.is_blank_ip() &&
420 !networks.empty()) {
421 int ipv4_r = !(ipv & CEPH_PICK_ADDRESS_IPV4) ? 0 : -1;
422 int ipv6_r = !(ipv & CEPH_PICK_ADDRESS_IPV6) ? 0 : -1;
522d829b
TL
423 // note: pass in ipv to filter the matching addresses
424 if ((ipv & CEPH_PICK_ADDRESS_IPV4) &&
425 (flags & CEPH_PICK_ADDRESS_PREFER_IPV4)) {
426 ipv4_r = fill_in_one_address(cct, ifa, CEPH_PICK_ADDRESS_IPV4,
427 networks, interfaces,
428 addrs,
429 preferred_numa_node);
430 }
431 if (ipv & CEPH_PICK_ADDRESS_IPV6) {
432 ipv6_r = fill_in_one_address(cct, ifa, CEPH_PICK_ADDRESS_IPV6,
433 networks, interfaces,
434 addrs,
435 preferred_numa_node);
436 }
437 if ((ipv & CEPH_PICK_ADDRESS_IPV4) &&
438 !(flags & CEPH_PICK_ADDRESS_PREFER_IPV4)) {
439 ipv4_r = fill_in_one_address(cct, ifa, CEPH_PICK_ADDRESS_IPV4,
440 networks, interfaces,
441 addrs,
442 preferred_numa_node);
443 }
444 if (ipv4_r < 0 || ipv6_r < 0) {
445 return -1;
11fdf7f2
TL
446 }
447 }
448
449 // note: we may have a blank addr here
450
451 // ipv4 and/or ipv6?
452 if (addrs->v.empty()) {
11fdf7f2
TL
453 addr.set_type(entity_addr_t::TYPE_MSGR2);
454 if ((ipv & CEPH_PICK_ADDRESS_IPV4) &&
455 (flags & CEPH_PICK_ADDRESS_PREFER_IPV4)) {
456 addr.set_family(AF_INET);
457 addrs->v.push_back(addr);
458 }
459 if (ipv & CEPH_PICK_ADDRESS_IPV6) {
460 addr.set_family(AF_INET6);
461 addrs->v.push_back(addr);
462 }
463 if ((ipv & CEPH_PICK_ADDRESS_IPV4) &&
464 !(flags & CEPH_PICK_ADDRESS_PREFER_IPV4)) {
465 addr.set_family(AF_INET);
466 addrs->v.push_back(addr);
467 }
468 }
469
470 // msgr2 or legacy or both?
471 if (msgrv == (CEPH_PICK_ADDRESS_MSGR1 | CEPH_PICK_ADDRESS_MSGR2)) {
472 vector<entity_addr_t> v;
473 v.swap(addrs->v);
474 for (auto a : v) {
475 a.set_type(entity_addr_t::TYPE_MSGR2);
476 if (flags & CEPH_PICK_ADDRESS_DEFAULT_MON_PORTS) {
477 a.set_port(CEPH_MON_PORT_IANA);
478 }
479 addrs->v.push_back(a);
480 a.set_type(entity_addr_t::TYPE_LEGACY);
481 if (flags & CEPH_PICK_ADDRESS_DEFAULT_MON_PORTS) {
482 a.set_port(CEPH_MON_PORT_LEGACY);
483 }
484 addrs->v.push_back(a);
485 }
486 } else if (msgrv == CEPH_PICK_ADDRESS_MSGR1) {
487 for (auto& a : addrs->v) {
488 a.set_type(entity_addr_t::TYPE_LEGACY);
489 }
490 } else {
491 for (auto& a : addrs->v) {
492 a.set_type(entity_addr_t::TYPE_MSGR2);
493 }
494 }
495
496 return 0;
497}
498
499int pick_addresses(
500 CephContext *cct,
501 unsigned flags,
502 entity_addrvec_t *addrs,
503 int preferred_numa_node)
504{
505 struct ifaddrs *ifa;
506 int r = getifaddrs(&ifa);
507 if (r < 0) {
508 r = -errno;
509 string err = cpp_strerror(r);
510 lderr(cct) << "unable to fetch interfaces and addresses: "
511 << cpp_strerror(r) << dendl;
512 return r;
513 }
514 r = pick_addresses(cct, flags, ifa, addrs, preferred_numa_node);
515 freeifaddrs(ifa);
516 return r;
517}
b5b8bbf5
FG
518
519std::string pick_iface(CephContext *cct, const struct sockaddr_storage &network)
520{
521 struct ifaddrs *ifa;
522 int r = getifaddrs(&ifa);
523 if (r < 0) {
524 string err = cpp_strerror(errno);
525 lderr(cct) << "unable to fetch interfaces and addresses: " << err << dendl;
526 return {};
527 }
522d829b 528 auto free_ifa = make_scope_guard([ifa] { freeifaddrs(ifa); });
f67539c2 529 const unsigned int prefix_len = std::max(sizeof(in_addr::s_addr), sizeof(in6_addr::s6_addr)) * CHAR_BIT;
522d829b
TL
530 for (auto addr = ifa; addr != nullptr; addr = addr->ifa_next) {
531 if (matches_with_net(*ifa, (const struct sockaddr *) &network, prefix_len,
532 CEPH_PICK_ADDRESS_IPV4 | CEPH_PICK_ADDRESS_IPV6)) {
533 return addr->ifa_name;
534 }
b5b8bbf5 535 }
522d829b 536 return {};
b5b8bbf5
FG
537}
538
539
f67539c2 540bool have_local_addr(CephContext *cct, const std::list<entity_addr_t>& ls, entity_addr_t *match)
7c673cae
FG
541{
542 struct ifaddrs *ifa;
543 int r = getifaddrs(&ifa);
544 if (r < 0) {
545 lderr(cct) << "unable to fetch interfaces and addresses: " << cpp_strerror(errno) << dendl;
546 exit(1);
547 }
522d829b 548 auto free_ifa = make_scope_guard([ifa] { freeifaddrs(ifa); });
7c673cae 549
11fdf7f2 550 for (struct ifaddrs *addrs = ifa; addrs != nullptr; addrs = addrs->ifa_next) {
7c673cae
FG
551 if (addrs->ifa_addr) {
552 entity_addr_t a;
553 a.set_sockaddr(addrs->ifa_addr);
11fdf7f2
TL
554 for (auto& p : ls) {
555 if (a.is_same_host(p)) {
556 *match = p;
522d829b 557 return true;
7c673cae
FG
558 }
559 }
560 }
561 }
522d829b 562 return false;
7c673cae 563}
11fdf7f2
TL
564
565int get_iface_numa_node(
566 const std::string& iface,
567 int *node)
568{
9f95a23c
TL
569 enum class iface_t {
570 PHY_PORT,
571 BOND_PORT
572 } ifatype = iface_t::PHY_PORT;
f67539c2 573 std::string_view ifa{iface};
9f95a23c
TL
574 if (auto pos = ifa.find(":"); pos != ifa.npos) {
575 ifa.remove_suffix(ifa.size() - pos);
92f5a8d4 576 }
9f95a23c 577 string fn = fmt::format("/sys/class/net/{}/device/numa_node", ifa);
92f5a8d4
TL
578 int fd = ::open(fn.c_str(), O_RDONLY);
579 if (fd < 0) {
9f95a23c 580 fn = fmt::format("/sys/class/net/{}/bonding/slaves", ifa);
92f5a8d4
TL
581 fd = ::open(fn.c_str(), O_RDONLY);
582 if (fd < 0) {
583 return -errno;
584 }
9f95a23c 585 ifatype = iface_t::BOND_PORT;
92f5a8d4 586 }
11fdf7f2
TL
587
588 int r = 0;
589 char buf[1024];
590 char *endptr = 0;
11fdf7f2
TL
591 r = safe_read(fd, &buf, sizeof(buf));
592 if (r < 0) {
593 goto out;
594 }
595 buf[r] = 0;
596 while (r > 0 && ::isspace(buf[--r])) {
597 buf[r] = 0;
598 }
92f5a8d4
TL
599
600 switch (ifatype) {
9f95a23c 601 case iface_t::PHY_PORT:
92f5a8d4
TL
602 *node = strtoll(buf, &endptr, 10);
603 if (endptr != buf + strlen(buf)) {
604 r = -EINVAL;
605 goto out;
606 }
607 r = 0;
608 break;
9f95a23c
TL
609 case iface_t::BOND_PORT:
610 int bond_node = -1;
92f5a8d4 611 std::vector<std::string> sv;
9f95a23c
TL
612 std::string ifacestr = buf;
613 get_str_vec(ifacestr, " ", sv);
92f5a8d4
TL
614 for (auto& iter : sv) {
615 int bn = -1;
616 r = get_iface_numa_node(iter, &bn);
617 if (r >= 0) {
618 if (bond_node == -1 || bn == bond_node) {
619 bond_node = bn;
620 } else {
621 *node = -2;
622 goto out;
623 }
624 } else {
625 goto out;
626 }
627 }
628 *node = bond_node;
629 break;
11fdf7f2 630 }
92f5a8d4
TL
631
632 out:
11fdf7f2
TL
633 ::close(fd);
634 return r;
635}
92f5a8d4 636