/* motoacc A command-line program for accessing files on Motorola cellphones via the USB bus. Requires the library p2kmoto by Dmitry Nezhevenko, available on sourceforge. Compile with: gcc -o motoacc motoacc.c -lp2kmoto -lusb motoacc is (c) 2006 Volker Schatz (www.volkerschatz.com) motoacc 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 3 of the License, or (at your option) any later version. sndsys 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. To obtain a copy of the GNU General Public License, see http://www.gnu.org/licenses/ . */ /*----------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include /*----------------------------------------------------------------------*/ #define EXEC_PREINIT 0 #define EXEC_PREOPEN 1 #define EXEC_NORMAL 2 // #define EXEC_NEEDLIST 3 typedef struct { const char *command, *doc; int (*execute)(int argc, char**argv); int argc, execwhen; } motoacc_cmd; /*----------------------------------------------------------------------*/ void p2kerrmsg( const int status, const char *doingwhat ); void allocfilelist(); void addtofilelist(const p2k_fileInfo *entry); int usagemessage( int argc, char **argv ); int listusbdev( int argc, char **argv ); int phoneinfo( int argc, char **argv ); void fl_print(p2k_fileInfo fileinfo); int listfiles( int argc, char **argv ); int getfile( int argc, char **argv ); int putfile( int argc, char **argv ); void fl_buildlist(p2k_fileInfo fileinfo); int backup( int argc, char **argv ); int remotechattr( int argc, char **argv ); int delfile( int argc, char **argv ); /*----------------------------------------------------------------------*/ motoacc_cmd commands[] = { "help", "help\t\tdisplay this usage message", usagemessage, -1, EXEC_PREINIT, "usbdev", "usbdev\t\tlist USB devices which might be cellphones", listusbdev, 2, EXEC_PREOPEN, "info","info\t\tprint phone type, drive name, free space and number of files", phoneinfo, 2, EXEC_NORMAL, "list", "list\t\tlists all files on the cellphone filesystem", listfiles, 2, EXEC_NORMAL, "get", "get [-z ] [...]\n\t\tdownloads file from cellphone. -z gives local file name. Otherwise multiple remote files may be given. Remote file names must always be followed by a colon and their size. This should be used only for regular files, as the attributes of the remote file are deleted as a side effect. Use backup to retain attributes.", getfile, -3, EXEC_NORMAL, "put", "put [-z ] [...]\n\t\tuploads file to cellphone. -z gives local file name. Otherwise multiple remote files may be given. Remote file names should include full paths; without -z the local files are expected to reside in the current directory. No remote file attributes are set. EXISTING REMOTE FILES WILL BE OVERWRITTEN!", putfile, -3, EXEC_NORMAL, "rm", "rm [...]\tremoves the remote file(s)", delfile, -3, EXEC_NORMAL, "backup", "backup []\n\t\tdownloads all files starting with to a local copy of the directory structure. Unlike `get', this keeps the remote files' attributes. The attributes are also reflected in the local file permissions: read-only -> no write permissions, hidden -> not group/world-readable, system -> executable, lock -> sticky flag", backup, -2, EXEC_NORMAL, "chattr", "chattr [ ...]\n\t\tsets the attributes of remote files. Attributes are: r (read only), h (hidden), s (system), l (lock) or 0 (no attributes).", remotechattr, -4, EXEC_NORMAL, NULL, NULL, NULL, 0, 0 }; #define FILELIST_BASESIZE 0x10 struct { p2k_fileInfo *files; int nalloc, n; } filelist = { NULL, 0, 0 }; char *filelist_path = NULL; /*----------------------------------------------------------------------*/ int main(int argc, char ** argv) { motoacc_cmd *cmd; int status; if( argc < 2 ) { usagemessage(argc, argv); return -1; } for( cmd= commands; cmd->command; ++cmd ) if( !strcmp(cmd->command, argv[1]) ) break; if( !cmd->command ) { fprintf( stderr, "%s: Error: unknown command `%s'.\n", argv[0], argv[1] ); usagemessage(argc, argv); return -1; } if( cmd->argc >= 0? (argc != cmd->argc) : (argc < -cmd->argc) ) { fprintf(stderr, "%s: Error: wrong number of arguments for command `%s'.\n", argv[0], argv[1]); usagemessage(argc, argv); return -1; } if( cmd->execwhen == EXEC_PREINIT ) return cmd->execute(argc, argv); p2k_init(); if( cmd->execwhen == EXEC_PREOPEN ) return cmd->execute(argc, argv); status= p2k_findPhone(); if( status == P2K_PHONE_AT ) { //We must switch phone to p2k mode by sending AT+MODE=8 //command. printf("Switching phone to P2K mode ...\n"); // This is hardcoded to /dev/ttyACM0 system("echo \"AT+MODE=8\" > /dev/ttyACM0"); // Wait while phone changes mode... sleep(5); status= p2k_findPhone(); } if( status == P2K_PHONE_NONE ) { fprintf(stderr, "%s: Error: No phone found. Aborting.\n", argv[0]); return -1; } else if( status == P2K_PHONE_AT ) { fprintf(stderr, "%s: Error: Could not switch phone to P2K mode. Aborting.\n", argv[0]); return -1; } p2k_openPhone(); // p2k_FSAC_Close(); // just in case somebody left something open cmd->execute(argc, argv); p2k_closePhone(); } char *p2kerrstr[] = { NULL, "Cannot find P2K phone", "Cannot open phone", "Cannot set configuration", "Cannot acquire interface", "Cannot close phone", "No connection", "Wrong answer count", "Wrong answer size", "Negative packet header buffer size - bug in p2kmoto library", "Packet header ID error", "Packet header status error - expected data", "Packet header status error - expected no data", "Data error - expected zero datum", "Error uploading data", "Erroneous read data size", "Error reading data (is file size correct?)", "Error in packet header", "Incorrect SEEM size", "Buffer too long - bug in motoacc" }; void p2kerrmsg( const int status, const char *doingwhat ) { if( status >= 0 ) return; if( doingwhat ) fprintf(stderr, "P2K error %s: ", doingwhat); else fprintf(stderr, "P2K error: "); if( status == P2K_E_BUG ) fprintf(stderr, "Unsupported filelist answer - bug in p2kmoto library.\n"); else if( status == P2K_E_OLD ) fprintf(stderr, "Error in SEEM data packet.\n"); else if( status < -19 ) fprintf(stderr, "Error with unknown error code - blame p2kmoto library.\n"); else fprintf(stderr, "%s.\n", p2kerrstr[-status]); } void allocfilelist() { if( !filelist.files ) free( filelist.files ); filelist.files= (p2k_fileInfo *)malloc( FILELIST_BASESIZE * sizeof(p2k_fileInfo) ); filelist.nalloc= FILELIST_BASESIZE; filelist.n= 0; } void addtofilelist(const p2k_fileInfo *entry) { p2k_fileInfo *oldlist; if( !filelist.files ) allocfilelist(); if( filelist.n == filelist.nalloc ) { oldlist= filelist.files; filelist.files= (p2k_fileInfo *)malloc( 2*filelist.nalloc * sizeof(p2k_fileInfo) ); if( !filelist.files ) { filelist.files= oldlist; return; } memcpy(filelist.files, oldlist, filelist.nalloc * sizeof(p2k_fileInfo)); filelist.nalloc *= 2; free(oldlist); } memcpy(filelist.files+filelist.n, entry, sizeof(p2k_fileInfo)); ++filelist.n; } int usagemessage( int argc, char **argv ) { motoacc_cmd *cmd; printf("usage: %s command [argument(s)]\n" "Accesses a Motorola cellphone via P2K. Commands and their arguments:\n\n", argv[0] ); for( cmd= commands; cmd->command; ++cmd ) printf("%s\n\n", cmd->doc); return 0; } int listusbdev( int argc, char **argv ) { p2k_devInfo *devinfo; int count; printf("USB device list:\n"); devinfo= p2k_getDevList(); while( devinfo[count].vendor>=0 ) { printf("%04x:%04x: [%s] [%s]\n", devinfo[count].vendor, devinfo[count].product, devinfo[count].manufacturerStr, devinfo[count].productStr); ++count; } return 0; } int phoneinfo( int argc, char **argv ) { unsigned char str[200]; int size, status= 0, p2kstatus; if( (p2kstatus= p2k_getPhoneModel( str )) > 0 ) printf("Phone Model: %s\n", str); else { p2kerrmsg(p2kstatus, "querying phone model"); printf("Cannot get phone model\n"); status= -1; } if( (p2kstatus= p2k_getDriveName(str)) > 0 ) printf("Drive: %s\n", str); else { p2kerrmsg(p2kstatus, "querying drive name"); printf("Cannot get drive name\n"); status= -1; } size= p2k_freeSpace(str); if( size > 0 ) printf("Free space: %d bytes\n", size); else { p2kerrmsg(size, "querying free space"); printf("Cannot get free space"); status= -1; } size= p2k_fileCount(); if( size > 0 ) printf("File count: %d\n", size); else { p2kerrmsg(p2kstatus, "querying file count"); printf("Cannot get file count"); status= -1; } return status; } void fl_print(p2k_fileInfo fileinfo) { static char *attrstr[]= { " ", "RO ", "Hidd", "RO+H", "Sys " }; char *thisattrstr; if( fileinfo.attr == 68 ) thisattrstr= "Lock"; else if( fileinfo.attr > 4 ) thisattrstr= "??? "; else thisattrstr= attrstr[fileinfo.attr]; printf("%4d %6ld %3d %s %s\n", fileinfo.id, fileinfo.size, fileinfo.owner, thisattrstr, fileinfo.name ); } int listfiles( int argc, char **argv ) { int status; printf(" # Size Own Attr Name\n"); status= p2k_fileList(fl_print); if( status < 0 ) p2kerrmsg(status, "getting file list"); return status; } int getfile( int argc, char **argv ) { FILE *fp; unsigned char *buf; char *localfile, *scan; long size; int result, remotefile, status; buf= malloc(0x400); if( !buf ) { fprintf(stderr, "%s get: Error: Could not allocate buffer.\n", argv[0]); return -1; } if( !strcmp(argv[2], "-z") ) { if( argc!=5 ) { if( argc > 5 ) fprintf(stderr, "%s get: Only one remote file is allowed when giving -z option.\n", argv[0]); else fprintf(stderr, "%s get: No remote file given.\n", argv[0]); free(buf); return -1; } localfile= argv[3]; remotefile= 4; } else { localfile= NULL; remotefile= 2; } while( remotefile < argc ) { for( scan= argv[remotefile]; *scan && *scan != ':'; ++scan ); if( !*scan ) { fprintf(stderr, "%s get: no file size given for file `%s'.\n", argv[0], argv[remotefile]); free(buf); return -1; } *scan= 0; size= strtol(scan+1, NULL, 0); printf("Downloading file `%s', size %ld...\n", argv[remotefile], size); if( !localfile ) { for( scan= argv[remotefile]; *scan; ++scan ); for( --scan ; scan >= argv[remotefile] && *scan != '/'; --scan ); localfile= scan+1; } fp= fopen( localfile, "w" ); if( !fp ) { fprintf(stderr, "%s get: Could not create local file `%s'.\n", argv[0], localfile); free(buf); return -1; } if( 0 > (result= p2k_FSAC_Open(argv[remotefile], 0)) ) { p2kerrmsg(result, "opening file"); fprintf(stderr, "%s get: Could not open remote file `%s'.\n", argv[0], argv[remotefile]); fclose(fp); free(buf); return -1; } while( size > 0x400L ) { result= p2k_FSAC_Read(buf, 0x400); if( result < 0 ) { p2kerrmsg(result, "reading data"); fprintf(stderr, "%s get: Error while reading data from file `%s'.\n", argv[0], argv[remotefile]); p2k_FSAC_Close(); fclose(fp); free(buf); return -1; } fwrite(buf, 1L, 0x400L, fp); size -= 0x400L; } result= p2k_FSAC_Read(buf, (int)size); if( result < 0 ) { p2kerrmsg(result, "reading data"); fprintf(stderr, "%s get: Error while reading data from file `%s'.\n", argv[0], argv[remotefile]); p2k_FSAC_Close(); fclose(fp); free(buf); return -1; } fwrite(buf, 1L, size, fp); p2k_FSAC_Close(); fclose(fp); localfile= NULL; ++remotefile; } free(buf); return 0; } int putfile( int argc, char **argv ) { FILE *fp; unsigned char *buf; char *localfile, *scan; long size; int result, remotefile, status; buf= malloc(0x400); if( !buf ) { fprintf(stderr, "%s put: Error: Could not allocate buffer.\n", argv[0]); return -1; } if( !strcmp(argv[2], "-z") ) { if( argc!=5 ) { if( argc > 5 ) fprintf(stderr, "%s put: Only one remote file is allowed when -z option is present.\n", argv[0]); else fprintf(stderr, "%s put: No remote file name given.\n", argv[0]); free(buf); return -1; } localfile= argv[3]; remotefile= 4; } else { localfile= NULL; remotefile= 2; } while( remotefile < argc ) { if( !localfile ) { for( scan= argv[remotefile]; *scan; ++scan ); for( --scan ; scan >= argv[remotefile] && *scan != '/'; --scan ); localfile= scan+1; } printf("Uploading file `%s' --> `%s'...\n", localfile, argv[remotefile]); fp= fopen( localfile, "r" ); if( !fp ) { fprintf(stderr, "%s put: Could not open local file `%s'.\n", argv[0], localfile); free(buf); return -1; } fseek(fp, 0L, SEEK_END); size= ftell(fp); fseek(fp, 0L, SEEK_SET); if( 0 > (result= p2k_FSAC_Open(argv[remotefile], 0)) ) { p2kerrmsg(result, "opening file"); fprintf(stderr, "%s get: Could not open remote file `%s'.\n", argv[0], argv[remotefile]); fclose(fp); free(buf); return -1; } while( size > 0x400L ) { fread(buf, 1L, 0x400L, fp); result= p2k_FSAC_Write(buf, 0x400); if( result < 0 ) { p2kerrmsg(result, "writing data"); fprintf(stderr, "%s put: Error while writing data to file `%s'.\n", argv[0], argv[remotefile]); p2k_FSAC_Close(); fclose(fp); free(buf); return -1; } size -= 0x400L; } fread(buf, 1L, size, fp); result= p2k_FSAC_Write(buf, (int)size); if( result < 0 ) { p2kerrmsg(result, "writing data"); fprintf(stderr, "%s put: Error while writing data to file `%s'.\n", argv[0], argv[remotefile]); p2k_FSAC_Close(); fclose(fp); free(buf); return -1; } p2k_FSAC_Close(); fclose(fp); localfile= NULL; ++remotefile; } free(buf); return 0; } void fl_buildlist(p2k_fileInfo fileinfo) { addtofilelist(&fileinfo); } int backup( int argc, char **argv ) { p2k_fileInfo *scan, *top; struct stat statresult; FILE *handle; unsigned char *buf; char *path, *dir, *partdir, *lpath; long size; int status, pathlen, fmode, basemode, found, errno_; printf("Creating file list...\n"); allocfilelist(); status= p2k_fileList(fl_buildlist); if( status < 0 ) { p2kerrmsg(status, "getting file list"); fprintf(stderr, "%s backup: Could not list files.\n", argv[0]); return status; } if( argc > 2 ) { path= argv[2]; pathlen= strlen(path); if( *path == '/' ) lpath= path+1; else lpath= path; for( dir= path+pathlen; dir > lpath && *dir != '/'; --dir ); if( *dir == '/' ) { *dir= 0; status= stat(lpath, &statresult); if( status < 0 ) { if( errno == ENOENT ) { partdir= lpath; while( 13 ) { while( *partdir && *partdir!='/' ) ++partdir; if( !*partdir ) break; *partdir= 0; // printf("creating %s\n", lpath); mkdir(lpath, 0777); *partdir= '/'; ++partdir; } // printf("creating %s\n", lpath); status= mkdir(lpath, 0777); if( status < 0 ) { perror(argv[0]); fprintf(stderr, "%s backup: Could not create local target directory `%s'.\n", argv[0], lpath); return -1; } } else { perror(argv[0]); fprintf(stderr, "%s backup: Error while checking local target directory `%s'.\n", argv[0], lpath); return -1; } } *dir= '/'; } } else { path= NULL; pathlen= 0; } buf= malloc(0x400); if( !buf ) { fprintf(stderr, "%s backup: Could not allocate buffer.\n", argv[0]); return -1; } basemode= umask(0); umask(basemode); basemode= ~basemode & 0666; found= 0; top= filelist.files + filelist.n; for( scan= filelist.files; scan< top; ++scan ) { if( !path || !strncmp(path, scan->name, pathlen) ) { found= 1; printf("Downloading %s ...\n", scan->name); if( *scan->name == '/' ) lpath= scan->name+1; else lpath= scan->name; for( dir= scan->name+pathlen; *dir; ++dir ); for( --dir; dir > scan->name+pathlen && *dir != '/'; --dir ); if( dir > scan->name+pathlen ) { *dir= 0; status= stat(lpath, &statresult); if( status < 0 && errno == ENOENT ) { partdir= scan->name+pathlen+1; while( *partdir && *partdir != '/' ) ++partdir; while( *partdir ) { *partdir= 0; // printf("creating %s\n", lpath); mkdir(lpath, 0777); *partdir= '/'; ++partdir; while( *partdir && *partdir != '/' ) ++partdir; } // printf("creating %s\n", lpath); status= mkdir(lpath, 0777); if( status < 0 ) { perror(argv[0]); fprintf(stderr, "%s backup: Could not create local target directory `%s'.\n", argv[0], lpath); continue; } } *dir= '/'; } handle= fopen(lpath, "w"); if( !handle ) { perror(argv[0]); fprintf(stderr, "%s backup: Could not create local file `%s'.\n", argv[0], lpath); continue; } status= p2k_FSAC_Open(scan->name, scan->attr); if( status < 0 ) { p2kerrmsg(status, "opening remote file"); fprintf(stderr, "%s backup: Error opening remote file `%s'.\n", argv[0], scan->name); fclose(handle); continue; } errno= 0; for( size= scan->size; size> 0x400; size -= 0x400 ) { status= p2k_FSAC_Read(buf, 0x400); if( status < 0 ) goto p2kreaderr; fwrite(buf, 1L, 0x400L, handle); } status= p2k_FSAC_Read(buf, (int)size); if( status < 0 ) { p2kreaderr: p2kerrmsg(status, "reading data"); fprintf(stderr, "%s backup: Error reading data from `%s'.\n", argv[0], lpath); p2k_FSAC_Close(); fclose(handle); continue; } p2k_FSAC_Close(); fwrite(buf, 1L, size, handle); fclose(handle); if( errno ) { fprintf(stderr, "%s backup: Error writing to local file `%s'.\n", argv[0], lpath); continue; } fmode= basemode; // We map the read-only attribute to read-only local files, hidden to // lacking read permission for group and others, "system" to executable, // "lock" to sticky. if( (scan->attr & 1) != 0 ) fmode &= ~0222; if( (scan->attr & 2) != 0 ) fmode &= ~0044; if( (scan->attr & 4) != 0 ) fmode |= ((fmode >> 2) & 0111); if( (scan->attr & 64) != 0 ) fmode |= 01000; status= chmod(lpath, fmode); if( status < 0 ) fprintf(stderr, "%s backup: Could not set file mode according to attributes.\n", argv[0]); } } if( !found ) fprintf(stderr, "%s backup: No suitable files found.\n", argv[0]); free(buf); return 0; } int remotechattr( int argc, char **argv ) { unsigned char attribs; int status, remotefile; char *scan; attribs= 0; if( *argv[2] == '0' ) if( argv[2][1] ) { fprintf(stderr, "%s chattr: 0 (no attribute) cannot be combined with any other attribute.\n", argv[0]); return -1; } else; else for( scan= argv[2]; *scan; ++scan ) if( *scan == 'r' ) attribs |= 1; else if( *scan == 'h' ) attribs |= 2; else if( *scan == 's' ) attribs |= 4; else if( *scan == 'l' ) attribs |= 64; else { fprintf(stderr, "%s chattr: unknown attribute `%c'.\n", argv[0], *scan); return -1; } for( remotefile= 3; remotefile < argc; ++remotefile ) { status= p2k_FSAC_Open(argv[remotefile], attribs); if( status < 0 ) { p2kerrmsg(status, "changing file attributes"); fprintf(stderr, "%s chattr: Could not change attributes of file `%s'.\n", argv[0], argv[remotefile]); } p2k_FSAC_Close(); } return 0; } int delfile( int argc, char **argv ) { int fileind, confirm, status, retcode= 0; for( fileind= 2; fileind < argc; ++fileind ) { printf("About to delete remote file `%s'. Please confirm by typing 'y':\n", argv[fileind]); confirm= getchar(); if( confirm != 'y' && confirm != 'Y' ) printf("NOT deleting `%s'.\n", argv[fileind]); else { status= p2k_FSAC_Delete(argv[fileind]); if( status < 0 ) { p2kerrmsg(status, "deleting file"); fprintf(stderr, "%s rm: Error deleting `%s'.\n", argv[0], argv[fileind]); retcode= -1; } } while( getchar() != '\n' ); } return retcode; }