vinyl-cache/vmod/vmod_vtc_storage.c
0
/*-
1
 * Copyright 2021,2023,2026 UPLEX - Nils Goroll Systemoptimierung
2
 * All rights reserved.
3
 *
4
 * Author: Nils Goroll <nils.goroll@uplex.de>
5
 *
6
 * SPDX-License-Identifier: BSD-2-Clause
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 *
29
 * interpose storage methods for testing purposes
30
 */
31
32
#include "config.h"
33
34
#include <stdlib.h>
35
36
#include "cache/cache_int.h"
37
#include "cache/cache_obj.h"
38
#include "storage/storage.h"
39
40
#include "vcc_vtc_if.h"
41
42
/* ----------------------------------------
43
 * stvi
44
 */
45
// override parameters
46
struct stvi_param {
47
        ssize_t                 maxspace;
48
        ssize_t                 frag;
49
};
50
51
// created once, only freed when the vmod goes cold
52
// i for interposer
53
struct stvi {
54
        unsigned                        magic;
55
#define STVI_MAGIC                      0x85ff17c0
56
        VRBT_ENTRY(stvi)                tree;
57
        uintptr_t                       stv;
58
59
        // original contents, copied on stvi creation
60
        struct stevedore                stv_save;
61
        struct obj_methods              om_save;
62
        // original methods pointer, for revert()
63
        const struct obj_methods        *om;
64
        void                            *freeme;
65
66
        struct stvi_param               param;
67
};
68
69
static inline int
70 1512
stvi_cmp(const struct stvi *s1, const struct stvi *s2)
71
{
72 1512
        if (s1->stv < s2->stv)
73 0
                return (-1);
74 1512
        if (s1->stv > s2->stv)
75 210
                return (1);
76 1302
        return (0);
77 1512
}
78
79
VRBT_HEAD(stvi_head, stvi);
80 0
VRBT_GENERATE_REMOVE_COLOR(stvi_head, stvi, tree, static)
81 0
VRBT_GENERATE_REMOVE(stvi_head, stvi, tree, static)
82 1722
VRBT_GENERATE_FIND(stvi_head, stvi, tree, stvi_cmp, static)
83 42
VRBT_GENERATE_INSERT_COLOR(stvi_head, stvi, tree, static)
84 252
VRBT_GENERATE_INSERT_FINISH(stvi_head, stvi, tree, static)
85 294
VRBT_GENERATE_INSERT(stvi_head, stvi, tree, stvi_cmp, static)
86 0
VRBT_GENERATE_NEXT(stvi_head, stvi, tree, static)
87 21
VRBT_GENERATE_MINMAX(stvi_head, stvi, tree, static)
88
89
static struct stvi_head stvi_head = VRBT_INITIALIZER(stvi);
90
static pthread_mutex_t stvi_mtx = PTHREAD_MUTEX_INITIALIZER;
91
92
// only return if exists
93
static struct stvi *
94 1554
stvi_find(VCL_STEVEDORE stv)
95
{
96
        struct stvi needle;
97
        struct stvi *si;
98
99 1554
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
100 1554
        needle.stv = (uintptr_t)TRUST_ME(stv);
101
102 1554
        PTOK(pthread_mutex_lock(&stvi_mtx));
103 1554
        si = VRBT_FIND(stvi_head, &stvi_head, &needle);
104 1554
        PTOK(pthread_mutex_unlock(&stvi_mtx));
105
106 1554
        CHECK_OBJ_ORNULL(si, STVI_MAGIC);
107 1554
        return (si);
108
}
109
110
// create if does not exist
111
static struct stvi *
112 1470
stvi_get(VCL_STEVEDORE stv)
113
{
114
        struct stevedore *wstv;
115
        struct obj_methods *wom;
116
        struct stvi *si, *old;
117
118 1470
        si = stvi_find(stv);
119 1470
        if (si)
120 1218
                return (si);
121
122 252
        ALLOC_OBJ(si, STVI_MAGIC);
123 252
        AN(si);
124 252
        si->stv = (uintptr_t)TRUST_ME(stv);
125
126 252
        PTOK(pthread_mutex_lock(&stvi_mtx));
127 252
        old = VRBT_INSERT(stvi_head, &stvi_head, si);
128
        // copy under the mutex to make sure that only the first _get() copies
129 252
        if (old == NULL) {
130 252
                si->stv_save = *stv;
131 252
                si->om_save = *stv->methods;
132 252
        }
133 252
        PTOK(pthread_mutex_unlock(&stvi_mtx));
134
135 252
        if (old) {
136 0
                FREE_OBJ(si);
137 0
                CHECK_OBJ(old, STVI_MAGIC);
138 0
                return (old);
139
        }
140
141
        // ensure methods are writable
142 252
        si->om = stv->methods;
143 252
        wom = malloc(sizeof *wom);
144 252
        AN(wom);
145 252
        *wom = *stv->methods;
146
147 252
        wstv = TRUST_ME(stv);
148 252
        wstv->methods = wom;
149
150 252
        return (si);
151 1470
}
152
153
/* ----------------------------------------
154
 * obji
155
 */
156
// freed via objfree
157
struct obji {
158
        unsigned                magic;
159
#define OBJI_MAGIC              0x65fc7452
160
        VRBT_ENTRY(obji)        tree;
161
        uintptr_t               oc;
162
163
        // remaining maxspace
164
        ssize_t                 canalloc;
165
};
166
167
static inline int
168 357
obji_cmp(const struct obji *oi1, const struct obji *oi2)
169
{
170 357
        if (oi1->oc < oi2->oc)
171 0
                return (-1);
172 357
        if (oi1->oc > oi2->oc)
173 0
                return (1);
174 357
        return (0);
175 357
}
176
177
VRBT_HEAD(obji_head, obji);
178 0
VRBT_GENERATE_REMOVE_COLOR(obji_head, obji, tree, static)
179 84
VRBT_GENERATE_REMOVE(obji_head, obji, tree, static)
180 441
VRBT_GENERATE_FIND(obji_head, obji, tree, obji_cmp, static)
181 0
VRBT_GENERATE_INSERT_COLOR(obji_head, obji, tree, static)
182 84
VRBT_GENERATE_INSERT_FINISH(obji_head, obji, tree, static)
183 84
VRBT_GENERATE_INSERT(obji_head, obji, tree, obji_cmp, static)
184
//VRBT_GENERATE_NEXT(obji_head, obji, tree, static)
185
//VRBT_GENERATE_MINMAX(obji_head, obji, tree, static)
186
187
static struct obji_head obji_head = VRBT_INITIALIZER(obji);
188
static pthread_mutex_t obji_mtx = PTHREAD_MUTEX_INITIALIZER;
189
190
// only return if exists
191
static struct obji *
192 357
obji_find(const struct objcore *oc)
193
{
194
        struct obji needle;
195
        struct obji *oi;
196
197 357
        needle.oc = (uintptr_t)oc;
198
199 357
        PTOK(pthread_mutex_lock(&obji_mtx));
200 357
        oi = VRBT_FIND(obji_head, &obji_head, &needle);
201 357
        PTOK(pthread_mutex_unlock(&obji_mtx));
202
203 357
        CHECK_OBJ_ORNULL(oi, OBJI_MAGIC);
204 357
        return (oi);
205
}
206
207
// creat if does not exist
208
static struct obji *
209 357
obji_get(const struct objcore *oc)
210
{
211
        struct obji *oi, *old;
212
213 357
        oi = obji_find(oc);
214 357
        if (oi)
215 273
                return (oi);
216
217 84
        ALLOC_OBJ(oi, OBJI_MAGIC);
218 84
        AN(oi);
219 84
        oi->oc = (uintptr_t)TRUST_ME(oc);
220
221 84
        PTOK(pthread_mutex_lock(&obji_mtx));
222 84
        old = VRBT_INSERT(obji_head, &obji_head, oi);
223 84
        PTOK(pthread_mutex_unlock(&obji_mtx));
224
225 84
        if (old) {
226 0
                FREE_OBJ(oi);
227 0
                CHECK_OBJ(old, OBJI_MAGIC);
228 0
                return (old);
229
        }
230 84
        return (oi);
231 357
}
232
233
static void
234 84
obji_free(const struct objcore *oc)
235
{
236
        struct obji needle;
237
        struct obji *oi;
238
239 84
        needle.oc = (uintptr_t)oc;
240
241 84
        PTOK(pthread_mutex_lock(&obji_mtx));
242 84
        oi = VRBT_FIND(obji_head, &obji_head, &needle);
243 84
        if (oi)
244 84
                VRBT_REMOVE(obji_head, &obji_head, oi);
245 84
        PTOK(pthread_mutex_unlock(&obji_mtx));
246
247 84
        if (oi == NULL)
248 0
                return;
249
250 84
        FREE_OBJ(oi);
251 84
}
252
253
// copy all stv pointers we are modifying
254
static void
255 252
stvcpy(struct stevedore *dst, const struct stevedore *src)
256
{
257
258 252
        CHECK_OBJ_NOTNULL(dst, STEVEDORE_MAGIC);
259 252
        CHECK_OBJ_NOTNULL(src, STEVEDORE_MAGIC);
260 252
        dst->allocobj = src->allocobj;
261 252
        dst->allocbuf = src->allocbuf;
262 252
}
263
264
// copy all om ponters we are modifying
265
static void
266 252
omcpy(struct obj_methods *dst, const struct obj_methods *src)
267
{
268
269 252
        dst->objfree = src->objfree;
270 252
        dst->objgetspace = src->objgetspace;
271 252
        dst->objextend = src->objextend;
272 252
}
273
274
// whenever we hack the stv & om pointers, we first
275
// restore the original state
276
#define REVERT(si, stv, wstv, wom)              \
277
        wstv = TRUST_ME(stv);                   \
278
        stvcpy(wstv, &si->stv_save);            \
279
        wom = TRUST_ME(stv->methods);           \
280
        omcpy(wom, &si->om_save)
281
282
#define PATCH_BEGIN(si, stv, wstv, wom)         \
283
        PTOK(pthread_mutex_lock(&stvi_mtx));    \
284
        REVERT(si, stv, wstv, wom)
285
286
#define PATCH_END()                             \
287
        PTOK(pthread_mutex_unlock(&stvi_mtx))
288
289
// when the last vcl using us goes cold, remove ourselves from all stevedores
290
// and objects. This is a bit racy, because the updated method pointers do not
291
// get visible immediately, and the methods assert that their stvi exists. Also,
292
// we had replaced obj_method with a malloc'ed copy, so we need to wait until we
293
// can assume safely that no code is using that pointer any more.
294
//
295
// Hence, do this in passes: First, restore all the stv methods, then destroy
296
// all the objis and finally destroy the stvis.
297
static void
298 21
revert(void)
299
{
300
        struct stevedore *stv;
301
        struct obji *oi;
302
        struct stvi *si;
303 21
        int check = 0;
304
305
        // pass 1: restore all pointers
306 21
        PTOK(pthread_mutex_lock(&stvi_mtx));
307 21
        VRBT_FOREACH(si, stvi_head, &stvi_head) {
308 0
                CHECK_OBJ(si, STVI_MAGIC);
309 0
                check++;
310 0
                CAST_OBJ_NOTNULL(stv, (void *)si->stv, STEVEDORE_MAGIC);
311 0
                stvcpy(stv, &si->stv_save);
312 0
                AZ(si->freeme);
313 0
                si->freeme = TRUST_ME(stv->methods);
314 0
                AN(si->om);
315 0
                stv->methods = si->om;
316 0
        }
317 21
        PTOK(pthread_mutex_unlock(&stvi_mtx));
318
319 21
        if (check > 0)
320 0
                sleep (1);
321
322
        // pass 2: free all objis: No interposer callback should run any more
323 21
        PTOK(pthread_mutex_lock(&obji_mtx));
324 21
        while ((oi = VRBT_ROOT(&obji_head)) != NULL) {
325 0
                CHECK_OBJ(oi, OBJI_MAGIC);
326 0
                VRBT_REMOVE(obji_head, &obji_head, oi);
327 0
                FREE_OBJ(oi);
328
        }
329 21
        PTOK(pthread_mutex_unlock(&obji_mtx));
330
331
        // pass 3: free
332 21
        PTOK(pthread_mutex_lock(&stvi_mtx));
333 21
        while ((si = VRBT_ROOT(&stvi_head)) != NULL) {
334 0
                CHECK_OBJ(si, STVI_MAGIC);
335 0
                check--;
336 0
                VRBT_REMOVE(stvi_head, &stvi_head, si);
337 0
                AN(si->freeme);
338 0
                free(si->freeme);
339 0
                FREE_OBJ(si);
340
        }
341 21
        PTOK(pthread_mutex_unlock(&stvi_mtx));
342 21
        assert(check == 0);
343 21
}
344
345
static unsigned ref = 0;
346
347
int v_matchproto_(vmod_event_f)
348 4158
vmod_vcl_event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
349
{
350
351 4158
        (void)ctx;
352 4158
        (void)priv;
353
354 4158
        switch (e) {
355
        case VCL_EVENT_WARM:
356 1995
                ref++;
357 1995
                return (0);
358
        case VCL_EVENT_COLD:
359 42
                AN(ref);
360 42
                if (--ref == 0)
361 21
                        revert();
362 42
                return (0);
363
        default:
364 2121
                return (0);
365
        }
366 4158
}
367
368
VCL_VOID
369 0
vmod_storage_revert(VRT_CTX, VCL_STEVEDORE stv)
370
{
371
        struct stevedore *wstv;
372
        struct obj_methods *wom;
373
        struct stvi *si;
374
375 0
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
376 0
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
377
378 0
        si = stvi_find(stv);
379 0
        if (si == NULL)
380 0
                return;
381
382 0
        PATCH_BEGIN(si, stv, wstv, wom);
383 0
        PATCH_END();
384 0
}
385
386
/* ----------------------------------------
387
 * storage_full
388
 */
389
390
static int v_matchproto_(objgetspace_f)
391 0
vtcstv_full_getspace(struct worker *wrk, struct objcore *oc, ssize_t *sz,
392
    uint8_t **ptr)
393
{
394 0
        (void)wrk;
395 0
        (void)oc;
396 0
        (void)sz;
397 0
        (void)ptr;
398
399 0
        return (0);
400
}
401
402
static int v_matchproto_(storage_allocobj_f)
403 21
vtcstv_full_allocobj(struct worker *wrk, const struct stevedore *stv,
404
    struct objcore *oc, unsigned len)
405
{
406 21
        (void)wrk;
407 21
        (void)stv;
408 21
        (void)oc;
409 21
        (void)len;
410
411 21
        return (0);
412
}
413
414
static void * v_matchproto_(storage_allocbuf_t)
415 0
vtcstv_full_allocbuf(struct worker *wrk, const struct stevedore *stv, size_t size,
416
    uintptr_t *ppriv)
417
{
418 0
        (void)wrk;
419 0
        (void)stv;
420 0
        (void)size;
421 0
        (void)ppriv;
422 0
        return (NULL);
423
}
424
425
VCL_VOID
426 21
vmod_storage_full(VRT_CTX, VCL_STEVEDORE stv)
427
{
428
        struct stevedore *wstv;
429
        struct obj_methods *wom;
430
        struct stvi *si;
431
432 21
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
433 21
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
434
435 21
        si = stvi_get(stv);
436 21
        AN(si);
437
438 21
        PATCH_BEGIN(si, stv, wstv, wom);
439 21
        wom->objgetspace = vtcstv_full_getspace;
440 21
        wstv->allocobj = vtcstv_full_allocobj;
441 21
        wstv->allocbuf = vtcstv_full_allocbuf;
442 21
        PATCH_END();
443 21
}
444
445
/* ----------------------------------------
446
 * storage_lessspace
447
 */
448
449
/* returns one byte less than requested */
450
static int v_matchproto_(objgetspace_f)
451 105
vtcstv_lessspace_getspace(struct worker *wrk, struct objcore *oc, ssize_t *sz,
452
    uint8_t **ptr)
453
{
454
        struct stvi *si;
455
        ssize_t less;
456
457 105
        CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
458 105
        si = stvi_get(oc->stobj->stevedore);
459 105
        AN(si);
460
461 105
        AN(sz);
462 105
        less = (*sz > 2) ? *sz - 1 : *sz;
463 210
        VSLb(wrk->vsl, SLT_Debug, "vtc.storage_lessspace: want %zd give %zd",
464 105
            *sz, less);
465 105
        *sz = less;
466 105
        return (si->om_save.objgetspace(wrk, oc, sz, ptr));
467
}
468
469
VCL_VOID
470 42
vmod_storage_lessspace(VRT_CTX, VCL_STEVEDORE stv)
471
{
472
        struct stevedore *wstv;
473
        struct obj_methods *wom;
474
        struct stvi *si;
475
476 42
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
477 42
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
478
479 42
        si = stvi_get(stv);
480 42
        AN(si);
481
482 42
        PATCH_BEGIN(si, stv, wstv, wom);
483 42
        wom->objgetspace = vtcstv_lessspace_getspace;
484 42
        PATCH_END();
485 42
}
486
487
/* ----------------------------------------
488
 * storage_maxspace
489
 */
490
491
static void v_matchproto_(objfree_f)
492 84
vtcstv_obji_objfree(struct worker *wrk, struct objcore *oc)
493
{
494
        struct stvi *si;
495
496 84
        CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
497 84
        si = stvi_find(oc->stobj->stevedore);
498 84
        AN(si);
499
500 84
        obji_free(oc);
501 84
        si->om_save.objfree(wrk, oc);
502 84
}
503
504
static int v_matchproto_(storage_allocobj_f)
505 84
vtcstv_maxspace_allocobj(struct worker *wrk, const struct stevedore *stv,
506
    struct objcore *oc, unsigned len)
507
{
508
        struct stvi *si;
509
        struct obji *oi;
510
511 84
        CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
512 84
        si = stvi_get(stv);
513 84
        AN(si);
514
515 84
        oi = obji_get(oc);
516 84
        AN(oi);
517 84
        oi->canalloc = si->param.maxspace;
518
519 84
        return (si->stv_save.allocobj(wrk, stv, oc, len));
520
}
521
522
static int v_matchproto_(objgetspace_f)
523 168
vtcstv_maxspace_getspace(struct worker *wrk, struct objcore *oc, ssize_t *sz,
524
    uint8_t **ptr)
525
{
526
        struct stvi *si;
527
        struct obji *oi;
528
        int r;
529
530 168
        AN(sz);
531
532 168
        si = stvi_get(oc->stobj->stevedore);
533 168
        AN(si);
534 168
        oi = obji_get(oc);
535 168
        AN(oi);
536
537 336
        VSLb(wrk->vsl, SLT_Debug, "vtc.storage_maxspace: want %zd canalloc %zd",
538 168
            *sz, oi->canalloc);
539
540 168
        assert(oi->canalloc >= 0);
541 168
        if (oi->canalloc == 0)
542 63
                return (0);
543
544 105
        *sz = vmin_t(ssize_t, *sz, oi->canalloc);
545
546 105
        r = si->om_save.objgetspace(wrk, oc, sz, ptr);
547 105
        return (r);
548 168
}
549
550
static void v_matchproto_(objextend_f)
551 105
vtcstv_maxspace_extend(struct worker *wrk, struct objcore *oc, ssize_t l)
552
{
553
        struct stvi *si;
554
        struct obji *oi;
555
556 105
        si = stvi_get(oc->stobj->stevedore);
557 105
        AN(si);
558 105
        oi = obji_get(oc);
559 105
        AN(oi);
560
561 210
        VSLb(wrk->vsl, SLT_Debug, "vtc.storage_maxspace: extend %zd canalloc %zd",
562 105
            l, oi->canalloc);
563
564 105
        if (l > oi->canalloc)
565 0
                oi->canalloc = 0;
566
        else
567 105
                oi->canalloc -= l;
568
569 105
        assert(l > 0);
570 105
        oc->stobj->priv2 += (uint64_t)l;
571 105
        si->om_save.objextend(wrk, oc, l);
572 105
}
573
574
VCL_VOID
575 84
vmod_storage_maxspace(VRT_CTX, VCL_STEVEDORE stv, VCL_BYTES b)
576
{
577
        struct stevedore *wstv;
578
        struct obj_methods *wom;
579
        struct stvi *si;
580
581 84
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
582 84
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
583
584 84
        if (b <= 0) {
585 21
                VRT_fail(ctx, "storage_maxspace: BYTES must be positive");
586 21
                return;
587
        }
588
589 63
        si = stvi_get(stv);
590 63
        AN(si);
591 63
        si->param.maxspace = b;
592
593 63
        PATCH_BEGIN(si, stv, wstv, wom);
594 63
        wom->objfree = vtcstv_obji_objfree;
595 63
        wom->objgetspace = vtcstv_maxspace_getspace;
596 63
        wom->objextend = vtcstv_maxspace_extend;
597 63
        wstv->allocobj = vtcstv_maxspace_allocobj;
598 63
        PATCH_END();
599 84
}
600
601
/* ----------------------------------------
602
 * storage_frag
603
 */
604
static int v_matchproto_(objgetspace_f)
605 756
vtcstv_frag_getspace(struct worker *wrk, struct objcore *oc, ssize_t *sz,
606
    uint8_t **ptr)
607
{
608
        struct stvi *si;
609
        int r;
610
611 756
        AN(sz);
612
613 756
        si = stvi_get(oc->stobj->stevedore);
614 756
        AN(si);
615
616 1512
        VSLb(wrk->vsl, SLT_Debug, "vtc.storage_frag: want %zd frag %zd",
617 756
            *sz, si->param.frag);
618
619 756
        *sz = si->param.frag;
620
621 756
        r = si->om_save.objgetspace(wrk, oc, sz, ptr);
622 756
        return (r);
623
}
624
625
VCL_VOID
626 147
vmod_storage_frag(VRT_CTX, VCL_STEVEDORE stv, VCL_BYTES b)
627
{
628
        struct stevedore *wstv;
629
        struct obj_methods *wom;
630
        struct stvi *si;
631
632 147
        CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
633 147
        CHECK_OBJ_NOTNULL(stv, STEVEDORE_MAGIC);
634
635 147
        if (b <= 0) {
636 21
                VRT_fail(ctx, "storage_frag: BYTES must be postive");
637 21
                return;
638
        }
639
640 126
        si = stvi_get(stv);
641 126
        AN(si);
642 126
        si->param.frag = b;
643
644 126
        PATCH_BEGIN(si, stv, wstv, wom);
645 126
        wom->objgetspace = vtcstv_frag_getspace;
646 126
        PATCH_END();
647 147
}