provide an error string when resuming fails - and use the proper error code,
[platform/upstream/curl.git] / lib / file.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2004, 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  * $Id$
22  ***************************************************************************/
23
24 #include "setup.h"
25
26 #ifndef CURL_DISABLE_FILE
27 /* -- WIN32 approved -- */
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <ctype.h>
33 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36 #ifdef HAVE_SYS_STAT_H
37 #include <sys/stat.h>
38 #endif
39
40 #include <errno.h>
41
42 #if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
43 #include <time.h>
44 #include <io.h>
45 #include <fcntl.h>
46 #else
47 #ifdef HAVE_SYS_SOCKET_H
48 #include <sys/socket.h>
49 #endif
50 #ifdef HAVE_NETINET_IN_H
51 #include <netinet/in.h>
52 #endif
53 #include <sys/time.h>
54 #ifdef HAVE_UNISTD_H
55 #include <unistd.h>
56 #endif
57 #ifdef HAVE_NETDB_H
58 #include <netdb.h>
59 #endif
60 #ifdef HAVE_ARPA_INET_H
61 #include <arpa/inet.h>
62 #endif
63 #ifdef HAVE_NET_IF_H
64 #include <net/if.h>
65 #endif
66 #include <sys/ioctl.h>
67 #include <signal.h>
68
69 #ifdef HAVE_SYS_PARAM_H
70 #include <sys/param.h>
71 #endif
72
73 #ifdef HAVE_FCNTL_H
74 #include <fcntl.h>
75 #endif
76
77 #endif
78
79 #include "urldata.h"
80 #include <curl/curl.h>
81 #include "progress.h"
82 #include "sendf.h"
83 #include "escape.h"
84 #include "file.h"
85 #include "speedcheck.h"
86 #include "getinfo.h"
87 #include "transfer.h"
88 #include "url.h"
89 #include "memory.h"
90
91 #define _MPRINTF_REPLACE /* use our functions only */
92 #include <curl/mprintf.h>
93
94 /* The last #include file should be: */
95 #include "memdebug.h"
96
97 /*
98  * Curl_file_connect() gets called from Curl_protocol_connect() to allow us to
99  * do protocol-specific actions at connect-time.  We emulate a
100  * connect-then-transfer protocol and "connect" to the file here
101  */
102 CURLcode Curl_file_connect(struct connectdata *conn)
103 {
104   char *real_path = curl_unescape(conn->path, 0);
105   struct FILEPROTO *file;
106   int fd;
107 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
108   int i;
109   char *actual_path;
110 #endif
111
112   if(!real_path)
113     return CURLE_OUT_OF_MEMORY;
114
115   file = (struct FILEPROTO *)calloc(sizeof(struct FILEPROTO), 1);
116   if(!file) {
117     free(real_path);
118     return CURLE_OUT_OF_MEMORY;
119   }
120
121   conn->proto.file = file;
122
123 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
124   /* If the first character is a slash, and there's
125      something that looks like a drive at the beginning of
126      the path, skip the slash.  If we remove the initial
127      slash in all cases, paths without drive letters end up
128      relative to the current directory which isn't how
129      browsers work.
130
131      Some browsers accept | instead of : as the drive letter
132      separator, so we do too.
133
134      On other platforms, we need the slash to indicate an
135      absolute pathname.  On Windows, absolute paths start
136      with a drive letter.
137   */
138   actual_path = real_path;
139   if ((actual_path[0] == '/') &&
140       actual_path[1] &&
141       (actual_path[2] == ':' || actual_path[2] == '|'))
142   {
143     actual_path[2] = ':';
144     actual_path++;
145   }
146
147   /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */
148   for (i=0; actual_path[i] != '\0'; ++i)
149     if (actual_path[i] == '/')
150       actual_path[i] = '\\';
151
152   fd = open(actual_path, O_RDONLY | O_BINARY);  /* no CR/LF translation! */
153   file->path = actual_path;
154 #else
155   fd = open(real_path, O_RDONLY);
156   file->path = real_path;
157 #endif
158   file->freepath = real_path; /* free this when done */
159
160   if(!conn->data->set.upload && (fd == -1)) {
161     failf(conn->data, "Couldn't open file %s", conn->path);
162     Curl_file_done(conn, CURLE_FILE_COULDNT_READ_FILE);
163     return CURLE_FILE_COULDNT_READ_FILE;
164   }
165   file->fd = fd;
166
167   return CURLE_OK;
168 }
169
170 #if defined(WIN32) && (SIZEOF_CURL_OFF_T > 4)
171 #define lseek(x,y,z) _lseeki64(x, y, z)
172 #endif
173
174 CURLcode Curl_file_done(struct connectdata *conn,
175                         CURLcode status)
176 {
177   struct FILEPROTO *file = conn->proto.file;
178   (void)status; /* not used */
179   Curl_safefree(file->freepath);
180
181   if(file->fd != -1)
182     close(file->fd);
183
184   return CURLE_OK;
185 }
186
187 #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
188 #define DIRSEP '\\'
189 #else
190 #define DIRSEP '/'
191 #endif
192
193 static CURLcode file_upload(struct connectdata *conn)
194 {
195   struct FILEPROTO *file = conn->proto.file;
196   char *dir = strchr(file->path, DIRSEP);
197   FILE *fp;
198   CURLcode res=CURLE_OK;
199   struct SessionHandle *data = conn->data;
200   char *buf = data->state.buffer;
201   size_t nread;
202   size_t nwrite;
203   curl_off_t bytecount = 0;
204   struct timeval now = Curl_tvnow();
205
206   /*
207    * Since FILE: doesn't do the full init, we need to provide some extra
208    * assignments here.
209    */
210   conn->fread = data->set.fread;
211   conn->fread_in = data->set.in;
212   conn->upload_fromhere = buf;
213
214   if(!dir)
215     return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
216
217   if(!dir[1])
218      return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */
219
220   fp = fopen(file->path, "wb");
221   if(!fp) {
222     failf(data, "Can't open %s for writing", file->path);
223     return CURLE_WRITE_ERROR;
224   }
225
226   if(-1 != data->set.infilesize)
227     /* known size of data to "upload" */
228     Curl_pgrsSetUploadSize(data, data->set.infilesize);
229
230   while (res == CURLE_OK) {
231     int readcount;
232     res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount);
233     if(res)
234       break;
235
236     nread = (size_t)readcount;
237
238     if (nread <= 0)
239       break;
240
241     /* write the data to the target */
242     nwrite = fwrite(buf, 1, nread, fp);
243     if(nwrite != nread) {
244       res = CURLE_SEND_ERROR;
245       break;
246     }
247
248     bytecount += nread;
249
250     Curl_pgrsSetUploadCounter(data, bytecount);
251
252     if(Curl_pgrsUpdate(conn))
253       res = CURLE_ABORTED_BY_CALLBACK;
254     else
255       res = Curl_speedcheck(data, now);
256   }
257   if(!res && Curl_pgrsUpdate(conn))
258     res = CURLE_ABORTED_BY_CALLBACK;
259
260   fclose(fp);
261
262   return res;
263 }
264
265 /*
266  * Curl_file() is the protocol-specific function for the do-phase, separated
267  * from the connect-phase above. Other protocols merely setup the transfer in
268  * the do-phase, to have it done in the main transfer loop but since some
269  * platforms we support don't allow select()ing etc on file handles (as
270  * opposed to sockets) we instead perform the whole do-operation in this
271  * function.
272  */
273 CURLcode Curl_file(struct connectdata *conn)
274 {
275   /* This implementation ignores the host name in conformance with
276      RFC 1738. Only local files (reachable via the standard file system)
277      are supported. This means that files on remotely mounted directories
278      (via NFS, Samba, NT sharing) can be accessed through a file:// URL
279   */
280   CURLcode res = CURLE_OK;
281   struct stat statbuf;
282   curl_off_t expected_size=0;
283   bool fstated=FALSE;
284   ssize_t nread;
285   struct SessionHandle *data = conn->data;
286   char *buf = data->state.buffer;
287   curl_off_t bytecount = 0;
288   int fd;
289   struct timeval now = Curl_tvnow();
290
291   Curl_readwrite_init(conn);
292   Curl_initinfo(data);
293   Curl_pgrsStartNow(data);
294
295   if(data->set.upload)
296     return file_upload(conn);
297
298   /* get the fd from the connection phase */
299   fd = conn->proto.file->fd;
300
301   /* VMS: This only works reliable for STREAMLF files */
302   if( -1 != fstat(fd, &statbuf)) {
303     /* we could stat it, then read out the size */
304     expected_size = statbuf.st_size;
305     fstated = TRUE;
306   }
307
308   /* If we have selected NOBODY and HEADER, it means that we only want file
309      information. Which for FILE can't be much more than the file size and
310      date. */
311   if(conn->bits.no_body && data->set.include_header && fstated) {
312     CURLcode result;
313     snprintf(buf, sizeof(data->state.buffer),
314              "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size);
315     result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
316     if(result)
317       return result;
318
319     result = Curl_client_write(data, CLIENTWRITE_BOTH,
320                                (char *)"Accept-ranges: bytes\r\n", 0);
321     if(result)
322       return result;
323
324 #ifdef HAVE_STRFTIME
325     if(fstated) {
326       struct tm *tm;
327       time_t clock = (time_t)statbuf.st_mtime;
328 #ifdef HAVE_GMTIME_R
329       struct tm buffer;
330       tm = (struct tm *)gmtime_r(&clock, &buffer);
331 #else
332       tm = gmtime(&clock);
333 #endif
334       /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */
335       strftime(buf, BUFSIZE-1, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT\r\n",
336                tm);
337       result = Curl_client_write(data, CLIENTWRITE_BOTH, buf, 0);
338     }
339 #endif
340     return result;
341   }
342
343   if (conn->resume_from <= expected_size)
344     expected_size -= conn->resume_from;
345   else {
346     failf(data, "failed to resume file:// transfer");
347     return CURLE_BAD_DOWNLOAD_RESUME;
348   }
349
350   if (fstated && (expected_size == 0))
351     return CURLE_OK;
352
353   /* The following is a shortcut implementation of file reading
354      this is both more efficient than the former call to download() and
355      it avoids problems with select() and recv() on file descriptors
356      in Winsock */
357   if(fstated)
358     Curl_pgrsSetDownloadSize(data, expected_size);
359
360   if(conn->resume_from)
361     lseek(fd, conn->resume_from, SEEK_SET);
362
363   Curl_pgrsTime(data, TIMER_STARTTRANSFER);
364
365   while (res == CURLE_OK) {
366     nread = read(fd, buf, BUFSIZE-1);
367
368     if ( nread > 0)
369       buf[nread] = 0;
370
371     if (nread <= 0)
372       break;
373
374     bytecount += nread;
375
376     res = Curl_client_write(data, CLIENTWRITE_BODY, buf, nread);
377     if(res)
378       return res;
379
380     Curl_pgrsSetDownloadCounter(data, bytecount);
381
382     if(Curl_pgrsUpdate(conn))
383       res = CURLE_ABORTED_BY_CALLBACK;
384     else
385       res = Curl_speedcheck(data, now);
386   }
387   if(Curl_pgrsUpdate(conn))
388     res = CURLE_ABORTED_BY_CALLBACK;
389
390   return res;
391 }
392
393 #endif