Ejemplo n.º 1
0
Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) {
    Value* blockdev_filename;
    Value* ranges;
    const uint8_t* digest = NULL;
    if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) {
        return NULL;
    }

    if (blockdev_filename->type != VAL_STRING) {
        ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
        goto done;
    }
    if (ranges->type != VAL_STRING) {
        ErrorAbort(state, "ranges argument to %s must be string", name);
        goto done;
    }

    int fd = open(blockdev_filename->data, O_RDWR);
    if (fd < 0) {
        ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
        goto done;
    }

    RangeSet* rs = parse_range(ranges->data);
    uint8_t buffer[BLOCKSIZE];

    SHA_CTX ctx;
    SHA_init(&ctx);

    int i, j;
    for (i = 0; i < rs->count; ++i) {
        check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET);
        for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) {
            readblock(fd, buffer, BLOCKSIZE);
            SHA_update(&ctx, buffer, BLOCKSIZE);
        }
    }
    digest = SHA_final(&ctx);
    close(fd);

  done:
    FreeValue(blockdev_filename);
    FreeValue(ranges);
    if (digest == NULL) {
        return StringValue(strdup(""));
    } else {
        return StringValue(PrintSha1(digest));
    }
}
Ejemplo n.º 2
0
// sha1_check(data)
//    to return the sha1 of the data (given in the format returned by
//    read_file).
//
// sha1_check(data, sha1_hex, [sha1_hex, ...])
//    returns the sha1 of the file if it matches any of the hex
//    strings passed, or "" if it does not equal any of them.
//
Value* Sha1CheckFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc < 1) {
        return ErrorAbort(state, "%s() expects at least 1 arg", name);
    }

    Value** args = ReadValueVarArgs(state, argc, argv);
    if (args == NULL) {
        return NULL;
    }

    if (args[0]->size < 0) {
        fprintf(stderr, "%s(): no file contents received", name);
        return StringValue(strdup(""));
    }
    uint8_t digest[SHA_DIGEST_SIZE];
    SHA(args[0]->data, args[0]->size, digest);
    FreeValue(args[0]);

    if (argc == 1) {
        return StringValue(PrintSha1(digest));
    }

    int i;
    uint8_t* arg_digest = malloc(SHA_DIGEST_SIZE);
    for (i = 1; i < argc; ++i) {
        if (args[i]->type != VAL_STRING) {
            fprintf(stderr, "%s(): arg %d is not a string; skipping",
                    name, i);
        } else if (ParseSha1(args[i]->data, arg_digest) != 0) {
            // Warn about bad args and skip them.
            fprintf(stderr, "%s(): error parsing \"%s\" as sha-1; skipping",
                    name, args[i]->data);
        } else if (memcmp(digest, arg_digest, SHA_DIGEST_SIZE) == 0) {
            break;
        }
        FreeValue(args[i]);
    }
    if (i >= argc) {
        // Didn't match any of the hex strings; return false.
        return StringValue(strdup(""));
    }
    // Found a match; free all the remaining arguments and return the
    // matched one.
    int j;
    for (j = i+1; j < argc; ++j) {
        FreeValue(args[j]);
    }
    return args[i];
}
Value* UpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 1) {
        return ErrorAbort(state, "%s() expects 6 args, got %d", name, argc);
    }

    char* type = strrchr(name, '_');
    if (type == NULL || *(type+1) == '\0') {
        return ErrorAbort(state, "%s() couldn't get type from function name",
                          name);
    }
    ++type;

    Value* image;

    if (ReadValueArgs(state, argv, 1, &image) <0) {
        return NULL;
    }

    if (image->type != VAL_BLOB) {
        printf("image argument is not blob (is type %d)\n", image->type);
        goto done;
    }

    install_firmware_update(type, image->data, image->size, "/tmp/recovery.log");
    printf("%s: install_firmware_update returned!\n", name);

  done:
    FreeValue(image);
    // install_firmware_update should reboot.  If it returns, it failed.
    return StringValue(strdup(""));
}
Ejemplo n.º 4
0
/* Function: LListDestroy
 * ----------------------
 * Destroys and frees memory of a linked list.
 * 
 * root:		LList_Node pointer to start of linked list.
 * FreeValue:	Function which frees the memory pointed to by a node.
 */
void LListDestroy(LList_Node *root, void (*FreeValue)(void *node_value)) {
	LList_Node *next;
	if (LListIsEmpty(root)) {
		free(root);
		return;
	}
	next = root->next;
	while (next != NULL) {
		FreeValue(root->node_value);
		free(root);
		root = next;
		next = next->next;
	}
	FreeValue(root->node_value);
	free(root);
}
Ejemplo n.º 5
0
// Evaluate the expressions in argv, giving 'count' Value* (the ... is
// zero or more Value** to put them in).  If any expression evaluates
// to NULL, free the rest and return -1.  Return 0 on success.
int
ReadValueArgs (State * state, Expr * argv[], int count, ...)
{
  Value **args = malloc (count * sizeof (Value *));
  va_list v;

  va_start (v, count);
  int i;

  for (i = 0; i < count; ++i)
	  {
	    args[i] = EvaluateValue (state, argv[i]);
	    if (args[i] == NULL)
		    {
		      va_end (v);
		      int j;

		      for (j = 0; j < i; ++j)
			      {
				FreeValue (args[j]);
			      }
		      free (args);
		      return -1;
		    }
	    *(va_arg (v, Value **)) = args[i];
	  }
  va_end (v);
  free (args);
  return 0;
}
Ejemplo n.º 6
0
void FreeExpression( AIExpType_t *exp )
{
	if ( !exp )
	{
		return;
	}

	if ( *exp == EX_FUNC )
	{
		AIValueFunc_t *v = ( AIValueFunc_t * ) exp;
		FreeValueFunc( v );
	}
	else if ( *exp == EX_VALUE )
	{
		AIValue_t *v = ( AIValue_t * ) exp;
		
		FreeValue( v );
	}
	else if ( *exp == EX_OP )
	{
		AIOp_t *op = ( AIOp_t * ) exp;

		FreeOp( op );
	}
}
Ejemplo n.º 7
0
CIdValue::~CIdValue()
{
	if (isLink || type == &CTokenTypes::Pointer)return;
	for (size_t k = 0; k < values.size(); k++)
		FreeValue(k);		
	values.clear();
}
Ejemplo n.º 8
0
void CIdValue::TruncTo(size_t k)
{
	if (k >= Size() || isLink)return;
	while (Size()>k)
	{
		FreeValue(Size()-1);
		values.pop_back();
	}
}
Ejemplo n.º 9
0
Value *
SequenceFn (const char *name, State * state, int argc, Expr * argv[])
{
  Value *left = EvaluateValue (state, argv[0]);

  if (left == NULL)
    return NULL;
  FreeValue (left);
  return EvaluateValue (state, argv[1]);
}
Ejemplo n.º 10
0
char* Evaluate(State* state, Expr* expr) {
    Value* v = expr->fn(expr->name, state, expr->argc, expr->argv);
    if (v == NULL) return NULL;
    if (v->type != VAL_STRING) {
        ErrorAbort(state, "expecting string, got value type %d", v->type);
        FreeValue(v);
        return NULL;
    }
    char* result = v->data;
    free(v);
    return result;
}
Ejemplo n.º 11
0
Value* WriteBootloaderFn(const char* name, State* state, int argc, Expr* argv[])
{
    int result = -1;
    Value* img;
    Value* xloader_loc;
    Value* sbl_loc;

    if (argc != 3) {
        return ErrorAbort(state, "%s() expects 3 args, got %d", name, argc);
    }

    if (ReadValueArgs(state, argv, 3, &img, &xloader_loc, &sbl_loc) < 0) {
        return NULL;
    }

    if(img->type != VAL_BLOB ||
       xloader_loc->type != VAL_STRING ||
       sbl_loc->type != VAL_STRING) {
      FreeValue(img);
      FreeValue(xloader_loc);
      FreeValue(sbl_loc);
      return ErrorAbort(state, "%s(): argument types are incorrect", name);
    }

    result = update_bootloader(img->data, img->size,
                               xloader_loc->data, sbl_loc->data);
    FreeValue(img);
    FreeValue(xloader_loc);
    FreeValue(sbl_loc);
    return StringValue(strdup(result == 0 ? "t" : ""));
}
Ejemplo n.º 12
0
// write_raw_image(filename_or_blob, partition)
Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
    char* result = NULL;

    Value* partition_value;
    Value* contents;
    if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) {
        return NULL;
    }

    char* partition = NULL;
    if (partition_value->type != VAL_STRING) {
        ErrorAbort(state, "partition argument to %s must be string", name);
        goto done;
    }
    partition = partition_value->data;
    if (strlen(partition) == 0) {
        ErrorAbort(state, "partition argument to %s can't be empty", name);
        goto done;
    }
    if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) {
        ErrorAbort(state, "file argument to %s can't be empty", name);
        goto done;
    }

    char* filename = contents->data;
    if (0 == restore_raw_partition(NULL, partition, filename))
        result = strdup(partition);
    else {
        result = strdup("");
        goto done;
    }

done:
    if (result != partition) FreeValue(partition_value);
    FreeValue(contents);
    return StringValue(result);
}
Ejemplo n.º 13
0
// ELEMENT の削除
void FreeElement(ELEMENT *e)
{
	UINT i;
	// 引数チェック
	if (e == NULL)
	{
		return;
	}

	for (i = 0;i < e->num_value;i++)
	{
		FreeValue(e->values[i], e->type);
	}
	Free(e->values);

	Free(e);
}
Ejemplo n.º 14
0
// apply_patch(srcfile, tgtfile, tgtsha1, tgtsize, sha1_1, patch_1, ...)
Value* ApplyPatchFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc < 6 || (argc % 2) == 1) {
        return ErrorAbort(state, "%s(): expected at least 6 args and an "
                                 "even number, got %d",
                          name, argc);
    }

    char* source_filename;
    char* target_filename;
    char* target_sha1;
    char* target_size_str;
    if (ReadArgs(state, argv, 4, &source_filename, &target_filename,
                 &target_sha1, &target_size_str) < 0) {
        return NULL;
    }

    char* endptr;
    size_t target_size = strtol(target_size_str, &endptr, 10);
    if (target_size == 0 && endptr == target_size_str) {
        ErrorAbort(state, "%s(): can't parse \"%s\" as byte count",
                   name, target_size_str);
        free(source_filename);
        free(target_filename);
        free(target_sha1);
        free(target_size_str);
        return NULL;
    }

    int patchcount = (argc-4) / 2;
    Value** patches = ReadValueVarArgs(state, argc-4, argv+4);

    int i;
    for (i = 0; i < patchcount; ++i) {
        if (patches[i*2]->type != VAL_STRING) {
            ErrorAbort(state, "%s(): sha-1 #%d is not string", name, i);
            break;
        }
        if (patches[i*2+1]->type != VAL_BLOB) {
            ErrorAbort(state, "%s(): patch #%d is not blob", name, i);
            break;
        }
    }
    if (i != patchcount) {
        for (i = 0; i < patchcount*2; ++i) {
            FreeValue(patches[i]);
        }
        free(patches);
        return NULL;
    }

    char** patch_sha_str = malloc(patchcount * sizeof(char*));
    for (i = 0; i < patchcount; ++i) {
        patch_sha_str[i] = patches[i*2]->data;
        patches[i*2]->data = NULL;
        FreeValue(patches[i*2]);
        patches[i] = patches[i*2+1];
    }

    int result = applypatch(source_filename, target_filename,
                            target_sha1, target_size,
                            patchcount, patch_sha_str, patches);

    for (i = 0; i < patchcount; ++i) {
        FreeValue(patches[i]);
    }
    free(patch_sha_str);
    free(patches);

    return StringValue(strdup(result == 0 ? "t" : ""));
}
Ejemplo n.º 15
0
// write_raw_image(filename_or_blob, partition)
Value* WriteRawImageFn(const char* name, State* state, int argc, Expr* argv[]) {
    char* result = NULL;

    Value* partition_value;
    Value* contents;
    if (ReadValueArgs(state, argv, 2, &contents, &partition_value) < 0) {
        return NULL;
    }

    char* partition = NULL;
    if (partition_value->type != VAL_STRING) {
        ErrorAbort(state, "partition argument to %s must be string", name);
        goto done;
    }
    partition = partition_value->data;
    if (strlen(partition) == 0) {
        ErrorAbort(state, "partition argument to %s can't be empty", name);
        goto done;
    }
    if (contents->type == VAL_STRING && strlen((char*) contents->data) == 0) {
        ErrorAbort(state, "file argument to %s can't be empty", name);
        goto done;
    }

    mtd_scan_partitions();
    const MtdPartition* mtd = mtd_find_partition_by_name(partition);
    if (mtd == NULL) {
        fprintf(stderr, "%s: no mtd partition named \"%s\"\n", name, partition);
        result = strdup("");
        goto done;
    }

    MtdWriteContext* ctx = mtd_write_partition(mtd);
    if (ctx == NULL) {
        fprintf(stderr, "%s: can't write mtd partition \"%s\"\n",
                name, partition);
        result = strdup("");
        goto done;
    }

    bool success;

    if (contents->type == VAL_STRING) {
        // we're given a filename as the contents
        char* filename = contents->data;
        FILE* f = fopen(filename, "rb");
        if (f == NULL) {
            fprintf(stderr, "%s: can't open %s: %s\n",
                    name, filename, strerror(errno));
            result = strdup("");
            goto done;
        }

        success = true;
        char* buffer = malloc(BUFSIZ);
        int read;
        while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) {
            int wrote = mtd_write_data(ctx, buffer, read);
            success = success && (wrote == read);
        }
        free(buffer);
        fclose(f);
    } else {
        // we're given a blob as the contents
        ssize_t wrote = mtd_write_data(ctx, contents->data, contents->size);
        success = (wrote == contents->size);
    }
    if (!success) {
        fprintf(stderr, "mtd_write_data to %s failed: %s\n",
                partition, strerror(errno));
    }

    if (mtd_erase_blocks(ctx, -1) == -1) {
        fprintf(stderr, "%s: error erasing blocks of %s\n", name, partition);
    }
    if (mtd_write_close(ctx) != 0) {
        fprintf(stderr, "%s: error closing write of %s\n", name, partition);
    }

    printf("%s %s partition\n",
           success ? "wrote" : "failed to write", partition);

    result = success ? partition : strdup("");

done:
    if (result != partition) FreeValue(partition_value);
    FreeValue(contents);
    return StringValue(result);
}
Value* UpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 7) {
        return ErrorAbort(state, "%s() expects 7 args, got %d", name, argc);
    }

    char* type = strrchr(name, '_');
    if (type == NULL || *(type+1) == '\0') {
        return ErrorAbort(state, "%s() couldn't get type from function name",
                          name);
    }
    ++type;

    Value* image;
    Value* width_string;
    Value* height_string;
    Value* bpp_string;
    Value* busy;
    Value* fail;
    Value* expected_sha1_string;
    if (ReadValueArgs(state, argv, 7, &image,
                      &width_string, &height_string, &bpp_string,
                      &busy, &fail, &expected_sha1_string) < 0) {
        return NULL;
    }

    // close the package
    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
    mzCloseZipArchive(za);
    ((UpdaterInfo*)(state->cookie))->package_zip = NULL;

    // Try to unmount /cache.  If we fail (because we're running in an
    // older recovery that still has the package file open), try to
    // remount it read-only.  If that fails, abort.
    sync();
    scan_mounted_volumes();
    MountedVolume* vol = find_mounted_volume_by_mount_point("/cache");
    int result = unmount_mounted_volume(vol);
    if (result != 0) {
        printf("%s(): failed to unmount cache (%d: %s)\n",
               name, result, strerror(errno));

        result = remount_read_only(vol);
        if (result != 0) {
            printf("%s(): failed to remount cache (%d: %s)\n",
                   name, result, strerror(errno));
            return StringValue(strdup(""));
        } else {
            printf("%s(): remounted cache\n", name);
        }
        sync();
    } else {
        printf("%s(): unmounted cache\n", name);
    }

    int width = 0, height = 0, bpp = 0;

    if (width_string->type != VAL_STRING ||
        (width = strtol(width_string->data, NULL, 10)) == 0) {
        printf("%s(): bad width argument", name);
    }
    if (height_string->type != VAL_STRING ||
        (height = strtol(height_string->data, NULL, 10)) == 0) {
        printf("%s(): bad height argument", name);
    }
    if (bpp_string->type != VAL_STRING ||
        (bpp = strtol(bpp_string->data, NULL, 10)) == 0) {
        printf("%s(): bad bpp argument", name);
    }

    if (image->type != VAL_BLOB) {
        printf("image argument is not blob (is type %d)\n", image->type);
        goto done;
    }

    uint8_t expected_sha1[SHA_DIGEST_SIZE];
    char* data = expected_sha1_string->data;
    if (expected_sha1_string->type != VAL_STRING ||
        strlen(data) != SHA_DIGEST_SIZE*2) {
        printf("%s(): bad expected_sha1 argument", name);
        goto done;
    }
    printf("expected sha1 is: ");
    int i;
    for (i = 0; i < SHA_DIGEST_SIZE; ++i) {
        char temp = data[i*2+2];
        data[i*2+2] = '\0';
        expected_sha1[i] = strtol(data+i*2, NULL, 16);
        data[i*2+2] = temp;
        printf("%02x", expected_sha1[i]);
    }
    printf("\n");

    install_firmware_update(
        type,
        image->data,
        image->size,
        width, height, bpp,
        busy->size > 0 ? busy->data : NULL,
        fail->size > 0 ? fail->data : NULL,
        "/tmp/recovery.log",
        expected_sha1);
    printf("%s: install_firmware_update returned!\n", name);

  done:
    FreeValue(image);
    FreeValue(width_string);
    FreeValue(height_string);
    FreeValue(bpp_string);
    FreeValue(busy);
    FreeValue(fail);
    // install_firmware_update should reboot.  If it returns, it failed.
    return StringValue(strdup(""));
}
Ejemplo n.º 17
0
Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
    Value* blockdev_filename;
    Value* transfer_list_value;
    char* transfer_list = NULL;
    Value* new_data_fn;
    Value* patch_data_fn;
    bool success = false;

    if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
                      &new_data_fn, &patch_data_fn) < 0) {
        return NULL;
    }

    if (blockdev_filename->type != VAL_STRING) {
        ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
        goto done;
    }
    if (transfer_list_value->type != VAL_BLOB) {
        ErrorAbort(state, "transfer_list argument to %s must be blob", name);
        goto done;
    }
    if (new_data_fn->type != VAL_STRING) {
        ErrorAbort(state, "new_data_fn argument to %s must be string", name);
        goto done;
    }
    if (patch_data_fn->type != VAL_STRING) {
        ErrorAbort(state, "patch_data_fn argument to %s must be string", name);
        goto done;
    }

    UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
    FILE* cmd_pipe = ui->cmd_pipe;

    ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;

    const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
    if (patch_entry == NULL) {
        ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data);
        goto done;
    }

    uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr +
        mzGetZipEntryOffset(patch_entry);

    const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data);
    if (new_entry == NULL) {
        ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data);
        goto done;
    }

    // The transfer list is a text file containing commands to
    // transfer data from one place to another on the target
    // partition.  We parse it and execute the commands in order:
    //
    //    zero [rangeset]
    //      - fill the indicated blocks with zeros
    //
    //    new [rangeset]
    //      - fill the blocks with data read from the new_data file
    //
    //    bsdiff patchstart patchlen [src rangeset] [tgt rangeset]
    //    imgdiff patchstart patchlen [src rangeset] [tgt rangeset]
    //      - read the source blocks, apply a patch, write result to
    //        target blocks.  bsdiff or imgdiff specifies the type of
    //        patch.
    //
    //    move [src rangeset] [tgt rangeset]
    //      - copy data from source blocks to target blocks (no patch
    //        needed; rangesets are the same size)
    //
    //    erase [rangeset]
    //      - mark the given blocks as empty
    //
    // The creator of the transfer list will guarantee that no block
    // is read (ie, used as the source for a patch or move) after it
    // has been written.
    //
    // Within one command the source and target ranges may overlap so
    // in general we need to read the entire source into memory before
    // writing anything to the target blocks.
    //
    // All the patch data is concatenated into one patch_data file in
    // the update package.  It must be stored uncompressed because we
    // memory-map it in directly from the archive.  (Since patches are
    // already compressed, we lose very little by not compressing
    // their concatenation.)

    pthread_t new_data_thread;
    NewThreadInfo nti;
    nti.za = za;
    nti.entry = new_entry;
    nti.rss = NULL;
    pthread_mutex_init(&nti.mu, NULL);
    pthread_cond_init(&nti.cv, NULL);

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_create(&new_data_thread, &attr, unzip_new_data, &nti);

    int i, j;

    char* linesave;
    char* wordsave;

    int fd = open(blockdev_filename->data, O_RDWR);
    if (fd < 0) {
        ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
        goto done;
    }

    char* line;
    char* word;

    // The data in transfer_list_value is not necessarily
    // null-terminated, so we need to copy it to a new buffer and add
    // the null that strtok_r will need.
    transfer_list = malloc(transfer_list_value->size+1);
    if (transfer_list == NULL) {
        fprintf(stderr, "failed to allocate %zd bytes for transfer list\n",
                transfer_list_value->size+1);
        exit(1);
    }
    memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size);
    transfer_list[transfer_list_value->size] = '\0';

    line = strtok_r(transfer_list, "\n", &linesave);

    // first line in transfer list is the version number; currently
    // there's only version 1.
    if (strcmp(line, "1") != 0) {
        ErrorAbort(state, "unexpected transfer list version [%s]\n", line);
        goto done;
    }

    // second line in transfer list is the total number of blocks we
    // expect to write.
    line = strtok_r(NULL, "\n", &linesave);
    int total_blocks = strtol(line, NULL, 0);
    // shouldn't happen, but avoid divide by zero.
    if (total_blocks == 0) ++total_blocks;
    int blocks_so_far = 0;

    uint8_t* buffer = NULL;
    size_t buffer_alloc = 0;

    // third and subsequent lines are all individual transfer commands.
    for (line = strtok_r(NULL, "\n", &linesave); line;
         line = strtok_r(NULL, "\n", &linesave)) {
        char* style;
        style = strtok_r(line, " ", &wordsave);

        if (strcmp("move", style) == 0) {
            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* src = parse_range(word);
            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* tgt = parse_range(word);

            printf("  moving %d blocks\n", src->size);

            allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
            size_t p = 0;
            for (i = 0; i < src->count; ++i) {
                check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
                size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
                readblock(fd, buffer+p, sz);
                p += sz;
            }

            p = 0;
            for (i = 0; i < tgt->count; ++i) {
                check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
                size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE;
                writeblock(fd, buffer+p, sz);
                p += sz;
            }

            blocks_so_far += tgt->size;
            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
            fflush(cmd_pipe);

            free(src);
            free(tgt);

        } else if (strcmp("zero", style) == 0 ||
                   (DEBUG_ERASE && strcmp("erase", style) == 0)) {
            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* tgt = parse_range(word);

            printf("  zeroing %d blocks\n", tgt->size);

            allocate(BLOCKSIZE, &buffer, &buffer_alloc);
            memset(buffer, 0, BLOCKSIZE);
            for (i = 0; i < tgt->count; ++i) {
                check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
                for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) {
                    writeblock(fd, buffer, BLOCKSIZE);
                }
            }

            if (style[0] == 'z') {   // "zero" but not "erase"
                blocks_so_far += tgt->size;
                fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
                fflush(cmd_pipe);
            }

            free(tgt);
        } else if (strcmp("new", style) == 0) {

            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* tgt = parse_range(word);

            printf("  writing %d blocks of new data\n", tgt->size);

            RangeSinkState rss;
            rss.fd = fd;
            rss.tgt = tgt;
            rss.p_block = 0;
            rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
            check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);

            pthread_mutex_lock(&nti.mu);
            nti.rss = &rss;
            pthread_cond_broadcast(&nti.cv);
            while (nti.rss) {
                pthread_cond_wait(&nti.cv, &nti.mu);
            }
            pthread_mutex_unlock(&nti.mu);

            blocks_so_far += tgt->size;
            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
            fflush(cmd_pipe);

            free(tgt);

        } else if (strcmp("bsdiff", style) == 0 ||
                   strcmp("imgdiff", style) == 0) {
            word = strtok_r(NULL, " ", &wordsave);
            size_t patch_offset = strtoul(word, NULL, 0);
            word = strtok_r(NULL, " ", &wordsave);
            size_t patch_len = strtoul(word, NULL, 0);

            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* src = parse_range(word);
            word = strtok_r(NULL, " ", &wordsave);
            RangeSet* tgt = parse_range(word);

            printf("  patching %d blocks to %d\n", src->size, tgt->size);

            // Read the source into memory.
            allocate(src->size * BLOCKSIZE, &buffer, &buffer_alloc);
            size_t p = 0;
            for (i = 0; i < src->count; ++i) {
                check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
                size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
                readblock(fd, buffer+p, sz);
                p += sz;
            }

            Value patch_value;
            patch_value.type = VAL_BLOB;
            patch_value.size = patch_len;
            patch_value.data = (char*)(patch_start + patch_offset);

            RangeSinkState rss;
            rss.fd = fd;
            rss.tgt = tgt;
            rss.p_block = 0;
            rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
            check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);

            if (style[0] == 'i') {      // imgdiff
                ApplyImagePatch(buffer, src->size * BLOCKSIZE,
                                &patch_value,
                                &RangeSinkWrite, &rss, NULL, NULL);
            } else {
                ApplyBSDiffPatch(buffer, src->size * BLOCKSIZE,
                                 &patch_value, 0,
                                 &RangeSinkWrite, &rss, NULL);
            }

            // We expect the output of the patcher to fill the tgt ranges exactly.
            if (rss.p_block != tgt->count || rss.p_remain != 0) {
                fprintf(stderr, "range sink underrun?\n");
            }

            blocks_so_far += tgt->size;
            fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
            fflush(cmd_pipe);

            free(src);
            free(tgt);
        } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) {
            struct stat st;
            if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
                word = strtok_r(NULL, " ", &wordsave);
                RangeSet* tgt = parse_range(word);

                printf("  erasing %d blocks\n", tgt->size);

                for (i = 0; i < tgt->count; ++i) {
                    uint64_t range[2];
                    // offset in bytes
                    range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE;
                    // len in bytes
                    range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE;

                    if (ioctl(fd, BLKDISCARD, &range) < 0) {
                        printf("    blkdiscard failed: %s\n", strerror(errno));
                    }
                }

                free(tgt);
            } else {
                printf("  ignoring erase (not block device)\n");
            }
        } else {
            fprintf(stderr, "unknown transfer style \"%s\"\n", style);
            exit(1);
        }
    }

    pthread_join(new_data_thread, NULL);
    success = true;

    free(buffer);
    printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks);
    printf("max alloc needed was %zu\n", buffer_alloc);

  done:
    free(transfer_list);
    FreeValue(blockdev_filename);
    FreeValue(transfer_list_value);
    FreeValue(new_data_fn);
    FreeValue(patch_data_fn);
    return StringValue(success ? strdup("t") : strdup(""));
}