FILE: Make upload-writes unbuffered by not using FILE streams
[platform/upstream/curl.git] / lib / file.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at http://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22
23 #include "setup.h"
24
25 #ifndef CURL_DISABLE_FILE
26
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
29 #endif
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
32 #endif
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #ifdef HAVE_NETDB_H
37 #include <netdb.h>
38 #endif
39 #ifdef HAVE_ARPA_INET_H
40 #include <arpa/inet.h>
41 #endif
42 #ifdef HAVE_NET_IF_H
43 #include <net/if.h>
44 #endif
45 #ifdef HAVE_SYS_IOCTL_H
46 #include <sys/ioctl.h>
47 #endif
48
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
52
53 #ifdef HAVE_FCNTL_H
54 #include <fcntl.h>
55 #endif
56
57 #include "strtoofft.h"
58 #include "urldata.h"
59 #include <curl/curl.h>
60 #include "progress.h"
61 #include "sendf.h"
62 #include "escape.h"
63 #include "file.h"
64 #include "speedcheck.h"
65 #include "getinfo.h"
66 #include "transfer.h"
67 #include "url.h"
68 #include "curl_memory.h"
69 #include "parsedate.h" /* for the week day and month names */
70 #include "warnless.h"
71
72 #define _MPRINTF_REPLACE /* use our functions only */
73 #include <curl/mprintf.h>
74
75 /* The last #include file should be: */
76 #include "memdebug.h"
77
78 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \
79   defined(__SYMBIAN32__)
80 #define DOS_FILESYSTEM 1
81 #endif
82
83 #ifdef OPEN_NEEDS_ARG3
84 #  define open_readonly(p,f) open((p),(f),(0))
85 #else
86 #  define open_readonly(p,f) open((p),(f))
87 #endif
88
89 /*
90  * Forward declarations.
91  */
92
93 static CURLcode file_do(struct connectdata *, bool *done);
94 static CURLcode file_done(struct connectdata *conn,
95                           CURLcode status, bool premature);
96 static CURLcode file_connect(struct connectdata *conn, bool *done);
97 static CURLcode file_disconnect(struct connectdata *conn,
98                                 bool dead_connection);
99
100
101 /*
102  * FILE scheme handler.
103  */
104
105 const struct Curl_handler Curl_handler_file = {
106   "FILE",                               /* scheme */
107   ZERO_NULL,                            /* setup_connection */
108   file_do,                              /* do_it */
109   file_done,                            /* done */
110   ZERO_NULL,                            /* do_more */
111   file_connect,                         /* connect_it */
112   ZERO_NULL,                            /* connecting */
113   ZERO_NULL,                            /* doing */
114   ZERO_NULL,                            /* proto_getsock */
115   ZERO_NULL,                            /* doing_getsock */
116   ZERO_NULL,                            /* domore_getsock */
117   ZERO_NULL,                            /* perform_getsock */
118   file_disconnect,                      /* disconnect */
119   ZERO_NULL,                            /* readwrite */
120   0,                                    /* defport */
121   CURLPROTO_FILE,                       /* protocol */
122   PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */
123 };
124
125
126  /*
127   Check if this is a range download, and if so, set the internal variables
128   properly. This code is copied from the FTP implementation and might as
129   well be factored out.
130  */
131 static CURLcode file_range(struct connectdata *conn)
132 {
133   curl_off_t from, to;
134   curl_off_t totalsize=-1;
135   char *ptr;
136   char *ptr2;
137   struct SessionHandle *data = conn->data;
138
139   if(data->state.use_range && data->state.range) {
140     from=curlx_strtoofft(data->state.range, &ptr, 0);
141     while(*ptr && (ISSPACE(*ptr) || (*ptr=='-')))
142       ptr++;
143     to=curlx_strtoofft(ptr, &ptr2, 0);
144     if(ptr == ptr2) {
145       /* we didn't get any digit */
146       to=-1;
147     }
148     if((-1 == to) && (from>=0)) {
149       /* X - */
150       data->state.resume_from = from;
151       DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n",
152                    from));
153     }
154     else if(from < 0) {
155       /* -Y */
156       data->req.maxdownload = -from;
157       data->state.resume_from = from;
158       DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n",
159                    -from));
160     }
161     else {
162       /* X-Y */
163       totalsize = to-from;
164       data->req.maxdownload = totalsize+1; /* include last byte */
165       data->state.resume_from = from;
166       DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T
167                    " getting %" FORMAT_OFF_T " bytes\n",
168                    from, data->req.maxdownload));
169     }
170     DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T
171                  " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n",
172                  from, to, data->req.maxdownload));
173   }
174   else
175     data->req.maxdownload = -1;
176   return CURLE_OK;
177 }
178
179 /*
180  * file_connect() gets called from Curl_protocol_connect() to allow us to
181  * do protocol-specific actions at connect-time.  We emulate a
182  * connect-then-transfer protocol and "connect" to the file here
183  */
184 static CURLcode file_connect(struct connectdata *conn, bool *done)
185 {
186   struct SessionHandle *data = conn->data;
187   char *real_path;
188   struct FILEPROTO *file;
189   int fd;
190 #ifdef DOS_FILESYSTEM
191   int i;
192   char *actual_path;
193 #endif
194
195   /* If there already is a protocol-specific struct allocated for this
196      sessionhandle, deal with it */
197   Curl_reset_reqproto(conn);
198
199   real_path = curl_easy_unescape(data, data->state.path, 0, NULL);
200   if(!real_path)
201     return CURLE_OUT_OF_MEMORY;
202
203   if(!data->state.proto.file) {
204     file = calloc(1, sizeof(struct FILEPROTO));
205     if(!file) {
206       free(real_path);
207       return CURLE_OUT_OF_MEMORY;
208     }
209     data->state.proto.file = file;
210   }
211   else {
212     /* file is not a protocol that can deal with "persistancy" */
213     file = data->state.proto.file;
214     Curl_safefree(file->freepath);
215     file->path = NULL;
216     if(file->fd != -1)
217       close(file->fd);
218     file->fd = -1;
219   }
220
221 #ifdef DOS_FILESYSTEM
222   /* If the first character is a slash, and there's
223      something that looks like a drive at the beginning of
224      the path, skip the slash.  If we remove the initial
225      slash in all cases, paths without drive letters end up
226      relative to the current directory which isn't how
227      browsers work.
228
229      Some browsers accept | instead of : as the drive letter
230      separator, so we do too.
231
232      On other platforms, we need the slash to indicate an
233      absolute pathname.  On Windows, absolute paths start
234      with a drive letter.
235   */
236   actual_path = real_path;
237   if((actual_path[0] == '/') &&
238       actual_path[1] &&
239      (actual_path[2] == ':' || actual_path[2] == '|')) {
240     actual_path[2] = ':';
241     actual_path++;
242   }
243
244   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
245   for(i=0; actual_path[i] != '\0'; ++i)
246     if(actual_path[i] == '/')
247       actual_path[i] = '\\';
248
249   fd = open_readonly(actual_path, O_RDONLY|O_BINARY);
250   file->path = actual_path;
251 #else
252   fd = open_readonly(real_path, O_RDONLY);
253   file->path = real_path;
254 #endif
255   file->freepath = real_path; /* free this when done */
256
257   file->fd = fd;
258   if(!data->set.upload && (fd == -1)) {
259     failf(data, "Couldn't open file %s", data->state.path);
260     file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE);
261     return CURLE_FILE_COULDNT_READ_FILE;
262   }
263   *done = TRUE;
264
265   return CURLE_OK;
266 }
267
268 static CURLcode file_done(struct connectdata *conn,
269                                CURLcode status, bool premature)
270 {
271   struct FILEPROTO *file = conn->data->state.proto.file;
272   (void)status; /* not used */
273   (void)premature; /* not used */
274
275   if(file) {
276     Curl_safefree(file->freepath);
277     file->path = NULL;
278     if(file->fd != -1)
279       close(file->fd);
280     file->fd = -1;
281   }
282
283   return CURLE_OK;
284 }
285
286 static CURLcode file_disconnect(struct connectdata *conn,
287                                 bool dead_connection)
288 {
289   struct FILEPROTO *file = conn->data->state.proto.file;
290   (void)dead_connection; /* not used */
291
292   if(file) {
293     Curl_safefree(file->freepath);
294     file->path = NULL;
295     if(file->fd != -1)
296       close(file->fd);
297     file->fd = -1;
298   }
299
300   return CURLE_OK;
301 }
302
303 #ifdef DOS_FILESYSTEM
304 #define DIRSEP '\\'
305 #else
306 #define DIRSEP '/'
307 #endif
308
309 static CURLcode file_upload(struct connectdata *conn)
310 {
311   struct FILEPROTO *file = conn->data->state.proto.file;
312   const char *dir = strchr(file->path, DIRSEP);
313   int fd;
314   int mode;
315   CURLcode res=CURLE_OK;
316   struct SessionHandle *data = conn->data;
317   char *buf = data->state.buffer;
318   size_t nread;
319   size_t nwrite;
320   curl_off_t bytecount = 0;
321   struct timeval now = Curl_tvnow();
322   struct_stat file_stat;
323   const char* buf2;
324
325   /*
326    * Since FILE: doesn't do the full init, we need to provide some extra
327    * assignments here.
328    */
329   conn->fread_func = data->set.fread_func;
330   conn->fread_in = data->set.in;
331   conn->data->req.upload_fromhere = buf;
332
333   if(!dir)
334     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
335
336   if(!dir[1])
337     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
338
339 #ifdef O_BINARY
340 #define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY
341 #else
342 #define MODE_DEFAULT O_WRONLY|O_CREAT
343 #endif
344
345   if(data->state.resume_from)
346     mode = MODE_DEFAULT|O_APPEND;
347   else
348     mode = MODE_DEFAULT|O_TRUNC;
349
350   fd = open(file->path, mode, conn->data->set.new_file_perms);
351   if(fd < 0) {
352     failf(data, "Can't open %s for writing", file->path);
353     return CURLE_WRITE_ERROR;
354   }
355
356   if(-1 != data->set.infilesize)
357     /* known size of data to "upload" */
358     Curl_pgrsSetUploadSize(data, data->set.infilesize);
359
360   /* treat the negative resume offset value as the case of "-" */
361   if(data->state.resume_from < 0) {
362     if(fstat(fd, &file_stat)) {
363       close(fd);
364       failf(data, "Can't get the size of %s", file->path);
365       return CURLE_WRITE_ERROR;
366     }
367     else
368       data->state.resume_from = (curl_off_t)file_stat.st_size;
369   }
370
371   while(res == CURLE_OK) {
372     int readcount;
373     res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
374     if(res)
375       break;
376
377     if(readcount <= 0)  /* fix questionable compare error. curlvms */
378       break;
379
380     nread = (size_t)readcount;
381
382     /*skip bytes before resume point*/
383     if(data->state.resume_from) {
384       if((curl_off_t)nread <= data->state.resume_from ) {
385         data->state.resume_from -= nread;
386         nread = 0;
387         buf2 = buf;
388       }
389       else {
390         buf2 = buf + data->state.resume_from;
391         nread -= (size_t)data->state.resume_from;
392         data->state.resume_from = 0;
393       }
394     }
395     else
396       buf2 = buf;
397
398     /* write the data to the target */
399     nwrite = write(fd, buf2, nread);
400     if(nwrite != nread) {
401       res = CURLE_SEND_ERROR;
402       break;
403     }
404
405     bytecount += nread;
406
407     Curl_pgrsSetUploadCounter(data, bytecount);
408
409     if(Curl_pgrsUpdate(conn))
410       res = CURLE_ABORTED_BY_CALLBACK;
411     else
412       res = Curl_speedcheck(data, now);
413   }
414   if(!res && Curl_pgrsUpdate(conn))
415     res = CURLE_ABORTED_BY_CALLBACK;
416
417   close(fd);
418
419   return res;
420 }
421
422 /*
423  * file_do() is the protocol-specific function for the do-phase, separated
424  * from the connect-phase above. Other protocols merely setup the transfer in
425  * the do-phase, to have it done in the main transfer loop but since some
426  * platforms we support don't allow select()ing etc on file handles (as
427  * opposed to sockets) we instead perform the whole do-operation in this
428  * function.
429  */
430 static CURLcode file_do(struct connectdata *conn, bool *done)
431 {
432   /* This implementation ignores the host name in conformance with
433      RFC 1738. Only local files (reachable via the standard file system)
434      are supported. This means that files on remotely mounted directories
435      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
436   */
437   CURLcode res = CURLE_OK;
438   struct_stat statbuf; /* struct_stat instead of struct stat just to allow the
439                           Windows version to have a different struct without
440                           having to redefine the simple word 'stat' */
441   curl_off_t expected_size=0;
442   bool fstated=FALSE;
443   ssize_t nread;
444   struct SessionHandle *data = conn->data;
445   char *buf = data->state.buffer;
446   curl_off_t bytecount = 0;
447   int fd;
448   struct timeval now = Curl_tvnow();
449
450   *done = TRUE; /* unconditionally */
451
452   Curl_initinfo(data);
453   Curl_pgrsStartNow(data);
454
455   if(data->set.upload)
456     return file_upload(conn);
457
458   /* get the fd from the connection phase */
459   fd = conn->data->state.proto.file->fd;
460
461   /* VMS: This only works reliable for STREAMLF files */
462   if(-1 != fstat(fd, &statbuf)) {
463     /* we could stat it, then read out the size */
464     expected_size = statbuf.st_size;
465     /* and store the modification time */
466     data->info.filetime = (long)statbuf.st_mtime;
467     fstated = TRUE;
468   }
469
470   if(fstated && !data->state.range && data->set.timecondition) {
471     if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) {
472       *done = TRUE;
473       return CURLE_OK;
474     }
475   }
476
477   /* If we have selected NOBODY and HEADER, it means that we only want file
478      information. Which for FILE can't be much more than the file size and
479      date. */
480   if(data->set.opt_no_body && data->set.include_header && fstated) {
481     CURLcode result;
482     snprintf(buf, sizeof(data->state.buffer),
483              "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
484     result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
485     if(result)
486       return result;
487
488     result = Curl_client_write(conn, CLIENTWRITE_BOTH,
489                                (char *)"Accept-ranges: bytes\r\n", 0);
490     if(result)
491       return result;
492
493     if(fstated) {
494       time_t filetime = (time_t)statbuf.st_mtime;
495       struct tm buffer;
496       const struct tm *tm = &buffer;
497       result = Curl_gmtime(filetime, &buffer);
498       if(result)
499         return result;
500
501       /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
502       snprintf(buf, BUFSIZE-1,
503                "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
504                Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
505                tm->tm_mday,
506                Curl_month[tm->tm_mon],
507                tm->tm_year + 1900,
508                tm->tm_hour,
509                tm->tm_min,
510                tm->tm_sec);
511       result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0);
512     }
513     /* if we fstat()ed the file, set the file size to make it available post-
514        transfer */
515     if(fstated)
516       Curl_pgrsSetDownloadSize(data, expected_size);
517     return result;
518   }
519
520   /* Check whether file range has been specified */
521   file_range(conn);
522
523   /* Adjust the start offset in case we want to get the N last bytes
524    * of the stream iff the filesize could be determined */
525   if(data->state.resume_from < 0) {
526     if(!fstated) {
527       failf(data, "Can't get the size of file.");
528       return CURLE_READ_ERROR;
529     }
530     else
531       data->state.resume_from += (curl_off_t)statbuf.st_size;
532   }
533
534   if(data->state.resume_from <= expected_size)
535     expected_size -= data->state.resume_from;
536   else {
537     failf(data, "failed to resume file:// transfer");
538     return CURLE_BAD_DOWNLOAD_RESUME;
539   }
540
541   /* A high water mark has been specified so we obey... */
542   if(data->req.maxdownload > 0)
543     expected_size = data->req.maxdownload;
544
545   if(fstated && (expected_size == 0))
546     return CURLE_OK;
547
548   /* The following is a shortcut implementation of file reading
549      this is both more efficient than the former call to download() and
550      it avoids problems with select() and recv() on file descriptors
551      in Winsock */
552   if(fstated)
553     Curl_pgrsSetDownloadSize(data, expected_size);
554
555   if(data->state.resume_from) {
556     if(data->state.resume_from !=
557        lseek(fd, data->state.resume_from, SEEK_SET))
558       return CURLE_BAD_DOWNLOAD_RESUME;
559   }
560
561   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
562
563   while(res == CURLE_OK) {
564     /* Don't fill a whole buffer if we want less than all data */
565     size_t bytestoread =
566       (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ?
567       curlx_sotouz(expected_size) : BUFSIZE - 1;
568
569     nread = read(fd, buf, bytestoread);
570
571     if(nread > 0)
572       buf[nread] = 0;
573
574     if(nread <= 0 || expected_size == 0)
575       break;
576
577     bytecount += nread;
578     expected_size -= nread;
579
580     res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread);
581     if(res)
582       return res;
583
584     Curl_pgrsSetDownloadCounter(data, bytecount);
585
586     if(Curl_pgrsUpdate(conn))
587       res = CURLE_ABORTED_BY_CALLBACK;
588     else
589       res = Curl_speedcheck(data, now);
590   }
591   if(Curl_pgrsUpdate(conn))
592     res = CURLE_ABORTED_BY_CALLBACK;
593
594   return res;
595 }
596
597 #endif