Back to index

tor  0.2.3.18-rc
Functions
microdesc.h File Reference

Header file for microdesc.c. More...

This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

microdesc_cache_tget_microdesc_cache (void)
 Return a pointer to the microdescriptor cache, loading it if necessary.
void microdesc_check_counts (void)
 Make sure that the reference count of every microdescriptor in cache is accurate.
smartlist_tmicrodescs_add_to_cache (microdesc_cache_t *cache, const char *s, const char *eos, saved_location_t where, int no_save, time_t listed_at, smartlist_t *requested_digests256)
 Decode the microdescriptors from the string starting at s and ending at eos, and store them in cache.
smartlist_tmicrodescs_add_list_to_cache (microdesc_cache_t *cache, smartlist_t *descriptors, saved_location_t where, int no_save)
 As microdescs_add_to_cache, but takes a list of micrdescriptors instead of a string to decode.
void microdesc_cache_clean (microdesc_cache_t *cache, time_t cutoff, int force)
 Remove all microdescriptors from cache that haven't been listed for a long time.
int microdesc_cache_rebuild (microdesc_cache_t *cache, int force)
 Regenerate the main cache file for cache, clear the journal file, and update every microdesc_t in the cache with pointers to its new location.
int microdesc_cache_reload (microdesc_cache_t *cache)
 Reload the contents of cache from disk.
void microdesc_cache_clear (microdesc_cache_t *cache)
 Remove every microdescriptor in cache.
microdesc_tmicrodesc_cache_lookup_by_digest256 (microdesc_cache_t *cache, const char *d)
 If there is a microdescriptor in cache whose sha256 digest is d, return it.
size_t microdesc_average_size (microdesc_cache_t *cache)
 Return the mean size of decriptors added to cache since it was last cleared.
smartlist_tmicrodesc_list_missing_digest256 (networkstatus_t *ns, microdesc_cache_t *cache, int downloadable_only, digestmap_t *skip)
 Return a smartlist of all the sha256 digest of the microdescriptors that are listed in ns but not present in cache.
void microdesc_free (microdesc_t *md)
 Deallocate a single microdescriptor.
void microdesc_free_all (void)
 Free all storage held in the microdesc.c module.
void update_microdesc_downloads (time_t now)
 Launch download requests for mircodescriptors as appropriate.
void update_microdescs_from_networkstatus (time_t now)
 For every microdescriptor listed in the current microdecriptor consensus, update its last_listed field to be at least as recent as the publication time of the current microdescriptor consensus.
int usable_consensus_flavor (void)
 Return the consensus flavor we actually want to use to build circuits.
int we_fetch_microdescriptors (const or_options_t *options)
 Return true iff we should try to download microdescriptors at all.
int we_fetch_router_descriptors (const or_options_t *options)
 Return true iff we should try to download router descriptors at all.
int we_use_microdescriptors_for_circuits (const or_options_t *options)
 Return true iff we should prefer to use microdescriptors rather than routerdescs for building circuits.

Detailed Description

Header file for microdesc.c.

Definition in file microdesc.h.


Function Documentation

Return a pointer to the microdescriptor cache, loading it if necessary.

Definition at line 114 of file microdesc.c.

{
  if (PREDICT_UNLIKELY(the_microdesc_cache==NULL)) {
    microdesc_cache_t *cache = tor_malloc_zero(sizeof(microdesc_cache_t));
    HT_INIT(microdesc_map, &cache->map);
    cache->cache_fname = get_datadir_fname("cached-microdescs");
    cache->journal_fname = get_datadir_fname("cached-microdescs.new");
    microdesc_cache_reload(cache);
    the_microdesc_cache = cache;
  }
  return the_microdesc_cache;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Return the mean size of decriptors added to cache since it was last cleared.

Used to estimate the size of large downloads.

Definition at line 617 of file microdesc.c.

{
  if (!cache)
    cache = get_microdesc_cache();
  if (!cache->n_seen)
    return 512;
  return (size_t)(cache->total_len_seen / cache->n_seen);
}

Here is the call graph for this function:

Here is the caller graph for this function:

void microdesc_cache_clean ( microdesc_cache_t cache,
time_t  cutoff,
int  force 
)

Remove all microdescriptors from cache that haven't been listed for a long time.

Does not rebuild the cache on disk. If cutoff is positive, specifically remove microdescriptors that have been unlisted since cutoff. If force is true, remove microdescriptors even if we have no current live microdescriptor consensus.

Definition at line 345 of file microdesc.c.

{
  microdesc_t **mdp, *victim;
  int dropped=0, kept=0;
  size_t bytes_dropped = 0;
  time_t now = time(NULL);

  /* If we don't know a live consensus, don't believe last_listed values: we
   * might be starting up after being down for a while. */
  if (! force &&
      ! networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC))
      return;

  if (cutoff <= 0)
    cutoff = now - TOLERATE_MICRODESC_AGE;

  for (mdp = HT_START(microdesc_map, &cache->map); mdp != NULL; ) {
    if ((*mdp)->last_listed < cutoff) {
      ++dropped;
      victim = *mdp;
      mdp = HT_NEXT_RMV(microdesc_map, &cache->map, mdp);
      victim->held_in_map = 0;
      bytes_dropped += victim->bodylen;
      microdesc_free(victim);
    } else {
      ++kept;
      mdp = HT_NEXT(microdesc_map, &cache->map, mdp);
    }
  }

  if (dropped) {
    log_info(LD_DIR, "Removed %d/%d microdescriptors as old.",
             dropped,dropped+kept);
    cache->bytes_dropped += bytes_dropped;
  }
}

Here is the call graph for this function:

Here is the caller graph for this function:

Remove every microdescriptor in cache.

Definition at line 271 of file microdesc.c.

{
  microdesc_t **entry, **next;
  for (entry = HT_START(microdesc_map, &cache->map); entry; entry = next) {
    microdesc_t *md = *entry;
    next = HT_NEXT_RMV(microdesc_map, &cache->map, entry);
    md->held_in_map = 0;
    microdesc_free(md);
  }
  HT_CLEAR(microdesc_map, &cache->map);
  if (cache->cache_content) {
    tor_munmap_file(cache->cache_content);
    cache->cache_content = NULL;
  }
  cache->total_len_seen = 0;
  cache->n_seen = 0;
  cache->bytes_dropped = 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

If there is a microdescriptor in cache whose sha256 digest is d, return it.

Otherwise return NULL.

Definition at line 604 of file microdesc.c.

{
  microdesc_t *md, search;
  if (!cache)
    cache = get_microdesc_cache();
  memcpy(search.digest, d, DIGEST256_LEN);
  md = HT_FIND(microdesc_map, &cache->map, &search);
  return md;
}

Here is the call graph for this function:

Here is the caller graph for this function:

int microdesc_cache_rebuild ( microdesc_cache_t cache,
int  force 
)

Regenerate the main cache file for cache, clear the journal file, and update every microdesc_t in the cache with pointers to its new location.

If force is true, do this unconditionally. If force is false, do it only if we expect to save space on disk.

Definition at line 405 of file microdesc.c.

{
  open_file_t *open_file;
  FILE *f;
  microdesc_t **mdp;
  smartlist_t *wrote;
  ssize_t size;
  off_t off = 0;
  int orig_size, new_size;

  if (cache == NULL) {
    cache = the_microdesc_cache;
    if (cache == NULL)
      return 0;
  }

  /* Remove dead descriptors */
  microdesc_cache_clean(cache, 0/*cutoff*/, 0/*force*/);

  if (!force && !should_rebuild_md_cache(cache))
    return 0;

  log_info(LD_DIR, "Rebuilding the microdescriptor cache...");

  orig_size = (int)(cache->cache_content ? cache->cache_content->size : 0);
  orig_size += (int)cache->journal_len;

  f = start_writing_to_stdio_file(cache->cache_fname,
                                  OPEN_FLAGS_REPLACE|O_BINARY,
                                  0600, &open_file);
  if (!f)
    return -1;

  wrote = smartlist_new();

  HT_FOREACH(mdp, microdesc_map, &cache->map) {
    microdesc_t *md = *mdp;
    size_t annotation_len;
    if (md->no_save)
      continue;

    size = dump_microdescriptor(f, md, &annotation_len);
    if (size < 0) {
      /* XXX handle errors from dump_microdescriptor() */
      /* log?  return -1?  die?  coredump the universe? */
      continue;
    }
    tor_assert(((size_t)size) == annotation_len + md->bodylen);
    md->off = off + annotation_len;
    off += size;
    if (md->saved_location != SAVED_IN_CACHE) {
      tor_free(md->body);
      md->saved_location = SAVED_IN_CACHE;
    }
    smartlist_add(wrote, md);
  }

  if (cache->cache_content)
    tor_munmap_file(cache->cache_content);

  finish_writing_to_file(open_file); /*XXX Check me.*/

  cache->cache_content = tor_mmap_file(cache->cache_fname);

  if (!cache->cache_content && smartlist_len(wrote)) {
    log_err(LD_DIR, "Couldn't map file that we just wrote to %s!",
            cache->cache_fname);
    smartlist_free(wrote);
    return -1;
  }
  SMARTLIST_FOREACH_BEGIN(wrote, microdesc_t *, md) {
    tor_assert(md->saved_location == SAVED_IN_CACHE);
    md->body = (char*)cache->cache_content->data + md->off;
    if (PREDICT_UNLIKELY(
             md->bodylen < 9 || fast_memneq(md->body, "onion-key", 9) != 0)) {
      /* XXXX once bug 2022 is solved, we can kill this block and turn it
       * into just the tor_assert(!memcmp) */
      off_t avail = cache->cache_content->size - md->off;
      char *bad_str;
      tor_assert(avail >= 0);
      bad_str = tor_strndup(md->body, MIN(128, (size_t)avail));
      log_err(LD_BUG, "After rebuilding microdesc cache, offsets seem wrong. "
              " At offset %d, I expected to find a microdescriptor starting "
              " with \"onion-key\".  Instead I got %s.",
              (int)md->off, escaped(bad_str));
      tor_free(bad_str);
      tor_assert(fast_memeq(md->body, "onion-key", 9));
    }
  } SMARTLIST_FOREACH_END(md);

  smartlist_free(wrote);

  write_str_to_file(cache->journal_fname, "", 1);
  cache->journal_len = 0;
  cache->bytes_dropped = 0;

  new_size = cache->cache_content ? (int)cache->cache_content->size : 0;
  log_info(LD_DIR, "Done rebuilding microdesc cache. "
           "Saved %d bytes; %d still used.",
           orig_size-new_size, new_size);

  return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

Reload the contents of cache from disk.

If it is empty, load it for the first time. Return 0 on success, -1 on failure.

Definition at line 293 of file microdesc.c.

{
  struct stat st;
  char *journal_content;
  smartlist_t *added;
  tor_mmap_t *mm;
  int total = 0;

  microdesc_cache_clear(cache);

  mm = cache->cache_content = tor_mmap_file(cache->cache_fname);
  if (mm) {
    added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size,
                                    SAVED_IN_CACHE, 0, -1, NULL);
    if (added) {
      total += smartlist_len(added);
      smartlist_free(added);
    }
  }

  journal_content = read_file_to_str(cache->journal_fname,
                                     RFTS_IGNORE_MISSING, &st);
  if (journal_content) {
    cache->journal_len = (size_t) st.st_size;
    added = microdescs_add_to_cache(cache, journal_content,
                                    journal_content+st.st_size,
                                    SAVED_IN_JOURNAL, 0, -1, NULL);
    if (added) {
      total += smartlist_len(added);
      smartlist_free(added);
    }
    tor_free(journal_content);
  }
  log_notice(LD_DIR, "Reloaded microdescriptor cache.  Found %d descriptors.",
             total);

  microdesc_cache_rebuild(cache, 0 /* don't force */);

  return 0;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void microdesc_check_counts ( void  )

Make sure that the reference count of every microdescriptor in cache is accurate.

Definition at line 512 of file microdesc.c.

{
  microdesc_t **mdp;
  if (!the_microdesc_cache)
    return;

  HT_FOREACH(mdp, microdesc_map, &the_microdesc_cache->map) {
    microdesc_t *md = *mdp;
    unsigned int found=0;
    const smartlist_t *nodes = nodelist_get_list();
    SMARTLIST_FOREACH(nodes, node_t *, node, {
        if (node->md == md) {
          ++found;
        }
      });
    tor_assert(found == md->held_by_nodes);
  }
}

Here is the call graph for this function:

void microdesc_free ( microdesc_t md)

Deallocate a single microdescriptor.

Note: the microdescriptor MUST have previously been removed from the cache if it had ever been inserted.

Definition at line 534 of file microdesc.c.

{
  if (!md)
    return;

  /* Make sure that the microdesc was really removed from the appropriate data
     structures. */
  if (md->held_in_map) {
    microdesc_cache_t *cache = get_microdesc_cache();
    microdesc_t *md2 = HT_FIND(microdesc_map, &cache->map, md);
    if (md2 == md) {
      log_warn(LD_BUG, "microdesc_free() called, but md was still in "
               "microdesc_map");
      HT_REMOVE(microdesc_map, &cache->map, md);
    } else {
      log_warn(LD_BUG, "microdesc_free() called with held_in_map set, but "
               "microdesc was not in the map.");
    }
    tor_fragile_assert();
  }
  if (md->held_by_nodes) {
    int found=0;
    const smartlist_t *nodes = nodelist_get_list();
    SMARTLIST_FOREACH(nodes, node_t *, node, {
        if (node->md == md) {
          ++found;
          node->md = NULL;
        }
      });
    if (found) {
      log_warn(LD_BUG, "microdesc_free() called, but md was still referenced "
               "%d node(s); held_by_nodes == %u", found, md->held_by_nodes);
    } else {
      log_warn(LD_BUG, "microdesc_free() called with held_by_nodes set to %u, "
               "but md was not referenced by any nodes", md->held_by_nodes);
    }
    tor_fragile_assert();
  }
  //tor_assert(md->held_in_map == 0);
  //tor_assert(md->held_by_nodes == 0);

  if (md->onion_pkey)
    crypto_pk_free(md->onion_pkey);
  if (md->body && md->saved_location != SAVED_IN_CACHE)
    tor_free(md->body);

  if (md->family) {
    SMARTLIST_FOREACH(md->family, char *, cp, tor_free(cp));
    smartlist_free(md->family);
  }
  short_policy_free(md->exit_policy);

  tor_free(md);
}

Here is the call graph for this function:

Here is the caller graph for this function:

void microdesc_free_all ( void  )

Free all storage held in the microdesc.c module.

Definition at line 591 of file microdesc.c.

Here is the call graph for this function:

Here is the caller graph for this function:

smartlist_t* microdesc_list_missing_digest256 ( networkstatus_t ns,
microdesc_cache_t cache,
int  downloadable_only,
digestmap_t *  skip 
)

Return a smartlist of all the sha256 digest of the microdescriptors that are listed in ns but not present in cache.

Returns pointers to internals of ns; you should not free the members of the resulting smartlist. Omit all microdescriptors whose digest appear in skip.

Definition at line 631 of file microdesc.c.

{
  smartlist_t *result = smartlist_new();
  time_t now = time(NULL);
  tor_assert(ns->flavor == FLAV_MICRODESC);
  SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) {
    if (microdesc_cache_lookup_by_digest256(cache, rs->descriptor_digest))
      continue;
    if (downloadable_only &&
        !download_status_is_ready(&rs->dl_status, now,
                                  MAX_MICRODESC_DOWNLOAD_FAILURES))
      continue;
    if (skip && digestmap_get(skip, rs->descriptor_digest))
      continue;
    if (tor_mem_is_zero(rs->descriptor_digest, DIGEST256_LEN)) {
      log_info(LD_BUG, "Found an entry in networkstatus with no "
               "microdescriptor digest. (Router %s=%s at %s:%d.)",
               rs->nickname, hex_str(rs->identity_digest, DIGEST_LEN),
               fmt_addr32(rs->addr), rs->or_port);
      continue;
    }
    /* XXXX Also skip if we're a noncache and wouldn't use this router.
     * XXXX NM Microdesc
     */
    smartlist_add(result, rs->descriptor_digest);
  } SMARTLIST_FOREACH_END(rs);
  return result;
}

Here is the call graph for this function:

Here is the caller graph for this function:

smartlist_t* microdescs_add_list_to_cache ( microdesc_cache_t cache,
smartlist_t descriptors,
saved_location_t  where,
int  no_save 
)

As microdescs_add_to_cache, but takes a list of micrdescriptors instead of a string to decode.

Frees any members of descriptors that it does not add.

Definition at line 194 of file microdesc.c.

{
  smartlist_t *added;
  open_file_t *open_file = NULL;
  FILE *f = NULL;
  //  int n_added = 0;
  ssize_t size = 0;

  if (where == SAVED_NOWHERE && !no_save) {
    f = start_writing_to_stdio_file(cache->journal_fname,
                                    OPEN_FLAGS_APPEND|O_BINARY,
                                    0600, &open_file);
    if (!f) {
      log_warn(LD_DIR, "Couldn't append to journal in %s: %s",
               cache->journal_fname, strerror(errno));
      return NULL;
    }
  }

  added = smartlist_new();
  SMARTLIST_FOREACH_BEGIN(descriptors, microdesc_t *, md) {
    microdesc_t *md2;
    md2 = HT_FIND(microdesc_map, &cache->map, md);
    if (md2) {
      /* We already had this one. */
      if (md2->last_listed < md->last_listed)
        md2->last_listed = md->last_listed;
      microdesc_free(md);
      if (where != SAVED_NOWHERE)
        cache->bytes_dropped += size;
      continue;
    }

    /* Okay, it's a new one. */
    if (f) {
      size_t annotation_len;
      size = dump_microdescriptor(f, md, &annotation_len);
      if (size < 0) {
        /* we already warned in dump_microdescriptor; */
        abort_writing_to_file(open_file);
        smartlist_clear(added);
        return added;
      }
      md->saved_location = SAVED_IN_JOURNAL;
      cache->journal_len += size;
    } else {
      md->saved_location = where;
    }

    md->no_save = no_save;

    HT_INSERT(microdesc_map, &cache->map, md);
    md->held_in_map = 1;
    smartlist_add(added, md);
    ++cache->n_seen;
    cache->total_len_seen += md->bodylen;
  } SMARTLIST_FOREACH_END(md);

  if (f)
    finish_writing_to_file(open_file); /*XXX Check me.*/

  {
    networkstatus_t *ns = networkstatus_get_latest_consensus();
    if (ns && ns->flavor == FLAV_MICRODESC)
      SMARTLIST_FOREACH(added, microdesc_t *, md, nodelist_add_microdesc(md));
  }

  if (smartlist_len(added))
    router_dir_info_changed();

  return added;
}

Here is the call graph for this function:

Here is the caller graph for this function:

smartlist_t* microdescs_add_to_cache ( microdesc_cache_t cache,
const char *  s,
const char *  eos,
saved_location_t  where,
int  no_save,
time_t  listed_at,
smartlist_t requested_digests256 
)

Decode the microdescriptors from the string starting at s and ending at eos, and store them in cache.

If no-save, mark them as non-writable to disk. If where is SAVED_IN_CACHE, leave their bodies as pointers to the mmap'd cache. If where is SAVED_NOWHERE, do not allow annotations. If listed_at is positive, set the last_listed field of every microdesc to listed_at. If requested_digests is non-null, then it contains a list of digests we mean to allow, so we should reject any non-requested microdesc with a different digest, and alter the list to contain only the digests of those microdescs we didn't find. Return a newly allocated list of the added microdescriptors, or NULL

Definition at line 145 of file microdesc.c.

{
  smartlist_t *descriptors, *added;
  const int allow_annotations = (where != SAVED_NOWHERE);
  const int copy_body = (where != SAVED_IN_CACHE);

  descriptors = microdescs_parse_from_string(s, eos,
                                             allow_annotations,
                                             copy_body);
  if (listed_at > 0) {
    SMARTLIST_FOREACH(descriptors, microdesc_t *, md,
                      md->last_listed = listed_at);
  }
  if (requested_digests256) {
    digestmap_t *requested; /* XXXX actuqlly we should just use a
                               digest256map */
    requested = digestmap_new();
    SMARTLIST_FOREACH(requested_digests256, const char *, cp,
      digestmap_set(requested, cp, (void*)1));
    SMARTLIST_FOREACH_BEGIN(descriptors, microdesc_t *, md) {
      if (digestmap_get(requested, md->digest)) {
        digestmap_set(requested, md->digest, (void*)2);
      } else {
        log_fn(LOG_PROTOCOL_WARN, LD_DIR, "Received non-requested microcdesc");
        microdesc_free(md);
        SMARTLIST_DEL_CURRENT(descriptors, md);
      }
    } SMARTLIST_FOREACH_END(md);
    SMARTLIST_FOREACH_BEGIN(requested_digests256, char *, cp) {
      if (digestmap_get(requested, cp) == (void*)2) {
        tor_free(cp);
        SMARTLIST_DEL_CURRENT(requested_digests256, cp);
      }
    } SMARTLIST_FOREACH_END(cp);
    digestmap_free(requested, NULL);
  }

  added = microdescs_add_list_to_cache(cache, descriptors, where, no_save);
  smartlist_free(descriptors);
  return added;
}

Here is the call graph for this function:

Here is the caller graph for this function:

void update_microdesc_downloads ( time_t  now)

Launch download requests for mircodescriptors as appropriate.

Specifically, we should launch download requests if we are configured to download mirodescriptors, and there are some microdescriptors listed the current microdesc consensus that we don't have, and either we never asked for them, or we failed to download them but we're willing to retry.

Definition at line 669 of file microdesc.c.

{
  const or_options_t *options = get_options();
  networkstatus_t *consensus;
  smartlist_t *missing;
  digestmap_t *pending;

  if (should_delay_dir_fetches(options))
    return;
  if (directory_too_idle_to_fetch_descriptors(options, now))
    return;

  consensus = networkstatus_get_reasonably_live_consensus(now, FLAV_MICRODESC);
  if (!consensus)
    return;

  if (!we_fetch_microdescriptors(options))
    return;

  pending = digestmap_new();
  list_pending_microdesc_downloads(pending);

  missing = microdesc_list_missing_digest256(consensus,
                                             get_microdesc_cache(),
                                             1,
                                             pending);
  digestmap_free(pending, NULL);

  launch_descriptor_downloads(DIR_PURPOSE_FETCH_MICRODESC,
                              missing, NULL, now);

  smartlist_free(missing);
}

Here is the call graph for this function:

Here is the caller graph for this function:

For every microdescriptor listed in the current microdecriptor consensus, update its last_listed field to be at least as recent as the publication time of the current microdescriptor consensus.

Definition at line 708 of file microdesc.c.

Here is the call graph for this function:

Here is the caller graph for this function:

int usable_consensus_flavor ( void  )

Return the consensus flavor we actually want to use to build circuits.

Definition at line 772 of file microdesc.c.

Here is the call graph for this function:

Here is the caller graph for this function:

int we_fetch_microdescriptors ( const or_options_t options)

Return true iff we should try to download microdescriptors at all.

Definition at line 750 of file microdesc.c.

{
  if (directory_caches_dir_info(options))
    return 1;
  if (options->FetchUselessDescriptors)
    return 1;
  return we_use_microdescriptors_for_circuits(options);
}

Here is the call graph for this function:

Here is the caller graph for this function:

int we_fetch_router_descriptors ( const or_options_t options)

Return true iff we should try to download router descriptors at all.

Definition at line 761 of file microdesc.c.

{
  if (directory_caches_dir_info(options))
    return 1;
  if (options->FetchUselessDescriptors)
    return 1;
  return ! we_use_microdescriptors_for_circuits(options);
}

Here is the call graph for this function:

Here is the caller graph for this function:

Return true iff we should prefer to use microdescriptors rather than routerdescs for building circuits.

Definition at line 730 of file microdesc.c.

{
  int ret = options->UseMicrodescriptors;
  if (ret == -1) {
    /* UseMicrodescriptors is "auto"; we need to decide: */
    /* If we are configured to use bridges and one of our bridges doesn't
     * know what a microdescriptor is, the answer is no. */
    if (options->UseBridges && any_bridges_dont_support_microdescriptors())
      return 0;
    /* Otherwise, we decide that we'll use microdescriptors iff we are
     * not a server, and we're not autofetching everything. */
    /* XXX023 what does not being a server have to do with it? also there's
     * a partitioning issue here where bridges differ from clients. */
    ret = !server_mode(options) && !options->FetchUselessDescriptors;
  }
  return ret;
}

Here is the call graph for this function:

Here is the caller graph for this function: