/* Various functions of utilitarian nature.
- Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
+ Copyright (C) 1995, 1996, 1997, 1998, 2000, 2001
+ Free Software Foundation, Inc.
-This file is part of Wget.
+This file is part of GNU Wget.
-This program is free software; you can redistribute it and/or modify
+GNU Wget is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
-This program is distributed in the hope that it will be useful,
+GNU Wget is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
+along with Wget; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include <config.h>
#include <fcntl.h>
#include <assert.h>
+/* For TIOCGWINSZ and friends: */
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+# include <termios.h>
+#endif
+
#include "wget.h"
#include "utils.h"
#include "fnmatch.h"
static void
memfatal (const char *what)
{
- /* HACK: expose save_log_p from log.c, so we can turn it off in
- order to prevent saving the log. Saving the log is dangerous
- because logprintf() and logputs() can call malloc(), so this
- could infloop. When logging is turned off, infloop can no longer
- happen.
-
- #### This is no longer really necessary because the new routines
- in log.c cons only if the line exceeds eighty characters. But
- this can come at the end of a line, so it's OK to be careful.
-
- On a more serious note, it would be good to have a
- log_forced_shutdown() routine that exposes this cleanly. */
- extern int save_log_p;
-
- save_log_p = 0;
+ /* Make sure we don't try to store part of the log line, and thus
+ call malloc. */
+ log_set_save_context (0);
logprintf (LOG_ALWAYS, _("%s: %s: Not enough memory.\n"), exec_name, what);
exit (1);
}
#endif /* DEBUG_MALLOC */
\f
+/* Utility function: like xstrdup(), but also lowercases S. */
+
+char *
+xstrdup_lower (const char *s)
+{
+ char *copy = xstrdup (s);
+ char *p = copy;
+ for (; *p; p++)
+ *p = TOLOWER (*p);
+ return copy;
+}
+
+/* Return a count of how many times CHR occurs in STRING. */
+
+int
+count_char (const char *string, char chr)
+{
+ const char *p;
+ int count = 0;
+ for (p = string; *p; p++)
+ if (*p == chr)
+ ++count;
+ return count;
+}
+
/* Copy the string formed by two pointers (one on the beginning, other
on the char after the last char) to a new, malloc-ed location.
0-terminate it. */
ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
return output;
}
-
-/* Returns an error message for ERRNUM. #### This requires more work.
- This function, as well as the whole error system, is very
- ill-conceived. */
-const char *
-uerrmsg (uerr_t errnum)
-{
- switch (errnum)
- {
- case URLUNKNOWN:
- return _("Unknown/unsupported protocol");
- break;
- case URLBADPORT:
- return _("Invalid port specification");
- break;
- case URLBADHOST:
- return _("Invalid host name");
- break;
- default:
- abort ();
- /* $@#@#$ compiler. */
- return NULL;
- }
-}
\f
/* The Windows versions of the following two functions are defined in
mswindows.c. */
-/* A cuserid() immitation using getpwuid(), to avoid hassling with
- utmp. Besides, not all systems have cuesrid(). Under Windows, it
- is defined in mswindows.c.
-
- If WHERE is non-NULL, the username will be stored there.
- Otherwise, it will be returned as a static buffer (as returned by
- getpwuid()). In the latter case, the buffer should be copied
- before calling getpwuid() or pwd_cuserid() again. */
#ifndef WINDOWS
-char *
-pwd_cuserid (char *where)
-{
- struct passwd *pwd;
-
- if (!(pwd = getpwuid (getuid ())) || !pwd->pw_name)
- return NULL;
- if (where)
- {
- strcpy (where, pwd->pw_name);
- return where;
- }
- else
- return pwd->pw_name;
-}
-
void
fork_to_background (void)
{
else if (pid != 0)
{
/* parent, no error */
- printf (_("Continuing in background.\n"));
+ printf (_("Continuing in background, pid %d.\n"), (int)pid);
if (changedp)
printf (_("Output will be written to `%s'.\n"), opt.lfilename);
- exit (0);
+ exit (0); /* #### should we use _exit()? */
}
- /* child: keep running */
+
+ /* child: give up the privileges and keep running. */
+ setsid ();
+ freopen ("/dev/null", "r", stdin);
+ freopen ("/dev/null", "w", stdout);
+ freopen ("/dev/null", "w", stderr);
}
#endif /* not WINDOWS */
\f
-/* Canonicalize PATH, and return a new path. The new path differs from PATH
- in that:
- Multple `/'s are collapsed to a single `/'.
- Leading `./'s and trailing `/.'s are removed.
- Trailing `/'s are removed.
- Non-leading `../'s and trailing `..'s are handled by removing
- portions of the path.
-
- E.g. "a/b/c/./../d/.." will yield "a/b". This function originates
- from GNU Bash.
-
- Changes for Wget:
- Always use '/' as stub_char.
- Don't check for local things using canon_stat.
- Change the original string instead of strdup-ing.
- React correctly when beginning with `./' and `../'. */
-void
+/* Resolve "." and ".." elements of PATH by destructively modifying
+ PATH. "." is resolved by removing that path element, and ".." is
+ resolved by removing the preceding path element. Leading and
+ trailing slashes are preserved.
+
+ Return non-zero if any changes have been made.
+
+ For example, "a/b/c/./../d/.." will yield "a/b/". More exhaustive
+ test examples are provided below. If you change anything in this
+ function, run test_path_simplify to make sure you haven't broken a
+ test case.
+
+ A previous version of this function was based on path_simplify()
+ from GNU Bash, but it has been rewritten for Wget 1.8.1. */
+
+int
path_simplify (char *path)
{
- register int i, start, ddot;
- char stub_char;
+ int change = 0;
+ char *p, *end;
- if (!*path)
- return;
+ if (path[0] == '/')
+ ++path; /* preserve the leading '/'. */
- /*stub_char = (*path == '/') ? '/' : '.';*/
- stub_char = '/';
+ p = path;
+ end = p + strlen (p) + 1; /* position past the terminating zero. */
- /* Addition: Remove all `./'-s preceding the string. If `../'-s
- precede, put `/' in front and remove them too. */
- i = 0;
- ddot = 0;
while (1)
{
- if (path[i] == '.' && path[i + 1] == '/')
- i += 2;
- else if (path[i] == '.' && path[i + 1] == '.' && path[i + 2] == '/')
+ again:
+ /* P should point to the beginning of a path element. */
+
+ if (*p == '.' && (*(p + 1) == '/' || *(p + 1) == '\0'))
{
- i += 3;
- ddot = 1;
+ /* Handle "./foo" by moving "foo" two characters to the
+ left. */
+ if (*(p + 1) == '/')
+ {
+ change = 1;
+ memmove (p, p + 2, end - p);
+ end -= 2;
+ goto again;
+ }
+ else
+ {
+ change = 1;
+ *p = '\0';
+ break;
+ }
}
- else
- break;
- }
- if (i)
- strcpy (path, path + i - ddot);
-
- /* Replace single `.' or `..' with `/'. */
- if ((path[0] == '.' && path[1] == '\0')
- || (path[0] == '.' && path[1] == '.' && path[2] == '\0'))
- {
- path[0] = stub_char;
- path[1] = '\0';
- return;
- }
- /* Walk along PATH looking for things to compact. */
- i = 0;
- while (1)
- {
- if (!path[i])
- break;
+ else if (*p == '.' && *(p + 1) == '.'
+ && (*(p + 2) == '/' || *(p + 2) == '\0'))
+ {
+ /* Handle "../foo" by moving "foo" one path element to the
+ left. */
+ char *b = p; /* not p-1 because P can equal PATH */
- while (path[i] && path[i] != '/')
- i++;
+ /* Backtrack by one path element, but not past the beginning
+ of PATH. */
- start = i++;
+ /* foo/bar/../baz */
+ /* ^ p */
+ /* ^ b */
- /* If we didn't find any slashes, then there is nothing left to do. */
- if (!path[start])
- break;
-
- /* Handle multiple `/'s in a row. */
- while (path[i] == '/')
- i++;
+ if (b > path)
+ {
+ /* Move backwards until B hits the beginning of the
+ previous path element or the beginning of path. */
+ for (--b; b > path && *(b - 1) != '/'; b--)
+ ;
+ }
- if ((start + 1) != i)
- {
- strcpy (path + start + 1, path + i);
- i = start + 1;
- }
+ change = 1;
+ if (*(p + 2) == '/')
+ {
+ memmove (b, p + 3, end - (p + 3));
+ end -= (p + 3) - b;
+ p = b;
+ }
+ else
+ {
+ *b = '\0';
+ break;
+ }
- /* Check for trailing `/'. */
- if (start && !path[i])
- {
- zero_last:
- path[--i] = '\0';
- break;
+ goto again;
}
-
- /* Check for `../', `./' or trailing `.' by itself. */
- if (path[i] == '.')
+ else if (*p == '/')
{
- /* Handle trailing `.' by itself. */
- if (!path[i + 1])
- goto zero_last;
-
- /* Handle `./'. */
- if (path[i + 1] == '/')
+ /* Remove empty path elements. Not mandated by rfc1808 et
+ al, but empty path elements are not all that useful, and
+ the rest of Wget might not deal with them well. */
+ char *q = p;
+ while (*q == '/')
+ ++q;
+ change = 1;
+ if (*q == '\0')
{
- strcpy (path + i, path + i + 1);
- i = (start < 0) ? 0 : start;
- continue;
+ *p = '\0';
+ break;
}
+ memmove (p, q, end - q);
+ end -= q - p;
+ goto again;
+ }
- /* Handle `../' or trailing `..' by itself. */
- if (path[i + 1] == '.' &&
- (path[i + 2] == '/' || !path[i + 2]))
- {
- while (--start > -1 && path[start] != '/');
- strcpy (path + start + 1, path + i + 2);
- i = (start < 0) ? 0 : start;
- continue;
- }
- } /* path == '.' */
- } /* while */
+ /* Skip to the next path element. */
+ while (*p && *p != '/')
+ ++p;
+ if (*p == '\0')
+ break;
- if (!*path)
- {
- *path = stub_char;
- path[1] = '\0';
+ /* Make sure P points to the beginning of the next path element,
+ which is location after the slash. */
+ ++p;
}
+
+ return change;
}
\f
/* "Touch" FILE, i.e. make its atime and mtime equal to the time
}
return 0;
}
+
+/* Merge BASE with FILE. BASE can be a directory or a file name, FILE
+ should be a file name.
+
+ file_merge("/foo/bar", "baz") => "/foo/baz"
+ file_merge("/foo/bar/", "baz") => "/foo/bar/baz"
+ file_merge("foo", "bar") => "bar"
+
+ In other words, it's a simpler and gentler version of uri_merge_1. */
+
+char *
+file_merge (const char *base, const char *file)
+{
+ char *result;
+ const char *cut = (const char *)strrchr (base, '/');
+
+ if (!cut)
+ return xstrdup (file);
+
+ result = (char *)xmalloc (cut - base + 1 + strlen (file) + 1);
+ memcpy (result, base, cut - base);
+ result[cut - base] = '/';
+ strcpy (result + (cut - base) + 1, file);
+
+ return result;
+}
\f
static int in_acclist PARAMS ((const char *const *, const char *, int));
match_backwards ("abc", "bc") -> 1
match_backwards ("abc", "ab") -> 0
match_backwards ("abc", "abc") -> 1 */
-static int
-match_backwards (const char *string, const char *pattern)
+int
+match_tail (const char *string, const char *pattern)
{
int i, j;
}
/* Checks whether string S matches each element of ACCEPTS. A list
- element are matched either with fnmatch() or match_backwards(),
+ element are matched either with fnmatch() or match_tail(),
according to whether the element contains wildcards or not.
If the BACKWARD is 0, don't do backward comparison -- just compare
{
if (backward)
{
- if (match_backwards (s, *accepts))
+ if (match_tail (s, *accepts))
return 1;
}
else
return 0;
}
-/* Return the malloc-ed suffix of STR. For instance:
+/* Return the location of STR's suffix (file extension). Examples:
suffix ("foo.bar") -> "bar"
suffix ("foo.bar.baz") -> "baz"
suffix ("/foo/bar") -> NULL
{
int i;
- for (i = strlen (str); i && str[i] != '/' && str[i] != '.'; i--);
+ for (i = strlen (str); i && str[i] != '/' && str[i] != '.'; i--)
+ ;
+
if (str[i++] == '.')
- return xstrdup (str + i);
+ return (char *)str + i;
else
return NULL;
}
-/* Read a line from FP. The function reallocs the storage as needed
- to accomodate for any length of the line. Reallocs are done
- storage exponentially, doubling the storage after each overflow to
- minimize the number of calls to realloc() and fgets(). The newline
- character at the end of line is retained.
+/* Read a line from FP and return the pointer to freshly allocated
+ storage. The stoarage space is obtained through malloc() and
+ should be freed with free() when it is no longer needed.
+
+ The length of the line is not limited, except by available memory.
+ The newline character at the end of line is retained. The line is
+ terminated with a zero character.
After end-of-file is encountered without anything being read, NULL
is returned. NULL is also returned on error. To distinguish
read_whole_line (FILE *fp)
{
int length = 0;
- int bufsize = 81;
+ int bufsize = 82;
char *line = (char *)xmalloc (bufsize);
while (fgets (line + length, bufsize - length, fp))
{
length += strlen (line + length);
- assert (length > 0);
+ if (length == 0)
+ /* Possible for example when reading from a binary file where
+ a line begins with \0. */
+ continue;
+
if (line[length - 1] == '\n')
break;
+
/* fgets() guarantees to read the whole line, or to use up the
space we've given it. We can double the buffer
unconditionally. */
/* First check whether the set element already exists. If it does,
do nothing so that we don't have to free() the old element and
then strdup() a new one. */
- if (hash_table_exists (ht, s))
+ if (hash_table_contains (ht, s))
return;
/* We use "1" as value. It provides us a useful and clear arbitrary
hash_table_put (ht, xstrdup (s), "1");
}
-/* Synonym for hash_table_exists... */
+/* Synonym for hash_table_contains... */
int
-string_set_exists (struct hash_table *ht, const char *s)
+string_set_contains (struct hash_table *ht, const char *s)
{
- return hash_table_exists (ht, s);
+ return hash_table_contains (ht, s);
}
static int
{
char inbuf[24];
/* Print the number into the buffer. */
- long_to_string (inbuf, l);
+ number_to_string (inbuf, l);
return legible_1 (inbuf);
}
bytes are sufficient. Using more might be a good idea.
This function does not go through the hoops that long_to_string
- goes to because it doesn't need to be fast. (It's called perhaps
+ goes to because it doesn't aspire to be fast. (It's called perhaps
once in a Wget run.) */
static void
/* Count the digits in a (long) integer. */
int
-numdigit (long a)
+numdigit (long number)
{
- int res = 1;
- if (a < 0)
- a = -a;
- while ((a /= 10) != 0)
- ++res;
- return res;
+ int cnt = 1;
+ if (number < 0)
+ {
+ number = -number;
+ ++cnt;
+ }
+ while ((number /= 10) > 0)
+ ++cnt;
+ return cnt;
}
#define ONE_DIGIT(figure) *p++ = n / (figure) + '0'
#define DIGITS_9(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_8 ((figure) / 10)
#define DIGITS_10(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_9 ((figure) / 10)
-/* DIGITS_<11-20> are only used on 64-bit machines. */
+/* DIGITS_<11-20> are only used on machines with 64-bit longs. */
#define DIGITS_11(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_10 ((figure) / 10)
#define DIGITS_12(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_11 ((figure) / 10)
#define DIGITS_18(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_17 ((figure) / 10)
#define DIGITS_19(figure) ONE_DIGIT_ADVANCE (figure); DIGITS_18 ((figure) / 10)
-/* Print NUMBER to BUFFER in base 10. This is completely equivalent
- to `sprintf(buffer, "%ld", number)', only much faster.
+/* Print NUMBER to BUFFER in base 10. This should be completely
+ equivalent to `sprintf(buffer, "%ld", number)', only much faster.
The speedup may make a difference in programs that frequently
convert numbers to strings. Some implementations of sprintf,
particularly the one in GNU libc, have been known to be extremely
slow compared to this function.
- BUFFER should accept as many bytes as you expect the number to take
- up. On 64-bit machines, the maximum needed size is 24 bytes. That
- includes all the digits, as well as the `-' sign for negative
- numbers and the trailing \0. */
+ Return the pointer to the location where the terminating zero was
+ printed. (Equivalent to calling buffer+strlen(buffer) after the
+ function is done.)
-void
-long_to_string (char *buffer, long number)
+ BUFFER should be big enough to accept as many bytes as you expect
+ the number to take up. On machines with 64-bit longs the maximum
+ needed size is 24 bytes. That includes the digits needed for the
+ largest 64-bit number, the `-' sign in case it's negative, and the
+ terminating '\0'. */
+
+char *
+number_to_string (char *buffer, long number)
{
char *p = buffer;
long n = number;
/* We are running in a strange or misconfigured environment. Let
sprintf cope with it. */
sprintf (buffer, "%ld", n);
+ p += strlen (buffer);
#else /* (SIZEOF_LONG == 4) || (SIZEOF_LONG == 8) */
if (n < 0)
*p = '\0';
#endif /* (SIZEOF_LONG == 4) || (SIZEOF_LONG == 8) */
+
+ return p;
}
#undef ONE_DIGIT
};
/* Allocate a timer. It is not legal to do anything with a freshly
- allocated timer, except call wtimer_reset(). */
+ allocated timer, except call wtimer_reset() or wtimer_delete(). */
struct wget_timer *
wtimer_allocate (void)
*p = '\0';
return res;
}
+
+/* Determine the width of the terminal we're running on. If that's
+ not possible, return 0. */
+
+int
+determine_screen_width (void)
+{
+ /* If there's a way to get the terminal size using POSIX
+ tcgetattr(), somebody please tell me. */
+#ifndef TIOCGWINSZ
+ return 0;
+#else /* TIOCGWINSZ */
+ int fd;
+ struct winsize wsz;
+
+ if (opt.lfilename != NULL)
+ return 0;
+
+ fd = fileno (stderr);
+ if (ioctl (fd, TIOCGWINSZ, &wsz) < 0)
+ return 0; /* most likely ENOTTY */
+
+ return wsz.ws_col;
+#endif /* TIOCGWINSZ */
+}
+
+#if 0
+/* A debugging function for checking whether an MD5 library works. */
+
+#include "gen-md5.h"
+
+char *
+debug_test_md5 (char *buf)
+{
+ unsigned char raw[16];
+ static char res[33];
+ unsigned char *p1;
+ char *p2;
+ int cnt;
+ ALLOCA_MD5_CONTEXT (ctx);
+
+ gen_md5_init (ctx);
+ gen_md5_update ((unsigned char *)buf, strlen (buf), ctx);
+ gen_md5_finish (ctx, raw);
+
+ p1 = raw;
+ p2 = res;
+ cnt = 16;
+ while (cnt--)
+ {
+ *p2++ = XDIGIT_TO_xchar (*p1 >> 4);
+ *p2++ = XDIGIT_TO_xchar (*p1 & 0xf);
+ ++p1;
+ }
+ *p2 = '\0';
+
+ return res;
+}
+#endif
+
+#if 0
+/* Debugging and testing support for path_simplify. */
+
+/* Debug: run path_simplify on PATH and return the result in a new
+ string. Useful for calling from the debugger. */
+static char *
+ps (char *path)
+{
+ char *copy = xstrdup (path);
+ path_simplify (copy);
+ return copy;
+}
+
+static void
+run_test (char *test, char *expected_result, int expected_change)
+{
+ char *test_copy = xstrdup (test);
+ int modified = path_simplify (test_copy);
+
+ if (0 != strcmp (test_copy, expected_result))
+ {
+ printf ("Failed path_simplify(\"%s\"): expected \"%s\", got \"%s\".\n",
+ test, expected_result, test_copy);
+ }
+ if (modified != expected_change)
+ {
+ if (expected_change == 1)
+ printf ("Expected no modification with path_simplify(\"%s\").\n",
+ test);
+ else
+ printf ("Expected modification with path_simplify(\"%s\").\n",
+ test);
+ }
+ xfree (test_copy);
+}
+
+static void
+test_path_simplify (void)
+{
+ static struct {
+ char *test, *result;
+ int should_modify;
+ } tests[] = {
+ { "", "", 0 },
+ { ".", "", 1 },
+ { "..", "", 1 },
+ { "foo", "foo", 0 },
+ { "foo/bar", "foo/bar", 0 },
+ { "foo///bar", "foo/bar", 1 },
+ { "foo/.", "foo/", 1 },
+ { "foo/./", "foo/", 1 },
+ { "foo./", "foo./", 0 },
+ { "foo/../bar", "bar", 1 },
+ { "foo/../bar/", "bar/", 1 },
+ { "foo/bar/..", "foo/", 1 },
+ { "foo/bar/../x", "foo/x", 1 },
+ { "foo/bar/../x/", "foo/x/", 1 },
+ { "foo/..", "", 1 },
+ { "foo/../..", "", 1 },
+ { "a/b/../../c", "c", 1 },
+ { "./a/../b", "b", 1 }
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE (tests); i++)
+ {
+ char *test = tests[i].test;
+ char *expected_result = tests[i].result;
+ int expected_change = tests[i].should_modify;
+ run_test (test, expected_result, expected_change);
+ }
+
+ /* Now run all the tests with a leading slash before the test case,
+ to prove that the slash is being preserved. */
+ for (i = 0; i < ARRAY_SIZE (tests); i++)
+ {
+ char *test, *expected_result;
+ int expected_change = tests[i].should_modify;
+
+ test = xmalloc (1 + strlen (tests[i].test) + 1);
+ sprintf (test, "/%s", tests[i].test);
+
+ expected_result = xmalloc (1 + strlen (tests[i].result) + 1);
+ sprintf (expected_result, "/%s", tests[i].result);
+
+ run_test (test, expected_result, expected_change);
+
+ xfree (test);
+ xfree (expected_result);
+ }
+}
+#endif