Published in <87r7j6vy9g.fsf@xemacs.org>.
+2005-02-24 Hrvoje Niksic <hniksic@xemacs.org>
+
+ * ftp.c (getftp): Ditto.
+
+ * http.c (gethttp): When we're not supposed to overwrite files,
+ use fopen_excl to open the file and recompute the file name.
+
+ * log.c (redirect_output): Use unique_create to avoid a race
+ condition.
+
+ * mswindows.c (fake_fork_child): Use unique_create.
+
+ * utils.c (fopen_excl): New function that opens a stdio stream
+ with the O_EXCL flag (where available).
+ (unique_create): New function, like unique_name, but also creating
+ the file and returning a file pointer.
+ (fork_to_background): Use unique_create to create the file
+ immediately to avoid race condition with multiple instances of
+ wget -b.
+
2005-02-24 Hrvoje Niksic <hniksic@xemacs.org>
* host.c (lookup_host): Test for AI_ADDRCONFIG directly, instead
mkalldirs (con->target);
if (opt.backups)
rotate_backups (con->target);
- /* #### Is this correct? */
- chmod (con->target, 0600);
- fp = fopen (con->target, restval ? "ab" : "wb");
+ if (restval)
+ fp = fopen (con->target, "ab");
+ else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
+ || opt.output_document)
+ fp = fopen (con->target, "wb");
+ else
+ {
+ fp = fopen_excl (con->target, 0);
+ if (!fp && errno == EEXIST)
+ {
+ /* We cannot just invent a new name and use it (which is
+ what functions like unique_create typically do)
+ because we told the user we'd use this name.
+ Instead, return and retry the download. */
+ logprintf (LOG_NOTQUIET, _("%s has sprung into existence.\n"),
+ con->target);
+ fd_close (csock);
+ con->csock = -1;
+ fd_close (dtsock);
+ fd_close (local_sock);
+ return FOPEN_EXCL_ERR;
+ }
+ }
if (!fp)
{
logprintf (LOG_NOTQUIET, "%s: %s\n", con->target, strerror (errno));
case CONSOCKERR: case CONERROR: case FTPSRVERR: case FTPRERR:
case WRITEFAILED: case FTPUNKNOWNTYPE: case FTPSYSERR:
case FTPPORTERR: case FTPLOGREFUSED: case FTPINVPASV:
+ case FOPEN_EXCL_ERR:
printwhat (count, opt.ntry);
/* non-fatal errors */
+ if (err == FOPEN_EXCL_ERR)
+ {
+ /* Re-determine the file name. */
+ xfree_null (con->target);
+ con->target = url_file_name (u);
+ locf = con->target;
+ }
continue;
break;
case FTPRETRINT:
mkalldirs (*hs->local_file);
if (opt.backups)
rotate_backups (*hs->local_file);
- fp = fopen (*hs->local_file, hs->restval ? "ab" : "wb");
+ if (hs->restval)
+ fp = fopen (*hs->local_file, "ab");
+ else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
+ || opt.output_document)
+ fp = fopen (*hs->local_file, "wb");
+ else
+ {
+ fp = fopen_excl (*hs->local_file, 0);
+ if (!fp && errno == EEXIST)
+ {
+ /* We cannot just invent a new name and use it (which is
+ what functions like unique_create typically do)
+ because we told the user we'd use this name.
+ Instead, return and retry the download. */
+ logprintf (LOG_NOTQUIET,
+ _("%s has sprung into existence.\n"),
+ *hs->local_file);
+ CLOSE_INVALIDATE (sock);
+ return FOPEN_EXCL_ERR;
+ }
+ }
if (!fp)
{
logprintf (LOG_NOTQUIET, "%s: %s\n", *hs->local_file, strerror (errno));
{
case HERR: case HEOF: case CONSOCKERR: case CONCLOSED:
case CONERROR: case READERR: case WRITEFAILED:
- case RANGEERR:
+ case RANGEERR: case FOPEN_EXCL_ERR:
/* Non-fatal errors continue executing the loop, which will
bring them to "while" statement at the end, to judge
whether the number of tries was exceeded. */
free_hstat (&hstat);
printwhat (count, opt.ntry);
+ if (err == FOPEN_EXCL_ERR)
+ {
+ /* Re-determine the file name. */
+ if (local_file && *local_file)
+ {
+ xfree (*local_file);
+ *local_file = url_file_name (u);
+ hstat.local_file = local_file;
+ }
+ else
+ {
+ xfree (dummy);
+ dummy = url_file_name (u);
+ hstat.local_file = &dummy;
+ }
+ /* be honest about where we will save the file */
+ if (local_file && opt.output_document)
+ *local_file = HYPHENP (opt.output_document) ? NULL : xstrdup (opt.output_document);
+ if (!opt.output_document)
+ locf = *hstat.local_file;
+ else
+ locf = opt.output_document;
+ }
continue;
break;
case HOSTERR: case CONIMPOSSIBLE: case PROXERR: case AUTHFAILED:
static void
redirect_output (void)
{
- char *logfile = unique_name (DEFAULT_LOGFILE, 0);
- fprintf (stderr, _("\n%s received, redirecting output to `%s'.\n"),
- redirect_request_signal_name, logfile);
- logfp = fopen (logfile, "w");
- if (!logfp)
+ char *logfile;
+ logfp = unique_create (DEFAULT_LOGFILE, 0, &logfile);
+ if (logfp)
+ {
+ fprintf (stderr, _("\n%s received, redirecting output to `%s'.\n"),
+ redirect_request_signal_name, logfile);
+ xfree (logfile);
+ /* Dump the context output to the newly opened log. */
+ log_dump_context ();
+ }
+ else
{
/* Eek! Opening the alternate log file has failed. Nothing we
can do but disable printing completely. */
+ fprintf (stderr, _("\n%s received.\n"), redirect_request_signal_name);
fprintf (stderr, _("%s: %s; disabling logging.\n"),
logfile, strerror (errno));
inhibit_logging = 1;
}
- else
- {
- /* Dump the context output to the newly opened log. */
- log_dump_context ();
- }
- xfree (logfile);
save_context_p = 0;
}
static void
ws_hangup (const char *reason)
{
- /* Whether we arrange our own version of opt.lfilename here. */
- int changedp = 0;
-
- if (!opt.lfilename)
- {
- opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
- changedp = 1;
- }
- printf (_("Continuing in background.\n"));
- if (changedp)
- printf (_("Output will be written to `%s'.\n"), opt.lfilename);
-
+ fprintf (stderr, _("Continuing in background.\n"));
log_request_redirect_output (reason);
/* Detach process from the current console. Under Windows 9x, if we
struct fake_fork_info
{
HANDLE event;
- int changedp;
+ int logfile_changed;
char lfilename[MAX_PATH + 1];
};
event = info->event;
+ info->logfile_changed = 0;
if (!opt.lfilename)
{
- opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
- info->changedp = 1;
- strncpy (info->lfilename, opt.lfilename, sizeof (info->lfilename));
- info->lfilename[sizeof (info->lfilename) - 1] = '\0';
+ /* See utils:fork_to_background for explanation. */
+ FILE *new_log_fp = unique_create (DEFAULT_LOGFILE, 0, &opt.lfilename);
+ if (new_log_fp)
+ {
+ info->logfile_changed = 1;
+ strncpy (info->lfilename, opt.lfilename, sizeof (info->lfilename));
+ info->lfilename[sizeof (info->lfilename) - 1] = '\0';
+ fclose (new_log_fp);
+ }
}
- else
- info->changedp = 0;
UnmapViewOfFile (info);
CloseHandle (section);
}
/* Ensure string is properly terminated. */
- if (info->changedp &&
+ if (info->logfile_changed &&
!memchr (info->lfilename, '\0', sizeof (info->lfilename)))
{
rv = FALSE;
}
printf (_("Continuing in background, pid %lu.\n"), pi.dwProcessId);
- if (info->changedp)
+ if (info->logfile_changed)
printf (_("Output will be written to `%s'.\n"), info->lfilename);
UnmapViewOfFile (info);
{
pid_t pid;
/* Whether we arrange our own version of opt.lfilename here. */
- int changedp = 0;
+ int logfile_changed = 0;
if (!opt.lfilename)
{
- opt.lfilename = unique_name (DEFAULT_LOGFILE, 0);
- changedp = 1;
+ /* We must create the file immediately to avoid either a race
+ condition (which arises from using unique_name and failing to
+ use fopen_excl) or lying to the user about the log file name
+ (which arises from using unique_name, printing the name, and
+ using fopen_excl later on.) */
+ FILE *new_log_fp = unique_create (DEFAULT_LOGFILE, 0, &opt.lfilename);
+ if (new_log_fp)
+ {
+ logfile_changed = 1;
+ fclose (new_log_fp);
+ }
}
pid = fork ();
if (pid < 0)
{
/* parent, no error */
printf (_("Continuing in background, pid %d.\n"), (int)pid);
- if (changedp)
+ if (logfile_changed)
printf (_("Output will be written to `%s'.\n"), opt.lfilename);
exit (0); /* #### should we use _exit()? */
}
exist at the point in time when the function was called.
Therefore, where security matters, don't rely that the file created
by this function exists until you open it with O_EXCL or
- something.
+ equivalent.
If ALLOW_PASSTHROUGH is 0, it always returns a freshly allocated
string. Otherwise, it may return FILE if the file doesn't exist
and return it. */
return unique_name_1 (file);
}
+
+/* Create a file based on NAME, except without overwriting an existing
+ file with that name. Providing O_EXCL is correctly implemented,
+ this function does not have the race condition associated with
+ opening the file returned by unique_name. */
+
+FILE *
+unique_create (const char *name, int binary, char **opened_name)
+{
+ /* unique file name, based on NAME */
+ char *uname = unique_name (name, 0);
+ FILE *fp;
+ while ((fp = fopen_excl (uname, binary)) == NULL && errno == EEXIST)
+ {
+ xfree (uname);
+ uname = unique_name (name, 0);
+ }
+ if (opened_name && fp != NULL)
+ {
+ if (fp)
+ *opened_name = uname;
+ else
+ {
+ *opened_name = NULL;
+ xfree (uname);
+ }
+ }
+ else
+ xfree (uname);
+ return fp;
+}
+
+/* Open the file for writing, with the addition that the file is
+ opened "exclusively". This means that, if the file already exists,
+ this function will *fail* and errno will be set to EEXIST. If
+ BINARY is set, the file will be opened in binary mode, equivalent
+ to fopen's "wb".
+
+ If opening the file fails for any reason, including the file having
+ previously existed, this function returns NULL and sets errno
+ appropriately. */
+
+FILE *
+fopen_excl (const char *fname, int binary)
+{
+#ifdef O_EXCL
+ int flags = O_WRONLY | O_CREAT | O_EXCL;
+# ifdef O_BINARY
+ if (binary)
+ flags |= O_BINARY
+# endif
+ int fd = open (fname, flags, 0666);
+ if (fd < 0)
+ return NULL;
+ return fdopen (fd, binary ? "wb" : "w");
+#else /* not O_EXCL */
+ return fopen (fname, binary ? "wb" : "w");
+#endif /* not O_EXCL */
+}
\f
/* Create DIRECTORY. If some of the pathname components of DIRECTORY
are missing, create them first. In case any mkdir() call fails,
wgint file_size PARAMS ((const char *));
int make_directory PARAMS ((const char *));
char *unique_name PARAMS ((const char *, int));
+FILE *unique_create PARAMS ((const char *, int, char **));
+FILE *fopen_excl PARAMS ((const char *, int));
char *file_merge PARAMS ((const char *, const char *));
int acceptable PARAMS ((const char *));
CONCLOSED, FTPOK, FTPLOGINC, FTPLOGREFUSED, FTPPORTERR, FTPSYSERR,
FTPNSFOD, FTPRETROK, FTPUNKNOWNTYPE, FTPRERR,
FTPREXC, FTPSRVERR, FTPRETRINT, FTPRESTFAIL, URLERROR,
- FOPENERR, FWRITEERR, HOK, HLEXC, HEOF,
+ FOPENERR, FOPEN_EXCL_ERR, FWRITEERR, HOK, HLEXC, HEOF,
HERR, RETROK, RECLEVELEXC, FTPACCDENIED, WRONGCODE,
FTPINVPASV, FTPNOPASV,
CONTNOTSUPPORTED, RETRUNNEEDED, RETRFINISHED, READERR, TRYLIMEXC,