/*********************************************************************
*
* Filename: psion5.c
* Version: 0.1
* Description: User space application for Psion 5 Palmtop Computers
* Status: Experimental.
* Author: Fons Botman <budely@tref.nl>
* Created at: Mon Apr 19 21:51:29 CEST 1999
*
* Copyright (c) 1999, Fons Botman, 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 as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* Neither Fons Botman nor anyone else admit liability nor
* provide warranty for any of this software. This material is
* provided "AS-IS" and at no charge.
*
********************************************************************/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <utime.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/irda.h>
#ifndef AF_IRDA
#define AF_IRDA 23
#endif /* AF_IRDA */
#define MAX_DEVICES 10
int discover_devices(int fd)
{
struct irda_device_list *list;
unsigned char *buf;
int len;
int i;
int daddr = 0;
len = sizeof(struct irda_device_list) +
sizeof(struct irda_device_info) * MAX_DEVICES;
/* FIXME */
system("echo 150 > /proc/sys/net/irda/slot_timeout");
if (!(buf = malloc(len))) {
fprintf(stderr, "Could not allocate discovery buffer.\n");
exit(1);
}
list = (struct irda_device_list *) buf;
/* FIXME: discovery does not return when there are no devices */
if (getsockopt(fd, SOL_IRLMP, IRLMP_ENUMDEVICES, buf, &len)) {
perror("getsockopt");
exit(-1);
}
if (len > 0) {
printf("Discovered:\n");
for (i=0;i<list->len;i++) {
printf(" daddr: %08x", list->dev[i].daddr);
printf(" saddr: %08x", list->dev[i].saddr);
printf(" name: %s\n", list->dev[i].info);
daddr = list->dev[i].daddr;
}
} else {
printf("No devices discovered.\n");
}
return daddr;
}
int irttp_get_mtu(int fd) {
int mtu;
int len = sizeof(int);
/* Check what the IrTTP data size is */
if (getsockopt(fd, SOL_IRLMP, IRTTP_MAX_SDU_SIZE,
(void *)&mtu, &len)) {
return -1;
}
return mtu;
}
int sendfile(char* filename) {
int fd;
struct sockaddr_irda peer;
int daddr;
FILE* f;
int buflen;
char *buf;
int rc;
struct stat s;
int cnt;
int t0, tx, t;
unsigned long long int fdatell;
fd = socket(AF_IRDA, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
if (errno == EINVAL)
fprintf(stderr, "Is IrDA active?, perhaps run irmanager\n");
exit(-1);
}
/* FIXME: We should use a better/any device selection mechanism */
daddr = discover_devices(fd);
if (!daddr) {
fprintf(stderr,"No IRDA device found\n");
exit(1);
}
peer.sir_family = AF_IRDA;
peer.sir_addr = daddr;
strcpy(peer.sir_name, "Epoc32:EikonIr:v1.0");
if (connect(fd, (struct sockaddr *) &peer,
sizeof(struct sockaddr_irda))) {
perror("connect");
if (errno == ENETUNREACH)
/* P5: System ^L IrDA is active,
but IR-receive not selected */
fprintf(stderr,
"No Psion5 or IR-receive is not selected\n");
exit(-1);
}
printf("Connected to %x\n", daddr);
buflen = irttp_get_mtu(fd);
printf("mtu = %d\n", buflen);
if (buflen < 2) {
perror("irttp_get_mtu");
exit(1);
}
if (!(buf = malloc(buflen))) {
perror("malloc");
exit(1);
}
/*
FIXME : I got strange results when the buffer size was less than
the mtu (e.g. 200), the psion did not seem to see the frames were
not full length, and stopped after the number of frames based on
the full mtu size.
investigate.
*/
/*
FIXME : psion connects to port 2, but does not get error back
from us. Linux bug?
*/
if (!(f = fopen(filename,"rb"))) {
perror(filename);
exit(1);
}
rc = stat(filename,&s);
if (rc != 0) {
perror("stat");
exit(1);
}
/* FIXME map psion mode bits to unix filemodes */
fdatell = 62168263200000000ULL + 1000000 *
( s.st_mtime & 0x00000000FFFFFFFFULL);
printf("date: %Ld\n", (unsigned long long) s.st_mtime);
printf("date: %Ld\n", fdatell);
sprintf(buf,"FILE %d %d %lu %lu %s",
(int) s.st_size,
32 | (s.st_mode & S_IWUSR ? 0 : 1),
(unsigned long) ((unsigned long long int) fdatell >> 32),
(unsigned long) (fdatell & 0x00000000FFFFFFFFULL),
filename);
rc = write(fd,buf,strlen(buf));
if (rc != strlen(buf)) {
perror("write");
fprintf(stderr,"rc = %d, strlen=%d\n",
rc, strlen(buf));
exit(1);
}
printf("sent: %s\n", buf);
rc = read(fd,buf,buflen-1);
printf("Received (%d) ", rc);
if (rc < 0) {
perror("reply error");
exit(1);
}
if (rc == 0) {
fprintf(stderr, "EOF on reply?\n");
exit(1);
}
if (rc < buflen) {
buf[rc] = 0;
}
/* should be "ACK Y" */
if (0 != strcmp(buf,"ACK Y")) {
fprintf(stderr, "Unexpected response: %s\n", buf);
exit(1);
}
printf("%s\n", buf);
cnt = 0;
t0 = tx = t = time(NULL);
while (!ferror(f) && !feof(f)) {
int wrc;
rc = fread(buf, 1, buflen, f);
if (rc == 0) continue;
wrc = write(fd,buf,rc);
if (wrc < 0) {
perror("write");
exit(1);
}
if (wrc < rc) {
fprintf(stderr, "Problem: only sent %d of %d\n",
wrc, rc);
exit(1);
}
/* progress indication */
t = time(NULL);
cnt += rc;
if (t - t0 == 0 || cnt == 0 || tx == t)
/* avoid division errors */
/* only once per second */
continue;
tx = t;
printf("sent %d/%lu bytes=%4g%% in %d sec,"
" %g Kbytes/s, to go %li sec \r",
cnt, s.st_size, 100.0 * cnt / s.st_size, t - t0,
cnt / 1000.0 / (t - t0),
( s.st_size - cnt ) * (t - t0) / cnt);
fflush(stdout);
}
if (ferror(f)) {
perror("ferror");
exit(1);
}
if (cnt != s.st_size) {
printf("Warning: "
"file size changed: initial: %lu, actual: %d\n",
s.st_size, cnt);
}
if (t == t0) t++; /* white lie for fast transfers */
printf("\r%79s\r",""); /* Cleanup the progress line */
printf("Sent %s, %d bytes in %d sec. %g KBytes/sec\n",
filename, cnt, t - t0, cnt / 1000.0 / (t - t0));
/* Check for close on the other side */
rc = read(fd,buf,buflen);
if (rc > 0) {
fprintf(stderr, "Strange: the other side responded.\n");
fprintf(stderr, "rc=%d, data:%s\n", rc, buf);
exit(1);
}
if (rc == 0) {
fprintf(stderr, "Received end of file.\n");
}
if (rc == -1) {
if (errno == EPERM) {
/* Strange error code to get in this case */
printf("Other side closed connection, OK\n");
} else {
perror("last read");
exit(1);
}
}
close(fd);
return 0;
}
int handle_client(int cfd) {
int buflen;
char* buf;
int rc;
/* fields of file transfer header */
unsigned int fsize;
unsigned int fmode;
unsigned int fdate1;
unsigned int fdate2;
char* fname;
unsigned int fdate;
unsigned long long int fdatell;
FILE* f;
int cnt;
int t0, tx, t;
buflen = irttp_get_mtu(cfd);
printf("mtu=%d\n", buflen);
if (buflen < 2) {
perror("irttp_get_mtu");
exit(1);
}
if (!(buf = malloc(buflen))) {
fprintf(stderr, "malloc buf failed\n");
exit(1);
}
/* Wait for the other side to send a header */
/*
Sample headers received:
DATA 185
FILE 55175 32 14689800 2691219200 Data
size mode datehi datelo name
*/
rc = read(cfd, buf, buflen);
if (rc < 0) {
perror("1st read");
exit(1);
}
if (rc == 0) {
perror("1st read 0");
exit(1);
}
assert(rc < buflen);
buf[rc] = 0;
printf("%s\n", buf);
fsize = 0;
fdate = 0;
if (0 == strncmp(buf, "FILE ", 5)) {
cnt = 0; /* to be safe */
rc = sscanf(buf, "FILE %u %u %u %u %n",
&fsize, &fmode, &fdate1, &fdate2,
&cnt);
if (!(rc == 4 || rc == 5)) {
/* grumble */
fprintf(stderr, "sscanf rc=%d\n", rc);
exit(1);
}
assert(cnt < buflen);
fname = strdup(buf+cnt);
fdatell = ((unsigned long long int) fdate1 << 32) + fdate2;
fdate = ( fdatell - 62168263200000000ULL) / 1000000 ;
printf("filename: %s\n", fname);
printf("filesize: %d\n", fsize);
printf("Filemode: %d", fmode);
printf("%s", (fmode & 1 ? ", Readonly" : ""));
printf("%s", (fmode & 2 ? ", Hidden" : ""));
printf("%s", (fmode & 32 ? ", Modified" : ""));
printf("%s", (fmode & ~35 ? ", Unknown" : ""));
printf("\n");
printf("fdate1: %u = 0x%x\n", fdate1, fdate1);
printf("fdate2: %u = 0x%x\n", fdate2, fdate2);
printf("filedate: %Ld\n", fdatell);
printf("filedate: %d = %s", fdate,
asctime(gmtime((time_t*)&fdate)));
if (!(f = fopen(fname,"wb"))) {
perror(fname);
exit(1);
}
} else if (0 == strncmp(buf, "DATA ", 5)) {
cnt = 0; /* to be safe */
rc = sscanf(buf, "DATA %d", &fsize);
if (rc != 1) {
fprintf(stderr, "sscanf rc=%d\n", rc);
exit(1);
}
assert(cnt < buflen);
fname = strdup("/tmp/psion5-data");
if (!(f = fopen(fname,"wb"))) {
perror(fname);
exit(1);
}
} else {
fprintf(stderr, "Unknown data type: %s\n", buf);
/* exit(1); */
}
rc = write(cfd,"ACK Y",5);
if (rc != 5) {
perror("1st write");
fprintf(stderr,"1st write rc = %d\n", rc);
exit(1);
}
cnt = 0;
t0 = tx = t = time(NULL);
while (cnt < fsize) {
int wrc;
rc = read(cfd,buf,buflen);
if (rc < 0) {
perror("data read");
/* EPERM on disconnect ? */
exit(1);
}
if (rc == 0) {
perror("data read 0");
exit(1);
}
wrc = fwrite(buf,rc,1,f);
if (wrc != 1) {
perror("fwrite");
exit(1);
}
cnt += rc;
/* progress indication */
t = time(NULL);
if (t - t0 == 0 || cnt == 0 || tx == t)
/* avoid division errors */
/* only once per second */
continue;
tx = t;
printf("got %d/%u bytes=%g%% in %d sec,"
" %g Kbytes/s, to go %i sec \r",
cnt, fsize, 100.0 * cnt / fsize, t - t0,
cnt / 1000.0 / (t - t0),
( fsize - cnt ) * (t - t0) / cnt);
fflush(stdout);
}
if (cnt != fsize) {
printf("Warning: "
"file size changed: initial: %u, actual: %d\n",
fsize, cnt);
}
if (t == t0) t++; /* white lie for fast transfers */
printf("\r%79s\r",""); /* Cleanup the progress line */
printf("Received %s, %d bytes in %d sec. %g KBytes/sec\n",
fname, cnt, t - t0, cnt / 1000.0 / (t - t0));
rc = fclose(f);
if (rc != 0) {
perror("fclose");
exit(1);
}
if (fdate) {
struct utimbuf utb;
utb.actime = fdate;
utb.modtime = fdate;
rc = utime(fname,&utb);
if (rc != 0) {
perror(fname);
}
}
free(fname);
close(cfd);
return 0;
}
int receivefile(int mode)
{
/*
The Psion 5 tries the following connections:
Epoc32:EikonIr:v1.0 IrDA:TinyTP:LsapSel
IrDA:IrCOMM Parameters
IrLPT IrDA:IrLMP:LsapSel
connect on 2
Warning: discovery reply after 101ms
*/
int addrlen = sizeof(struct sockaddr_irda);
/* int oflags; */
/* int mtu; */
int fd;
struct sockaddr_irda peer;
int cfd;
/* Create socket */
fd = socket(AF_IRDA, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
exit(-1);
}
/* Bind local service */
peer.sir_family = AF_IRDA;
strcpy(peer.sir_name, "Epoc32:EikonIr:v1.0");
peer.sir_lsap_sel = LSAP_ANY;
if (bind(fd, (struct sockaddr*)&peer, sizeof(struct sockaddr_irda))) {
perror("bind");
return -1;
}
if (listen(fd, 2)) {
perror("listen");
return -1;
}
/* FIXME: allow more simultaneous clients */
for (;;) {
cfd = accept(fd, (struct sockaddr *) &peer, &addrlen);
if (cfd < 0) {
perror("accept");
return -1;
}
if (handle_client(cfd))
break;
if (mode == 1)
break;
}
sleep(1);
close(fd);
return 0;
}
int
main(int argc, char* argv[]) {
char* argv0 = argv[0];
if (argc <= 1) {
fprintf(stderr, "Usage: %s [-s file] [-r] [-d]\n", argv0);
fprintf(stderr, "\t-s file\tSend file to the Psion\n");
fprintf(stderr, "\t-r\tReceive a file from the Psion\n");
fprintf(stderr, "\t-b\tReceive multiple files (batch mode)\n");
exit(1);
}
/* skip program name */
argv++; argc--;
for ( ; argc>0 && argv[0] ; argc-- , argv++) {
if (0 == strcmp(argv[0],"-s")) {
/* FIXME: sending multiple files does not
work yet. We need to wait for the user
to select receive again on the psion */
if (argv[1] && 0 == strcmp(argv[1],"--")) {
/* Allow the user to send ANY filename */
argv++; argv++;
for ( ; argv[1] ; argv++ , argc--) {
sendfile(argv[1]);
}
} else {
/* Send files upto next switch */
while (argv[1] && *argv[1] != '-') {
sendfile(argv[1]);
argv++; argc--;
}
}
} else if (argv[0] && 0 == strcmp(argv[0],"-r")) {
receivefile(1);
} else if (argv[0] && 0 == strcmp(argv[0],"-b"))
receivefile(0);
else {
fprintf(stderr,"Error: unknown switch %s\n", argv[0]);
fprintf(stderr,"Call without args for usage: %s\n",
argv0);
exit(1);
}
}
return 0;
} |