Moving semaphore code to inside raw_channel so that clients don't have to care about it.
[barry.git] / tools / brawchannel.cc
blob45e15aa802ef77d04642598c8bdce0df012b7b1c
1 ///
2 /// \file brawchannel.cc
3 /// Directs a named raw channel over STDIN/STDOUT
4 ///
6 /*
7 Copyright (C) 2010, RealVNC Ltd.
9 Some parts are inspired from bjavaloader.cc
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 See the GNU General Public License in the COPYING file at the
21 root directory of this project for more details.
25 #include <barry/barry.h>
26 #include <iostream>
27 #include <vector>
28 #include <string>
29 #include <cstring>
30 #include <cstdio>
31 #include <cstdlib>
32 #include <algorithm>
33 #include <getopt.h>
34 #include <fstream>
35 #include <string.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <signal.h>
39 #include <errno.h>
40 #include <pthread.h>
42 #include "i18n.h"
44 using namespace std;
45 using namespace Barry;
47 // How long to wait between reads before checking if should shutdown
48 #define READ_TIMEOUT_SECONDS 1
50 static volatile bool signalReceived = false;
52 static void signalHandler(int signum)
54 signalReceived = true;
57 class CallbackHandler : public Barry::Mode::RawChannelDataCallback
59 private:
60 volatile bool* m_continuePtr;
61 bool m_verbose;
63 public:
64 CallbackHandler(volatile bool& keepGoing, bool verbose)
65 : m_continuePtr(&keepGoing)
66 , m_verbose(verbose)
71 public: // From RawChannelDataCallback
72 virtual void DataReceived(Data& data);
73 virtual void ChannelError(string msg);
74 virtual void ChannelClose();
78 void CallbackHandler::DataReceived(Data& data)
80 if( m_verbose ) {
81 cerr << "From BB: ";
82 data.DumpHex(cerr);
83 cerr << "\n";
86 size_t toWrite = data.GetSize();
87 size_t written = 0;
89 while( written < toWrite && *m_continuePtr ) {
90 ssize_t writtenThisTime = write(STDOUT_FILENO, &(data.GetData()[written]), toWrite - written);
91 if( m_verbose ) {
92 cerr.setf(ios::dec, ios::basefield);
93 cerr << "Written " << writtenThisTime << " bytes over stdout" << endl;
95 fflush(stdout);
96 if( writtenThisTime < 0 ) {
97 ChannelClose();
99 else {
100 written += writtenThisTime;
105 void CallbackHandler::ChannelError(string msg)
107 cerr << "CallbackHandler: Received error: " << msg << endl;
108 ChannelClose();
111 void CallbackHandler::ChannelClose()
113 *m_continuePtr = false;
116 // Class which extends the functionality of SocketRoutingQueue to add
117 // error detection and setting of a continue boolean to false when an
118 // error is detected.
119 // This code is heavily based on the thread
120 // creation code of SocketRoutingQueue, which sadly has too many
121 // private variables to just sub-class.
122 class ErrorHandlingSocketRoutingQueue
124 protected:
125 static void* ReadThreadFunction(void* userPtr)
127 ErrorHandlingSocketRoutingQueue *q = (ErrorHandlingSocketRoutingQueue *)userPtr;
129 // read from USB and write to stdout until finished
130 string msg;
131 while( q->m_runningThread ) {
132 if( !q->m_socketRoutingQueue.DoRead(msg, READ_TIMEOUT_SECONDS * 1000) &&
133 // Only report the first failure, so check m_continuePtr
134 *q->m_continuePtr ) {
135 cerr << "Error in ReadThread: " << msg << endl;
136 *q->m_continuePtr = false;
139 return 0;
142 SocketRoutingQueue m_socketRoutingQueue;
143 volatile bool* m_continuePtr;
144 volatile bool m_runningThread;
145 pthread_t m_usb_read_thread;
146 public:
147 ErrorHandlingSocketRoutingQueue(volatile bool& continuePtr)
148 : m_socketRoutingQueue()
149 , m_continuePtr(&continuePtr)
150 , m_runningThread(false)
152 // Nothing to do
155 ~ErrorHandlingSocketRoutingQueue()
157 // Is the read thread still running
158 if( m_runningThread ) {
159 m_runningThread = false;
160 pthread_join(m_usb_read_thread, NULL);
164 // Utility function to make it easier to create the
165 // USB pure-read thread.
166 // Throws Barry::ErrnoError on thread creation error.
167 void SpinoffReadThread()
169 // signal that it's ok to run inside the thread
170 if( m_runningThread )
171 return; // already running
172 m_runningThread = true;
174 // Start USB read thread, to handle all routing
175 int ret = pthread_create(&m_usb_read_thread, NULL, &ReadThreadFunction, this);
176 if( ret ) {
177 m_runningThread = false;
178 throw Barry::ErrnoError("SocketRoutingQueue: Error creating USB read thread.", ret);
182 SocketRoutingQueue* GetSocketRoutingQueue()
184 return &m_socketRoutingQueue;
188 void Usage()
190 int major, minor;
191 const char *Version = Barry::Version(major, minor);
193 cerr
194 << "brawchannel - Command line USB Blackberry raw channel interface\n"
195 << " Copyright 2010, RealVNC Ltd.\n"
196 << " Using: " << Version << "\n"
197 << "\n"
198 << "Usage:\n"
199 << "brawchannel [options] <channel name>\n"
200 << "\n"
201 << " -h This help\n"
202 << " -p pin PIN of device to talk with\n"
203 << " If only one device is plugged in, this flag is optional\n"
204 << " -P pass Simplistic method to specify device password\n"
205 << " -v Dump protocol data during operation\n"
206 << " This will cause libusb output to appear on STDOUT unless\n"
207 << " the environment variable USB_DEBUG is set to 0,1 or 2.\n"
208 << endl;
211 // Helper class to restore signal handlers when shutdown is occuring
212 // This class isn't responsible for setting up the signal handlers
213 // as they need to be restored before the Barry::Socket starts closing.
214 class SignalRestorer
216 private:
217 int m_signum;
218 sighandler_t m_handler;
219 public:
220 SignalRestorer(int signum, sighandler_t handler)
221 : m_signum(signum), m_handler(handler) {}
222 ~SignalRestorer() { signal(m_signum, m_handler); }
225 int main(int argc, char *argv[])
227 INIT_I18N(PACKAGE);
229 // Setup signal handling
230 sighandler_t oldSigHup = signal(SIGHUP, &signalHandler);
231 sighandler_t oldSigTerm = signal(SIGTERM, &signalHandler);
232 sighandler_t oldSigInt = signal(SIGINT, &signalHandler);
233 sighandler_t oldSigQuit = signal(SIGQUIT, &signalHandler);
235 cerr.sync_with_stdio(true); // since libusb uses
236 // stdio for debug messages
238 // Buffer to hold data read in from STDIN before sending it
239 // to the BlackBerry.
240 unsigned char* buf = NULL;
241 try {
242 uint32_t pin = 0;
243 bool data_dump = false;
244 string password;
246 // process command line options
247 for( ;; ) {
248 int cmd = getopt(argc, argv, "hp:P:v");
249 if( cmd == -1 ) {
250 break;
253 switch( cmd )
255 case 'p': // Blackberry PIN
256 pin = strtoul(optarg, NULL, 16);
257 break;
259 case 'P': // Device password
260 password = optarg;
261 break;
263 case 'v': // data dump on
264 data_dump = true;
265 break;
267 case 'h': // help
268 default:
269 Usage();
270 return 0;
274 argc -= optind;
275 argv += optind;
277 if( argc < 1 ) {
278 cerr << "Error: Missing raw channel name." << endl;
279 Usage();
280 return 1;
283 if( argc > 1 ) {
284 cerr << "Error: Too many arguments." << endl;
285 Usage();
286 return 1;
289 // Fetch command from remaining arguments
290 string channelName = argv[0];
291 argc --;
292 argv ++;
295 if( data_dump ) {
296 // Warn if USB_DEBUG isn't set to 0, 1 or 2
297 // as that usually means libusb will write to STDOUT
298 char* val = getenv("USB_DEBUG");
299 int parsedValue = -1;
300 if( val ) {
301 parsedValue = atoi(val);
303 if( parsedValue != 0 && parsedValue != 1 && parsedValue != 2 ) {
304 cerr << "Warning: Protocol dump enabled without setting USB_DEBUG to 0, 1 or 2.\n"
305 << " libusb might log to STDOUT and ruin data stream." << endl;
309 // Initialize the barry library. Must be called before
310 // anything else.
311 Barry::Init(data_dump, &cerr);
313 // Probe the USB bus for Blackberry devices.
314 // If user has specified a PIN, search for it in the
315 // available device list here as well
316 Barry::Probe probe;
317 int activeDevice = probe.FindActive(pin);
318 if( activeDevice == -1 ) {
319 cerr << "No device selected, or PIN not found" << endl;
320 return 1;
323 // Now get setup to open the channel.
324 if( data_dump ) {
325 cerr << "Connected to device, starting read/write\n";
328 volatile bool running = true;
330 // Create the thing which will write onto stdout
331 // and perform other callback duties.
332 CallbackHandler callbackHandler(running, data_dump);
334 // Start a thread to handle any data arriving from
335 // the BlackBerry.
336 auto_ptr<ErrorHandlingSocketRoutingQueue> router;
337 router.reset(new ErrorHandlingSocketRoutingQueue(running));
338 router->SpinoffReadThread();
340 // Create our controller object
341 Barry::Controller con(probe.Get(activeDevice), *router->GetSocketRoutingQueue());
343 Barry::Mode::RawChannel rawChannel(con, callbackHandler);
345 // Try to open the requested channel now everything is setup
346 rawChannel.Open(password.c_str(), channelName.c_str());
348 // We now have a thread running to read from the
349 // BB and write over stdout; in this thread we'll
350 // read from stdin and write to the BB.
351 const size_t bufSize = rawChannel.MaximumSendSize();
352 buf = new unsigned char[bufSize];
353 fd_set rfds;
354 struct timeval tv;
355 FD_ZERO(&rfds);
357 // Set up the signal restorers to restore signal
358 // handling (in their destructors) before the socket
359 // starts to be closed. This allows, for example,
360 // double control-c presses to stop graceful close
361 // down.
362 SignalRestorer srh(SIGHUP, oldSigHup);
363 SignalRestorer srt(SIGTERM, oldSigTerm);
364 SignalRestorer sri(SIGINT, oldSigInt);
365 SignalRestorer srq(SIGQUIT, oldSigQuit);
367 while( running && !signalReceived ) {
368 FD_SET(STDIN_FILENO, &rfds);
369 tv.tv_sec = READ_TIMEOUT_SECONDS;
370 tv.tv_usec = 0;
372 int ret = select(1, &rfds, NULL, NULL, &tv);
373 if( ret < 0 ) {
374 cerr << "Select failed with errno: " << errno << endl;
375 running = false;
377 else if ( ret && FD_ISSET(STDIN_FILENO, &rfds) ) {
378 ssize_t haveRead = read(STDIN_FILENO, buf, bufSize);
379 if( haveRead > 0 ) {
380 Data toWrite(buf, haveRead);
381 if( data_dump ) {
382 cerr.setf(ios::dec, ios::basefield);
383 cerr << "Sending " << haveRead << " bytes stdin->USB\n";
384 cerr << "To BB: ";
385 toWrite.DumpHex(cerr);
386 cerr << "\n";
388 rawChannel.Send(toWrite);
389 if( data_dump ) {
390 cerr.setf(ios::dec, ios::basefield);
391 cerr << "Sent " << ios::dec << haveRead << " bytes stdin->USB\n";
394 else if( haveRead < 0 ) {
395 running = false;
400 catch( Usb::Error &ue ) {
401 cerr << "Usb::Error caught: " << ue.what() << endl;
402 return 1;
404 catch( Barry::Error &se ) {
405 cerr << "Barry::Error caught: " << se.what() << endl;
406 return 1;
408 catch( exception &e ) {
409 cerr << "exception caught: " << e.what() << endl;
410 return 1;
413 delete[] buf;
415 return 0;