1/* $NetBSD: utoppya.c,v 1.5 2011/09/05 18:11:53 joerg Exp $ */ 2 3/*- 4 * Copyright (c) 2006 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Steve C. Woodford. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32#include <sys/types.h> 33#include <sys/stat.h> 34#include <sys/ioctl.h> 35 36#include <err.h> 37#include <errno.h> 38#include <fcntl.h> 39#include <libgen.h> 40#include <sysexits.h> 41#include <stdio.h> 42#include <stdlib.h> 43#include <strings.h> 44#include <unistd.h> 45#include <time.h> 46#include <inttypes.h> 47 48#include <dev/usb/utoppy.h> 49 50#define GLOBAL 51#include "progressbar.h" 52 53#define _PATH_DEV_UTOPPY "/dev/utoppy0" 54 55/* 56 * This looks weird for a reason. The toppy protocol allows for data to be 57 * transferred in 65535-byte chunks only. Anything more than this has to be 58 * split within the driver. The following value leaves enough space for the 59 * packet header plus some alignmnent slop. 60 */ 61#define TOPPY_IO_SIZE 0xffec 62 63static int toppy_fd; 64 65static void cmd_df(int, char **); 66static void cmd_ls(int, char **); 67static void cmd_rm(int, char **); 68static void cmd_mkdir(int, char **); 69static void cmd_rename(int, char **); 70static void cmd_get(int, char **); 71static void cmd_put(int, char **); 72 73static const struct toppy_command { 74 const char *tc_cmd; 75 void (*tc_handler)(int, char **); 76} toppy_commands[] = { 77 {"df", cmd_df}, 78 {"ls", cmd_ls}, 79 {"get", cmd_get}, 80 {"mkdir", cmd_mkdir}, 81 {"put", cmd_put}, 82 {"rename", cmd_rename}, 83 {"rm", cmd_rm}, 84 {NULL, NULL} 85}; 86 87__dead static void 88usage(void) 89{ 90 91 fprintf(stderr, "usage: %s [-f <path>] <cmd> ...\n", 92 getprogname()); 93 94 exit(EX_USAGE); 95} 96 97int 98main(int argc, char *argv[]) 99{ 100 const struct toppy_command *tc; 101 const char *devpath; 102 int ch; 103 104 setprogname(argv[0]); 105 devpath = _PATH_DEV_UTOPPY; 106 107 while ((ch = getopt(argc, argv, "f:")) != -1) { 108 switch (ch) { 109 case 'f': 110 devpath = optarg; 111 break; 112 113 default: 114 usage(); 115 } 116 } 117 argc -= optind; 118 argv += optind; 119 120 if (argc == 0) 121 usage(); 122 123 for (tc = toppy_commands; tc->tc_cmd != NULL; tc++) 124 if (strcasecmp(argv[0], tc->tc_cmd) == 0) 125 break; 126 127 if (tc->tc_cmd == NULL) 128 errx(EX_USAGE, "'%s' is not a valid command", argv[0]); 129 130 if ((toppy_fd = open(devpath, O_RDWR)) < 0) 131 err(EX_OSERR, "open(%s)", devpath); 132 133 (*tc->tc_handler)(argc, argv); 134 135 close(toppy_fd); 136 137 return (0); 138} 139 140static int 141find_toppy_dirent(const char *path, struct utoppy_dirent *udp) 142{ 143 struct utoppy_dirent ud; 144 char *d, *b, dir[FILENAME_MAX]; 145 ssize_t l; 146 147 strncpy(dir, path, sizeof(dir)); 148 b = basename(dir); 149 d = dirname(dir); 150 if (strcmp(b, "/") == 0 || strcmp(b, ".") == 0 || strcmp(d, ".") == 0) 151 errx(EX_USAGE, "'%s' is not a valid Toppy pathname", path); 152 153 if (ioctl(toppy_fd, UTOPPYIOREADDIR, &d) < 0) 154 err(EX_OSERR, "ioctl(UTOPPYIOREADDIR, %s)", d); 155 156 if (udp == NULL) 157 udp = &ud; 158 159 while ((l = read(toppy_fd, udp, sizeof(*udp))) == sizeof(*udp)) { 160 if (strcmp(b, udp->ud_path) == 0) 161 break; 162 } 163 164 if (l < 0) 165 err(EX_OSERR, "read(TOPPYDIR, %s)", d); 166 167 if (l == 0) 168 return (0); 169 170 while (read(toppy_fd, &ud, sizeof(ud)) > 0) 171 ; 172 173 return (1); 174} 175 176static void 177cmd_df(int argc, char **argv) 178{ 179 struct utoppy_stats us; 180 181 if (ioctl(toppy_fd, UTOPPYIOSTATS, &us) < 0) 182 err(EX_OSERR, "ioctl(UTOPPYIOSTATS)"); 183 184 printf("Hard Disk Size: %" PRId64 " MB\n", us.us_hdd_size / (1024 * 1024)); 185 printf("Hard Disk Free: %" PRId64 " MB\n", us.us_hdd_free / (1024 * 1024)); 186} 187 188static void 189cmd_ls(int argc, char **argv) 190{ 191 struct utoppy_dirent ud; 192 struct tm *tm; 193 char *dir, *ext, dirbuf[2], ex, ft, tmbuf[32]; 194 ssize_t l; 195 196 if (argc == 1) { 197 dirbuf[0] = '/'; 198 dirbuf[1] = '\0'; 199 dir = dirbuf; 200 } else 201 if (argc == 2) 202 dir = argv[1]; 203 else 204 errx(EX_USAGE, "usage: ls [toppy-pathname]"); 205 206 if (ioctl(toppy_fd, UTOPPYIOREADDIR, &dir) < 0) 207 err(EX_OSERR, "ioctl(UTOPPYIOREADDIR, %s)", dir); 208 209 while ((l = read(toppy_fd, &ud, sizeof(ud))) == sizeof(ud)) { 210 switch (ud.ud_type) { 211 default: 212 ft = '?'; 213 break; 214 215 case UTOPPY_DIRENT_DIRECTORY: 216 ft = 'd'; 217 break; 218 219 case UTOPPY_DIRENT_FILE: 220 ft = '-'; 221 break; 222 } 223 224 if ((ext = strrchr(ud.ud_path, '.')) != NULL && 225 strcasecmp(ext, ".tap") == 0) 226 ex = 'x'; 227 else 228 ex = '-'; 229 230 tm = localtime(&ud.ud_mtime); 231 strftime(tmbuf, sizeof(tmbuf), "%b %e %G %R", tm); 232 233 printf("%crw%c %11lld %s %s\n", ft, ex, (long long)ud.ud_size, 234 tmbuf, ud.ud_path); 235 } 236 237 if (l < 0) 238 err(EX_OSERR, "read(utoppy_dirent)"); 239} 240 241static void 242cmd_rm(int argc, char **argv) 243{ 244 char *path; 245 246 if (argc != 2) 247 errx(EX_USAGE, "usage: rm <toppy-pathname>"); 248 249 path = argv[1]; 250 251 if (ioctl(toppy_fd, UTOPPYIODELETE, &path) < 0) 252 err(EX_OSERR, "ioctl(UTOPPYIODELETE, %s)", path); 253} 254 255static void 256cmd_mkdir(int argc, char **argv) 257{ 258 char *path; 259 260 if (argc != 2) 261 errx(EX_USAGE, "usage: mkdir <toppy-pathname>"); 262 263 path = argv[1]; 264 265 if (find_toppy_dirent(path, NULL)) 266 errx(EX_DATAERR, "'%s' already exists", path); 267 268 if (ioctl(toppy_fd, UTOPPYIOMKDIR, &path) < 0) 269 err(EX_OSERR, "ioctl(UTOPPYIOMKDIR, %s)", path); 270} 271 272static void 273cmd_rename(int argc, char **argv) 274{ 275 struct utoppy_dirent ud; 276 struct utoppy_rename ur; 277 char *oldpath, *newpath, *o, *n; 278 279 if (argc != 3) 280 errx(EX_USAGE, "usage: rename <from> <to>"); 281 282 o = oldpath = argv[1]; 283 n = newpath = argv[2]; 284 285 for (o = oldpath; *o != '\0'; o++) 286 if (*o == '\\') 287 *o = '/'; 288 for (n = newpath; *n != '\0'; n++) 289 if (*n == '\\') 290 *n = '/'; 291 292 for (o = oldpath; *o && *o == '\\'; o++) 293 ; 294 for (n = newpath; *n && *n == '\\'; n++) 295 ; 296 297 if (strcmp(n, o) == 0) 298 errx(EX_DATAERR, "'%s' and '%s' refer to the same file", 299 oldpath, newpath); 300 301 if (find_toppy_dirent(oldpath, &ud) == 0) 302 errx(EX_DATAERR, "'%s' does not exist on the Toppy", oldpath); 303 304 if (ud.ud_type != UTOPPY_DIRENT_FILE) 305 errx(EX_DATAERR, "%s: not a regular file", oldpath); 306 307 if (find_toppy_dirent(newpath, &ud)) 308 errx(EX_DATAERR, "'%s' already exists", newpath); 309 310 ur.ur_old_path = o; 311 ur.ur_new_path = n; 312 313 if (ioctl(toppy_fd, UTOPPYIORENAME, &ur) < 0) 314 err(EX_OSERR, "ioctl(UTOPPYIORENAME, %s, %s)", oldpath, 315 newpath); 316} 317 318 319static void 320init_progress(FILE *to, char *f, off_t fsize, off_t restart) 321{ 322 struct ttysize ts; 323 324 if (ioctl(fileno(to), TIOCGSIZE, &ts) == -1) 325 ttywidth = 80; 326 else 327 ttywidth = ts.ts_cols; 328 329 ttyout = to; 330 progress = 1; 331 bytes = 0; 332 filesize = fsize; 333 restart_point = restart; 334 prefix = f; 335} 336 337static void 338cmd_get(int argc, char **argv) 339{ 340 struct utoppy_readfile ur; 341 struct utoppy_dirent ud; 342 struct stat st; 343 char *dst, dstbuf[FILENAME_MAX]; 344 uint8_t *buf; 345 ssize_t l; 346 size_t rv; 347 int ch, turbo_mode = 0, reget = 0, progbar = 0; 348 FILE *ofp, *to; 349 350 optind = 1; 351 optreset = 1; 352 353 while ((ch = getopt(argc, argv, "prt")) != -1) { 354 switch (ch) { 355 case 'p': 356 progbar = 1; 357 break; 358 case 'r': 359 reget = 1; 360 break; 361 case 't': 362 turbo_mode = 1; 363 break; 364 default: 365 get_usage: 366 errx(EX_USAGE, "usage: get [-prt] <toppy-pathname> " 367 "[file | directory]"); 368 } 369 } 370 argc -= optind; 371 argv += optind; 372 373 if (argc == 1) 374 dst = basename(argv[0]); 375 else 376 if (argc == 2) { 377 dst = argv[1]; 378 if (stat(dst, &st) == 0 && S_ISDIR(st.st_mode)) { 379 snprintf(dstbuf, sizeof(dstbuf), "%s/%s", dst, 380 basename(argv[0])); 381 dst = dstbuf; 382 } 383 } else 384 goto get_usage; 385 386 ur.ur_path = argv[0]; 387 ur.ur_offset = 0; 388 389 if ((buf = malloc(TOPPY_IO_SIZE)) == NULL) 390 err(EX_OSERR, "malloc(TOPPY_IO_SIZE)"); 391 392 if (strcmp(dst, "-") == 0) { 393 ofp = stdout; 394 to = stderr; 395 if (reget) 396 warnx("Ignoring -r option in combination with stdout"); 397 } else { 398 to = stdout; 399 400 if (reget) { 401 if (stat(dst, &st) < 0) { 402 if (errno != ENOENT) 403 err(EX_OSERR, "stat(%s)", dst); 404 } else 405 if (!S_ISREG(st.st_mode)) 406 errx(EX_DATAERR, "-r only works with regular " 407 "files"); 408 else 409 ur.ur_offset = st.st_size; 410 } 411 412 if ((ofp = fopen(dst, reget ? "a" : "w")) == NULL) 413 err(EX_OSERR, "fopen(%s)", dst); 414 } 415 416 if (progbar) { 417 if (find_toppy_dirent(ur.ur_path, &ud) == 0) 418 ud.ud_size = 0; 419 init_progress(to, dst, ud.ud_size, ur.ur_offset); 420 } 421 422 if (ioctl(toppy_fd, UTOPPYIOTURBO, &turbo_mode) < 0) 423 err(EX_OSERR, "ioctl(UTOPPYIOTURBO, %d)", turbo_mode); 424 425 if (ioctl(toppy_fd, UTOPPYIOREADFILE, &ur) < 0) 426 err(EX_OSERR, "ioctl(UTOPPYIOREADFILE, %s)", ur.ur_path); 427 428 if (progbar) 429 progressmeter(-1); 430 431 for (;;) { 432 while ((l = read(toppy_fd, buf, TOPPY_IO_SIZE)) < 0 && 433 errno == EINTR) 434 ; 435 436 if (l <= 0) 437 break; 438 439 rv = fwrite(buf, 1, l, ofp); 440 441 if (rv != (size_t)l) { 442 if (ofp != stdout) 443 fclose(ofp); 444 progressmeter(1); 445 err(EX_OSERR, "fwrite(%s)", dst); 446 } 447 bytes += l; 448 } 449 450 if (progbar) 451 progressmeter(1); 452 453 if (ofp != stdout) 454 fclose(ofp); 455 456 if (l < 0) 457 err(EX_OSERR, "read(TOPPY: ur.ur_path)"); 458 459 free(buf); 460} 461 462static void 463cmd_put(int argc, char **argv) 464{ 465 struct utoppy_writefile uw; 466 struct utoppy_dirent ud; 467 struct stat st; 468 char dstbuf[FILENAME_MAX]; 469 char *src; 470 void *buf; 471 ssize_t rv; 472 size_t l; 473 int ch, turbo_mode = 0, reput = 0, progbar = 0; 474 FILE *ifp; 475 476 optind = 1; 477 optreset = 1; 478 479 while ((ch = getopt(argc, argv, "prt")) != -1) { 480 switch (ch) { 481 case 'p': 482 progbar = 1; 483 break; 484 case 'r': 485 reput = 1; 486 break; 487 case 't': 488 turbo_mode = 1; 489 break; 490 default: 491 put_usage: 492 errx(EX_USAGE, "usage: put [-prt] <local-pathname> " 493 "<toppy-pathname>"); 494 } 495 } 496 argc -= optind; 497 argv += optind; 498 499 if (argc != 2) 500 goto put_usage; 501 502 src = argv[0]; 503 uw.uw_path = argv[1]; 504 505 if (stat(src, &st) < 0) 506 err(EX_OSERR, "%s", src); 507 508 if (!S_ISREG(st.st_mode)) 509 errx(EX_DATAERR, "'%s' is not a regular file", src); 510 511 uw.uw_size = st.st_size; 512 uw.uw_mtime = st.st_mtime; 513 uw.uw_offset = 0; 514 515 if (find_toppy_dirent(uw.uw_path, &ud)) { 516 if (ud.ud_type == UTOPPY_DIRENT_DIRECTORY) { 517 snprintf(dstbuf, sizeof(dstbuf), "%s/%s", uw.uw_path, 518 basename(src)); 519 uw.uw_path = dstbuf; 520 } else 521 if (ud.ud_type != UTOPPY_DIRENT_FILE) 522 errx(EX_DATAERR, "'%s' is not a regular file.", 523 uw.uw_path); 524 else 525 if (reput) { 526 if (ud.ud_size > uw.uw_size) 527 errx(EX_DATAERR, "'%s' is already larger than " 528 "'%s'", uw.uw_path, src); 529 530 uw.uw_size -= ud.ud_size; 531 uw.uw_offset = ud.ud_size; 532 } 533 } 534 535 if ((buf = malloc(TOPPY_IO_SIZE)) == NULL) 536 err(EX_OSERR, "malloc(TOPPY_IO_SIZE)"); 537 538 if ((ifp = fopen(src, "r")) == NULL) 539 err(EX_OSERR, "fopen(%s)", src); 540 541 if (ioctl(toppy_fd, UTOPPYIOTURBO, &turbo_mode) < 0) 542 err(EX_OSERR, "ioctl(UTOPPYIOTURBO, %d)", turbo_mode); 543 544 if (ioctl(toppy_fd, UTOPPYIOWRITEFILE, &uw) < 0) 545 err(EX_OSERR, "ioctl(UTOPPYIOWRITEFILE, %s)", uw.uw_path); 546 547 if (progbar) 548 init_progress(stdout, src, st.st_size, uw.uw_offset); 549 550 if (progbar) 551 progressmeter(-1); 552 553 while ((l = fread(buf, 1, TOPPY_IO_SIZE, ifp)) > 0) { 554 rv = write(toppy_fd, buf, l); 555 if ((size_t)rv != l) { 556 fclose(ifp); 557 if (progbar) 558 progressmeter(1); 559 err(EX_OSERR, "write(TOPPY: %s)", uw.uw_path); 560 } 561 bytes += l; 562 } 563 564 if (progbar) 565 progressmeter(1); 566 567 if (ferror(ifp)) 568 err(EX_OSERR, "fread(%s)", src); 569 570 fclose(ifp); 571 free(buf); 572} 573