Viewing file: cache.h (22.39 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/*- * Copyright (c) 2006 Verdens Gang AS * Copyright (c) 2006-2019 Varnish Software AS * All rights reserved. * * Author: Poul-Henning Kamp <phk@phk.freebsd.dk> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */
#ifdef VRT_H_INCLUDED # error "vrt.h included before cache.h - they are exclusive" #endif
#ifdef CACHE_H_INCLUDED # error "cache.h included multiple times" #endif
#include <math.h> #include <pthread.h> #include <stdarg.h> #include <sys/types.h>
#include "vdef.h" #include "vrt.h"
#define CACHE_H_INCLUDED
#include "miniobj.h" #include "vas.h" #include "vqueue.h"
#include "vapi/vsl_int.h"
/*--------------------------------------------------------------------*/
enum body_status { #define BODYSTATUS(U,l) BS_##U, #include "tbl/body_status.h" };
/*--------------------------------------------------------------------*/
enum req_body_state_e { #define REQ_BODY(U) REQ_BODY_##U, #include "tbl/req_body.h" };
/*--------------------------------------------------------------------*/
enum sess_close { SC_NULL = 0, #define SESS_CLOSE(nm, stat, err, desc) SC_##nm, #include "tbl/sess_close.h" };
/*-------------------------------------------------------------------- * Indices into http->hd[] */ enum { #define SLTH(tag, ind, req, resp, sdesc, ldesc) ind, #include "tbl/vsl_tags_http.h" };
/*--------------------------------------------------------------------*/
struct VSC_lck; struct VSC_main; struct VSC_main_wrk; struct backend; struct ban; struct ban_proto; struct cli; struct http_conn; struct mempool; struct objcore; struct objhead; struct pool; struct sess; struct transport; struct worker; struct listen_sock;
#define DIGEST_LEN 32
/*--------------------------------------------------------------------*/
typedef struct { const char *b; const char *e; } txt;
/*--------------------------------------------------------------------*/
enum req_step { R_STP_NONE = 0, #define REQ_STEP(l, u, arg) R_STP_##u, #include "tbl/steps.h" };
enum fetch_step { F_STP_NONE = 0, #define FETCH_STEP(l, U, arg) F_STP_##U, #include "tbl/steps.h" };
/*--------------------------------------------------------------------*/
struct lock { void *priv; }; // Opaque
/*-------------------------------------------------------------------- * Workspace structure for quick memory allocation. */
struct ws { unsigned magic; #define WS_MAGIC 0x35fac554 char id[4]; /* identity */ char *s; /* (S)tart of buffer */ char *f; /* (F)ree/front pointer */ char *r; /* (R)eserved length */ char *e; /* (E)nd of buffer */ };
/*-------------------------------------------------------------------- * */
struct http { unsigned magic; #define HTTP_MAGIC 0x6428b5c9
uint16_t shd; /* Size of hd space */ txt *hd; unsigned char *hdf; #define HDF_FILTER (1 << 0) /* Filtered by Connection */
/* NB: ->nhd and below zeroed/initialized by http_Teardown */ uint16_t nhd; /* Next free hd */
enum VSL_tag_e logtag; /* Must be SLT_*Method */ struct vsl_log *vsl;
struct ws *ws; uint16_t status; uint8_t protover; uint8_t conds; /* If-* headers present */ };
/*--------------------------------------------------------------------*/
struct acct_req { #define ACCT(foo) uint64_t foo; #include "tbl/acct_fields_req.h" };
/*--------------------------------------------------------------------*/
struct acct_bereq { #define ACCT(foo) uint64_t foo; #include "tbl/acct_fields_bereq.h" };
/*--------------------------------------------------------------------*/
struct vsl_log { uint32_t *wlb, *wlp, *wle; unsigned wlr; unsigned wid; };
/*--------------------------------------------------------------------*/
struct vxid_pool { uint32_t next; uint32_t count; };
/*--------------------------------------------------------------------*/
struct vrt_privs { unsigned magic; #define VRT_PRIVS_MAGIC 0x03ba7501 VTAILQ_HEAD(,vrt_priv) privs; };
/* Worker pool stuff -------------------------------------------------*/
typedef void task_func_t(struct worker *wrk, void *priv);
struct pool_task { VTAILQ_ENTRY(pool_task) list; task_func_t *func; void *priv; };
/* * tasks are taken off the queues in this order * * TASK_QUEUE_{REQ|STR} are new req's (H1/H2), and subject to queue limit. * * TASK_QUEUE_RUSH is req's returning from waiting list * * NOTE: When changing the number of classes, update places marked with * TASK_QUEUE__END in mgt_pool.c */ enum task_prio { TASK_QUEUE_BO, TASK_QUEUE_RUSH, TASK_QUEUE_REQ, TASK_QUEUE_STR, TASK_QUEUE_VCA, TASK_QUEUE__END };
#define TASK_QUEUE_HIGHEST_PRIORITY TASK_QUEUE_BO #define TASK_QUEUE_CLIENT(prio) \ (prio == TASK_QUEUE_REQ || prio == TASK_QUEUE_STR)
/*--------------------------------------------------------------------*/
struct worker { unsigned magic; #define WORKER_MAGIC 0x6391adcf struct pool *pool; struct objhead *nobjhead; struct objcore *nobjcore; void *nhashpriv; struct VSC_main_wrk *stats; struct vsl_log *vsl; // borrowed from req/bo
struct pool_task task;
vtim_real lastused; int strangelove;
struct v1l *v1l;
pthread_cond_t cond;
struct vcl *vcl;
struct ws aws[1];
struct vxid_pool vxid_pool;
unsigned cur_method; unsigned seen_methods; unsigned handling; };
/* Stored object ----------------------------------------------------- * This is just to encapsulate the fields owned by the stevedore */
struct storeobj { const struct stevedore *stevedore; void *priv; uintptr_t priv2; };
/* Busy Objcore structure -------------------------------------------- * */
/* * The macro-states we expose outside the fetch code */ enum boc_state_e { #define BOC_STATE(U, l) BOS_##U, #include "tbl/boc_state.h" };
struct boc { unsigned magic; #define BOC_MAGIC 0x70c98476 unsigned refcount; struct lock mtx; pthread_cond_t cond; void *stevedore_priv; enum boc_state_e state; uint8_t *vary; uint64_t len_so_far; };
/* Object core structure --------------------------------------------- * Objects have sideways references in the binary heap and the LRU list * and we want to avoid paging in a lot of objects just to move them up * or down the binheap or to move a unrelated object on the LRU list. * To avoid this we use a proxy object, objcore, to hold the relevant * housekeeping fields parts of an object. */
enum obj_attr { #define OBJ_FIXATTR(U, l, s) OA_##U, #define OBJ_VARATTR(U, l) OA_##U, #define OBJ_AUXATTR(U, l) OA_##U, #include "tbl/obj_attr.h" OA__MAX, };
enum obj_flags { #define OBJ_FLAG(U, l, v) OF_##U = v, #include "tbl/obj_attr.h" };
enum oc_flags { #define OC_FLAG(U, l, v) OC_F_##U = v, #include "tbl/oc_flags.h" };
enum oc_exp_flags { #define OC_EXP_FLAG(U, l, v) OC_EF_##U = v, #include "tbl/oc_exp_flags.h" };
struct objcore { unsigned magic; #define OBJCORE_MAGIC 0x4d301302 int refcnt; struct storeobj stobj[1]; struct objhead *objhead; struct boc *boc; double timer_when; long hits;
double t_origin; float ttl; float grace; float keep;
uint8_t flags;
uint8_t exp_flags;
uint16_t oa_present;
unsigned timer_idx; // XXX 4Gobj limit double last_lru; VTAILQ_ENTRY(objcore) hsh_list; VTAILQ_ENTRY(objcore) lru_list; VTAILQ_ENTRY(objcore) ban_list; VSTAILQ_ENTRY(objcore) exp_list; struct ban *ban; };
/* Busy Object structure --------------------------------------------- * * The busyobj structure captures the aspects of an object related to, * and while it is being fetched from the backend. * * One of these aspects will be how much has been fetched, which * streaming delivery will make use of. */
enum director_state_e { DIR_S_NULL = 0, DIR_S_HDRS = 1, DIR_S_BODY = 2, };
struct busyobj { unsigned magic; #define BUSYOBJ_MAGIC 0x23b95567
char *end;
/* * All fields from retries and down are zeroed when the busyobj * is recycled. */ int retries; struct req *req; struct sess *sp; struct worker *wrk;
struct vfp_ctx *vfc;
struct ws ws[1]; uintptr_t ws_bo; struct http *bereq0; struct http *bereq; struct http *beresp; struct objcore *stale_oc; struct objcore *fetch_objcore;
struct http_conn *htc;
struct pool_task fetch_task;
#define BO_FLAG(l, r, w, d) unsigned l:1; #include "tbl/bo_flags.h"
/* Timeouts */ vtim_dur connect_timeout; vtim_dur first_byte_timeout; vtim_dur between_bytes_timeout;
/* Timers */ double t_first; /* First timestamp logged */ double t_prev; /* Previous timestamp logged */
/* Acct */ struct acct_bereq acct;
const struct stevedore *storage; const struct director *director_req; const struct director *director_resp; enum director_state_e director_state; struct vcl *vcl;
struct vsl_log vsl[1];
uint8_t digest[DIGEST_LEN]; struct vrt_privs privs[1];
uint16_t err_code; const char *err_reason;
enum req_body_state_e initial_req_body_status; struct objcore *bereq_body; };
/*--------------------------------------------------------------------*/
struct req { unsigned magic; #define REQ_MAGIC 0x2751aaa1
enum req_step req_step; volatile enum req_body_state_e req_body_status; enum sess_close doclose; int restarts; int esi_level; struct req *top; /* esi_level == 0 request */
#define REQ_FLAG(l, r, w, d) unsigned l:1; #include "tbl/req_flags.h"
uint16_t err_code; const char *err_reason;
struct sess *sp; struct worker *wrk; struct pool_task task;
const struct transport *transport; void *transport_priv;
VTAILQ_ENTRY(req) w_list;
struct objcore *body_oc;
/* The busy objhead we sleep on */ struct objhead *hash_objhead;
/* Built Vary string */ uint8_t *vary_b; uint8_t *vary_l; uint8_t *vary_e;
uint8_t digest[DIGEST_LEN];
double d_ttl; double d_grace;
ssize_t req_bodybytes; /* Parsed req bodybytes */ const struct stevedore *storage;
const struct director *director_hint; struct vcl *vcl;
uintptr_t ws_req; /* WS above request data */
/* Timestamps */ double t_first; /* First timestamp logged */ double t_prev; /* Previous timestamp logged */ double t_req; /* Headers complete */
struct http_conn *htc; struct vfp_ctx *vfc; const char *client_identity;
/* HTTP request */ struct http *http; struct http *http0;
/* HTTP response */ struct http *resp; intmax_t resp_len;
struct ws ws[1]; struct objcore *objcore; struct objcore *stale_oc;
/* Deliver pipeline */ struct vdp_ctx *vdc;
/* Delivery mode */ unsigned res_mode; #define RES_LEN (1<<1) #define RES_EOF (1<<2) #define RES_CHUNKED (1<<3) #define RES_ESI (1<<4) #define RES_ESI_CHILD (1<<5) #define RES_GUNZIP (1<<6) #define RES_PIPE (1<<7)
/* Transaction VSL buffer */ struct vsl_log vsl[1];
/* Temporary accounting */ struct acct_req acct;
struct vrt_privs privs[1]; };
/*-------------------------------------------------------------------- * Struct sess is a high memory-load structure because sessions typically * hang around the waiter for relatively long time. * * The size goal for struct sess + struct memitem is <512 bytes * * Getting down to the next relevant size (<256 bytes because of how malloc * works, is not realistic without a lot of code changes. */
enum sess_attr { #define SESS_ATTR(UP, low, typ, len) SA_##UP, SA_TRANSPORT, #include "tbl/sess_attr.h" SA_LAST };
struct sess { unsigned magic; #define SESS_MAGIC 0x2c2f9c5a
uint16_t sattr[SA_LAST]; struct listen_sock *listen_sock; int refcnt; int fd; uint32_t vxid;
struct lock mtx;
struct pool *pool;
struct ws ws[1];
vtim_real t_open; /* fd accepted */ vtim_real t_idle; /* fd accepted or resp sent */
vtim_dur timeout_idle; };
#define SESS_TMO(sp, tmo) \ (isnan((sp)->tmo) ? cache_param->tmo : (sp)->tmo)
/* Prototypes etc ----------------------------------------------------*/
/* cache_ban.c */
/* for constructing bans */ struct ban_proto *BAN_Build(void); const char *BAN_AddTest(struct ban_proto *, const char *, const char *, const char *); const char *BAN_Commit(struct ban_proto *b); void BAN_Abandon(struct ban_proto *b);
/* cache_cli.c [CLI] */ extern pthread_t cli_thread; #define ASSERT_CLI() do {assert(pthread_self() == cli_thread);} while (0)
/* cache_http.c */ unsigned HTTP_estimate(unsigned nhttp); void HTTP_Copy(struct http *to, const struct http * const fm); struct http *HTTP_create(void *p, uint16_t nhttp, unsigned); const char *http_Status2Reason(unsigned, const char **); unsigned http_EstimateWS(const struct http *fm, unsigned how); void http_PutResponse(struct http *to, const char *proto, uint16_t status, const char *response); void http_FilterReq(struct http *to, const struct http *fm, unsigned how); void HTTP_Encode(const struct http *fm, uint8_t *, unsigned len, unsigned how); int HTTP_Decode(struct http *to, const uint8_t *fm); void http_ForceHeader(struct http *to, const char *hdr, const char *val); void http_PrintfHeader(struct http *to, const char *fmt, ...) v_printflike_(2, 3); void http_TimeHeader(struct http *to, const char *fmt, double now); void http_Proto(struct http *to); void http_SetHeader(struct http *to, const char *hdr); void http_SetH(struct http *to, unsigned n, const char *fm); void http_ForceField(struct http *to, unsigned n, const char *t); void HTTP_Setup(struct http *, struct ws *, struct vsl_log *, enum VSL_tag_e); void http_Teardown(struct http *ht); int http_GetHdr(const struct http *hp, const char *hdr, const char **ptr); int http_GetHdrToken(const struct http *hp, const char *hdr, const char *token, const char **pb, const char **pe); int http_GetHdrField(const struct http *hp, const char *hdr, const char *field, const char **ptr); double http_GetHdrQ(const struct http *hp, const char *hdr, const char *field); ssize_t http_GetContentLength(const struct http *hp); uint16_t http_GetStatus(const struct http *hp); int http_IsStatus(const struct http *hp, int); void http_SetStatus(struct http *to, uint16_t status); const char *http_GetMethod(const struct http *hp); int http_HdrIs(const struct http *hp, const char *hdr, const char *val); void http_CopyHome(const struct http *hp); void http_Unset(struct http *hp, const char *hdr); unsigned http_CountHdr(const struct http *hp, const char *hdr); void http_CollectHdr(struct http *hp, const char *hdr); void http_CollectHdrSep(struct http *hp, const char *hdr, const char *sep); void http_VSL_log(const struct http *hp); void HTTP_Merge(struct worker *, struct objcore *, struct http *to); uint16_t HTTP_GetStatusPack(struct worker *, struct objcore *oc); int HTTP_IterHdrPack(struct worker *, struct objcore *, const char **); #define HTTP_FOREACH_PACK(wrk, oc, ptr) \ for ((ptr) = NULL; HTTP_IterHdrPack(wrk, oc, &(ptr));) const char *HTTP_GetHdrPack(struct worker *, struct objcore *, const char *hdr); enum sess_close http_DoConnection(struct http *hp); int http_IsFiltered(const struct http *hp, unsigned u, unsigned how);
#define HTTPH_R_PASS (1 << 0) /* Request (c->b) in pass mode */ #define HTTPH_R_FETCH (1 << 1) /* Request (c->b) for fetch */ #define HTTPH_A_INS (1 << 2) /* Response (b->o) for insert */ #define HTTPH_A_PASS (1 << 3) /* Response (b->o) for pass */ #define HTTPH_C_SPECIFIC (1 << 4) /* Connection-specific */
#define HTTPH(a, b, c) extern char b[]; #include "tbl/http_headers.h"
extern const char H__Status[]; extern const char H__Proto[]; extern const char H__Reason[];
/* cache_main.c */ #define VXID(u) ((u) & VSL_IDENTMASK) uint32_t VXID_Get(struct worker *, uint32_t marker); extern pthread_key_t witness_key;
/* cache_lck.c */
/* Internal functions, call only through macros below */ void Lck__Lock(struct lock *lck, const char *p, int l); void Lck__Unlock(struct lock *lck, const char *p, int l); int Lck__Trylock(struct lock *lck, const char *p, int l); void Lck__New(struct lock *lck, struct VSC_lck *, const char *); int Lck__Held(const struct lock *lck); int Lck__Owned(const struct lock *lck);
/* public interface: */ void Lck_Delete(struct lock *lck); int Lck_CondWait(pthread_cond_t *cond, struct lock *lck, vtim_real);
#define Lck_New(a, b) Lck__New(a, b, #b) #define Lck_Lock(a) Lck__Lock(a, __func__, __LINE__) #define Lck_Unlock(a) Lck__Unlock(a, __func__, __LINE__) #define Lck_Trylock(a) Lck__Trylock(a, __func__, __LINE__) #define Lck_AssertHeld(a) \ do { \ assert(Lck__Held(a)); \ assert(Lck__Owned(a)); \ } while (0)
struct VSC_lck *Lck_CreateClass(struct vsc_seg **, const char *); void Lck_DestroyClass(struct vsc_seg **);
#define LOCK(nam) extern struct VSC_lck *lck_##nam; #include "tbl/locks.h"
/* cache_obj.c */
int ObjHasAttr(struct worker *, struct objcore *, enum obj_attr); const void *ObjGetAttr(struct worker *, struct objcore *, enum obj_attr, ssize_t *len);
typedef int objiterate_f(void *priv, int flush, const void *ptr, ssize_t len);
int ObjIterate(struct worker *, struct objcore *, void *priv, objiterate_f *func, int final);
unsigned ObjGetXID(struct worker *, struct objcore *); uint64_t ObjGetLen(struct worker *, struct objcore *); int ObjGetDouble(struct worker *, struct objcore *, enum obj_attr, double *); int ObjGetU32(struct worker *, struct objcore *, enum obj_attr, uint32_t *); int ObjGetU64(struct worker *, struct objcore *, enum obj_attr, uint64_t *); int ObjCheckFlag(struct worker *, struct objcore *, enum obj_flags of); /* cache_session.c [SES] */
#define SESS_ATTR(UP, low, typ, len) \ int SES_Get_##low(const struct sess *sp, typ **dst); #include "tbl/sess_attr.h" const char *SES_Get_String_Attr(const struct sess *sp, enum sess_attr a);
/* cache_shmlog.c */ void VSLv(enum VSL_tag_e tag, uint32_t vxid, const char *fmt, va_list va); void VSL(enum VSL_tag_e tag, uint32_t vxid, const char *fmt, ...) v_printflike_(3, 4); void VSLbv(struct vsl_log *, enum VSL_tag_e tag, const char *fmt, va_list va); void VSLb(struct vsl_log *, enum VSL_tag_e tag, const char *fmt, ...) v_printflike_(3, 4); void VSLbt(struct vsl_log *, enum VSL_tag_e tag, txt t); void VSLb_ts(struct vsl_log *, const char *event, double first, double *pprev, double now); void VSLb_bin(struct vsl_log *, enum VSL_tag_e, ssize_t, const void*);
static inline void VSLb_ts_req(struct req *req, const char *event, double now) {
if (isnan(req->t_first) || req->t_first == 0.) req->t_first = req->t_prev = now; VSLb_ts(req->vsl, event, req->t_first, &req->t_prev, now); }
static inline void VSLb_ts_busyobj(struct busyobj *bo, const char *event, double now) {
if (isnan(bo->t_first) || bo->t_first == 0.) bo->t_first = bo->t_prev = now; VSLb_ts(bo->vsl, event, bo->t_first, &bo->t_prev, now); }
/* cache_vcl.c */ const char *VCL_Name(const struct vcl *);
/* cache_vrt.c */ /* * These prototypes go here, because we do not want to pollute vrt.h * with va_list. VCC never generates direct calls to them. * XXX: We should deprecate these (ref: STRANDS) */ const char *VRT_String(struct ws *ws, const char *h, const char *p, va_list ap); char *VRT_StringList(char *d, unsigned dl, const char *p, va_list ap);
/* cache_wrk.c */
typedef void *bgthread_t(struct worker *, void *priv); void WRK_BgThread(pthread_t *thr, const char *name, bgthread_t *func, void *priv);
/* cache_ws.c */
void WS_Init(struct ws *ws, const char *id, void *space, unsigned len);
/* WS_Reserve(): Use WS_ReserveSize() or WS_ReserveAll() */ unsigned WS_Reserve(struct ws *ws, unsigned bytes); unsigned WS_ReserveSize(struct ws *, unsigned); unsigned WS_ReserveAll(struct ws *); unsigned WS_ReserveLumps(struct ws *ws, size_t sz); void WS_MarkOverflow(struct ws *ws); void WS_Release(struct ws *ws, unsigned bytes); void WS_ReleaseP(struct ws *ws, const char *ptr); void WS_Assert(const struct ws *ws); void WS_Reset(struct ws *ws, uintptr_t); void *WS_Alloc(struct ws *ws, unsigned bytes); void *WS_Copy(struct ws *ws, const void *str, int len); uintptr_t WS_Snapshot(struct ws *ws); int WS_Overflowed(const struct ws *ws); void *WS_Printf(struct ws *ws, const char *fmt, ...) v_printflike_(2, 3); int WS_Inside(const struct ws *, const void *, const void *); void WS_Assert_Allocated(const struct ws *ws, const void *ptr, ssize_t len);
static inline char* WS_Front(const struct ws *ws) { return ws->f; }
/* cache_rfc2616.c */ void RFC2616_Ttl(struct busyobj *, double now, double *t_origin, float *ttl, float *grace, float *keep); unsigned RFC2616_Req_Gzip(const struct http *); int RFC2616_Do_Cond(const struct req *sp); void RFC2616_Weaken_Etag(struct http *hp); void RFC2616_Vary_AE(struct http *hp); void RFC2616_Response_Body(const struct worker *, const struct busyobj *);
/* * A normal pointer difference is signed, but we never want a negative value * so this little tool will make sure we don't get that. */
static inline unsigned pdiff(const void *b, const void *e) {
assert(b <= e); return ((unsigned)((const unsigned char *)e - (const unsigned char *)b)); }
#define Tcheck(t) do { \ AN((t).b); \ AN((t).e); \ assert((t).b <= (t).e); \ } while(0)
/* * unsigned length of a txt */
static inline unsigned Tlen(const txt t) {
Tcheck(t); return ((unsigned)(t.e - t.b)); }
/* * We want to cache the most recent timestamp in wrk->lastused to avoid * extra timestamps in cache_pool.c. Hide this detail with a macro */ #define W_TIM_real(w) ((w)->lastused = VTIM_real())
#define PAN_CheckMagic(vsb, ptr, exp) \ do { \ if ((ptr)->magic != (exp)) \ VSB_printf((vsb), \ "MAGIC at %p is 0x%08x (Should be: %s/0x%08x)\n", \ ptr, (ptr)->magic, #exp, exp); \ } while(0)
|