/* * DCAP - dCache Access Protocol client interface * * Copyright (C) 2000,2004 DESY Hamburg DMG-Division. * * AUTHOR: Tigran Mkrtchayn (tigran.mkrtchyan@desy.de) * * This program can be distributed under the terms of the GNU LGPL. * See the file COPYING.LIB * */ /* * $Id: dccp.c,v 1.77 2007-02-22 10:19:46 tigran Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #ifndef WIN32 # include # include "dcap_signal.h" #else # include "dcap_unix2win.h" extern int getopt(int, char * const *, const char *); #endif #include "dcap.h" #include "sigfig.h" #include "print_size.h" #include "dcap_str_util.h" #define DEFAULT_BUFFER 1048570L /* 1Mb */ #ifdef WIN32 # define PATH_SEPARATOR '\\' # define S_IWUSR 0 /* */ #else # define PATH_SEPARATOR '/' # ifndef O_BINARY # define O_BINARY 0 /* there is no BINARY OPEN in unix world */ # endif /* O_BINARY */ #endif /* WIN32 */ #ifndef MAXPATHLEN #define MAXPATHLEN 4096 #endif /* Number of bytes transferred for one sweep of the activity bar */ #define ACTIVITY_BAR_ONE_SWEEP_SIZE 107374182 /* How many hash symbols should form the activity bar */ #define ACTIVITY_BAR_SIZE 5 /* Number of characters to leave blank at the end of the line. This could be zero; however, due to latency in obtaining the correct window size, if the user reduces the window size then the line will overflow, corrupting the output. We leave a small gap to reduce the likelihood of this happening. */ #define END_OF_LINE_EMPTY 4 /* Placeholder value for total-length when file's length is unknown */ #define SIZE_FOR_UNKNOWN_TRANSFER_LENGTH 0 /* The width of window to assume when cannot establish the terminal's actual width */ #define DEFAULT_TERMINAL_WIDTH 80 /* Number of items we could potentially put in the line output */ #define ITEM_COUNT 3 typedef enum { ett_set, ett_measure } operation_t; typedef enum { progress_set, progress_finished } progress_op_t; typedef struct { char *content; int should_display; int hide_order; size_t length; } display_item_t; static int is_feedback_enabled; static void usage(); static int copyfile(int src, int dest, size_t buffsize, off64_t *size, off64_t total_size); static void hash_printing_accept_byte_count(progress_op_t op, off64_t total_bytes, off64_t total_size); static void build_output(char *buffer, int width, off64_t bytes_so_far, off64_t total_size); static void append_spaces_if_shrunk( char *buffer, int width); static void write_bytes_written(char *buffer, off64_t bytes_written, off64_t total_size); static void write_avr_rate(char *buffer, off64_t bytes_written, off64_t total_size); static void write_percent(char *buffer, off64_t bytes_written, off64_t total_size); static void write_percentage_progress_bar(char *buffer, size_t progress_bar_size, off64_t bytes_written, off64_t total_size); static void write_activity_progress_bar(char *buffer, size_t progress_bar_size, off64_t bytes_written); static void write_spaces( char *buffer, int count); static time_t elapsed_transfer_time( operation_t op); static int get_terminal_width(); static int transfer_has_known_size( off64_t total_size); static int build_item( display_item_t *item, off64_t bytes_written, off64_t total_size, int priority, int bar_size, void (*fn)(char *buffer, off64_t bytes_written, off64_t total_size)); static int hide_items_for_minimum_bar_size( display_item_t *item, int bar_size, int minimum_size); static void write_items( char *buffer, display_item_t *items); static int hide_items_for_minimum_bar_size( display_item_t *item, int bar_size, int minimum_size); int main(int argc, char *argv[]) { int src, dest; struct stat64 sbuf, sbuf2; time_t copy_time; off64_t size=0, total_size; size_t buffer_size = DEFAULT_BUFFER; /* transfer buffer size */ int rc ; char filename[MAXPATHLEN],*inpfile, *outfile; char formatted_rate[12], formatted_size[12]; char extraOption[MAXPATHLEN]; char allocSpaceOption[MAXPATHLEN]; char *cp ; int c; int overwrite = 1; int isStdin = 0; mode_t mode = 0666; char *firstP, *lastP; unsigned short first_port, last_port; int stage = 0; int stagetime = 0; int unsafeWrite = 0; char *stagelocation = NULL; int ahead = 0; size_t ra_buffer_size = 1048570L; int doCheckSum = 1; /* for getopt */ extern char *optarg; extern int optind; if (argc < 3) { usage(); } extraOption[0] = '\0'; allocSpaceOption[0] = '\0'; if( getenv("DCACHE_SHOW_PROGRESS") != NULL) { is_feedback_enabled = 1; } /* FIXME removing the DC_LOCAL_CACHE_BUFFER environment * variable vetos dcap's use of the lcb (the local cache). * This is an ugly work-around needed because the current lcb * code gives terrible performance when the client streams * data in large chunks. Rather than rewrite LCB, we * introduce this as a "temporary" work-around. * * Although clients should tune their software for their * access patterns, this is "impossible" (or at least * unlikely); therefore LCB should be rewritten to provide * better performance in this case. */ unsetenv("DC_LOCAL_CACHE_BUFFER"); while( (c = getopt(argc, argv, "Ad:o:h:iX:Pt:l:aB:b:up:T:r:s:w:cC:H")) != EOF) { switch(c) { case 'd': dc_setStrDebugLevel(optarg); break; case 'o': dc_setOpenTimeout(atol(optarg)); break; case 'h': dc_setReplyHostName(optarg); break; case 'i': overwrite = 0; break; case 'P': stage = 1; break; case 't' : stagetime = atoi(optarg); break; case 'l' : stagelocation = optarg; break; case 'B' : buffer_size = atol(optarg); break; case 'a' : ahead = 1; break; case 'b' : ra_buffer_size = atol(optarg); break; case 'X': dc_snaprintf(extraOption, sizeof(extraOption), " %s", optarg); break; case 'u': unsafeWrite = 1; break; case 'p': lastP = strchr(optarg, ':'); if( lastP == NULL ) { first_port = atoi(optarg); last_port = first_port; }else{ firstP = optarg; /*just to be simple */ lastP[0] = '\0'; first_port = atoi(firstP); last_port = atoi(lastP +1); } dc_setCallbackPortRange(first_port, last_port); break; case 'T': dc_setTunnel(optarg); break; case 'r': dc_setTCPReceiveBuffer( atoi(optarg) ); break; case 's': dc_setTCPSendBuffer( atoi(optarg) ); break; case 'w': dc_setTunnelType(optarg); break; case 'c': doCheckSum = 0; break; case 'A': dc_setClientActive(); break; case 'C': dc_setCloseTimeout(atoi(optarg)); break; case 'H': is_feedback_enabled=1; break; case '?': usage(); } } if(((argc - optind) != 2) && (!stage)) { usage(); } #ifndef WIN32 dcap_signal(); #endif inpfile = argv[optind]; if(stage) { dc_setExtraOption(extraOption); if ( (rc = dc_stage(inpfile, stagetime, stagelocation)) < 0 ) { dc_perror("dc_stage fail"); rc = -1; } return rc; } outfile = argv[optind+1]; #ifndef WIN32 if(strcmp(inpfile, "-") == 0) { isStdin = 1; src = fileno(stdin); inpfile = strdup("/dev/stdin"); } if(strcmp(outfile, "-") == 0) { outfile = strdup("/dev/stdout"); } #endif /* WIN32 */ if(!isStdin) { dc_setExtraOption(extraOption); rc = dc_stat64(inpfile, &sbuf); if ( (rc == 0) && ( S_ISDIR(sbuf.st_mode) || S_ISCHR(sbuf.st_mode)) ) { fprintf(stderr,"file %s: Not a regular file\n",inpfile); return -1 ; } if( rc == 0 ) { /* if file do not exist it can be a url, and dc_open will handle this */ mode = sbuf.st_mode & 0777; /* tell to pool how many bytes we want to write */ #ifdef WIN32 dc_snaprintf(allocSpaceOption, sizeof(allocSpaceOption), " -alloc-size=%lld", (__int64)sbuf.st_size); #else dc_snaprintf(allocSpaceOption, sizeof(allocSpaceOption), " -alloc-size=%lld", (long long)sbuf.st_size); #endif } total_size = sbuf.st_size; } else { total_size = SIZE_FOR_UNKNOWN_TRANSFER_LENGTH; } dc_setExtraOption(extraOption); if ( dc_stat64( outfile, &sbuf2) == 0 && S_ISDIR(sbuf2.st_mode) ) { if ( (cp = strrchr(inpfile,PATH_SEPARATOR)) != NULL ) { cp++; }else{ cp = inpfile; } sprintf(filename, "%s%c%s", outfile, PATH_SEPARATOR, cp); }else{ strcpy(filename,outfile) ; } dc_setExtraOption(extraOption); if((!overwrite) && (dc_access(filename, F_OK) == 0)) { fprintf(stderr, "%s: Skipping existing file %s.\n", argv[0], filename); return 0; } errno = 0 ; if(!isStdin) { dc_setExtraOption(extraOption); src = dc_open(inpfile,O_RDONLY | O_BINARY ); if (src < 0) { dc_perror("Can't open source file"); return -1; } } if(!ahead || (ra_buffer_size <= buffer_size)) { dc_noBuffering(src); }else{ dc_setBufferSize(src,ra_buffer_size); } errno = 0 ; #ifdef WIN32 mode = _S_IWRITE ; #endif /* WIN32 */ dc_setExtraOption(extraOption); dc_setExtraOption(allocSpaceOption); dest = dc_open( filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, mode|S_IWUSR); if (dest < 0) { dc_perror("Can't open destination file"); return -1; } if(unsafeWrite) { dc_unsafeWrite(dest); } if( ! doCheckSum ) { dc_noCheckSum(dest); } elapsed_transfer_time( ett_set); rc = copyfile(src, dest, buffer_size, &size, total_size); if (dc_close(src) < 0) { perror("Failed to close source file"); rc = -1; } if (dc_close(dest) < 0) { perror("Failed to close destination file"); dc_stat64( outfile, &sbuf2); mode = sbuf2.st_mode & S_IFMT; if (mode == S_IFREG) dc_unlink(outfile); rc = -1; } if (rc != -1 ) { copy_time = elapsed_transfer_time(ett_measure); dc_bytes_as_size(formatted_size, size); fprintf(stderr,"%lld bytes (%s) in %lu seconds", (long long)size, formatted_size, copy_time); if ( copy_time > 0) { dc_bytes_as_size(formatted_rate, (double)size / copy_time); fprintf(stderr," (%s/s)\n", formatted_rate); }else{ fprintf(stderr,"\n"); } }else{ fprintf(stderr,"dccp failed.\n"); /* remove destination if copy failed */ dc_stat64( outfile, &sbuf2); mode = sbuf2.st_mode & S_IFMT; if (mode == S_IFREG) dc_unlink(outfile); } return rc; } int copyfile(int src, int dest, size_t bufsize, off64_t *size, off64_t total_size) { ssize_t n, m ; char * cpbuf; size_t count; off64_t total_bytes = 0; size_t off; if ( ( cpbuf = malloc(bufsize) ) == NULL ) { perror("malloc"); return -1; } if( is_feedback_enabled) { hash_printing_accept_byte_count(progress_set, 0, total_size); } do{ off = 0; do{ n = dc_read(src, cpbuf + off, bufsize - off); if( n <=0 ) break; off += n; } while (off != bufsize ); /* do not continue if read fails*/ if (n < 0) { /* Read failed. */ free(cpbuf); return -1; } if (off > 0) { count = 0; while ((count != off) && ((m = dc_write(dest, cpbuf+count, off-count)) > 0)) { total_bytes += (off64_t)m; count += m; if( is_feedback_enabled) { hash_printing_accept_byte_count(progress_set, total_bytes, total_size); } } if (m < 0) { /* Write failed. */ free(cpbuf); return -1; } } } while (n != 0); if(size != NULL) { *size = total_bytes; } if( is_feedback_enabled) { hash_printing_accept_byte_count( progress_finished, 0, 0); } free(cpbuf); return 0; } void usage() { fprintf(stderr,"DiskCache Copy Program. LibDCAP version: %s\n", getDcapVersion()); fprintf(stderr,"Usage: dccp [-H] [-d ] [-h ] [-i]\n"); fprintf(stderr," [-P [-t