XRootD
Loading...
Searching...
No Matches
XrdRmcReal.cc
Go to the documentation of this file.
1/******************************************************************************/
2/* */
3/* X r d R m c R e a l . c c */
4/* */
5/* (c) 2019 by the Board of Trustees of the Leland Stanford, Jr., University */
6/* All Rights Reserved */
7/* Produced by Andrew Hanushevsky for Stanford University under contract */
8/* DE-AC02-76-SFO0515 with the Department of Energy */
9/* */
10/* This file is part of the XRootD software suite. */
11/* */
12/* XRootD is free software: you can redistribute it and/or modify it under */
13/* the terms of the GNU Lesser General Public License as published by the */
14/* Free Software Foundation, either version 3 of the License, or (at your */
15/* option) any later version. */
16/* */
17/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
18/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
19/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
20/* License for more details. */
21/* */
22/* You should have received a copy of the GNU Lesser General Public License */
23/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
24/* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
25/* */
26/* The copyright holder's institutional names and contributor's names may not */
27/* be used to endorse or promote products derived from this software without */
28/* specific prior written permission of the institution or contributor. */
29/******************************************************************************/
30
31#include <cerrno>
32#include <cstdio>
33#include <cstdlib>
34#include <cstring>
35#include <unistd.h>
36#include <sys/mman.h>
37#include <sys/types.h>
38
39#include "XrdRmc/XrdRmcData.hh"
41
42#ifdef __APPLE__
43#ifndef MAP_ANONYMOUS
44#define MAP_ANONYMOUS MAP_ANON
45#endif
46#endif
47
48/******************************************************************************/
49/* C o n s t r u c t o r */
50/******************************************************************************/
51
52void *XrdRmcRealPRXeq(void *parg)
53{ XrdRmcReal *cP = (XrdRmcReal *)parg;
54 cP->PreRead();
55 return (void *)0;
56}
57
60 : XrdOucCache("rmc"),
61 Slots(0), Slash(0), Base((char *)MAP_FAILED), Dbg(0), Lgs(0),
62 AZero(0), Attached(0), prFirst(0), prLast(0),
63 prReady(0), prStop(0), prNum(0)
64{
65 size_t Bytes;
66 int n, minPag, isServ = ParmV.Options & XrdRmc::isServer;
67
68// Copy over options
69//
70 rc = ENOMEM;
71 Options = ParmV.Options;
72 if (Options & XrdRmc::Debug) Lgs = Dbg = (Options & XrdRmc::Debug);
73 if (Options & XrdRmc::logStats) Lgs = 1;
74 minPag = (ParmV.minPages <= 0 ? 256 : ParmV.minPages);
75
76// Establish maximum number of attached files
77//
78 if (ParmV.MaxFiles <= 0) maxFiles = (isServ ? 16384 : 256);
79 else {maxFiles = (ParmV.MaxFiles > 32764 ? 32764 : ParmV.MaxFiles);
80 maxFiles = maxFiles/sizeof(int)*sizeof(int);
81 if (!maxFiles) maxFiles = 256;
82 }
83
84// Adjust segment size to be a power of two and atleast 4k.
85//
86 if (ParmV.PageSize <= 0) SegSize = 32768;
87 else if (!(SegSize = ParmV.PageSize & ~0xfff)) SegSize = 4096;
88// else if (SegSize > 16*1024*1024) SegSize = 16*1024*1024;
89 SegShft = 0; n = SegSize-1;
90 while(n) {SegShft++; n = n >> 1;}
91 SegSize = 1<<SegShft;
92
93// The max to cache is also adjusted accrodingly based on segment size.
94//
95 OffMask = SegSize-1;
96 SegCnt = (ParmV.CacheSize > 0 ? ParmV.CacheSize : 104857600)/SegSize;
97 if (SegCnt < minPag) SegCnt = minPag;
98 if (ParmV.Max2Cache < SegSize) maxCache = SegSize;
99 else maxCache = ParmV.Max2Cache/SegSize*SegSize;
100 SegFull = (Options & XrdRmc::isServer ? XrdRmcSlot::lenMask : SegSize);
101
102// Allocate the cache plus the cache hash table
103//
104 Bytes = static_cast<size_t>(SegSize)*SegCnt;
105 Base = (char *)mmap(0, Bytes + SegCnt*sizeof(int), PROT_READ|PROT_WRITE,
106 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
107 if (Base == MAP_FAILED) {rc = errno; return;}
108 Slash = (int *)(Base + Bytes); HNum = SegCnt/2*2-1;
109
110// Now allocate the actual slots. We add additional slots to map files. These
111// do not have any memory backing but serve as anchors for memory mappings.
112//
113 if (!(Slots = new XrdRmcSlot[SegCnt+maxFiles])) return;
114 XrdRmcSlot::Init(Slots, SegCnt);
115
116// Set pointers to be able to keep track of CacheIO objects and map them to
117// CacheData objects. The hash table will be the first page of slot memory.
118//
119 hTab = (int *)Base;
120 hMax = SegSize/sizeof(int);
121 sBeg = sFree = SegCnt;
122 sEnd = SegCnt + maxFiles;
123
124// Now iniialize the slots to be used for the CacheIO objects
125//
126 for (n = sBeg; n < sEnd; n++)
127 {Slots[n].Own.Next = Slots[n].Own.Prev = n;
128 Slots[n].HLink = n+1;
129 }
130 Slots[sEnd-1].HLink = 0;
131
132// Setup the pre-readers if pre-read is enabled
133//
134 if (Options & XrdRmc::canPreRead)
135 {pthread_t tid;
136 n = (Options & XrdRmc::isServer ? 9 : 3);
137 while(n--)
138 {if (XrdSysThread::Run(&tid, XrdRmcRealPRXeq, (void *)this,
139 0, "Prereader")) break;
140 prNum++;
141 }
142 if (aprP && prNum) XrdRmcData::setAPR(aprDefault, *aprP, SegSize);
143 }
144
145// All done
146//
147 rc = 0;
148}
149
150/******************************************************************************/
151/* D e s t r u c t o r */
152/******************************************************************************/
153
155{
156// Wait for all attachers to go away
157//
158 CMutex.Lock();
159 if (Attached)
160 {XrdSysSemaphore aZero(0);
161 AZero = &aZero;
162 CMutex.UnLock();
163 aZero.Wait();
164 CMutex.Lock();
165 }
166
167// If any preread threads exist, then stop them now
168//
169 prMutex.Lock();
170 if (prNum)
171 {XrdSysSemaphore prDone(0);
172 prStop = &prDone;
173 prReady.Post();
174 prMutex.UnLock();
175 prDone.Wait();
176 prMutex.Lock();
177 }
178
179// Delete the slots
180//
181 delete Slots; Slots = 0;
182
183// Unmap cache memory and associated hash table
184//
185 if (Base != MAP_FAILED)
186 {munmap(Base, static_cast<size_t>(SegSize)*SegCnt);
187 Base = (char *)(MAP_FAILED);
188 }
189
190// Release all locks, we are done
191//
192 prMutex.UnLock();
193 CMutex.UnLock();
194}
195
196/******************************************************************************/
197/* A t t a c h */
198/******************************************************************************/
199
201{
202 static int Inst = 0;
203 XrdSysMutexHelper Monitor(CMutex);
204 XrdRmcData *dP;
205 int Cnt, Fnum = 0, theOpts = Opts & optRW;
206
207// Check if we are being deleted
208//
209 if (AZero) {errno = ECANCELED; return ioP;}
210
211// Setup structured/unstructured option
212//
213 if ((Opts & optFIS) || (Options & XrdRmc::isStructured)) theOpts |= optFIS;
214
215// Get an entry in the filename table.
216//
217 if (!(Cnt = ioAdd(ioP, Fnum)))
218 {errno = EMFILE;
219 return ioP;
220 }
221
222// If this is the first addition then we need to get a new CacheData object.
223// Otherwise, simply reuse the previous cache data object.
224//
225 if (Cnt != 1) dP = Slots[Fnum].Status.Data;
226 else {long long vNum = static_cast<long long>(Fnum-SegCnt) << Shift
227 | (static_cast<long long>(Inst) << (Shift - 16));
228 Inst = (Inst+1) & 0xffff;
229 if ((dP = new XrdRmcData(this, ioP, vNum, theOpts)))
230 {Attached++; Slots[Fnum].Status.Data = dP;}
231 }
232
233// Some debugging
234//
235 if (Dbg) std::cerr <<"Cache: Attached " <<Cnt <<'/' <<Attached <<' '
236 <<std::hex << Fnum <<std::dec <<' ' <<ioP->Path() <<std::endl;
237
238// All done
239//
240 if (!dP) {errno = ENOMEM; return ioP;}
241 return (XrdOucCacheIO *)dP;
242}
243
244/******************************************************************************/
245/* D e t a c h */
246/******************************************************************************/
247
248int XrdRmcReal::Detach(XrdOucCacheIO *ioP)
249{
250 XrdSysMutexHelper Monitor(CMutex);
251 XrdRmcSlot *sP, *oP;
252 int sNum, Fnum, Free = 0, Faults = 0;
253
254// Now we delete this CacheIO from the cache set and see if its still ref'd.
255//
256 sNum = ioDel(ioP, Fnum);
257 if (!sNum || sNum > 1) return 0;
258
259// We will be deleting the CramData object. So, we need to recycle its slots.
260//
261 oP = &Slots[Fnum];
262 while(oP->Own.Next != Fnum)
263 {sP = &Slots[oP->Own.Next];
264 sP->Owner(Slots);
265 if (sP->Contents < 0 || sP->Status.LRU.Next < 0) Faults++;
266 else {sP->Hide(Slots, Slash, sP->Contents%HNum);
267 sP->Pull(Slots);
268 sP->unRef(Slots);
269 Free++;
270 }
271 }
272
273// Reduce attach count and check if the cache is being deleted
274//
275 Attached--;
276 if (AZero && Attached <= 0) AZero->Post();
277
278// Issue debugging message
279//
280 if (Dbg) std::cerr <<"Cache: " <<Attached <<" att; rel " <<Free <<" slots; "
281 <<Faults <<" Faults; " <<std::hex << Fnum <<std::dec <<' '
282 <<ioP->Path() <<std::endl;
283
284// All done, tell the caller to delete itself
285//
286 return 1;
287}
288
289/******************************************************************************/
290/* e M s g */
291/******************************************************************************/
292
293void XrdRmcReal::eMsg(const char *Path, const char *What, long long xOff,
294 int xAmt, int eCode)
295{
296 char Buff[128];
297
298 if (Dbg)
299 {sprintf(Buff, "Cache: Error %d %s %d bytes at %lld; path=",
300 eCode, What, xAmt, xOff);
301 std::cerr <<Buff <<Path <<std::endl;
302 }
303}
304
305/******************************************************************************/
306/* G e t */
307/******************************************************************************/
308
309char *XrdRmcReal::Get(XrdOucCacheIO *ioP, long long lAddr, int &rAmt, int &noIO)
310{
311 XrdSysMutexHelper Monitor(CMutex);
312 XrdRmcSlot::ioQ *Waiter;
313 XrdRmcSlot *sP;
314 int nUse, Fnum, Slot, segHash = lAddr%HNum;
315 char *cBuff;
316
317// See if we have this logical address in the cache. Check if the page is in
318// transit and, if so, wait for it to arrive before proceeding.
319//
320 noIO = 1;
321 if (Slash[segHash]
322 && (Slot = XrdRmcSlot::Find(Slots, lAddr, Slash[segHash])))
323 {sP = &Slots[Slot];
324 if (sP->Count & XrdRmcSlot::inTrans)
325 {XrdSysSemaphore ioSem(0);
326 XrdRmcSlot::ioQ ioTrans(sP->Status.waitQ, &ioSem);
327 sP->Status.waitQ = &ioTrans;
328 if (Dbg > 1) std::cerr <<"Cache: Wait slot " <<Slot <<std::endl;
329 CMutex.UnLock(); ioSem.Wait(); CMutex.Lock();
330 if (sP->Contents != lAddr) {rAmt = -EIO; return 0;}
331 } else {
332 if (sP->Status.inUse < 0) sP->Status.inUse--;
333 else {sP->Pull(Slots); sP->Status.inUse = -1;}
334 }
335 rAmt = (sP->Count < 0 ? sP->Count & XrdRmcSlot::lenMask : SegSize);
336 if (sP->Count & XrdRmcSlot::isNew)
337 {noIO = -1; sP->Count &= ~XrdRmcSlot::isNew;}
338 if (Dbg > 2) std::cerr <<"Cache: Hit slot " <<Slot <<" sz " <<rAmt <<" nio "
339 <<noIO <<" uc " <<sP->Status.inUse <<std::endl;
340 return Base+(static_cast<long long>(Slot)*SegSize);
341 }
342
343// Page is not here. If no allocation wanted or we cannot obtain a free slot
344// return and indicate there is no associated cache page.
345//
346 if (!ioP || !(Slot = Slots[Slots->Status.LRU.Next].Pull(Slots)))
347 {rAmt = -ENOMEM; return 0;}
348
349// Remove ownership over this slot and remove it from the hash table
350//
351 sP = &Slots[Slot];
352 if (sP->Contents >= 0)
353 {if (sP->Own.Next != Slot) sP->Owner(Slots);
354 sP->Hide(Slots, Slash, sP->Contents%HNum);
355 }
356
357// Read the data into the buffer
358//
360 sP->Status.waitQ = 0;
361 CMutex.UnLock();
362 cBuff = Base+(static_cast<long long>(Slot)*SegSize);
363 rAmt = ioP->Read(cBuff, (lAddr & Strip) << SegShft, SegSize);
364 CMutex.Lock();
365
366// Post anybody waiting for this slot. We hold the cache lock which will give us
367// time to complete the slot definition before the waiting thread looks at it.
368//
369 nUse = -1;
370 while((Waiter = sP->Status.waitQ))
371 {sP->Status.waitQ = sP->Status.waitQ->Next;
372 sP->Status.waitQ->ioEnd->Post();
373 nUse--;
374 }
375
376// If I/O succeeded, reinitialize the slot. Otherwise, return free it up
377//
378 noIO = 0;
379 if (rAmt >= 0)
380 {sP->Contents = lAddr;
381 sP->HLink = Slash[segHash];
382 Slash[segHash] = Slot;
383 Fnum = (lAddr >> Shift) + SegCnt;
384 Slots[Fnum].Owner(Slots, sP);
385 sP->Count = (rAmt == SegSize ? SegFull : rAmt|XrdRmcSlot::isShort);
386 sP->Status.inUse = nUse;
387 if (Dbg > 2) std::cerr <<"Cache: Miss slot " <<Slot <<" sz "
388 <<(sP->Count & XrdRmcSlot::lenMask) <<std::endl;
389 } else {
390 eMsg(ioP->Path(), "reading", (lAddr & Strip) << SegShft, SegSize, rAmt);
391 cBuff = 0;
392 sP->Contents = -1;
393 sP->unRef(Slots);
394 }
395
396// Return the associated buffer or zero, as per above
397//
398 return cBuff;
399}
400
401/******************************************************************************/
402/* i o A d d */
403/******************************************************************************/
404
405int XrdRmcReal::ioAdd(XrdOucCacheIO *KeyVal, int &iNum)
406{
407 int hip, phip, hent = ioEnt(KeyVal);
408
409// Look up the entry. If found, return it.
410//
411 if ((hip = hTab[hent]) && (hip = ioLookup(phip, hip, KeyVal)))
412 {iNum = hip; return ++Slots[hip].Count;}
413
414// Add the entry
415//
416 if ((hip = sFree))
417 {sFree = Slots[sFree].HLink;
418 Slots[hip].File(KeyVal, hTab[hent]);
419 hTab[hent] = hip;
420 }
421
422// Return information to the caller
423//
424 iNum = hip;
425 return (hip ? 1 : 0);
426}
427
428/******************************************************************************/
429/* i o D e l */
430/******************************************************************************/
431
432int XrdRmcReal::ioDel(XrdOucCacheIO *KeyVal, int &iNum)
433{
434 int cnt, hip, phip, hent = ioEnt(KeyVal);
435
436// Look up the entry.
437//
438 if (!(hip = hTab[hent]) || !(hip = ioLookup(phip, hip, KeyVal))) return 0;
439 iNum = hip;
440
441// Delete the item, if need be, and return
442//
443 cnt = --(Slots[hip].Count);
444 if (cnt <= 0)
445 {if (phip) Slots[phip].HLink = Slots[hip].HLink;
446 else hTab[hent] = Slots[hip].HLink;
447 Slots[hip].HLink = sFree; sFree = hip;
448 }
449 return (cnt < 0 ? 1 : cnt+1);
450}
451
452/******************************************************************************/
453/* P r e R e a d */
454/******************************************************************************/
455
457{
458 prTask *prP;
459
460// Simply wait and dispatch elements
461//
462 if (Dbg) std::cerr <<"Cache: preread thread started; now " <<prNum <<std::endl;
463 while(1)
464 {prReady.Wait();
465 prMutex.Lock();
466 if (prStop) break;
467 if ((prP = prFirst))
468 {if (!(prFirst = prP->Next)) prLast = 0;
469 prMutex.UnLock();
470 prP->Data->Preread();
471 } else prMutex.UnLock();
472 }
473
474// The cache is being deleted, wind down the prereads
475//
476 prNum--;
477 if (prNum > 0) prReady.Post();
478 else prStop->Post();
479 if (Dbg) std::cerr <<"Cache: preread thread exited; left " <<prNum <<std::endl;
480 prMutex.UnLock();
481}
482
483/******************************************************************************/
484
485void XrdRmcReal::PreRead(XrdRmcReal::prTask *prReq)
486{
487
488// Place this element on the queue
489//
490 prMutex.Lock();
491 if (prLast) {prLast->Next = prReq; prLast = prReq;}
492 else prLast = prFirst = prReq;
493 prReq->Next = 0;
494
495// Tell a pre-reader that something is ready
496//
497 prReady.Post();
498 prMutex.UnLock();
499}
500
501/******************************************************************************/
502/* R e f */
503/******************************************************************************/
504
505int XrdRmcReal::Ref(char *Addr, int rAmt, int sFlags)
506{
507 XrdRmcSlot *sP = &Slots[(Addr-Base)>>SegShft];
508 int eof = 0;
509
510// Indicate how much data was not yet referenced
511//
512 CMutex.Lock();
513 if (sP->Contents >= 0)
514 {if (sP->Count < 0) eof = 1;
515 sP->Status.inUse++;
516 if (sP->Status.inUse < 0)
517 {if (sFlags) sP->Count |= sFlags;
518 else if (!eof && (sP->Count -= rAmt) < 0) sP->Count = 0;
519 } else {
520 if (sFlags) {sP->Count |= sFlags; sP->reRef(Slots);}
521 else { if (sP->Count & XrdRmcSlot::isSUSE)
522 sP->unRef(Slots);
523 else if (eof || (sP->Count -= rAmt) > 0) sP->reRef(Slots);
524 else {sP->Count = SegSize/2; sP->unRef(Slots);}
525 }
526 }
527 } else eof = 1;
528
529// All done
530//
531 if (Dbg > 2) std::cerr <<"Cache: Ref " <<std::hex <<sP->Contents <<std::dec
532 << " slot " <<((Addr-Base)>>SegShft)
533 <<" sz " <<(sP->Count & XrdRmcSlot::lenMask)
534 <<" uc " <<sP->Status.inUse <<std::endl;
535 CMutex.UnLock();
536 return !eof;
537}
538
539/******************************************************************************/
540/* T r u n c */
541/******************************************************************************/
542
543void XrdRmcReal::Trunc(XrdOucCacheIO *ioP, long long lAddr)
544{
545 XrdSysMutexHelper Monitor(CMutex);
546 XrdRmcSlot *sP, *oP;
547 int sNum, Free = 0, Left = 0, Fnum = (lAddr >> Shift) + SegCnt;
548
549// We will be truncating CacheData pages. So, we need to recycle those slots.
550//
551 oP = &Slots[Fnum]; sP = &Slots[oP->Own.Next];
552 while(oP != sP)
553 {sNum = sP->Own.Next;
554 if (sP->Contents < lAddr) Left++;
555 else {sP->Owner(Slots);
556 sP->Hide(Slots, Slash, sP->Contents%HNum);
557 sP->Pull(Slots);
558 sP->unRef(Slots);
559 Free++;
560 }
561 sP = &Slots[sNum];
562 }
563
564// Issue debugging message
565//
566 if (Dbg) std::cerr <<"Cache: Trunc " <<Free <<" slots; "
567 <<Left <<" Left; " <<std::hex << Fnum <<std::dec <<' '
568 <<ioP->Path() <<std::endl;
569}
570
571/******************************************************************************/
572/* U p d */
573/******************************************************************************/
574
575void XrdRmcReal::Upd(char *Addr, int wLen, int wOff)
576{
577 XrdRmcSlot *sP = &Slots[(Addr-Base)>>SegShft];
578
579// Check if we extended a short page
580//
581 CMutex.Lock();
582 if (sP->Count < 0)
583 {int theLen = sP->Count & XrdRmcSlot::lenMask;
584 if (wLen + wOff > theLen)
585 sP->Count = (wLen+wOff) | XrdRmcSlot::isShort;
586 }
587
588// Adjust the reference counter and if no references, place on the LRU chain
589//
590 sP->Status.inUse++;
591 if (sP->Status.inUse >= 0) sP->reRef(Slots);
592
593// All done
594//
595 if (Dbg > 2) std::cerr <<"Cache: Upd " <<std::hex <<sP->Contents <<std::dec
596 << " slot " <<((Addr-Base)>>SegShft)
597 <<" sz " <<(sP->Count & XrdRmcSlot::lenMask)
598 <<" uc " <<sP->Status.inUse <<std::endl;
599 CMutex.UnLock();
600}
void * XrdRmcRealPRXeq(void *parg)
Definition XrdRmcReal.cc:52
int Dbg
XrdOucString Path
#define eMsg(x)
virtual int Read(char *buff, long long offs, int rlen)=0
virtual const char * Path()=0
static const int optRW
File is read/write (o/w read/only)
static const int optFIS
File is structured (e.g. root file)
static int setAPR(aprParms &Dest, aprParms &Src, int pSize)
XrdOucCacheIO * Attach(XrdOucCacheIO *ioP, int Options=0)
XrdRmcReal(int &rc, XrdRmc::Parms &Parms, XrdOucCacheIO::aprParms *aprP=0)
Definition XrdRmcReal.cc:58
friend class XrdRmcData
Definition XrdRmcReal.hh:41
void PreRead()
void File(XrdOucCacheIO *kV, int you)
Definition XrdRmcSlot.hh:45
void Owner(XrdRmcSlot *Base)
Definition XrdRmcSlot.hh:90
void unRef(XrdRmcSlot *Base)
static const int isShort
struct SlotList LRU
static const int lenMask
SlotList Own
SlotState Status
static void Init(XrdRmcSlot *Base, int Num)
Definition XrdRmcSlot.hh:63
int Pull(XrdRmcSlot *Base)
Definition XrdRmcSlot.hh:74
static const int inTrans
void reRef(XrdRmcSlot *Base)
static int Find(XrdRmcSlot *Base, long long What, int n)
Definition XrdRmcSlot.hh:48
static const int isNew
void Hide(XrdRmcSlot *Base, int *hTab, int hI)
Definition XrdRmcSlot.hh:53
static const int isSUSE
static const int Debug
Produce some debug messages (levels 0, 1, 2, or 3)
Definition XrdRmc.hh:136
static const int logStats
Display statistics upon detach.
Definition XrdRmc.hh:127
static const int isServer
This is server application; not a user application.
Definition XrdRmc.hh:118
static const int isStructured
Definition XrdRmc.hh:121
static const int canPreRead
Enable pre-read operations (o/w ignored)
Definition XrdRmc.hh:124
static int Run(pthread_t *, void *(*proc)(void *), void *arg, int opts=0, const char *desc=0)
XrdSysSemaphore * ioEnd
Parameters for a newly created memory cache.
Definition XrdRmc.hh:101
int MaxFiles
Maximum number of files (default 256 or 8K)
Definition XrdRmc.hh:104
int PageSize
Size of each page in bytes (default 32KB)
Definition XrdRmc.hh:102
long long CacheSize
Size of cache in bytes (default 100MB)
Definition XrdRmc.hh:101
int Max2Cache
Largest read to cache (default PageSize)
Definition XrdRmc.hh:103
short minPages
Minimum number of pages (default 256)
Definition XrdRmc.hh:106
int Options
Options as defined below (default r/o cache)
Definition XrdRmc.hh:105