/* * * OBEX library with GLib integration * * Copyright (C) 2011 Intel Corporation. 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 version 2 as * published by the Free Software Foundation. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include static GMainLoop *main_loop = NULL; static GObex *obex = NULL; static gboolean option_packet = FALSE; static gboolean option_bluetooth = FALSE; static char *option_source = NULL; static char *option_dest = NULL; static int option_channel = -1; static int option_imtu = -1; static int option_omtu = -1; static void sig_term(int sig) { g_print("Terminating due to signal %d\n", sig); g_main_loop_quit(main_loop); } static GOptionEntry options[] = { { "unix", 'u', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_bluetooth, "Use a UNIX socket" }, { "bluetooth", 'b', 0, G_OPTION_ARG_NONE, &option_bluetooth, "Use Bluetooth" }, { "source", 's', 0, G_OPTION_ARG_STRING, &option_source, "Bluetooth adapter address", "00:..." }, { "destination", 'd', 0, G_OPTION_ARG_STRING, &option_dest, "Remote bluetooth address", "00:..." }, { "channel", 'c', 0, G_OPTION_ARG_INT, &option_channel, "Transport channel", "CHANNEL" }, { "packet", 'p', 0, G_OPTION_ARG_NONE, &option_packet, "Packet based transport" }, { "stream", 's', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &option_packet, "Stream based transport" }, { "input-mtu", 'i', 0, G_OPTION_ARG_INT, &option_imtu, "Transport input MTU", "MTU" }, { "output-mtu", 'o', 0, G_OPTION_ARG_INT, &option_omtu, "Transport output MTU", "MTU" }, { NULL }, }; static void conn_complete(GObex *obex, GError *err, GObexPacket *rsp, gpointer user_data) { if (err != NULL) g_print("Connect failed: %s\n", err->message); else g_print("Connect succeeded\n"); } static void cmd_connect(int argc, char **argv) { g_obex_connect(obex, conn_complete, NULL, NULL, G_OBEX_HDR_INVALID); } struct transfer_data { int fd; }; static void transfer_complete(GObex *obex, GError *err, gpointer user_data) { struct transfer_data *data = user_data; if (err != NULL) g_printerr("failed: %s\n", err->message); else g_print("transfer succeeded\n"); close(data->fd); g_free(data); } static gssize put_data_cb(void *buf, gsize len, gpointer user_data) { struct transfer_data *data = user_data; return read(data->fd, buf, len); } static void cmd_put(int argc, char **argv) { struct transfer_data *data; GError *err = NULL; int fd; if (argc < 2) { g_printerr("Filename required\n"); return; } fd = open(argv[1], O_RDONLY | O_NOCTTY, 0); if (fd < 0) { g_printerr("open: %s\n", strerror(errno)); return; } data = g_new0(struct transfer_data, 1); data->fd = fd; g_obex_put_req(obex, put_data_cb, transfer_complete, data, &err, G_OBEX_HDR_NAME, argv[1], G_OBEX_HDR_INVALID); if (err != NULL) { g_printerr("put failed: %s\n", err->message); g_error_free(err); close(data->fd); g_free(data); } } static gboolean get_data_cb(const void *buf, gsize len, gpointer user_data) { struct transfer_data *data = user_data; if (write(data->fd, buf, len) < 0) { g_printerr("write: %s\n", strerror(errno)); return FALSE; } return TRUE; } static void cmd_get(int argc, char **argv) { struct transfer_data *data; GError *err = NULL; int fd; if (argc < 2) { g_printerr("Filename required\n"); return; } fd = open(argv[1], O_WRONLY | O_CREAT | O_NOCTTY, 0600); if (fd < 0) { g_printerr("open: %s\n", strerror(errno)); return; } data = g_new0(struct transfer_data, 1); data->fd = fd; g_obex_get_req(obex, get_data_cb, transfer_complete, data, &err, G_OBEX_HDR_NAME, argv[1], G_OBEX_HDR_INVALID); if (err != NULL) { g_printerr("get failed: %s\n", err->message); g_error_free(err); close(data->fd); g_free(data); } } static void cmd_help(int argc, char **argv); static void cmd_exit(int argc, char **argv) { g_main_loop_quit(main_loop); } static struct { const char *cmd; void (*func)(int argc, char **argv); const char *params; const char *desc; } commands[] = { { "help", cmd_help, "", "Show this help"}, { "exit", cmd_exit, "", "Exit application" }, { "quit", cmd_exit, "", "Exit application" }, { "connect", cmd_connect, "[target]", "OBEX Connect" }, { "put", cmd_put, "", "Send a file" }, { "get", cmd_get, "", "Receive a file" }, { NULL }, }; static void cmd_help(int argc, char **argv) { int i; for (i = 0; commands[i].cmd; i++) printf("%-15s %-30s %s\n", commands[i].cmd, commands[i].params, commands[i].desc); } static void parse_line(char *line_read) { gchar **argvp; int argcp; int i; if (line_read == NULL) { g_print("\n"); g_main_loop_quit(main_loop); return; } line_read = g_strstrip(line_read); if (*line_read == '\0') { free(line_read); return; } add_history(line_read); g_shell_parse_argv(line_read, &argcp, &argvp, NULL); free(line_read); for (i = 0; commands[i].cmd; i++) if (strcasecmp(commands[i].cmd, argvp[0]) == 0) break; if (commands[i].cmd) commands[i].func(argcp, argvp); else g_print("%s: command not found\n", argvp[0]); g_strfreev(argvp); } static gboolean prompt_read(GIOChannel *chan, GIOCondition cond, gpointer user_data) { if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { g_main_loop_quit(main_loop); return FALSE; } rl_callback_read_char(); return TRUE; } static void disconn_func(GObex *obex, GError *err, gpointer user_data) { g_printerr("Disconnected: %s\n", err ? err->message : "(no error)"); g_main_loop_quit(main_loop); } static void transport_connect(GIOChannel *io, GObexTransportType transport) { GIOChannel *input; GIOCondition events; g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); g_io_channel_set_close_on_unref(io, TRUE); obex = g_obex_new(io, transport, option_imtu, option_omtu); g_obex_set_disconnect_function(obex, disconn_func, NULL); input = g_io_channel_unix_new(STDIN_FILENO); g_io_channel_set_close_on_unref(input, TRUE); events = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; g_io_add_watch(input, events, prompt_read, NULL); g_io_channel_unref(input); rl_callback_handler_install("client> ", parse_line); } static GIOChannel *unix_connect(GObexTransportType transport) { GIOChannel *io; struct sockaddr_un addr = { AF_UNIX, "\0/gobex/server" }; int sk, err, sock_type; if (option_packet) sock_type = SOCK_SEQPACKET; else sock_type = SOCK_STREAM; sk = socket(PF_LOCAL, sock_type, 0); if (sk < 0) { err = errno; g_printerr("Can't create unix socket: %s (%d)\n", strerror(err), err); return NULL; } if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { err = errno; g_printerr("connect: %s (%d)\n", strerror(err), err); return NULL; } io = g_io_channel_unix_new(sk); g_print("Unix socket created: %d\n", sk); transport_connect(io, transport); return io; } static void conn_callback(GIOChannel *io, GError *err, gpointer user_data) { GObexTransportType transport = GPOINTER_TO_UINT(user_data); if (err != NULL) { g_printerr("%s\n", err->message); return; } g_print("Bluetooth socket connected\n"); transport_connect(io, transport); } static GIOChannel *l2cap_connect(GObexTransportType transport, GError **err) { if (option_source) return bt_io_connect(conn_callback, GUINT_TO_POINTER(transport), NULL, err, BT_IO_OPT_SOURCE, option_source, BT_IO_OPT_DEST, option_dest, BT_IO_OPT_PSM, option_channel, BT_IO_OPT_MODE, BT_IO_MODE_ERTM, BT_IO_OPT_OMTU, option_omtu, BT_IO_OPT_IMTU, option_imtu, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); return bt_io_connect(conn_callback, GUINT_TO_POINTER(transport), NULL, err, BT_IO_OPT_DEST, option_dest, BT_IO_OPT_PSM, option_channel, BT_IO_OPT_MODE, BT_IO_MODE_ERTM, BT_IO_OPT_OMTU, option_omtu, BT_IO_OPT_IMTU, option_imtu, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); } static GIOChannel *rfcomm_connect(GObexTransportType transport, GError **err) { if (option_source) return bt_io_connect(conn_callback, GUINT_TO_POINTER(transport), NULL, err, BT_IO_OPT_SOURCE, option_source, BT_IO_OPT_DEST, option_dest, BT_IO_OPT_CHANNEL, option_channel, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); return bt_io_connect(conn_callback, GUINT_TO_POINTER(transport), NULL, err, BT_IO_OPT_DEST, option_dest, BT_IO_OPT_CHANNEL, option_channel, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); } static GIOChannel *bluetooth_connect(GObexTransportType transport) { GIOChannel *io; GError *err = NULL; if (option_dest == NULL || option_channel < 0) return NULL; if (option_channel > 31) io = l2cap_connect(transport, &err); else io = rfcomm_connect(transport, &err); if (io != NULL) return io; g_printerr("%s\n", err->message); g_error_free(err); return NULL; } int main(int argc, char *argv[]) { GOptionContext *context; GError *err = NULL; struct sigaction sa; GIOChannel *io; GObexTransportType transport; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); g_option_context_parse(context, &argc, &argv, &err); if (err != NULL) { g_printerr("%s\n", err->message); g_error_free(err); exit(EXIT_FAILURE); } if (option_packet) transport = G_OBEX_TRANSPORT_PACKET; else transport = G_OBEX_TRANSPORT_STREAM; if (option_bluetooth) io = bluetooth_connect(transport); else io = unix_connect(transport); if (io == NULL) exit(EXIT_FAILURE); memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_term; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); main_loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(main_loop); rl_callback_handler_remove(); clear_history(); g_obex_unref(obex); g_option_context_free(context); g_main_loop_unref(main_loop); exit(EXIT_SUCCESS); }