New --set-content-location functionality...

Anjan Samanta <[email protected]> Sat, 13 Aug 2005 16:31:34 +0100
Newsgroups gmane.comp.web.wget.patches
Message-ID <[email protected]>
I got tired of not knowing where all my files came from,
I thought that my shell experience could be a little richer,
and so I wrote this patch to do some information capture for me.

When you use --set-content-location, wget will add an extended attribute
onto all of your downloaded files telling you where the file came from.
The extended attribute is called user.Content-Location, and it will
naturally only be created on file-systems that support extended attributes.

This patch can be broken down into 4 parts:
 - `./configure --enable-xattr' option:  configure.in INSTALL NEWS
 - `wget --set-content-location' option: options.h init.c main.c
 - core functionality:                   http.c ftp.c ftp-ls.c convert.c
 - documentation:                        wget.texi sample.wgetrc

I think that the ChangeLog entries should look something like this:

2005-08-13  Anjan Samanta  <[email protected]>
	New --set-content-location functionality.
	* NEWS: New --set-content-location flag.
	* INSTALL: New --enable-xattr option to ./configure.
	* configure.in: New --enable-xattr option checks for extended file
	attribute functionality.

2005-08-13  Anjan Samanta  <[email protected]>
	New --set-content-location functionality.
	* options.h (struct options): New flag opt.content_location_xattr.
	* init.c (commands): Added setcontentlocation.
	* main.c (option_data): Added set-content-location/setcontentlocation.
	(print_help): Added --set-content-location flag.
	(main) Now unlink()-s opt.output_document if we were going to truncate
	it anyway, in order to have a nice clean file to set xattrs on.

	New --set-content-location functionality.
	* http.c (gethttp): Compute and set user.Content-Location xattr.
	* ftp.c (getftp, ftp_retrieve_list): Set user.Content-Location xattr.
	* ftp-ls (ftp_index): Set user.Content-Location xattr on symlink.
	* convert.c (convert_links): Transfer user.Content-Location xattr
	from the unlink()-d file to the file that replaces it.

2005-08-13  Anjan Samanta  <[email protected]>
	New --set-content-location functionality.
	* wget.texi (Download Options): Added --set-content-location flag.
	* sample.wgetrc: Added set-content-location flag.

Here's the patch (the output of: svn diff):

Index: configure.in
===================================================================
--- configure.in	(revision 2052)
+++ configure.in	(working copy)
@@ -447,6 +447,30 @@
 dnl internationalization macros
 WGET_WITH_NLS
 
+
+AC_ARG_ENABLE(xattr,
+[  --disable-xattr         disable support for extended attributes on files],
+ENABLE_XATTR=$enableval, ENABLE_XATTR=yes)
+if test x"${ENABLE_XATTR}" = xyes; then
+  AC_CHECK_HEADER([sys/xattr.h], [], [
+    AC_MSG_NOTICE([Disabling xattr support; your system does not provide sys/xattr.h])
+    ENABLE_XATTR=no
+  ])
+else
+  AC_MSG_NOTICE([Skipping support for xattr])
+fi
+if test x"${ENABLE_XATTR}" = xyes; then
+  AC_CHECK_FUNCS(fsetxattr lsetxattr getxattr, [], [
+    AC_MSG_NOTICE([Disabling xattr support; cannot find getxattr/setxattr(2) implementation])
+    ENABLE_XATTR=no
+  ])
+fi
+if test x"${ENABLE_XATTR}" = xyes; then
+  AC_DEFINE([ENABLE_XATTR], 1, [Define if you want to enable xattr functionality.])
+  AC_MSG_NOTICE([Enabling support for xattr])
+fi
+
+
 dnl
 dnl Find makeinfo.  We used to provide support for Emacs processing
 dnl Texinfo using `emacs -batch -eval ...' where makeinfo is
Index: src/options.h
===================================================================
--- src/options.h	(revision 2052)
+++ src/options.h	(working copy)
@@ -220,6 +220,12 @@
     prefer_none
   } prefer_family;		/* preferred address family when more
 				   than one type is available */
+
+#ifdef ENABLE_XATTR
+  bool content_location_xattr;  /* whether to try to set an extended file
+				   attribute, user.Content-Type, on the files
+				   downloaded.  */
+#endif
 };
 
 extern struct options opt;
Index: src/init.c
===================================================================
--- src/init.c	(revision 2052)
+++ src/init.c	(working copy)
@@ -224,6 +224,9 @@
   { "secureprotocol",	&opt.secure_protocol,	cmd_spec_secure_protocol },
 #endif
   { "serverresponse",	&opt.server_response,	cmd_boolean },
+#ifdef ENABLE_XATTR
+  { "setcontentlocation", &opt.content_location_xattr, cmd_boolean },
+#endif
   { "spanhosts",	&opt.spanhost,		cmd_boolean },
   { "spider",		&opt.spider,		cmd_boolean },
   { "strictcomments",	&opt.strict_comments,	cmd_boolean },
Index: src/http.c
===================================================================
--- src/http.c	(revision 2052)
+++ src/http.c	(working copy)
@@ -39,6 +39,9 @@
 #include <errno.h>
 #include <time.h>
 #include <locale.h>
+#ifdef ENABLE_XATTR
+# include <sys/xattr.h>
+#endif
 
 #include "wget.h"
 #include "http.h"
@@ -1141,6 +1144,7 @@
   struct request *req;
 
   char *type;
+  char *content_location = 0;
   char *user, *passwd;
   char *proxyauth;
   int statcode;
@@ -1717,6 +1721,15 @@
 	  *tmp = '\0';
 	}
     }
+  content_location = resp_header_strdup (resp, "Content-Location");
+  if (content_location)
+    {
+      char *tmp = uri_merge (u->url, content_location);
+      xfree_null (content_location);
+      content_location = tmp;
+    }
+  else
+    content_location = xstrdup (u->url);
   hs->newloc = resp_header_strdup (resp, "Location");
   hs->remote_time = resp_header_strdup (resp, "Last-Modified");
 
@@ -1772,6 +1785,7 @@
 	  else
 	    CLOSE_INVALIDATE (sock);
 	  xfree_null (type);
+	  xfree_null (content_location);
 	  return NEWLOCATION;
 	}
     }
@@ -1830,6 +1844,7 @@
       /* Mark as successfully retrieved. */
       *dt |= RETROKF;
       xfree_null (type);
+      xfree_null (content_location);
       CLOSE_INVALIDATE (sock);	/* would be CLOSE_FINISH, but there
 				   might be more bytes in the body. */
       return RETRUNNEEDED;
@@ -1840,6 +1855,7 @@
       /* The Range request was somehow misunderstood by the server.
 	 Bail out.  */
       xfree_null (type);
+      xfree_null (content_location);
       CLOSE_INVALIDATE (sock);
       return RANGEERR;
     }
@@ -1889,6 +1905,7 @@
       hs->len = 0;
       hs->res = 0;
       xfree_null (type);
+      xfree_null (content_location);
       if (head_only)
 	/* Pre-1.10 Wget used CLOSE_INVALIDATE here.  Now we trust the
 	   servers not to send body in response to a HEAD request, and
@@ -1911,12 +1928,12 @@
       if (opt.backups)
 	rotate_backups (*hs->local_file);
       if (hs->restval)
-	fp = fopen (*hs->local_file, "ab");
+	fp = fopen (*hs->local_file, "ab"); /* xattr set below */
       else if (ALLOW_CLOBBER)
-	fp = fopen (*hs->local_file, "wb");
+	fp = fopen (*hs->local_file, "wb"); /* xattr set below */
       else
 	{
-	  fp = fopen_excl (*hs->local_file, true);
+	  fp = fopen_excl (*hs->local_file, true); /* xattr set below */
 	  if (!fp && errno == EEXIST)
 	    {
 	      /* We cannot just invent a new name and use it (which is
@@ -1940,6 +1957,18 @@
   else
     fp = output_stream;
 
+#ifdef ENABLE_XATTR
+  if (opt.content_location_xattr)
+    {
+      if (fsetxattr (fileno (fp), "user.Content-Location", content_location,
+                     strlen (content_location), XATTR_CREATE) != 0)
+	fsetxattr (fileno (fp), "user.Content-Location", "", 0, 0);
+    }
+#endif
+
+  xfree_null (content_location);
+  content_location = NULL;	/* We don't need it any more.  */
+
   /* #### This confuses the timestamping code that checks for file
      size.  Maybe we should save some additional information?  */
   if (opt.save_headers)
Index: src/ftp.c
===================================================================
--- src/ftp.c	(revision 2052)
+++ src/ftp.c	(working copy)
@@ -37,6 +37,9 @@
 #endif
 #include <assert.h>
 #include <errno.h>
+#ifdef ENABLE_XATTR
+# include <sys/xattr.h>
+#endif
 
 #include "wget.h"
 #include "utils.h"
@@ -914,13 +917,13 @@
 	rotate_backups (con->target);
 
       if (restval)
-	fp = fopen (con->target, "ab");
+	fp = fopen (con->target, "ab"); /* xattr set below */
       else if (opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct
 	       || opt.output_document)
-	fp = fopen (con->target, "wb");
+	fp = fopen (con->target, "wb"); /* xattr set below */
       else
 	{
-	  fp = fopen_excl (con->target, true);
+	  fp = fopen_excl (con->target, true); /* xattr set below */
 	  if (!fp && errno == EEXIST)
 	    {
 	      /* We cannot just invent a new name and use it (which is
@@ -949,6 +952,15 @@
   else
     fp = output_stream;
 
+#ifdef ENABLE_XATTR
+  if (opt.content_location_xattr)
+    {
+      if (fsetxattr (fileno (fp), "user.Content-Location",
+                     u->url, strlen (u->url), XATTR_CREATE) != 0)
+	fsetxattr (fileno (fp), "user.Content-Location", "", 0, 0);
+    }
+#endif
+
   if (*len)
     {
       print_length (*len, restval, true);
@@ -1492,6 +1504,11 @@
 		  unlink (con->target);
 		  if (symlink (f->linkto, con->target) == -1)
 		    logprintf (LOG_NOTQUIET, "symlink: %s\n", strerror (errno));
+#ifdef ENABLE_XATTR
+		  if (opt.content_location_xattr)
+		    lsetxattr (con->target, "user.Content-Location",
+                               u->url, strlen (u->url), 0);
+#endif /* ENABLE_XATTR */
 		  logputs (LOG_VERBOSE, "\n");
 		} /* have f->linkto */
 #else  /* not HAVE_SYMLINK */
Index: src/ftp-ls.c
===================================================================
--- src/ftp-ls.c	(revision 2052)
+++ src/ftp-ls.c	(working copy)
@@ -37,6 +37,9 @@
 #endif
 #include <errno.h>
 #include <time.h>
+#ifdef ENABLE_XATTR
+# include <sys/xattr.h>
+#endif
 
 #include "wget.h"
 #include "utils.h"
@@ -857,7 +860,7 @@
 
   if (!output_stream)
     {
-      fp = fopen (file, "wb");
+      fp = fopen (file, "wb"); /* xattr set below */
       if (!fp)
 	{
 	  logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
@@ -866,6 +869,16 @@
     }
   else
     fp = output_stream;
+
+#ifdef ENABLE_XATTR
+  if (opt.content_location_xattr)
+    {
+      if (fsetxattr (fileno (fp), "user.Content-Location",
+                     u->url, strlen (u->url), XATTR_CREATE) != 0)
+	fsetxattr (fileno (fp), "user.Content-Location", "", 0, 0);
+    }
+#endif
+
   if (u->user)
     {
       char *tmpu, *tmpp;        /* temporary, clean user and passwd */
Index: src/convert.c
===================================================================
--- src/convert.c	(revision 2052)
+++ src/convert.c	(working copy)
@@ -37,6 +37,9 @@
 #endif /* HAVE_UNISTD_H */
 #include <errno.h>
 #include <assert.h>
+#ifdef ENABLE_XATTR
+# include <sys/xattr.h>
+#endif
 
 #include "wget.h"
 #include "convert.h"
@@ -189,6 +192,7 @@
   FILE *fp;
   const char *p;
   downloaded_file_t downloaded_file_return;
+  char *content_location = 0;
 
   struct urlpos *link;
   int to_url_count = 0, to_file_count = 0;
@@ -223,6 +227,25 @@
   if (opt.backup_converted && downloaded_file_return)
     write_backup_file (file, downloaded_file_return);
 
+#ifdef ENABLE_XATTR
+  if (opt.content_location_xattr)
+    {
+      ssize_t tmp = getxattr (file, "user.Content-Location", 0, 0);
+      if (tmp >= 0)
+	{
+	  content_location = xmalloc (tmp + 1);
+	  tmp = getxattr (file, "user.Content-Location", content_location, tmp);
+	  if (tmp >= 0)
+	    content_location[tmp] = '\0';
+	  else
+	    {
+	      xfree_null (content_location);
+	      content_location = 0;
+	    }
+	}
+    }
+#endif
+
   /* Before opening the file for writing, unlink the file.  This is
      important if the data in FM is mmaped.  In such case, nulling the
      file, which is what fopen() below does, would make us read all
@@ -232,6 +255,7 @@
       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
 		 file, strerror (errno));
       read_file_free (fm);
+      xfree_null (content_location);
       return;
     }
   /* Now open the file for writing.  */
@@ -241,9 +265,21 @@
       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
 		 file, strerror (errno));
       read_file_free (fm);
+      xfree_null (content_location);
       return;
     }
+#ifdef ENABLE_XATTR
+  else
+    {
+      if (opt.content_location_xattr && content_location)
+	fsetxattr (fileno (fp), "user.Content-Location",
+                   content_location, strlen (content_location), 0);
+    }
+#endif
 
+  xfree_null (content_location);
+  content_location = 0;
+
   /* Here we loop through all the URLs in file, replacing those of
      them that are downloaded with relative references.  */
   p = fm->content;
Index: src/main.c
===================================================================
--- src/main.c	(revision 2052)
+++ src/main.c	(working copy)
@@ -227,6 +227,9 @@
     { "save-headers", 0, OPT_BOOLEAN, "saveheaders", -1 },
     { IF_SSL ("secure-protocol"), 0, OPT_VALUE, "secureprotocol", -1 },
     { "server-response", 'S', OPT_BOOLEAN, "serverresponse", -1 },
+#ifdef ENABLE_XATTR
+    { "set-content-location", 0, OPT_BOOLEAN, "setcontentlocation", -1 },
+#endif
     { "span-hosts", 'H', OPT_BOOLEAN, "spanhosts", -1 },
     { "spider", 0, OPT_BOOLEAN, "spider", -1 },
     { "strict-comments", 0, OPT_BOOLEAN, "strictcomments", -1 },
@@ -462,6 +465,11 @@
        --user=USER               set both ftp and http user to USER.\n"),
     N_("\
        --password=PASS           set both ftp and http password to PASS.\n"),
+#ifdef ENABLE_XATTR
+    N_("\
+       --set-content-location    set an extended file attribute,\n\
+                                 user.Content-Location, on downloaded files.\n"),
+#endif
     "\n",
 
     N_("\
@@ -897,8 +905,11 @@
       else
 	{
 	  struct_fstat st;
+	  if (opt.always_rest == 0)
+	    unlink (opt.output_document); /* nuke problematic state, if any */
 	  output_stream = fopen (opt.output_document,
 				 opt.always_rest ? "ab" : "wb");
+	  /* xattr set just before stream usage */
 	  if (output_stream == NULL)
 	    {
 	      perror (opt.output_document);
Index: doc/sample.wgetrc
===================================================================
--- doc/sample.wgetrc	(revision 2052)
+++ doc/sample.wgetrc	(working copy)
@@ -58,6 +58,12 @@
 ## are doing before doing so.
 ##
 
+# Set this to on to put an extended attribute called
+# user.Content-Location on all your downloaded files containing the url
+# of where the file was downloaded from. (This option has no effect if
+# your filesystem does not support extended attributes.)
+#set-content-location = off
+
 # Set this to on to use timestamping by default:
 #timestamping = off
 
Index: doc/wget.texi
===================================================================
--- doc/wget.texi	(revision 2052)
+++ doc/wget.texi	(working copy)
@@ -969,6 +969,13 @@
 using the @samp{--ftp-user} and @samp{--ftp-password} options for 
 @sc{ftp} connections and the @samp{--http-user} and @samp{--http-password} 
 options for @sc{http} connections.
+
+@cindex Content-Location, capture
+@cindex extended file attributes
+@item --set-content-location
+On filesystems that support it, add onto every downloaded file, an
+extended attribute called user.Content-Location, containing the url of
+where the file was downloaded from.
 @end table
 
 @node Directory Options
Index: INSTALL
===================================================================
--- INSTALL	(revision 2052)
+++ INSTALL	(working copy)
@@ -98,6 +98,7 @@
   --disable-largefile     omit support for large files
   --disable-ipv6          disable IPv6 support
   --disable-rpath         do not hardcode runtime library paths
+  --disable-xattr         disable support for extended attributes on files
 
 You can inspect decisions made by configure by editing the generated
 Makefiles and the `src/config.h' include file.  The defaults should
Index: NEWS
===================================================================
--- NEWS	(revision 2052)
+++ NEWS	(working copy)
@@ -7,6 +7,12 @@
 
 * Changes in Wget 1.11.
 
+** The new function `--set-content-location' makes Wget put an
+extended attribute called `user.Content-Location' on all downloaded
+files containing the url of where the file was downloaded file. (This
+option has no effect if your filesystem does not support extended
+attributes.)
+
 ** The new function `--ignore-case' makes Wget ignore case when
 matching files, directories, and wildcards.  This affects the -X, -I,
 -A, and -R options, as well as globbing in FTP URLs.