Imported Upstream version 7.44.0
[platform/upstream/curl.git] / lib / hostip.c
index 2ea0ab6..82f3897 100644 (file)
 #include "url.h"
 #include "inet_ntop.h"
 #include "warnless.h"
-
-#define _MPRINTF_REPLACE /* use our functions only */
-#include <curl/mprintf.h>
-
+#include "curl_printf.h"
 #include "curl_memory.h"
 /* The last #include file should be: */
 #include "memdebug.h"
@@ -140,11 +137,7 @@ struct curl_hash *Curl_global_host_cache_init(void)
 void Curl_global_host_cache_dtor(void)
 {
   if(host_cache_initialized) {
-    /* first make sure that any custom "CURLOPT_RESOLVE" names are
-       cleared off */
-    Curl_hostcache_clean(NULL, &hostname_cache);
-    /* then free the remaining hash completely */
-    Curl_hash_clean(&hostname_cache);
+    Curl_hash_destroy(&hostname_cache);
     host_cache_initialized = 0;
   }
 }
@@ -237,7 +230,8 @@ hostcache_timestamp_remove(void *datap, void *hc)
     (struct hostcache_prune_data *) datap;
   struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
 
-  return !c->inuse && (data->now - c->timestamp >= data->cache_timeout);
+  return (0 != c->timestamp)
+    && (data->now - c->timestamp >= data->cache_timeout);
 }
 
 /*
@@ -283,40 +277,54 @@ void Curl_hostcache_prune(struct SessionHandle *data)
     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
 }
 
-/*
- * Check if the entry should be pruned. Assumes a locked cache.
- */
-static int
-remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns)
+#ifdef HAVE_SIGSETJMP
+/* Beware this is a global and unique instance. This is used to store the
+   return address that we can jump back to from inside a signal handler. This
+   is not thread-safe stuff. */
+sigjmp_buf curl_jmpenv;
+#endif
+
+/* lookup address, returns entry if found and not stale */
+static struct Curl_dns_entry *
+fetch_addr(struct connectdata *conn,
+                const char *hostname,
+                int port)
 {
-  struct hostcache_prune_data user;
+  char *entry_id = NULL;
+  struct Curl_dns_entry *dns = NULL;
+  size_t entry_len;
+  struct SessionHandle *data = conn->data;
 
-  if(!dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache ||
-     dns->inuse)
-    /* cache forever means never prune, and NULL hostcache means we can't do
-       it, if it still is in use then we leave it */
-    return 0;
+  /* Create an entry id, based upon the hostname and port */
+  entry_id = create_hostcache_id(hostname, port);
+  /* If we can't create the entry id, fail */
+  if(!entry_id)
+    return dns;
 
-  time(&user.now);
-  user.cache_timeout = data->set.dns_cache_timeout;
+  entry_len = strlen(entry_id);
 
-  if(!hostcache_timestamp_remove(&user,dns) )
-    return 0;
+  /* See if its already in our dns cache */
+  dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
 
-  Curl_hash_clean_with_criterium(data->dns.hostcache,
-                                 (void *) &user,
-                                 hostcache_timestamp_remove);
+  if(dns && (data->set.dns_cache_timeout != -1))  {
+    /* See whether the returned entry is stale. Done before we release lock */
+    struct hostcache_prune_data user;
 
-  return 1;
-}
+    time(&user.now);
+    user.cache_timeout = data->set.dns_cache_timeout;
 
+    if(hostcache_timestamp_remove(&user, dns)) {
+      infof(data, "Hostname in DNS cache was stale, zapped\n");
+      dns = NULL; /* the memory deallocation is being handled by the hash */
+      Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1);
+    }
+  }
 
-#ifdef HAVE_SIGSETJMP
-/* Beware this is a global and unique instance. This is used to store the
-   return address that we can jump back to from inside a signal handler. This
-   is not thread-safe stuff. */
-sigjmp_buf curl_jmpenv;
-#endif
+  /* free the allocated entry_id again */
+  free(entry_id);
+
+  return dns;
+}
 
 /*
  * Curl_fetch_addr() fetches a 'Curl_dns_entry' already in the DNS cache.
@@ -328,38 +336,27 @@ sigjmp_buf curl_jmpenv;
  * lookups for the same hostname requested by different handles.
  *
  * Returns the Curl_dns_entry entry pointer or NULL if not in the cache.
+ *
+ * The returned data *MUST* be "unlocked" with Curl_resolv_unlock() after
+ * use, or we'll leak memory!
  */
 struct Curl_dns_entry *
 Curl_fetch_addr(struct connectdata *conn,
                 const char *hostname,
                 int port)
 {
-  char *entry_id = NULL;
-  struct Curl_dns_entry *dns = NULL;
-  size_t entry_len;
   struct SessionHandle *data = conn->data;
-  int stale;
-
-  /* Create an entry id, based upon the hostname and port */
-  entry_id = create_hostcache_id(hostname, port);
-  /* If we can't create the entry id, fail */
-  if(!entry_id)
-    return dns;
+  struct Curl_dns_entry *dns = NULL;
 
-  entry_len = strlen(entry_id);
+  if(data->share)
+    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
-  /* See if its already in our dns cache */
-  dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1);
+  dns = fetch_addr(conn, hostname, port);
 
-  /* free the allocated entry_id again */
-  free(entry_id);
+  if(dns) dns->inuse++; /* we use it! */
 
-  /* See whether the returned entry is stale. Done before we release lock */
-  stale = remove_entry_if_stale(data, dns);
-  if(stale) {
-    infof(data, "Hostname in DNS cache was stale, zapped\n");
-    dns = NULL; /* the memory deallocation is being handled by the hash */
-  }
+  if(data->share)
+    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
 
   return dns;
 }
@@ -398,11 +395,11 @@ Curl_cache_addr(struct SessionHandle *data,
     return NULL;
   }
 
-  dns->inuse = 0;   /* init to not used */
+  dns->inuse = 1;   /* the cache has the first reference */
   dns->addr = addr; /* this is the address(es) */
   time(&dns->timestamp);
   if(dns->timestamp == 0)
-    dns->timestamp = 1;   /* zero indicates that entry isn't in hash table */
+    dns->timestamp = 1;   /* zero indicates CURLOPT_RESOLVE entry */
 
   /* Store the resolved data in our DNS cache. */
   dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1,
@@ -458,7 +455,7 @@ int Curl_resolv(struct connectdata *conn,
   if(data->share)
     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
-  dns = Curl_fetch_addr(conn, hostname, port);
+  dns = fetch_addr(conn, hostname, port);
 
   if(dns) {
     infof(data, "Hostname %s was found in DNS cache\n", hostname);
@@ -610,32 +607,6 @@ int Curl_resolv_timeout(struct connectdata *conn,
        we want to wait less than one second we must bail out already now. */
     return CURLRESOLV_TIMEDOUT;
 
-  /*************************************************************
-   * Set signal handler to catch SIGALRM
-   * Store the old value to be able to set it back later!
-   *************************************************************/
-#ifdef HAVE_SIGACTION
-  sigaction(SIGALRM, NULL, &sigact);
-  keep_sigact = sigact;
-  keep_copysig = TRUE; /* yes, we have a copy */
-  sigact.sa_handler = alarmfunc;
-#ifdef SA_RESTART
-  /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
-  sigact.sa_flags &= ~SA_RESTART;
-#endif
-  /* now set the new struct */
-  sigaction(SIGALRM, &sigact, NULL);
-#else /* HAVE_SIGACTION */
-  /* no sigaction(), revert to the much lamer signal() */
-#ifdef HAVE_SIGNAL
-  keep_sigact = signal(SIGALRM, alarmfunc);
-#endif
-#endif /* HAVE_SIGACTION */
-
-  /* alarm() makes a signal get sent when the timeout fires off, and that
-     will abort system calls */
-  prev_alarm = alarm(curlx_sltoui(timeout/1000L));
-
   /* This allows us to time-out from the name resolver, as the timeout
      will generate a signal and we will siglongjmp() from that here.
      This technique has problems (see alarmfunc).
@@ -648,6 +619,33 @@ int Curl_resolv_timeout(struct connectdata *conn,
     rc = CURLRESOLV_ERROR;
     goto clean_up;
   }
+  else {
+    /*************************************************************
+     * Set signal handler to catch SIGALRM
+     * Store the old value to be able to set it back later!
+     *************************************************************/
+#ifdef HAVE_SIGACTION
+    sigaction(SIGALRM, NULL, &sigact);
+    keep_sigact = sigact;
+    keep_copysig = TRUE; /* yes, we have a copy */
+    sigact.sa_handler = alarmfunc;
+#ifdef SA_RESTART
+    /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
+    sigact.sa_flags &= ~SA_RESTART;
+#endif
+    /* now set the new struct */
+    sigaction(SIGALRM, &sigact, NULL);
+#else /* HAVE_SIGACTION */
+    /* no sigaction(), revert to the much lamer signal() */
+#ifdef HAVE_SIGNAL
+    keep_sigact = signal(SIGALRM, alarmfunc);
+#endif
+#endif /* HAVE_SIGACTION */
+
+    /* alarm() makes a signal get sent when the timeout fires off, and that
+       will abort system calls */
+    prev_alarm = alarm(curlx_sltoui(timeout/1000L));
+  }
 
 #else
 #ifndef CURLRES_ASYNCH
@@ -719,54 +717,37 @@ clean_up:
  */
 void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns)
 {
-  DEBUGASSERT(dns && (dns->inuse>0));
-
   if(data && data->share)
     Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
 
-  dns->inuse--;
-  /* only free if nobody is using AND it is not in hostcache (timestamp ==
-     0) */
-  if(dns->inuse == 0 && dns->timestamp == 0) {
-    Curl_freeaddrinfo(dns->addr);
-    free(dns);
-  }
+  freednsentry(dns);
 
   if(data && data->share)
     Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
 }
 
 /*
- * File-internal: free a cache dns entry.
+ * File-internal: release cache dns entry reference, free if inuse drops to 0
  */
 static void freednsentry(void *freethis)
 {
-  struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis;
+  struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis;
+  DEBUGASSERT(dns && (dns->inuse>0));
 
-  /* mark the entry as not in hostcache */
-  p->timestamp = 0;
-  if(p->inuse == 0) {
-    Curl_freeaddrinfo(p->addr);
-    free(p);
+  dns->inuse--;
+  if(dns->inuse == 0) {
+    Curl_freeaddrinfo(dns->addr);
+    free(dns);
   }
 }
 
 /*
- * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it.
+ * Curl_mk_dnscache() inits a new DNS cache and returns success/failure.
  */
-struct curl_hash *Curl_mk_dnscache(void)
+int Curl_mk_dnscache(struct curl_hash *hash)
 {
-  return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry);
-}
-
-static int hostcache_inuse(void *data, void *hc)
-{
-  struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc;
-
-  if(c->inuse == 1)
-    Curl_resolv_unlock(data, c);
-
-  return 1; /* free all entries */
+  return Curl_hash_init(hash, 7, Curl_hash_str, Curl_str_key_compare,
+                        freednsentry);
 }
 
 /*
@@ -779,11 +760,13 @@ static int hostcache_inuse(void *data, void *hc)
 void Curl_hostcache_clean(struct SessionHandle *data,
                           struct curl_hash *hash)
 {
-  /* Entries added to the hostcache with the CURLOPT_RESOLVE function are
-   * still present in the cache with the inuse counter set to 1. Detect them
-   * and cleanup!
-   */
-  Curl_hash_clean_with_criterium(hash, data, hostcache_inuse);
+  if(data && data->share)
+    Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+
+  Curl_hash_clean(hash);
+
+  if(data && data->share)
+    Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
 }
 
 
@@ -798,18 +781,52 @@ CURLcode Curl_loadhostpairs(struct SessionHandle *data)
     if(!hostp->data)
       continue;
     if(hostp->data[0] == '-') {
-      /* TODO: mark an entry for removal */
+      char *entry_id;
+      size_t entry_len;
+
+      if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) {
+        infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'!\n",
+              hostp->data);
+        continue;
+      }
+
+      /* Create an entry id, based upon the hostname and port */
+      entry_id = create_hostcache_id(hostname, port);
+      /* If we can't create the entry id, fail */
+      if(!entry_id) {
+        return CURLE_OUT_OF_MEMORY;
+      }
+
+      entry_len = strlen(entry_id);
+
+      if(data->share)
+        Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE);
+
+      /* delete entry, ignore if it didn't exist */
+      Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1);
+
+      if(data->share)
+        Curl_share_unlock(data, CURL_LOCK_DATA_DNS);
+
+      /* free the allocated entry_id again */
+      free(entry_id);
     }
-    else if(3 == sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
-                        address)) {
+    else {
       struct Curl_dns_entry *dns;
       Curl_addrinfo *addr;
       char *entry_id;
       size_t entry_len;
 
+      if(3 != sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port,
+                     address)) {
+        infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n",
+              hostp->data);
+        continue;
+      }
+
       addr = Curl_str2addr(address, port);
       if(!addr) {
-        infof(data, "Resolve %s found illegal!\n", hostp->data);
+        infof(data, "Address in '%s' found illegal!\n", hostp->data);
         continue;
       }
 
@@ -832,9 +849,16 @@ CURLcode Curl_loadhostpairs(struct SessionHandle *data)
       /* free the allocated entry_id again */
       free(entry_id);
 
-      if(!dns)
+      if(!dns) {
         /* if not in the cache already, put this host in the cache */
         dns = Curl_cache_addr(data, addr, hostname, port);
+        if(dns) {
+          dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */
+          /* release the returned reference; the cache itself will keep the
+           * entry alive: */
+          dns->inuse--;
+        }
+      }
       else
         /* this is a duplicate, free it again */
         Curl_freeaddrinfo(addr);