OSDN Git Service

Don't enable the SCO server socket when not necessary
[android-x86/external-bluetooth-bluez.git] / common / glib-helper.c
1 /*
2  *
3  *  BlueZ - Bluetooth protocol stack for Linux
4  *
5  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
6  *
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <sys/ioctl.h>
33 #include <sys/socket.h>
34
35 #include <bluetooth/bluetooth.h>
36 #include <bluetooth/hci.h>
37 #include <bluetooth/hci_lib.h>
38 #include <bluetooth/rfcomm.h>
39 #include <bluetooth/l2cap.h>
40 #include <bluetooth/sco.h>
41 #include <bluetooth/sdp.h>
42 #include <bluetooth/sdp_lib.h>
43
44 #include <glib.h>
45
46 #include "glib-helper.h"
47
48 /* Number of seconds to keep a sdp_session_t in the cache */
49 #define CACHE_TIMEOUT 2
50
51 struct cached_sdp_session {
52         bdaddr_t src;
53         bdaddr_t dst;
54         sdp_session_t *session;
55         guint timer;
56 };
57
58 static GSList *cached_sdp_sessions = NULL;
59
60 struct hci_cmd_data {
61         bt_hci_result_t         cb;
62         uint16_t                handle;
63         uint16_t                ocf;
64         gpointer                caller_data;
65 };
66
67 static gboolean cached_session_expired(gpointer user_data)
68 {
69         struct cached_sdp_session *cached = user_data;
70
71         cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached);
72
73         sdp_close(cached->session);
74
75         g_free(cached);
76
77         return FALSE;
78 }
79
80 static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst)
81 {
82         GSList *l;
83
84         for (l = cached_sdp_sessions; l != NULL; l = l->next) {
85                 struct cached_sdp_session *c = l->data;
86                 sdp_session_t *session;
87
88                 if (bacmp(&c->src, src) || bacmp(&c->dst, dst))
89                         continue;
90
91                 g_source_remove(c->timer);
92
93                 session = c->session;
94
95                 cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c);
96                 g_free(c);
97
98                 return session;
99         }
100
101         return sdp_connect(src, dst, SDP_NON_BLOCKING);
102 }
103
104 static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst,
105                                 sdp_session_t *session)
106 {
107         struct cached_sdp_session *cached;
108
109         cached = g_new0(struct cached_sdp_session, 1);
110
111         bacpy(&cached->src, src);
112         bacpy(&cached->dst, dst);
113
114         cached->session = session;
115
116         cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached);
117
118         cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT,
119                                                 cached_session_expired,
120                                                 cached);
121 }
122
123 int set_nonblocking(int fd)
124 {
125         long arg;
126
127         arg = fcntl(fd, F_GETFL);
128         if (arg < 0)
129                 return -errno;
130
131         /* Return if already nonblocking */
132         if (arg & O_NONBLOCK)
133                 return 0;
134
135         arg |= O_NONBLOCK;
136         if (fcntl(fd, F_SETFL, arg) < 0)
137                 return -errno;
138
139         return 0;
140 }
141
142 struct search_context {
143         bdaddr_t                src;
144         bdaddr_t                dst;
145         sdp_session_t           *session;
146         bt_callback_t           cb;
147         bt_destroy_t            destroy;
148         gpointer                user_data;
149         uuid_t                  uuid;
150         guint                   io_id;
151 };
152
153 static GSList *context_list = NULL;
154
155 static void search_context_cleanup(struct search_context *ctxt)
156 {
157         context_list = g_slist_remove(context_list, ctxt);
158
159         if (ctxt->destroy)
160                 ctxt->destroy(ctxt->user_data);
161
162         g_free(ctxt);
163 }
164
165 static void search_completed_cb(uint8_t type, uint16_t status,
166                         uint8_t *rsp, size_t size, void *user_data)
167 {
168         struct search_context *ctxt = user_data;
169         sdp_list_t *recs = NULL;
170         int scanned, seqlen = 0, bytesleft = size;
171         uint8_t dataType;
172         int err = 0;
173
174         if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
175                 err = -EPROTO;
176                 goto done;
177         }
178
179         scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
180         if (!scanned || !seqlen)
181                 goto done;
182
183         rsp += scanned;
184         bytesleft -= scanned;
185         do {
186                 sdp_record_t *rec;
187                 int recsize;
188
189                 recsize = 0;
190                 rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
191                 if (!rec)
192                         break;
193
194                 if (!recsize) {
195                         sdp_record_free(rec);
196                         break;
197                 }
198
199                 scanned += recsize;
200                 rsp += recsize;
201                 bytesleft -= recsize;
202
203                 recs = sdp_list_append(recs, rec);
204         } while (scanned < (ssize_t) size && bytesleft > 0);
205
206 done:
207         cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session);
208
209         if (ctxt->cb)
210                 ctxt->cb(recs, err, ctxt->user_data);
211
212         if (recs)
213                 sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
214
215         search_context_cleanup(ctxt);
216 }
217
218 static gboolean search_process_cb(GIOChannel *chan,
219                         GIOCondition cond, void *user_data)
220 {
221         struct search_context *ctxt = user_data;
222         int err = 0;
223
224         if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
225                 err = EIO;
226                 goto failed;
227         }
228
229         if (sdp_process(ctxt->session) < 0)
230                 goto failed;
231
232         return TRUE;
233
234 failed:
235         if (err) {
236                 sdp_close(ctxt->session);
237                 ctxt->session = NULL;
238
239                 if (ctxt->cb)
240                         ctxt->cb(NULL, err, ctxt->user_data);
241
242                 search_context_cleanup(ctxt);
243         }
244
245         return FALSE;
246 }
247
248 static gboolean connect_watch(GIOChannel *chan, GIOCondition cond, gpointer user_data)
249 {
250         struct search_context *ctxt = user_data;
251         sdp_list_t *search, *attrids;
252         uint32_t range = 0x0000ffff;
253         socklen_t len;
254         int sk, err = 0;
255
256         sk = g_io_channel_unix_get_fd(chan);
257         ctxt->io_id = 0;
258
259         len = sizeof(err);
260         if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
261                 err = errno;
262                 goto failed;
263         }
264
265         if (err != 0)
266                 goto failed;
267
268         if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) {
269                 err = EIO;
270                 goto failed;
271         }
272
273         search = sdp_list_append(NULL, &ctxt->uuid);
274         attrids = sdp_list_append(NULL, &range);
275         if (sdp_service_search_attr_async(ctxt->session,
276                                 search, SDP_ATTR_REQ_RANGE, attrids) < 0) {
277                 sdp_list_free(attrids, NULL);
278                 sdp_list_free(search, NULL);
279                 err = EIO;
280                 goto failed;
281         }
282
283         sdp_list_free(attrids, NULL);
284         sdp_list_free(search, NULL);
285
286         /* Set callback responsible for update the internal SDP transaction */
287         ctxt->io_id = g_io_add_watch(chan,
288                                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
289                                 search_process_cb, ctxt);
290         return FALSE;
291
292 failed:
293         sdp_close(ctxt->session);
294         ctxt->session = NULL;
295
296         if (ctxt->cb)
297                 ctxt->cb(NULL, -err, ctxt->user_data);
298
299         search_context_cleanup(ctxt);
300
301         return FALSE;
302 }
303
304 static int create_search_context(struct search_context **ctxt,
305                                 const bdaddr_t *src, const bdaddr_t *dst,
306                                 uuid_t *uuid)
307 {
308         sdp_session_t *s;
309         GIOChannel *chan;
310
311         if (!ctxt)
312                 return -EINVAL;
313
314         s = get_sdp_session(src, dst);
315         if (!s)
316                 return -errno;
317
318         *ctxt = g_try_malloc0(sizeof(struct search_context));
319         if (!*ctxt) {
320                 sdp_close(s);
321                 return -ENOMEM;
322         }
323
324         bacpy(&(*ctxt)->src, src);
325         bacpy(&(*ctxt)->dst, dst);
326         (*ctxt)->session = s;
327         (*ctxt)->uuid = *uuid;
328
329         chan = g_io_channel_unix_new(sdp_get_socket(s));
330         (*ctxt)->io_id = g_io_add_watch(chan,
331                                 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
332                                 connect_watch, *ctxt);
333         g_io_channel_unref(chan);
334
335         return 0;
336 }
337
338 int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
339                         uuid_t *uuid, bt_callback_t cb, void *user_data,
340                         bt_destroy_t destroy)
341 {
342         struct search_context *ctxt = NULL;
343         int err;
344
345         if (!cb)
346                 return -EINVAL;
347
348         err = create_search_context(&ctxt, src, dst, uuid);
349         if (err < 0)
350                 return err;
351
352         ctxt->cb        = cb;
353         ctxt->destroy   = destroy;
354         ctxt->user_data = user_data;
355
356         context_list = g_slist_append(context_list, ctxt);
357
358         return 0;
359 }
360
361 int bt_discover_services(const bdaddr_t *src, const bdaddr_t *dst,
362                 bt_callback_t cb, void *user_data, bt_destroy_t destroy)
363 {
364         uuid_t uuid;
365
366         sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
367
368         return bt_search_service(src, dst, &uuid, cb, user_data, destroy);
369 }
370
371 static int find_by_bdaddr(const void *data, const void *user_data)
372 {
373         const struct search_context *ctxt = data, *search = user_data;
374
375         return (bacmp(&ctxt->dst, &search->dst) &&
376                                         bacmp(&ctxt->src, &search->src));
377 }
378
379 int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst)
380 {
381         struct search_context search, *ctxt;
382         GSList *match;
383
384         memset(&search, 0, sizeof(search));
385         bacpy(&search.src, src);
386         bacpy(&search.dst, dst);
387
388         /* Ongoing SDP Discovery */
389         match = g_slist_find_custom(context_list, &search, find_by_bdaddr);
390         if (!match)
391                 return -ENODATA;
392
393         ctxt = match->data;
394         if (!ctxt->session)
395                 return -ENOTCONN;
396
397         if (ctxt->io_id)
398                 g_source_remove(ctxt->io_id);
399
400         if (ctxt->session)
401                 sdp_close(ctxt->session);
402
403         search_context_cleanup(ctxt);
404         return 0;
405 }
406
407 char *bt_uuid2string(uuid_t *uuid)
408 {
409         gchar *str;
410         uuid_t uuid128;
411         unsigned int data0;
412         unsigned short data1;
413         unsigned short data2;
414         unsigned short data3;
415         unsigned int data4;
416         unsigned short data5;
417
418         if (!uuid)
419                 return NULL;
420
421         switch (uuid->type) {
422         case SDP_UUID16:
423                 sdp_uuid16_to_uuid128(&uuid128, uuid);
424                 break;
425         case SDP_UUID32:
426                 sdp_uuid32_to_uuid128(&uuid128, uuid);
427                 break;
428         case SDP_UUID128:
429                 memcpy(&uuid128, uuid, sizeof(uuid_t));
430                 break;
431         default:
432                 /* Type of UUID unknown */
433                 return NULL;
434         }
435
436         memcpy(&data0, &uuid128.value.uuid128.data[0], 4);
437         memcpy(&data1, &uuid128.value.uuid128.data[4], 2);
438         memcpy(&data2, &uuid128.value.uuid128.data[6], 2);
439         memcpy(&data3, &uuid128.value.uuid128.data[8], 2);
440         memcpy(&data4, &uuid128.value.uuid128.data[10], 4);
441         memcpy(&data5, &uuid128.value.uuid128.data[14], 2);
442
443         str = g_try_malloc0(MAX_LEN_UUID_STR);
444         if (!str)
445                 return NULL;
446
447         sprintf(str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x",
448                         g_ntohl(data0), g_ntohs(data1),
449                         g_ntohs(data2), g_ntohs(data3),
450                         g_ntohl(data4), g_ntohs(data5));
451
452         return str;
453 }
454
455 static struct {
456         const char      *name;
457         uint16_t        class;
458 } bt_services[] = {
459         { "vcp",        VIDEO_CONF_SVCLASS_ID           },
460         { "pbap",       PBAP_SVCLASS_ID                 },
461         { "sap",        SAP_SVCLASS_ID                  },
462         { "ftp",        OBEX_FILETRANS_SVCLASS_ID       },
463         { "bpp",        BASIC_PRINTING_SVCLASS_ID       },
464         { "bip",        IMAGING_SVCLASS_ID              },
465         { "synch",      IRMC_SYNC_SVCLASS_ID            },
466         { "dun",        DIALUP_NET_SVCLASS_ID           },
467         { "opp",        OBEX_OBJPUSH_SVCLASS_ID         },
468         { "fax",        FAX_SVCLASS_ID                  },
469         { "spp",        SERIAL_PORT_SVCLASS_ID          },
470         { "hsp",        HEADSET_SVCLASS_ID              },
471         { "hfp",        HANDSFREE_SVCLASS_ID            },
472         { }
473 };
474
475 uint16_t bt_name2class(const char *pattern)
476 {
477         int i;
478
479         for (i = 0; bt_services[i].name; i++) {
480                 if (strcasecmp(bt_services[i].name, pattern) == 0)
481                         return bt_services[i].class;
482         }
483
484         return 0;
485 }
486
487 static inline gboolean is_uuid128(const char *string)
488 {
489         return (strlen(string) == 36 &&
490                         string[8] == '-' &&
491                         string[13] == '-' &&
492                         string[18] == '-' &&
493                         string[23] == '-');
494 }
495
496 char *bt_name2string(const char *pattern)
497 {
498         uuid_t uuid;
499         uint16_t uuid16;
500         int i;
501
502         /* UUID 128 string format */
503         if (is_uuid128(pattern))
504                 return g_strdup(pattern);
505
506         /* Friendly service name format */
507         uuid16 = bt_name2class(pattern);
508         if (uuid16)
509                 goto proceed;
510
511         /* HEX format */
512         uuid16 = strtol(pattern, NULL, 16);
513         for (i = 0; bt_services[i].class; i++) {
514                 if (bt_services[i].class == uuid16)
515                         goto proceed;
516         }
517
518         return NULL;
519
520 proceed:
521         sdp_uuid16_create(&uuid, uuid16);
522
523         return bt_uuid2string(&uuid);
524 }
525
526 int bt_string2uuid(uuid_t *uuid, const char *string)
527 {
528         uint32_t data0, data4;
529         uint16_t data1, data2, data3, data5;
530
531         if (is_uuid128(string) &&
532                         sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
533                                 &data0, &data1, &data2, &data3, &data4, &data5) == 6) {
534                 uint8_t val[16];
535
536                 data0 = g_htonl(data0);
537                 data1 = g_htons(data1);
538                 data2 = g_htons(data2);
539                 data3 = g_htons(data3);
540                 data4 = g_htonl(data4);
541                 data5 = g_htons(data5);
542
543                 memcpy(&val[0], &data0, 4);
544                 memcpy(&val[4], &data1, 2);
545                 memcpy(&val[6], &data2, 2);
546                 memcpy(&val[8], &data3, 2);
547                 memcpy(&val[10], &data4, 4);
548                 memcpy(&val[14], &data5, 2);
549
550                 sdp_uuid128_create(uuid, val);
551
552                 return 0;
553         } else {
554                 uint16_t class = bt_name2class(string);
555                 if (class) {
556                         sdp_uuid16_create(uuid, class);
557                         return 0;
558                 }
559         }
560
561         return -1;
562 }
563
564 gchar *bt_list2string(GSList *list)
565 {
566         GSList *l;
567         gchar *str, *tmp;
568
569         if (!list)
570                 return NULL;
571
572         str = g_strdup((const gchar *) list->data);
573
574         for (l = list->next; l; l = l->next) {
575                 tmp = g_strconcat(str, " " , (const gchar *) l->data, NULL);
576                 g_free(str);
577                 str = tmp;
578         }
579
580         return str;
581 }
582
583 GSList *bt_string2list(const gchar *str)
584 {
585         GSList *l = NULL;
586         gchar **uuids;
587         int i = 0;
588
589         if (!str)
590                 return NULL;
591
592         /* FIXME: eglib doesn't support g_strsplit */
593         uuids = g_strsplit(str, " ", 0);
594         if (!uuids)
595                 return NULL;
596
597         while (uuids[i]) {
598                 l = g_slist_append(l, uuids[i]);
599                 i++;
600         }
601
602         g_free(uuids);
603
604         return l;
605 }
606
607 static gboolean hci_event_watch(GIOChannel *io,
608                         GIOCondition cond, gpointer user_data)
609 {
610         unsigned char buf[HCI_MAX_EVENT_SIZE], *body;
611         struct hci_cmd_data *cmd = user_data;
612         evt_cmd_status *evt_status;
613         evt_auth_complete *evt_auth;
614         evt_encrypt_change *evt_enc;
615         hci_event_hdr *hdr;
616         set_conn_encrypt_cp cp;
617         int dd;
618         uint16_t ocf;
619         uint8_t status = HCI_OE_POWER_OFF;
620
621         if (cond & G_IO_NVAL) {
622                 cmd->cb(status, cmd->caller_data);
623                 return FALSE;
624         }
625
626         if (cond & (G_IO_ERR | G_IO_HUP))
627                 goto failed;
628
629         dd = g_io_channel_unix_get_fd(io);
630
631         if (read(dd, buf, sizeof(buf)) < 0)
632                 goto failed;
633
634         hdr = (hci_event_hdr *) (buf + 1);
635         body = buf + (1 + HCI_EVENT_HDR_SIZE);
636
637         switch (hdr->evt) {
638         case EVT_CMD_STATUS:
639                 evt_status = (evt_cmd_status *) body;
640                 ocf = cmd_opcode_ocf(evt_status->opcode);
641                 if (ocf != cmd->ocf)
642                         return TRUE;
643                 switch (ocf) {
644                 case OCF_AUTH_REQUESTED:
645                 case OCF_SET_CONN_ENCRYPT:
646                         if (evt_status->status != 0) {
647                                 /* Baseband rejected command */
648                                 status = evt_status->status;
649                                 goto failed;
650                         }
651                         break;
652                 default:
653                         return TRUE;
654                 }
655                 /* Wait for the next event */
656                 return TRUE;
657         case EVT_AUTH_COMPLETE:
658                 evt_auth = (evt_auth_complete *) body;
659                 if (evt_auth->handle != cmd->handle) {
660                         /* Skipping */
661                         return TRUE;
662                 }
663
664                 if (evt_auth->status != 0x00) {
665                         status = evt_auth->status;
666                         /* Abort encryption */
667                         goto failed;
668                 }
669
670                 memset(&cp, 0, sizeof(cp));
671                 cp.handle  = cmd->handle;
672                 cp.encrypt = 1;
673
674                 cmd->ocf = OCF_SET_CONN_ENCRYPT;
675
676                 if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT,
677                                         SET_CONN_ENCRYPT_CP_SIZE, &cp) < 0) {
678                         status = HCI_COMMAND_DISALLOWED;
679                         goto failed;
680                 }
681                 /* Wait for encrypt change event */
682                 return TRUE;
683         case EVT_ENCRYPT_CHANGE:
684                 evt_enc = (evt_encrypt_change *) body;
685                 if (evt_enc->handle != cmd->handle)
686                         return TRUE;
687
688                 /* Procedure finished: reporting status */
689                 status = evt_enc->status;
690                 break;
691         default:
692                 /* Skipping */
693                 return TRUE;
694         }
695
696 failed:
697         cmd->cb(status, cmd->caller_data);
698         g_io_channel_shutdown(io, TRUE, NULL);
699
700         return FALSE;
701 }
702
703 int bt_acl_encrypt(const bdaddr_t *src, const bdaddr_t *dst,
704                         bt_hci_result_t cb, gpointer user_data)
705 {
706         GIOChannel *io;
707         struct hci_cmd_data *cmd;
708         struct hci_conn_info_req *cr;
709         auth_requested_cp cp;
710         struct hci_filter nf;
711         int dd, dev_id, err;
712         char src_addr[18];
713         uint32_t link_mode;
714         uint16_t handle;
715
716         ba2str(src, src_addr);
717         dev_id = hci_devid(src_addr);
718         if (dev_id < 0)
719                 return -errno;
720
721         dd = hci_open_dev(dev_id);
722         if (dd < 0)
723                 return -errno;
724
725         cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info));
726         cr->type = ACL_LINK;
727         bacpy(&cr->bdaddr, dst);
728
729         err = ioctl(dd, HCIGETCONNINFO, cr);
730         link_mode = cr->conn_info->link_mode;
731         handle = cr->conn_info->handle;
732         g_free(cr);
733
734         if (err < 0) {
735                 err = errno;
736                 goto failed;
737         }
738
739         if (link_mode & HCI_LM_ENCRYPT) {
740                 /* Already encrypted */
741                 err = EALREADY;
742                 goto failed;
743         }
744
745         memset(&cp, 0, sizeof(cp));
746         cp.handle = htobs(handle);
747
748         if (hci_send_cmd(dd, OGF_LINK_CTL, OCF_AUTH_REQUESTED,
749                                 AUTH_REQUESTED_CP_SIZE, &cp) < 0) {
750                 err = errno;
751                 goto failed;
752         }
753
754         cmd = g_new0(struct hci_cmd_data, 1);
755         cmd->handle = handle;
756         cmd->ocf = OCF_AUTH_REQUESTED;
757         cmd->cb = cb;
758         cmd->caller_data = user_data;
759
760         hci_filter_clear(&nf);
761         hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
762         hci_filter_set_event(EVT_CMD_STATUS, &nf);
763         hci_filter_set_event(EVT_AUTH_COMPLETE, &nf);
764         hci_filter_set_event(EVT_ENCRYPT_CHANGE, &nf);
765
766         if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
767                 err = errno;
768                 goto failed;
769         }
770
771         io = g_io_channel_unix_new(dd);
772         g_io_channel_set_close_on_unref(io, FALSE);
773         g_io_add_watch_full(io, G_PRIORITY_DEFAULT,
774                         G_IO_HUP | G_IO_ERR | G_IO_NVAL | G_IO_IN,
775                         hci_event_watch, cmd, g_free);
776         g_io_channel_unref(io);
777
778         return 0;
779
780 failed:
781         close(dd);
782
783         return -err;
784 }