Improved signal handling and close/shutdown behaviour.
[barry.git] / tools / brawchannel.cc
blob0460b94ff95e379c1401ead82e045fc04c095f34
1 ///
2 /// \file brawchannel.cc
3 ///
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 Condvar;
59 // Wrapper for pthread_mutex_t
60 class Mutex
62 public:
63 friend class Condvar;
64 Mutex();
65 ~Mutex();
66 void Lock();
67 void Unlock();
68 private:
69 bool m_initialized;
70 pthread_mutex_t m_mutex;
73 Mutex::Mutex()
74 : m_initialized(false)
76 int ret = pthread_mutex_init(&m_mutex, NULL);
77 if (ret != 0)
78 throw Barry::Error("Mutex: failed to create mutex");
79 m_initialized = true;
82 Mutex::~Mutex()
84 if (m_initialized)
86 int ret = pthread_mutex_destroy(&m_mutex);
87 if (ret != 0)
88 cerr << "Failed to destroy mutex with error: " << ret << endl;
92 void Mutex::Lock()
94 int ret = pthread_mutex_lock(&m_mutex);
95 if (ret != 0)
96 throw Barry::Error("Mutex: failed to lock mutex");
99 void Mutex::Unlock()
101 int ret = pthread_mutex_unlock(&m_mutex);
102 if (ret != 0)
103 throw Barry::Error("Mutex: failed to unlock mutex");
106 // RIAA wrapper for locking Mutex class
107 class MutexLock
109 public:
110 MutexLock(Mutex& mutex)
111 : m_locked(false),
112 m_mutex(mutex)
113 { mutex.Lock(); m_locked = true; };
114 void Unlock()
116 if (m_locked)
118 m_mutex.Unlock();
119 m_locked = false;
122 ~MutexLock() { Unlock(); }
123 private:
124 bool m_locked;
125 Mutex& m_mutex;
128 // Wrapper for pthread_cont_t
129 class Condvar
131 public:
132 Condvar();
133 ~Condvar();
134 void Wait(Mutex& mutex);
135 void Signal();
136 private:
137 bool m_initialized;
138 pthread_cond_t m_cv;
141 Condvar::Condvar()
142 : m_initialized(false)
144 int ret = pthread_cond_init(&m_cv, NULL);
145 if (ret != 0)
146 throw Barry::Error("Condvar: failed to create condvar");
147 m_initialized = true;
150 Condvar::~Condvar()
152 if (m_initialized)
154 int ret = pthread_cond_destroy(&m_cv);
155 if (ret != 0)
156 cerr << "Failed to destroy condvar with error: " << ret << endl;
160 void Condvar::Wait(Mutex& mutex)
162 int ret = pthread_cond_wait(&m_cv, &mutex.m_mutex);
163 if (ret != 0)
164 throw Barry::Error("Condvar: failed to wait on condvar");
167 void Condvar::Signal()
169 int ret = pthread_cond_signal(&m_cv);
170 if (ret != 0)
171 throw Barry::Error("Condvar: failed to signal condvar");
174 // Semaphore class for signalling between threads
175 class Semaphore
177 public:
178 Semaphore(int initialVal = 0);
179 ~Semaphore();
180 void WaitForSignal();
181 void Signal();
182 private:
183 int m_value;
184 Mutex m_mutex;
185 Condvar m_cv;
188 Semaphore::Semaphore(int initialVal)
189 : m_value(initialVal),
190 m_mutex(),
191 m_cv()
195 Semaphore::~Semaphore()
199 void Semaphore::WaitForSignal()
201 MutexLock lock(m_mutex);
202 while (m_value <= 0) {
203 m_cv.Wait(m_mutex);
205 --m_value;
206 lock.Unlock();
209 void Semaphore::Signal()
211 MutexLock lock(m_mutex);
212 ++m_value;
213 m_cv.Signal();
214 lock.Unlock();
217 class StdoutWriter : public Barry::Mode::RawChannelDataCallback
219 public:
220 StdoutWriter(volatile bool& keepGoing, bool verbose, Semaphore& semaphore)
221 : m_continuePtr(&keepGoing),
222 m_verbose(verbose),
223 m_semaphore(semaphore)
228 public: // From RawChannelDataCallback
229 virtual void DataReceived(Data& data);
230 virtual void DataSendAck();
231 virtual void ChannelError(string msg);
232 virtual void ChannelClose();
234 private:
235 volatile bool* m_continuePtr;
236 bool m_verbose;
237 Semaphore& m_semaphore;
241 void StdoutWriter::DataReceived(Data& data)
243 if (m_verbose) {
244 cerr << "From BB: ";
245 data.DumpHex(cerr);
246 cerr << "\n";
249 size_t toWrite = data.GetSize();
250 size_t written = 0;
252 while (written < toWrite && *m_continuePtr) {
253 ssize_t writtenThisTime = write(STDOUT_FILENO, &(data.GetData()[written]), toWrite - written);
254 if (m_verbose) {
255 cerr.setf(ios::dec, ios::basefield);
256 cerr << "Written " << writtenThisTime << " bytes over stdout" << endl;
258 fflush(stdout);
259 if (writtenThisTime < 0)
261 ChannelClose();
263 else
265 written += writtenThisTime;
270 void StdoutWriter::DataSendAck()
272 m_semaphore.Signal();
275 void StdoutWriter::ChannelError(string msg)
277 cerr << "StdoutWriter: Received error: " << msg << endl;
278 ChannelClose();
281 void StdoutWriter::ChannelClose()
283 *m_continuePtr = false;
284 m_semaphore.Signal();
287 // Class which adds error detection and setting of a continue boolean
288 // to false when an error is detected to SocketRoutingQueue.
289 // This code is heavily based on the thread creation code of
290 // SocketRoutingQueue, which sadly has too many private variables
291 // to just sub-class.
292 class ErrorHandlingSocketRoutingQueue
294 public:
295 ErrorHandlingSocketRoutingQueue(volatile bool& continuePtr, Semaphore& semaphore)
296 : m_socketRoutingQueue(),
297 m_continuePtr(&continuePtr),
298 m_runningThread(false),
299 m_semaphore(semaphore)
301 // Nothing to do
304 ~ErrorHandlingSocketRoutingQueue() {
305 // Is the read thread still running
306 if( m_runningThread ) {
307 m_runningThread = false;
308 pthread_join(m_usb_read_thread, NULL);
312 // Utility function to make it easier to create the
313 // USB pure-read thread.
314 // Throws Barry::ErrnoError on thread creation error.
315 void SpinoffReadThread() {
316 // signal that it's ok to run inside the thread
317 if( m_runningThread )
318 return; // already running
319 m_runningThread = true;
321 // Start USB read thread, to handle all routing
322 int ret = pthread_create(&m_usb_read_thread, NULL, &ReadThreadFunction, this);
323 if (ret) {
324 m_runningThread = false;
325 throw Barry::ErrnoError("SocketRoutingQueue: Error creating USB read thread.", ret);
329 SocketRoutingQueue* GetSocketRoutingQueue() {
330 return &m_socketRoutingQueue;
333 protected:
334 static void* ReadThreadFunction(void* userPtr) {
335 ErrorHandlingSocketRoutingQueue *q = (ErrorHandlingSocketRoutingQueue *)userPtr;
337 // read from USB and write to stdout until finished
338 string msg;
339 while (q->m_runningThread) {
340 if( !q->m_socketRoutingQueue.DoRead(msg, READ_TIMEOUT_SECONDS * 1000) &&
341 // Only report the first failure, so check m_continuePtr
342 *q->m_continuePtr) {
343 cerr << "Error in ReadThread: " << msg << endl;
344 *q->m_continuePtr = false;
345 q->m_semaphore.Signal();
348 return 0;
351 protected:
352 SocketRoutingQueue m_socketRoutingQueue;
353 volatile bool* m_continuePtr;
354 volatile bool m_runningThread;
355 pthread_t m_usb_read_thread;
356 Semaphore& m_semaphore;
359 void Usage()
361 int major, minor;
362 const char *Version = Barry::Version(major, minor);
364 cerr
365 << "brawchannel - Command line USB Blackberry raw channel interface\n"
366 << " Copyright 2010, RealVNC Ltd.\n"
367 << " Using: " << Version << "\n"
368 << "\n"
369 << "Usage:\n"
370 << "brawchannel [options] <channel name>\n"
371 << "\n"
372 << " -h This help\n"
373 << " -p pin PIN of device to talk with\n"
374 << " If only one device is plugged in, this flag is optional\n"
375 << " -P pass Simplistic method to specify device password\n"
376 << " -v Dump protocol data during operation\n"
377 << " This will cause libusb output to appear on STDOUT unless\n"
378 << " the environment variable USB_DEBUG is set to 0,1 or 2.\n"
379 << endl;
382 // Helper class to restore signal handlers when shutdown is occuring
383 // This class isn't responsible for setting up the signal handlers
384 // as they need to be restored before the Barry::Socket starts closing.
385 class SignalRestorer
387 public:
388 SignalRestorer(int signum, sighandler_t handler)
389 : m_signum(signum), m_handler(handler) {}
390 ~SignalRestorer() { signal(m_signum, m_handler); }
391 private:
392 int m_signum;
393 sighandler_t m_handler;
396 int main(int argc, char *argv[])
398 INIT_I18N(PACKAGE);
400 // Setup signal handling
401 sighandler_t oldSigHup = signal(SIGHUP, &signalHandler);
402 sighandler_t oldSigTerm = signal(SIGTERM, &signalHandler);
403 sighandler_t oldSigInt = signal(SIGINT, &signalHandler);
404 sighandler_t oldSigQuit = signal(SIGQUIT, &signalHandler);
406 cerr.sync_with_stdio(true); // since libusb uses
407 // stdio for debug messages
408 unsigned char* buf = NULL;
409 try {
410 uint32_t pin = 0;
411 bool data_dump = false;
412 string password;
413 vector<string> params;
414 string busname;
415 string devname;
416 string iconvCharset;
417 Usb::EndpointPair epOverride;
419 // process command line options
420 for(;;) {
421 int cmd = getopt(argc, argv, "hp:P:v");
422 if( cmd == -1 )
423 break;
425 switch( cmd )
427 case 'p': // Blackberry PIN
428 pin = strtoul(optarg, NULL, 16);
429 break;
431 case 'P': // Device password
432 password = optarg;
433 break;
435 case 'v': // data dump on
436 data_dump = true;
437 break;
439 case 'h': // help
440 default:
441 Usage();
442 return 0;
446 argc -= optind;
447 argv += optind;
449 if( argc < 1 ) {
450 cerr << "Error: Missing raw channel name." << endl;
451 Usage();
452 return 1;
455 // Fetch command from remaining arguments
456 string channelName = argv[0];
457 argc --;
458 argv ++;
460 // Put the remaining arguments into an array
461 for (; argc > 0; argc --, argv ++) {
462 params.push_back(string(argv[0]));
465 if (data_dump)
467 // Warn if USB_DEBUG isn't set to 0, 1 or 2
468 // as that usually means libusb will write to STDOUT
469 char* val = getenv("USB_DEBUG");
470 int parsedValue = -1;
471 if(val)
473 parsedValue = atoi(val);
475 if(parsedValue != 0 && parsedValue != 1 && parsedValue != 2)
477 cerr << "Warning: Protocol dump enabled without setting USB_DEBUG to 0, 1 or 2.\n"
478 << " libusb might log to STDOUT and ruin data stream." << endl;
482 // Initialize the barry library. Must be called before
483 // anything else.
484 Barry::Init(data_dump, &cerr);
486 // Probe the USB bus for Blackberry devices and display.
487 // If user has specified a PIN, search for it in the
488 // available device list here as well
489 Barry::Probe probe;
490 int activeDevice = probe.FindActive(pin);
491 if( activeDevice == -1 ) {
492 cerr << "No device selected, or PIN not found" << endl;
493 return 1;
496 // Now start to read from stdin and get ready to write
497 // to the BlackBerry.
498 if (data_dump)
499 cerr << "Connected to device, starting read/write\n";
501 volatile bool running = true;
503 Semaphore sem;
505 // Create the thing which will write onto stdout
506 StdoutWriter stdoutWriter(running, data_dump, sem);
508 // Set up the BlackBerry gubbins
509 // Start a thread to handle any data arriving from
510 // the BlackBerry.
511 auto_ptr<ErrorHandlingSocketRoutingQueue> router;
512 router.reset(new ErrorHandlingSocketRoutingQueue(running, sem));
513 router->SpinoffReadThread();
515 // Create our controller object
516 Barry::Controller con(probe.Get(activeDevice), *router->GetSocketRoutingQueue());
518 Barry::Mode::RawChannel rawChannel(con, stdoutWriter);
521 // execute each mode that was turned on
523 rawChannel.Open(password.c_str(), channelName.c_str());
525 // We now have a thread running to read from the
526 // BB and write over stdout; in this thread we'll
527 // read from stdin and write to the BB.
528 const size_t bufSize = rawChannel.MaximumSendSize();
529 buf = new unsigned char[bufSize];
530 fd_set rfds;
531 struct timeval tv;
532 FD_ZERO(&rfds);
534 SignalRestorer srh(SIGHUP, oldSigHup);
535 SignalRestorer srt(SIGTERM, oldSigTerm);
536 SignalRestorer sri(SIGINT, oldSigInt);
537 SignalRestorer srq(SIGQUIT, oldSigQuit);
539 while (running && !signalReceived) {
540 FD_SET(STDIN_FILENO, &rfds);
541 tv.tv_sec = READ_TIMEOUT_SECONDS;
542 tv.tv_usec = 0;
544 int ret = select(1, &rfds, NULL, NULL, &tv);
545 if (ret < 0) {
546 cerr << "Select failed with errno: " << errno << endl;
547 running = false;
549 else if (ret && FD_ISSET(STDIN_FILENO, &rfds)) {
550 ssize_t haveRead = read(STDIN_FILENO, buf, bufSize);
551 if (haveRead > 0) {
552 Data toWrite(buf, haveRead);
553 if (data_dump) {
554 cerr.setf(ios::dec, ios::basefield);
555 cerr << "Sending " << haveRead << " bytes stdin->USB\n";
556 cerr << "To BB: ";
557 toWrite.DumpHex(cerr);
558 cerr << "\n";
560 rawChannel.Send(toWrite);
561 if (data_dump) {
562 cerr.setf(ios::dec, ios::basefield);
563 cerr << "Sent " << ios::dec << haveRead << " bytes stdin->USB\n";
565 sem.WaitForSignal();
567 else if (haveRead < 0) {
568 running = false;
573 catch( Usb::Error &ue) {
574 cerr << "Usb::Error caught: " << ue.what() << endl;
575 return 1;
577 catch( Barry::Error &se ) {
578 cerr << "Barry::Error caught: " << se.what() << endl;
579 return 1;
581 catch( exception &e ) {
582 cerr << "exception caught: " << e.what() << endl;
583 return 1;
586 delete[] buf;
588 return 0;