forked from rpm-software-management/rpm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reflink.c
400 lines (364 loc) · 11.5 KB
/
reflink.c
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
#include "system.h"
#include <errno.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#if defined(__linux__)
#include <linux/fs.h> /* For FICLONE */
#endif
#include <rpm/rpmlog.h>
#include "lib/rpmlib.h"
#include "lib/rpmplugin.h"
#include "lib/rpmextents_internal.h"
#include "lib/rpmte_internal.h"
#include <rpm/rpmfileutil.h>
#include "rpmio/rpmio_internal.h"
#include "debug.h"
#include <sys/ioctl.h>
/* use hash table to remember inode -> ix (for rpmfilesFN(ix)) lookups */
#undef HASHTYPE
#undef HTKEYTYPE
#undef HTDATATYPE
#define HASHTYPE inodeIndexHash
#define HTKEYTYPE rpm_ino_t
#define HTDATATYPE const char *
#include "lib/rpmhash.H"
#include "lib/rpmhash.C"
/* We use this in find to indicate a key wasn't found. This is an
* unrecoverable error, but we can at least show a decent error. 0 is never a
* valid offset because it's the offset of the start of the file.
*/
#define NOT_FOUND 0
#define BUFFER_SIZE (1024 * 128)
struct reflink_state_s {
/* Stuff that's used across rpms */
long fundamental_block_size;
char *buffer;
/* stuff that's used/updated per psm */
uint32_t keys, keysize;
/* table for current rpm, keys * (keysize + sizeof(rpm_loff_t)) */
unsigned char *table;
FD_t fd;
rpmfiles files;
inodeIndexHash inodeIndexes;
int transcoded;
};
typedef struct reflink_state_s * reflink_state;
/*
* bsearch_r: implements a re-entrant version of stdlib's bsearch.
* code taken and adapted from /usr/include/bits/stdlib-bsearch.h
*/
inline void *
bsearch_r (const void *__key, const void *__base, size_t __nmemb, size_t __size,
__compar_d_fn_t __compar, void *__arg)
{
size_t __l, __u, __idx;
const void *__p;
int __comparison;
__l = 0;
__u = __nmemb;
while (__l < __u)
{
__idx = (__l + __u) / 2;
__p = (const void *) (((const char *) __base) + (__idx * __size));
__comparison = (*__compar) (__key, __p, __arg);
if (__comparison < 0)
__u = __idx;
else if (__comparison > 0)
__l = __idx + 1;
else
{
#if __GNUC_PREREQ(4, 6)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-qual"
#endif
return (void *) __p;
#if __GNUC_PREREQ(4, 6)
# pragma GCC diagnostic pop
#endif
}
}
return NULL;
}
static int cmpdigest(const void *k1, const void *k2, void *data) {
rpmlog(RPMLOG_DEBUG, _("reflink: cmpdigest k1=%p k2=%p\n"), k1, k2);
return memcmp(k1, k2, *(int *)data);
}
static int inodeCmp(rpm_ino_t a, rpm_ino_t b)
{
return (a != b);
}
static unsigned int inodeId(rpm_ino_t a)
{
/* rpm_ino_t is uint32_t so maps safely to unsigned int */
return (unsigned int)a;
}
static rpmRC reflink_init(rpmPlugin plugin, rpmts ts) {
reflink_state state = rcalloc(1, sizeof(struct reflink_state_s));
/* IOCTL-FICLONERANGE(2): ...Disk filesystems generally require the offset
* and length arguments to be aligned to the fundamental block size.
*
* The value of "fundamental block size" is directly related to the
* system's page size, so we should use that.
*/
state->fundamental_block_size = sysconf(_SC_PAGESIZE);
state->buffer = rcalloc(1, BUFFER_SIZE);
rpmPluginSetData(plugin, state);
return RPMRC_OK;
}
static void reflink_cleanup(rpmPlugin plugin) {
reflink_state state = rpmPluginGetData(plugin);
free(state->buffer);
free(state);
}
static rpmRC reflink_psm_pre(rpmPlugin plugin, rpmte te) {
rpmRC rc;
size_t len;
reflink_state state = rpmPluginGetData(plugin);
state->fd = rpmteFd(te);
if (state->fd == 0) {
rpmlog(RPMLOG_DEBUG, _("reflink: fd = 0, no install\n"));
return RPMRC_OK;
}
rpm_loff_t current = Ftell(state->fd);
rc = isTranscodedRpm(state->fd);
switch(rc){
// Fail to parse the file, fail the plugin.
case RPMRC_FAIL:
return RPMRC_FAIL;
// This is not a transcoded file, do nothing.
case RPMRC_NOTFOUND:
return RPMRC_OK;
default:
break;
}
rpmlog(RPMLOG_DEBUG, _("reflink: *is* transcoded\n"));
state->transcoded = 1;
state->files = rpmteFiles(te);
/* tail of file contains offset_table, offset_checksums then magic */
if (Fseek(state->fd, -(sizeof(rpm_loff_t) * 2 + sizeof(extents_magic_t)), SEEK_END) < 0) {
rpmlog(RPMLOG_ERR, _("reflink: failed to seek for tail %p\n"),
state->fd);
return RPMRC_FAIL;
}
rpm_loff_t table_start;
len = sizeof(table_start);
if (Fread(&table_start, len, 1, state->fd) != len) {
rpmlog(RPMLOG_ERR, _("reflink: unable to read table_start\n"));
return RPMRC_FAIL;
}
if (Fseek(state->fd, table_start, SEEK_SET) < 0) {
rpmlog(RPMLOG_ERR, _("reflink: unable to seek to table_start\n"));
return RPMRC_FAIL;
}
len = sizeof(state->keys);
if (Fread(&state->keys, len, 1, state->fd) != len) {
rpmlog(RPMLOG_ERR, _("reflink: unable to read number of keys\n"));
return RPMRC_FAIL;
}
len = sizeof(state->keysize);
if (Fread(&state->keysize, len, 1, state->fd) != len) {
rpmlog(RPMLOG_ERR, _("reflink: unable to read keysize\n"));
return RPMRC_FAIL;
}
rpmlog(
RPMLOG_DEBUG,
_("reflink: table_start=0x%lx, keys=%d, keysize=%d\n"),
table_start, state->keys, state->keysize
);
/* now get digest table if there is a reason to have one. */
if (state->keys == 0 || state->keysize == 0) {
/* no files (or no digests(!)) */
state->table = NULL;
} else {
int table_size = state->keys * (state->keysize + sizeof(rpm_loff_t));
state->table = rcalloc(1, table_size);
if (Fread(state->table, table_size, 1, state->fd) != table_size) {
rpmlog(RPMLOG_ERR, _("reflink: unable to read table\n"));
return RPMRC_FAIL;
}
state->inodeIndexes = inodeIndexHashCreate(
state->keys, inodeId, inodeCmp, NULL, (inodeIndexHashFreeData)rfree
);
}
/* Seek back to original location.
* Might not be needed if we seek to offset immediately
*/
if (Fseek(state->fd, current, SEEK_SET) < 0) {
rpmlog(RPMLOG_ERR,
_("reflink: unable to seek back to original location\n"));
return RPMRC_FAIL;
}
return RPMRC_OK;
}
static rpmRC reflink_psm_post(rpmPlugin plugin, rpmte te, int res)
{
reflink_state state = rpmPluginGetData(plugin);
state->files = rpmfilesFree(state->files);
if (state->table) {
free(state->table);
state->table = NULL;
}
if (state->inodeIndexes) {
inodeIndexHashFree(state->inodeIndexes);
state->inodeIndexes = NULL;
}
return RPMRC_OK;
}
/* have a prototype, warnings system */
rpm_loff_t find(const unsigned char *digest, reflink_state state);
rpm_loff_t find(const unsigned char *digest, reflink_state state) {
rpmlog(RPMLOG_DEBUG,
_("reflink: bsearch_r(key=%p, base=%p, nmemb=%d, size=%lu)\n"),
digest, state->table, state->keys,
state->keysize + sizeof(rpm_loff_t));
char *entry = bsearch_r(digest, state->table, state->keys,
state->keysize + sizeof(rpm_loff_t), cmpdigest,
&state->keysize);
if (entry == NULL) {
return NOT_FOUND;
}
rpm_loff_t offset = *(rpm_loff_t *)(entry + state->keysize);
return offset;
}
static rpmRC reflink_fsm_file_install(rpmPlugin plugin, rpmfi fi, const char* path,
mode_t file_mode, rpmFsmOp op)
{
struct file_clone_range fcr;
rpm_loff_t size;
int dst, rc;
const char **hl_target = NULL;
reflink_state state = rpmPluginGetData(plugin);
if (state->table == NULL) {
/* no table means rpm is not in reflink format, so leave. Now. */
return RPMRC_OK;
}
if (op == FA_TOUCH) {
/* we're not overwriting an existing file. */
return RPMRC_OK;
}
fcr.dest_offset = 0;
if (S_ISREG(file_mode) && !(rpmfiFFlags(fi) & RPMFILE_GHOST)) {
rpm_ino_t inode = rpmfiFInode(fi);
/* check for hard link entry in table. GetEntry overwrites hlix with
* the address of the first match.
*/
if (inodeIndexHashGetEntry(state->inodeIndexes, inode, &hl_target,
NULL, NULL)) {
/* entry is in table, use hard link */
if (link(hl_target[0], path) != 0) {
rpmlog(RPMLOG_ERR,
_("reflink: Unable to hard link %s -> %s due to %s\n"),
hl_target[0], path, strerror(errno));
return RPMRC_FAIL;
}
return RPMRC_PLUGIN_CONTENTS;
}
/* if we didn't hard link, then we'll track this inode as being
* created soon
*/
if (rpmfiFNlink(fi) > 1) {
/* minor optimization: only store files with more than one link */
inodeIndexHashAddEntry(state->inodeIndexes, inode, rstrdup(path));
}
/* derived from wfd_open in fsm.c */
mode_t old_umask = umask(0577);
dst = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR);
umask(old_umask);
if (dst == -1) {
rpmlog(RPMLOG_ERR,
_("reflink: Unable to open %s for writing due to %s, flags = %x\n"),
path, strerror(errno), rpmfiFFlags(fi));
return RPMRC_FAIL;
}
size = rpmfiFSize(fi);
if (size > 0) {
/* round src_length down to fundamental_block_size multiple */
fcr.src_length = size / state->fundamental_block_size * state->fundamental_block_size;
if ((size % state->fundamental_block_size) > 0) {
/* round up to next fundamental_block_size. We expect the data
* in the rpm to be similarly padded.
*/
fcr.src_length += state->fundamental_block_size;
}
fcr.src_fd = Fileno(state->fd);
if (fcr.src_fd == -1) {
close(dst);
rpmlog(RPMLOG_ERR, _("reflink: src fd lookup failed\n"));
return RPMRC_FAIL;
}
fcr.src_offset = find(rpmfiFDigest(fi, NULL, NULL), state);
if (fcr.src_offset == NOT_FOUND) {
close(dst);
rpmlog(RPMLOG_ERR, _("reflink: digest not found\n"));
return RPMRC_FAIL;
}
rpmlog(RPMLOG_DEBUG,
_("reflink: Reflinking %llu bytes at %llu to %s orig size=%ld, file=%lld\n"),
fcr.src_length, fcr.src_offset, path, size, fcr.src_fd);
rc = ioctl(dst, FICLONERANGE, &fcr);
if (rc) {
rpmlog(RPMLOG_WARNING,
_("reflink: falling back to copying bits for %s due to %d, %d = %s\n"),
path, rc, errno, strerror(errno));
if (Fseek(state->fd, fcr.src_offset, SEEK_SET) < 0) {
close(dst);
rpmlog(RPMLOG_ERR,
_("reflink: unable to seek on copying bits\n"));
return RPMRC_FAIL;
}
rpm_loff_t left = size;
size_t len, read, written;
while (left) {
len = (left > BUFFER_SIZE ? BUFFER_SIZE : left);
read = Fread(state->buffer, len, 1, state->fd);
if (read != len) {
close(dst);
rpmlog(RPMLOG_ERR,
_("reflink: short read on copying bits\n"));
return RPMRC_FAIL;
}
written = write(dst, state->buffer, len);
if (read != written) {
close(dst);
rpmlog(RPMLOG_ERR,
_("reflink: short write on copying bits\n"));
return RPMRC_FAIL;
}
left -= len;
}
} else {
/* reflink worked, so truncate */
rc = ftruncate(dst, size);
if (rc) {
rpmlog(RPMLOG_ERR,
_("reflink: Unable to truncate %s to %ld due to %s\n"),
path, size, strerror(errno));
return RPMRC_FAIL;
}
}
}
close(dst);
return RPMRC_PLUGIN_CONTENTS;
}
return RPMRC_OK;
}
static rpmRC reflink_fsm_file_archive_reader(rpmPlugin plugin, FD_t payload,
rpmfiles files, rpmfi *fi) {
reflink_state state = rpmPluginGetData(plugin);
if(state->transcoded) {
*fi = rpmfilesIter(files, RPMFI_ITER_FWD);
return RPMRC_PLUGIN_CONTENTS;
}
return RPMRC_OK;
}
struct rpmPluginHooks_s reflink_hooks = {
.init = reflink_init,
.cleanup = reflink_cleanup,
.psm_pre = reflink_psm_pre,
.psm_post = reflink_psm_post,
.fsm_file_install = reflink_fsm_file_install,
.fsm_file_archive_reader = reflink_fsm_file_archive_reader,
};