/** * Get information about the filesystem mounted under the given directory * by filling the fs_info structure. */ static void get_fs_info(const char *path, struct fs_info *fsi) { filesize_t free_space = MAX_INT_VAL(filesize_t); filesize_t total_space = MAX_INT_VAL(filesize_t); g_assert(path); g_assert(fsi); (void) path; #if defined(HAS_STATVFS) { /* statvfs() is a POSIX.1-2001 system call */ struct statvfs buf; if (-1 == statvfs(path, &buf)) { g_warning("statvfs(\"%s\") failed: %s", path, g_strerror(errno)); } else { free_space = ((filesize_t) 0 + buf.f_bavail) * buf.f_bsize; total_space = ((filesize_t) 0 + buf.f_blocks) * buf.f_frsize; } } #elif defined(HAS_STATFS) { /* statfs() is deprecated but older systems may not have statvfs() */ struct statfs buf; if (-1 == statfs(path, &buf)) { g_warning("statfs(\"%s\") failed: %s", path, g_strerror(errno)); } else { free_space = ((filesize_t) 0 + buf.f_bavail) * buf.f_bsize; total_space = ((filesize_t) 0 + buf.f_blocks) * buf.f_bsize; } } #elif defined(MINGW32) { struct mingw_statvfs buf; if (-1 == mingw_statvfs(path, &buf)) { g_warning("statvfs(\"%s\") failed: %s", path, g_strerror(errno)); } else { free_space = ((filesize_t) 0 + buf.f_cavail) * buf.f_csize; total_space = ((filesize_t) 0 + buf.f_clusters) * buf.f_csize; } } #endif /* HAS_STATVFS || HAS_STATFS || MINGW32 */ fsi->free_space = free_space; fsi->total_space = total_space; }
/** * Initialize security token caching. */ G_GNUC_COLD void tcache_init(void) { dbstore_kv_t kv = { KUID_RAW_SIZE, NULL, sizeof(struct tokdata), sizeof(struct tokdata) + MAX_INT_VAL(uint8) }; dbstore_packing_t packing = { serialize_tokdata, deserialize_tokdata, free_tokdata }; g_assert(NULL == db_tokdata); g_assert(NULL == tcache_prune_ev); /* Legacy: remove after 0.97 -- RAM, 2011-05-03 */ dbstore_move(settings_config_dir(), settings_dht_db_dir(), db_tcache_base); db_tokdata = dbstore_create(db_tcache_what, settings_dht_db_dir(), db_tcache_base, kv, packing, TOK_DB_CACHE_SIZE, kuid_hash, kuid_eq, GNET_PROPERTY(dht_storage_in_memory)); dbmw_set_map_cache(db_tokdata, TOK_MAP_CACHE_SIZE); dbmw_set_debugging(db_tokdata, &tcache_dbmw_dbg); token_life = MIN(TOK_LIFE, token_lifetime()); if (GNET_PROPERTY(dht_tcache_debug)) g_debug("DHT cached token lifetime set to %u secs", (unsigned) token_life); tcache_prune_ev = cq_periodic_main_add(TCACHE_PRUNE_PERIOD, tcache_periodic_prune, NULL); }
/** * Write data to stream. * * @param os the output stream * @param data start of data to write * @param len length of data to write * * @return size of data written, -1 on error. */ ssize_t ostream_write(ostream_t *os, const void *data, size_t len) { ssize_t w = (ssize_t) -1; ostream_check(os); len = MIN(len, MAX_INT_VAL(ssize_t)); switch (os->type) { case OSTREAM_T_FILE: { size_t n = fwrite(data, len, 1, os->u.f); w = (0 == n) ? -1 : (ssize_t) len; } break; case OSTREAM_T_FD: w = write(os->u.fd, data, len); break; case OSTREAM_T_MEM: pmsg_slist_append(os->u.sl, data, len); w = len; break; case OSTREAM_T_PMSG: w = pmsg_write(os->u.mb, data, len); w = (len == UNSIGNED(w)) ? w : -1; break; case OSTREAM_T_MAX: g_assert_not_reached(); } if (-1 == w) os->ioerr = TRUE; return w; }
/** * Add a port forwarding (*:ext_port -> addr:port) [IP or PPP connection]. * * @param usd the UPnP service to contact * @param proto mapping protocol * @param ext_port the mapped external port for which we want the mapping * @param int_addr the internal client address * @param int_port the internal port for which we want the mapping * @param desc comment description * @param lease lease duration (0 = permanent) * @param cb callback to invoke when reply is available * @param arg additional callback argument * * @return UPnP request handle if the SOAP RPC was initiated, NULL otherwise * (in which case callbacks will never be called). */ upnp_ctrl_t * upnp_ctrl_AddPortMapping(const upnp_service_t *usd, enum upnp_map_proto proto, guint16 ext_port, host_addr_t int_addr, guint16 int_port, const char *desc, time_delta_t lease, upnp_ctrl_cb_t cb, void *arg) { nv_pair_t *argv[8]; char ext_port_buf[UINT16_DEC_BUFLEN]; char int_port_buf[UINT16_DEC_BUFLEN]; char int_addr_buf[HOST_ADDR_BUFLEN]; char lease_buf[UINT32_DEC_BUFLEN]; const char *protocol; const char *description; g_assert(lease >= 0); g_assert(lease <= MAX_INT_VAL(gint32)); g_assert(ext_port != 0); g_assert(int_port != 0); int32_to_string_buf(ext_port, ext_port_buf, sizeof ext_port_buf); int32_to_string_buf(int_port, int_port_buf, sizeof int_port_buf); host_addr_to_string_buf(int_addr, int_addr_buf, sizeof int_addr_buf); protocol = upnp_map_proto_to_string(proto); int32_to_string_buf(lease, lease_buf, sizeof lease_buf); description = str_smsg("%s (%s)", desc, protocol); argv[0] = nv_pair_make_static_str(ARG_REMOTE_HOST, EMPTY); /* Wildcard */ argv[1] = nv_pair_make_static_str(ARG_EXTERNAL_PORT, ext_port_buf); argv[2] = nv_pair_make_static_str(ARG_PROTOCOL, protocol); argv[3] = nv_pair_make_static_str(ARG_INTERNAL_PORT, int_port_buf); argv[4] = nv_pair_make_static_str(ARG_INTERNAL_CLIENT, int_addr_buf); argv[5] = nv_pair_make_static_str(ARG_ENABLED, ONE); /* Enable! */ argv[6] = nv_pair_make_static_str(ARG_PORTMAP_DESC, description); argv[7] = nv_pair_make_static_str(ARG_LEASE_DURATION, lease_buf); /* * TODO: when talking to a v2 WANIPConnection service, we can use * the AddAnyPortMapping() call. This will require that GTKG maintains * knowledge about the remote port so that it can advertise that remote * port instead of the local listening port. * * Attempts must be made to get the same external port for both TCP and UDP, * or this will create problems to servents assuming that they will always * be identical (like GTKG does when it uses the TCP listening port of * a remote host to send a push-proxy request via UDP).. * --RAM, 2011-01-18 */ return upnp_ctrl_launch(usd, "AddPortMapping", argv, G_N_ELEMENTS(argv), cb, arg, NULL); }
/** * Allocate a single block in the file, without extending it. * * @param db the sdbm database * @param first first block to consider * * @return the block number if found, 0 otherwise. */ static size_t big_falloc(DBM *db, size_t first) { DBMBIG *dbg = db->big; long max_bitmap = dbg->bitmaps; long i; long bmap; size_t first_bit; bmap = first / BIG_BITCOUNT; /* Bitmap handling this block */ first_bit = first & (BIG_BITCOUNT - 1); /* Index within bitmap */ g_assert(first_bit != 0); /* Bit 0 is the bitmap itself */ /* * Loop through all the currently existing bitmaps. */ for (i = bmap; i < max_bitmap; i++) { size_t bno; if (!fetch_bitbuf(db, i)) return 0; bno = bit_field_first_clear(dbg->bitbuf, first_bit, BIG_BITCOUNT - 1); if ((size_t) -1 == bno) continue; /* * Found a free block. */ bit_field_set(dbg->bitbuf, bno); dbg->bitbuf_dirty = TRUE; /* * Correct the block number corresponding to "bno", if we did * not find it in bitmap #0. */ bno = size_saturate_add(bno, size_saturate_mult(BIG_BITCOUNT, i)); /* Make sure we can represent the block number in 32 bits */ g_assert(bno <= MAX_INT_VAL(guint32)); return bno; /* Allocated block number */ } return 0; /* No free block found */ }
/** * Replace value data in-place. * * @param db the sdbm database * @param bval start of big value in the page * @param data the new value * @param len length of data * * @return 0 if OK, -1 on error with errno set. */ int big_replace(DBM *db, char *bval, const char *data, size_t len) { size_t old_len = big_length(bval); g_assert(size_is_non_negative(len)); g_assert(bigblocks(old_len) == bigblocks(len)); g_assert(len <= MAX_INT_VAL(guint32)); /* * Write data on the same blocks as before, since we know it will fit. */ poke_be32(bval, (guint32) len); /* First 4 bytes: real data length */ return big_store(db, bigval_blocks(bval), data, len); }
/** * Load spam database from the supplied FILE. * * The current file format is as follows: * * # Comment * SHA1 <SHA-1> * ADDED <date> * END * * @returns the amount of entries loaded or -1 on failure. */ static G_GNUC_COLD gulong spam_load(FILE *f) { static const struct spam_item zero_item; struct spam_item item; char line[1024]; guint line_no = 0; bit_array_t tag_used[BIT_ARRAY_SIZE(NUM_SPAM_TAGS)]; gulong item_count = 0; g_assert(f); /* Reset state */ item = zero_item; bit_array_init(tag_used, NUM_SPAM_TAGS); while (fgets(line, sizeof line, f)) { const char *tag_name, *value; char *sp, *nl; spam_tag_t tag; line_no++; nl = strchr(line, '\n'); if (!nl) { /* * If the line is too long or unterminated the file is either * corrupt or was manually edited without respecting the * exact format. If we continued, we would read from the * middle of a line which could be the filename or ID. */ g_warning("spam_load(): " "line too long or missing newline in line %u", line_no); break; } *nl = '\0'; /* Skip comments and empty lines */ if (*line == '#' || *line == '\0') continue; sp = strchr(line, ' '); if (sp) { *sp = '\0'; value = &sp[1]; } else { value = strchr(line, '\0'); } tag_name = line; tag = spam_string_to_tag(tag_name); g_assert(UNSIGNED(tag) < UNSIGNED(NUM_SPAM_TAGS)); if (SPAM_TAG_UNKNOWN != tag && !bit_array_flip(tag_used, tag)) { g_warning("spam_load(): duplicate tag \"%s\" in entry in line %u", tag_name, line_no); continue; } switch (tag) { case SPAM_TAG_ADDED: { time_t t; t = date2time(value, tm_time()); if ((time_t) -1 == t) { item.damaged = TRUE; } } break; case SPAM_TAG_SHA1: { if (strlen(value) != SHA1_BASE32_SIZE) { item.damaged = TRUE; g_warning("spam_load(): SHA-1 has wrong length."); } else { const struct sha1 *raw; raw = base32_sha1(value); if (raw) item.sha1 = *raw; else item.damaged = TRUE; } } break; case SPAM_TAG_NAME: { if ('\0' == value[0]) { item.damaged = TRUE; g_warning("spam_load(): Missing filename pattern."); } else if (!utf8_is_valid_string(value)) { item.damaged = TRUE; g_warning("spam_load(): Filename pattern is not UTF-8."); } else { item.name = h_strdup(value); } } break; case SPAM_TAG_SIZE: { const char *endptr; guint64 u; int error; u = parse_uint64(value, &endptr, 10, &error); if (error) { item.damaged = TRUE; g_warning("spam_load(): Cannot parse SIZE: %s", value); } else { item.min_size = u; item.max_size = u; if ('-' == endptr[0]) { u = parse_uint64(&endptr[1], &endptr, 10, &error); if (error) { item.damaged = TRUE; g_warning("spam_load(): Cannot parse SIZE: %s", value); } if (u < item.min_size) { item.damaged = TRUE; g_warning("spam_load(): " "Maximum size below minimum size"); } else { item.max_size = u; } } } } break; case SPAM_TAG_END: if ( !bit_array_get(tag_used, SPAM_TAG_SHA1) && !bit_array_get(tag_used, SPAM_TAG_NAME) ) { g_warning("spam_load(): missing SHA1 or NAME tag"); item.damaged = TRUE; } if (!bit_array_get(tag_used, SPAM_TAG_ADDED)) { g_warning("spam_load(): missing ADDED tag"); item.damaged = TRUE; } item.done = TRUE; break; case SPAM_TAG_UNKNOWN: /* Ignore */ break; case NUM_SPAM_TAGS: g_assert_not_reached(); break; } if (item.done && !item.damaged) { if (bit_array_get(tag_used, SPAM_TAG_SHA1)) { spam_sha1_add(&item.sha1); item_count++; } if (bit_array_get(tag_used, SPAM_TAG_NAME)) { if (!bit_array_get(tag_used, SPAM_TAG_SIZE)) { item.min_size = 0; item.max_size = MAX_INT_VAL(filesize_t); } if ( spam_add_name_and_size(item.name, item.min_size, item.max_size) ) { item.damaged = TRUE; } else { item_count++; } } } if (item.damaged) { g_warning("Damaged spam entry in line %u: " "tag_name=\"%s\", value=\"%s\"", line_no, tag_name, value); } if (item.done) { /* Reset state */ HFREE_NULL(item.name); item = zero_item; bit_array_clear_range(tag_used, 0, NUM_SPAM_TAGS - 1U); } } spam_sha1_sync(); return item_count; }
/** * Add file to the current query hit. * * @return TRUE if we kept the file, FALSE if we did not include it in the hit. */ static bool g2_build_qh2_add(struct g2_qh2_builder *ctx, const shared_file_t *sf) { const sha1_t *sha1; g2_tree_t *h, *c; shared_file_check(sf); /* * Make sure the file is still in the library. */ if (0 == shared_file_index(sf)) return FALSE; /* * On G2, the H/URN child is required, meaning we need the SHA1 at least. */ if (!sha1_hash_available(sf)) return FALSE; /* * Do not send duplicates, as determined by the SHA1 of the resource. * * A user may share several files with different names but the same SHA1, * and if all of them are hits, we only want to send one instance. * * When generating hits for host-browsing, we do not care about duplicates * and ctx->hs is NULL then. */ sha1 = shared_file_sha1(sf); /* This is an atom */ if (ctx->hs != NULL) { if (hset_contains(ctx->hs, sha1)) return FALSE; hset_insert(ctx->hs, sha1); } /* * Create the "H" child and attach it to the current tree. */ if (NULL == ctx->t) g2_build_qh2_start(ctx); h = g2_tree_alloc_empty("H"); g2_tree_add_child(ctx->t, h); /* * URN -- Universal Resource Name * * If there is a known TTH, then we can generate a bitprint, otherwise * we just convey the SHA1. */ { const tth_t * const tth = shared_file_tth(sf); char payload[SHA1_RAW_SIZE + TTH_RAW_SIZE + sizeof G2_URN_BITPRINT]; char *p = payload; if (NULL == tth) { p = mempcpy(p, G2_URN_SHA1, sizeof G2_URN_SHA1); p += clamp_memcpy(p, sizeof payload - ptr_diff(p, payload), sha1, SHA1_RAW_SIZE); } else { p = mempcpy(p, G2_URN_BITPRINT, sizeof G2_URN_BITPRINT); p += clamp_memcpy(p, sizeof payload - ptr_diff(p, payload), sha1, SHA1_RAW_SIZE); p += clamp_memcpy(p, sizeof payload - ptr_diff(p, payload), tth, TTH_RAW_SIZE); } g_assert(ptr_diff(p, payload) <= sizeof payload); c = g2_tree_alloc_copy("URN", payload, ptr_diff(p, payload)); g2_tree_add_child(h, c); } /* * URL -- empty to indicate that we share the file via uri-res. */ if (ctx->flags & QHIT_F_G2_URL) { uint known; uint16 csc; c = g2_tree_alloc_empty("URL"); g2_tree_add_child(h, c); /* * CSC -- if we know alternate sources, indicate how many in "CSC". * * This child is only emitted when they requested "URL". */ known = dmesh_count(sha1); csc = MIN(known, MAX_INT_VAL(uint16)); if (csc != 0) { char payload[2]; poke_le16(payload, csc); c = g2_tree_alloc_copy("CSC", payload, sizeof payload); g2_tree_add_child(h, c); } /* * PART -- if we only have a partial file, indicate how much we have. * * This child is only emitted when they requested "URL". */ if (shared_file_is_partial(sf) && !shared_file_is_finished(sf)) { filesize_t available = shared_file_available(sf); char payload[8]; /* If we have to encode file size as 64-bit */ uint32 av32; time_t mtime = shared_file_modification_time(sf); c = g2_tree_alloc_empty("PART"); g2_tree_add_child(h, c); av32 = available; if (av32 == available) { /* Fits within a 32-bit quantity */ poke_le32(payload, av32); g2_tree_set_payload(c, payload, sizeof av32, TRUE); } else { /* Encode as a 64-bit quantity then */ poke_le64(payload, available); g2_tree_set_payload(c, payload, sizeof payload, TRUE); } /* * GTKG extension: encode the last modification time of the * partial file in an "MT" child. This lets the other party * determine whether the host is still able to actively complete * the file. */ poke_le32(payload, (uint32) mtime); g2_tree_add_child(c, g2_tree_alloc_copy("MT", payload, sizeof(uint32))); } /* * CT -- creation time of the resource (GTKG extension). */ { time_t create_time = shared_file_creation_time(sf); if ((time_t) -1 != create_time) { char payload[8]; int n; create_time = MAX(0, create_time); n = vlint_encode(create_time, payload); g2_tree_add_child(h, g2_tree_alloc_copy("CT", payload, n)); /* No trailing 0s */ } } } /* * DN -- distinguished name. * * Note that the presence of DN also governs the presence of SZ if the * file length does not fit a 32-bit unsigned quantity. */ if (ctx->flags & QHIT_F_G2_DN) { char payload[8]; /* If we have to encode file size as 64-bit */ uint32 fs32; filesize_t fs = shared_file_size(sf); const char *name; const char *rp; c = g2_tree_alloc_empty("DN"); fs32 = fs; if (fs32 == fs) { /* Fits within a 32-bit quantity */ poke_le32(payload, fs32); g2_tree_set_payload(c, payload, sizeof fs32, TRUE); } else { /* Does not fit a 32-bit quantity, emit a SZ child */ poke_le64(payload, fs); g2_tree_add_child(h, g2_tree_alloc_copy("SZ", payload, sizeof payload)); } name = shared_file_name_nfc(sf); g2_tree_append_payload(c, name, shared_file_name_nfc_len(sf)); g2_tree_add_child(h, c); /* * GTKG extension: if there is a file path, expose it as a "P" child * under the DN node. */ rp = shared_file_relative_path(sf); if (rp != NULL) { g2_tree_add_child(c, g2_tree_alloc_copy("P", rp, strlen(rp))); } } /* * GTKG extension: if they requested alt-locs in the /Q2/I with "A", then * send them some known alt-locs in an "ALT" child. * * Note that these alt-locs can be for Gnutella hosts: since both Gnutella * and G2 share a common HTTP-based file transfer mechanism with compatible * extra headers, there is no need to handle them separately. */ if (ctx->flags & QHIT_F_G2_ALT) { gnet_host_t hvec[G2_BUILD_QH2_MAX_ALT]; int hcnt = 0; hcnt = dmesh_fill_alternate(sha1, hvec, N_ITEMS(hvec)); if (hcnt > 0) { int i; c = g2_tree_alloc_empty("ALT"); for (i = 0; i < hcnt; i++) { host_addr_t addr; uint16 port; addr = gnet_host_get_addr(&hvec[i]); port = gnet_host_get_port(&hvec[i]); if (host_addr_is_ipv4(addr)) { char payload[6]; host_ip_port_poke(payload, addr, port, NULL); g2_tree_append_payload(c, payload, sizeof payload); } } /* * If the payload is still empty, then drop the "ALT" child. * Otherwise, attach it to the "H" node. */ if (NULL == g2_tree_node_payload(c, NULL)) { g2_tree_free_null(&c); } else { g2_tree_add_child(h, c); } } } /* * Update the size of the query hit we're generating. */ ctx->current_size += g2_frame_serialize(h, NULL, 0); return TRUE; }
/** * Allocate "n" consecutive (sequential) blocks in the file, without * attempting to extend it. * * @param db the sdbm database * @param bmap bitmap number from which we need to start looking * @param n amount of consecutive blocks we want * * @return the block number of the first "n" blocks if found, 0 if nothing * was found. */ static size_t big_falloc_seq(DBM *db, int bmap, int n) { DBMBIG *dbg = db->big; long max_bitmap = dbg->bitmaps; long i; g_assert(bmap >= 0); g_assert(n > 0); /* * Loop through all the currently existing bitmaps, starting at the * specified bitmap number. */ for (i = bmap; i < max_bitmap; i++) { size_t first; size_t j; int r; /* Remaining blocks to allocate consecutively */ if (!fetch_bitbuf(db, i)) return 0; /* * We start at bit #1 since bit #0 is the bitmap itself. * * Bit #0 should always be set but in case the file is corrupted, * we don't want to start allocating data in the bitmap itself!. */ first = bit_field_first_clear(dbg->bitbuf, 1, BIG_BITCOUNT - 1); if ((size_t) -1 == first) continue; for (j = first + 1, r = n - 1; r > 0 && j < BIG_BITCOUNT; r--, j++) { if (bit_field_get(dbg->bitbuf, j)) break; } /* * If "r" is 0, we have no remaining page to allocate: we found our * "n" consecutive free blocks. */ if (0 == r) { /* * Mark the "n" consecutive blocks as busy. */ for (j = first, r = n; r > 0; r--, j++) { bit_field_set(dbg->bitbuf, j); } dbg->bitbuf_dirty = TRUE; /* * Correct the block number corresponding to "first", if we did * not find it in bitmap #0. */ first = size_saturate_add(first, size_saturate_mult(BIG_BITCOUNT, i)); /* Make sure we can represent all block numbers in 32 bits */ g_assert(size_saturate_add(first, n - 1) <= MAX_INT_VAL(guint32)); return first; /* "n" consecutive free blocks found */ } } return 0; /* No free block found */ }
/** * Prepare reception of THEX data by building an appropriate RX stack. * * @return TRUE if we may continue with the download. */ bool thex_download_receive(struct thex_download *ctx, filesize_t content_length, gnet_host_t *host, struct wrap_io *wio, uint32 flags) { g_assert(ctx != NULL); gnet_host_copy(&ctx->host, host); /* * Freeing of the RX stack must be asynchronous: each time we establish * a new connection, dismantle the previous stack. Otherwise the RX * stack will be freed when the corresponding download structure is * reclaimed. */ if (ctx->rx != NULL) { rx_free(ctx->rx); ctx->rx = NULL; } /* * If there is a Content-Length indication in the HTTP reply, it is * supplied here and will be used as a limit of the data we'll read. * * If there was none (for instance if the output is chunked), then 0 * is given and we'll use a hardwired maximum. */ if (content_length > MAX_INT_VAL(size_t)) return FALSE; ctx->max_size = content_length ? (size_t) content_length : THEX_DOWNLOAD_MAX_SIZE; { struct rx_link_args args; args.cb = &thex_rx_link_cb; args.bws = bsched_in_select_by_addr(gnet_host_get_addr(&ctx->host)); args.wio = wio; ctx->rx = rx_make(ctx, &ctx->host, rx_link_get_ops(), &args); } if (flags & THEX_DOWNLOAD_F_CHUNKED) { struct rx_chunk_args args; args.cb = &thex_rx_chunk_cb; ctx->rx = rx_make_above(ctx->rx, rx_chunk_get_ops(), &args); } if (flags & THEX_DOWNLOAD_F_INFLATE) { struct rx_inflate_args args; args.cb = &thex_rx_inflate_cb; ctx->rx = rx_make_above(ctx->rx, rx_inflate_get_ops(), &args); } rx_set_data_ind(ctx->rx, thex_download_data_ind); rx_enable(ctx->rx); return TRUE; }
bin->vals[bin->nvals++] = entry; } /** * Makes a bin take as little memory as needed. */ static void bin_compact(struct st_bin *bin) { g_assert(bin->vals != NULL); /* Or it would not have been allocated */ bin->vals = hrealloc(bin->vals, bin->nvals * sizeof bin->vals[0]); bin->nslots = bin->nvals; } static guchar map[MAX_INT_VAL(guchar)]; static void setup_map(void) { static gboolean done; guint i; if (done) return; for (i = 0; i < G_N_ELEMENTS(map); i++) { guchar c; if (i > 0 && utf8_byte_is_allowed(i)) { if (is_ascii_upper(i)) {
/** * Writes the THEX data of the context ``ctx'' to the buffer * ``dest''. This must be called multiple times to retrieve the complete * data until zero is returned i.e., the end of file is reached. * * This routine deals with query hit data generation. * * @param special_upload an initialized THEX upload context. * @param dest the destination buffer. * @param size the amount of bytes ``dest'' can hold. * * @return -1 on failure, zero at the end-of-file condition or if size * was zero. On success, the amount of bytes copied to ``dest'' * is returned. */ static ssize_t thex_upload_read(struct special_upload *special_upload, void * const dest, size_t size) { struct thex_upload *ctx = cast_to_thex_upload(special_upload); char *p = dest; g_assert(ctx); g_assert(0 == size || NULL != dest); g_assert(0 == ctx->size || NULL != ctx->data); g_assert(ctx->offset <= ctx->size); g_assert(UNSIGNED(ctx->state) < NUM_THEX_STATES); size = MIN(size, MAX_INT_VAL(ssize_t)); while (size > 0) { switch (ctx->state) { case THEX_STATE_INITIAL: g_assert(NULL == ctx->data); if (!thex_upload_get_xml(ctx)) goto error; ctx->state++; break; case THEX_STATE_XML_SENT: g_assert(NULL == ctx->data); if (!thex_upload_get_tree(ctx)) goto error; ctx->state++; break; case THEX_STATE_XML: case THEX_STATE_TREE: { size_t n; g_assert(ctx->data); g_assert(ctx->size > ctx->offset); n = ctx->size - ctx->offset; n = MIN(n, size); memcpy(p, &ctx->data[ctx->offset], n); ctx->offset += n; p += n; size -= n; if (ctx->offset == ctx->size) { thex_upload_free_data(ctx); ctx->state++; } } break; case THEX_STATE_TREE_SENT: size = 0; break; case NUM_THEX_STATES: g_assert_not_reached(); } } return p - cast_to_char_ptr(dest); error: errno = EIO; return (ssize_t)-1; }