summaryrefslogtreecommitdiff
path: root/src/patch.c
blob: 5fae2bbb1512c2d792797db1dd2a7fa6bac54f33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*= -*- c-basic-offset: 4; indent-tabs-mode: nil; -*-
 *
 * librsync -- the library for network deltas
 *
 * Copyright (C) 2000, 2001 by Martin Pool <mbp@sourcefrog.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

                              /*=
                               | This is Tranquility Base.
                               */

/** \file patch.c
 * Apply a delta to an old file to generate a new file. */

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "librsync.h"
#include "job.h"
#include "netint.h"
#include "stream.h"
#include "command.h"
#include "prototab.h"
#include "trace.h"

static rs_result rs_patch_s_cmdbyte(rs_job_t *);
static rs_result rs_patch_s_params(rs_job_t *);
static rs_result rs_patch_s_run(rs_job_t *);
static rs_result rs_patch_s_literal(rs_job_t *);
static rs_result rs_patch_s_copy(rs_job_t *);
static rs_result rs_patch_s_copying(rs_job_t *);

/** State of trying to read the first byte of a command. Once we've taken that
 * in, we can know how much data to read to get the arguments. */
static rs_result rs_patch_s_cmdbyte(rs_job_t *job)
{
    rs_result result;

    if ((result = rs_suck_byte(job, &job->op)) != RS_DONE)
        return result;
    job->cmd = &rs_prototab[job->op];
    rs_trace("got command %#04x (%s), len_1=%d, len_2=%d", job->op,
             rs_op_kind_name(job->cmd->kind), job->cmd->len_1, job->cmd->len_2);
    if (job->cmd->len_1)
        job->statefn = rs_patch_s_params;
    else {
        job->param1 = job->cmd->immediate;
        job->statefn = rs_patch_s_run;
    }
    return RS_RUNNING;
}

/** Called after reading a command byte to pull in its parameters and then
 * setup to execute the command. */
static rs_result rs_patch_s_params(rs_job_t *job)
{
    rs_result result;
    const size_t len = (size_t)(job->cmd->len_1 + job->cmd->len_2);
    void *p;

    assert(len);
    result = rs_scoop_readahead(job, len, &p);
    if (result != RS_DONE)
        return result;
    /* we now must have LEN bytes buffered */
    result = rs_suck_netint(job, &job->param1, job->cmd->len_1);
    /* shouldn't fail, since we already checked */
    assert(result == RS_DONE);
    if (job->cmd->len_2) {
        result = rs_suck_netint(job, &job->param2, job->cmd->len_2);
        assert(result == RS_DONE);
    }
    job->statefn = rs_patch_s_run;
    return RS_RUNNING;
}

/** Called when we've read in the whole command and we need to execute it. */
static rs_result rs_patch_s_run(rs_job_t *job)
{
    rs_trace("running command %#04x", job->op);
    switch (job->cmd->kind) {
    case RS_KIND_LITERAL:
        job->statefn = rs_patch_s_literal;
        return RS_RUNNING;
    case RS_KIND_END:
        return RS_DONE;
        /* so we exit here; trying to continue causes an error */
    case RS_KIND_COPY:
        job->statefn = rs_patch_s_copy;
        return RS_RUNNING;
    default:
        rs_error("bogus command %#04x", job->op);
        return RS_CORRUPT;
    }
}

/** Called when trying to copy through literal data. */
static rs_result rs_patch_s_literal(rs_job_t *job)
{
    const rs_long_t len = job->param1;
    rs_stats_t *stats = &job->stats;

    rs_trace("LITERAL(length=" FMT_LONG ")", len);
    if (len <= 0 || len > SIZE_MAX) {
        rs_error("invalid length=" FMT_LONG " on LITERAL command", len);
        return RS_CORRUPT;
    }
    stats->lit_cmds++;
    stats->lit_bytes += len;
    stats->lit_cmdbytes += 1 + job->cmd->len_1;
    rs_tube_copy(job, (size_t)len);
    job->statefn = rs_patch_s_cmdbyte;
    return RS_RUNNING;
}

static rs_result rs_patch_s_copy(rs_job_t *job)
{
    const rs_long_t pos = job->param1;
    const rs_long_t len = job->param2;
    rs_stats_t *stats = &job->stats;

    rs_trace("COPY(position=" FMT_LONG ", length=" FMT_LONG ")", pos, len);
    if (len <= 0) {
        rs_error("invalid length=" FMT_LONG " on COPY command", len);
        return RS_CORRUPT;
    }
    if (pos < 0) {
        rs_error("invalid position=" FMT_LONG " on COPY command", pos);
        return RS_CORRUPT;
    }
    stats->copy_cmds++;
    stats->copy_bytes += len;
    stats->copy_cmdbytes += 1 + job->cmd->len_1 + job->cmd->len_2;
    job->basis_pos = pos;
    job->basis_len = len;
    job->statefn = rs_patch_s_copying;
    return RS_RUNNING;
}

/** Called when we're executing a COPY command and waiting for all the data to
 * be retrieved from the callback. */
static rs_result rs_patch_s_copying(rs_job_t *job)
{
    rs_result result;
    rs_buffers_t *buffs = job->stream;
    rs_long_t req = job->basis_len;
    size_t len = buffs->avail_out;
    void *ptr = buffs->next_out;

    /* We are blocked if there is no space left to copy into. */
    if (!len)
        return RS_BLOCKED;
    /* Adjust request to min of amount requested and space available. */
    if (len < req)
        req = (rs_long_t)len;
    rs_trace("copy " FMT_LONG " bytes from basis at offset " FMT_LONG "", req,
             job->basis_pos);
    len = (size_t)req;
    result = (job->copy_cb) (job->copy_arg, job->basis_pos, &len, &ptr);
    if (result != RS_DONE) {
        rs_trace("copy callback returned %s", rs_strerror(result));
        return result;
    }
    rs_trace("got " FMT_SIZE " bytes back from basis callback", len);
    /* Actual copied length cannot be greater than requested length. */
    assert(len <= req);
    /* Backwards-compatible defensively handle this for NDEBUG builds. */
    if (len > req) {
        rs_warn("copy_cb() returned more than the requested length");
        len = (size_t)req;
    }
    /* copy back to out buffer only if the callback has used its own buffer */
    if (ptr != buffs->next_out)
        memcpy(buffs->next_out, ptr, len);
    /* Update buffs and copy for copied data. */
    buffs->next_out += len;
    buffs->avail_out -= len;
    job->basis_pos += (rs_long_t)len;
    job->basis_len -= (rs_long_t)len;
    if (!job->basis_len) {
        /* Nothing left to copy, we are done! */
        job->statefn = rs_patch_s_cmdbyte;
    }
    return RS_RUNNING;
}

/** Called while we're trying to read the header of the patch. */
static rs_result rs_patch_s_header(rs_job_t *job)
{
    int v;
    rs_result result;

    if ((result = rs_suck_n4(job, &v)) != RS_DONE)
        return result;
    if (v != RS_DELTA_MAGIC) {
        rs_error("got magic number %#x rather than expected value %#x", v,
                 RS_DELTA_MAGIC);
        return RS_BAD_MAGIC;
    } else
        rs_trace("got patch magic %#x", v);
    job->statefn = rs_patch_s_cmdbyte;
    return RS_RUNNING;
}

rs_job_t *rs_patch_begin(rs_copy_cb * copy_cb, void *copy_arg)
{
    rs_job_t *job = rs_job_new("patch", rs_patch_s_header);

    job->copy_cb = copy_cb;
    job->copy_arg = copy_arg;
    rs_mdfour_begin(&job->output_md4);
    return job;
}