XRootD
Loading...
Searching...
No Matches
XrdOucStream.cc
Go to the documentation of this file.
1/******************************************************************************/
2/* */
3/* X r d O u c S t r e a m . c c */
4/* */
5/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */
6/* Produced by Andrew Hanushevsky for Stanford University under contract */
7/* DE-AC02-76-SFO0515 with the Deprtment of Energy */
8/* */
9/* This file is part of the XRootD software suite. */
10/* */
11/* XRootD is free software: you can redistribute it and/or modify it under */
12/* the terms of the GNU Lesser General Public License as published by the */
13/* Free Software Foundation, either version 3 of the License, or (at your */
14/* option) any later version. */
15/* */
16/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
17/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
18/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
19/* License for more details. */
20/* */
21/* You should have received a copy of the GNU Lesser General Public License */
22/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
23/* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
24/* */
25/* The copyright holder's institutional names and contributor's names may not */
26/* be used to endorse or promote products derived from this software without */
27/* specific prior written permission of the institution or contributor. */
28/******************************************************************************/
29
30#include <cctype>
31#include <fcntl.h>
32#include <cstdlib>
33#include <cstring>
34#include <cstdio>
35#ifndef WIN32
36#include <poll.h>
37#include <unistd.h>
38#include <strings.h>
39#if !defined(__linux__) && !defined(__CYGWIN__) && !defined(__GNU__) && !defined(__FreeBSD__)
40#include <sys/conf.h>
41#endif
42#include <sys/stat.h>
43#include <termios.h>
44#include <sys/types.h>
45#include <sys/wait.h>
46#else // WIN32
47#include "XrdSys/XrdWin32.hh"
48#include <process.h>
49#endif // WIN32
50
51#include <set>
52#include <string>
53
54#include "XrdOuc/XrdOucEnv.hh"
58#include "XrdOuc/XrdOucTList.hh"
59#include "XrdOuc/XrdOucUtils.hh"
60#include "XrdSys/XrdSysE2T.hh"
61#include "XrdSys/XrdSysFD.hh"
66
67/******************************************************************************/
68/* l o c a l d e f i n e s */
69/******************************************************************************/
70
71#define MaxARGC 64
72#define XrdOucStream_EOM 0x01
73#define XrdOucStream_BUSY 0x02
74#define XrdOucStream_ELIF 0x80
75
76#define XrdOucStream_CADD 0x010000
77#define XrdOucStream_CONT 0xff0000
78#define XrdOucStream_CMAX 0x0f0000
79
80#define Erq(p, a, b) Err(p, a, b, (char *)0)
81#define Err(p, a, b, c) (ecode=(Eroute ? Eroute->Emsg(#p, a, b, c) : a), -1)
82#define Erp(p, a, b, c) ecode=(Eroute ? Eroute->Emsg(#p, a, b, c) : a)
83
84// The following is used by child processes prior to exec() to avoid deadlocks
85//
86#define Erx(p, a, b) std::cerr <<#p <<": " <<XrdSysE2T(a) <<' ' <<b <<std::endl;
87
88/******************************************************************************/
89/* S t a t i c M e m b e r s & O b j e c t s */
90/******************************************************************************/
91
92// The following mutex is used to allow only one fork at a time so that
93// we do not leak file descriptors. It is a short-lived lock.
94//
95namespace {XrdSysMutex forkMutex;}
96
97XrdOucString *XrdOucStream::theCFG = 0;
98
99/******************************************************************************/
100/* L o c a l C l a s s e s */
101/******************************************************************************/
102
104 {char *myHost;
105 char *myName;
106 char *myExec;
107
108 std::set<std::string> *fcList;
109 std::set<std::string>::iterator itFC;
110
112 fcList(0) {}
113 ~StreamInfo() {if (fcList) delete fcList;}
114 };
115
116namespace
117{
118class contHandler
119{
120public:
121
122char *path;
123XrdOucTList *tlP;
124
125void Add(const char *sfx) {tlP = new XrdOucTList(sfx,(int)strlen(sfx),tlP);}
126
127 contHandler() : path(0), tlP(0) {}
128 ~contHandler() {XrdOucTList *tlN;
129 while(tlP) {tlN = tlP; tlP = tlP->next; delete tlN;}
130 if (path) free(path);
131 }
132};
133}
134
135/******************************************************************************/
136/* L o c a l F u n c t i o n s */
137/******************************************************************************/
138
139namespace
140{
141bool KeepFile(const char *fname, XrdOucTList *tlP)
142{
143 struct sfxList {const char *txt; int len;};
144 static sfxList sfx[] = {{".cfsaved", 8},
145 {".rpmsave", 8},
146 {".rpmnew", 7},
147 {".dpkg-old", 9},
148 {".dpkg-dist", 10},
149 {"~", 1}
150 };
151 static int sfxLNum = sizeof(sfx)/sizeof(struct sfxList);
152 int n;
153
154// We don't keep file that start with a dot
155//
156 if (*fname == '.') return false;
157 n = strlen(fname);
158
159// Process white list first, otherwise use the black list
160//
161 if (tlP)
162 {while(tlP)
163 {if (tlP->ival[0] < n && !strcmp(tlP->text, fname+n-tlP->ival[0]))
164 return true;
165 tlP = tlP->next;
166 }
167 return false;
168 }
169
170// Check all other suffixes we wish to avoid
171//
172 for (int i = 0; i < sfxLNum; i++)
173 {if (sfx[i].len < n && !strcmp(sfx[i].txt, fname+n-sfx[i].len))
174 return false;
175 }
176
177// This file can be kept
178//
179 return true;
180}
181}
182
183/******************************************************************************/
184/* o o u c _ S t r e a m C o n s t r u c t o r */
185/******************************************************************************/
186
187XrdOucStream::XrdOucStream(XrdSysError *erobj, const char *ifname,
188 XrdOucEnv *anEnv, const char *Pfx)
189{
190 char *cp;
191
192
193 if (ifname)
194 {myInst = strdup(ifname);
195 myInfo = new StreamInfo;
196 if (!(cp = index(myInst, ' '))) {cp = myInst; myInfo->myExec = 0;}
197 else {*cp = '\0'; cp++;
198 myInfo->myExec = (*myInst ? myInst : 0);
199 }
200 if ( (myInfo->myHost = index(cp, '@')))
201 {*(myInfo->myHost) = '\0';
202 myInfo->myHost++;
203 myInfo->myName = (*cp ? cp : 0);
204 } else {myInfo->myHost = cp; myInfo->myName = 0;}
205 } else {myInst = 0; myInfo = 0;}
206 myRsv1 = myRsv2 = 0;
207
208 FD = -1;
209 FE = -1;
210 bsize = 0;
211 buff = 0;
212 bnext = 0;
213 bleft = 0;
214 recp = 0;
215 token = 0;
216 flags = 0;
217 child = 0;
218 ecode = 0;
219 notabs = 0;
220 xcont = 1;
221 xline = 0;
222 Eroute = erobj;
223 myEnv = anEnv;
224 sawif = 0;
225 skpel = 0;
226 if (myEnv && Eroute)
227 {llBuff = (char *)malloc(llBsz);
228 llBcur = llBuff; llBok = 0; llBleft = llBsz; *llBuff = '\0';
229 Verbose= 1;
230 } else {
231 Verbose= 0;
232 llBuff = 0;
233 llBcur = 0;
234 llBleft= 0;
235 llBok = 0;
236 }
237 varVal = (myEnv ? new char[maxVLen+1] : 0);
238 llPrefix = Pfx;
239}
240
241/******************************************************************************/
242/* A t t a c h */
243/******************************************************************************/
244
245int XrdOucStream::AttachIO(int infd, int outfd, int bsz)
246{
247 if (Attach(infd, bsz)) return -1;
248 FE = outfd;
249 return 0;
250}
251
252int XrdOucStream::Attach(int FileDescriptor, int bsz)
253{
254
255 // Close the current stream. Close will handle unopened streams.
256 //
257 StreamInfo *saveInfo = myInfo; myInfo = 0;
258 Close();
259 myInfo = saveInfo;
260
261 // Allocate a new buffer for this stream
262 //
263 if (!bsz) buff = 0;
264 else if (!(buff = (char *)malloc(bsz+1)))
265 return Erq(Attach, errno, "allocate stream buffer");
266
267 // Initialize the stream
268 //
269 FD= FE = FileDescriptor;
270 bnext = buff;
271 bsize = bsz+1;
272 bleft = 0;
273 recp = 0;
274 token = 0;
275 flags = 0;
276 ecode = 0;
277 xcont = 1;
278 xline = 0;
279 sawif = 0;
280 skpel = 0;
281 if (llBuff)
282 {llBcur = llBuff; *llBuff = '\0'; llBleft = llBsz; llBok = 0;}
283 return 0;
284}
285
286/******************************************************************************/
287/* C a p t u r e */
288/******************************************************************************/
289
290void XrdOucStream::Capture(const char **cVec, bool linefeed)
291{
292// Make sure we can handle this
293//
294 if (theCFG && cVec && cVec[0])
295 {if (linefeed) theCFG->append("\n# ");
296 else theCFG->append("# ");
297 int i = 0;
298 while(cVec[i]) theCFG->append(cVec[i++]);
299 theCFG->append('\n');
300 }
301}
302
303/******************************************************************************/
304
306{
307 XrdOucString *oldCFG = theCFG;
308 theCFG = newCFG;
309 return oldCFG;
310}
311
312/******************************************************************************/
313
315{
316 return theCFG;
317}
318
319/******************************************************************************/
320/* C l o s e */
321/******************************************************************************/
322
324{
325
326 // Wait for any associated process on this stream
327 //
328 if (!hold && child) Drain();
329 else child = 0;
330
331 // Close the associated file descriptor if it was open
332 //
333 if (FD >= 0) close(FD);
334 if (FE >= 0 && FE != FD) close(FE);
335
336 // Release the buffer if it was allocated.
337 //
338 if (buff) free(buff);
339
340 // Clear all data values by attaching a dummy FD
341 //
342 FD = FE = -1;
343 buff = 0;
344
345 // Check if we should echo the last line
346 //
347 if (llBuff)
348 {if (Verbose && *llBuff && llBok > 1)
349 {if (Eroute) Eroute->Say(llPrefix, llBuff);
350 if (theCFG) add2CFG(llBuff);
351 }
352 llBok = 0;
353 }
354
355 // Delete any info object we have allocated
356 //
357 if (myInfo)
358 {delete myInfo;
359 myInfo = 0;
360 }
361}
362
363/******************************************************************************/
364/* D r a i n */
365/******************************************************************************/
366
368{
369 int Status = 0;
370
371 // Drain any outstanding processes (i.e., kill the process group)
372 //
373#ifndef WIN32
374 int retc;
375 if (child) {kill(-child, 9);
376 do {retc = waitpid(child, &Status, 0);}
377 while(retc > 0 || (retc == -1 && errno == EINTR));
378 child = 0;
379 }
380#else
381 if (child) {
382 TerminateProcess((HANDLE)child, 0);
383 child = 0;
384 }
385#endif
386 return Status;
387}
388
389/******************************************************************************/
390/* E c h o */
391/******************************************************************************/
392
394{
395 if (llBok > 1 && Verbose && llBuff)
396 {if (Eroute) Eroute->Say(llPrefix,llBuff);
397 if (theCFG) add2CFG(llBuff);
398 }
399 llBok = 0;
400}
401
402/******************************************************************************/
403/* E c h o O n l y */
404/******************************************************************************/
405
406void XrdOucStream::Echo(bool capture)
407{
408 if (llBok && Verbose && llBuff)
409 {if (Eroute) Eroute->Say(llPrefix,llBuff);
410 if (capture && theCFG) add2CFG(llBuff);
411 }
412 llBok = 0;
413}
414
415/******************************************************************************/
416/* E x e c */
417/******************************************************************************/
418
419int XrdOucStream::Exec(const char *theCmd, int inrd, int efd)
420{
421 int j;
422 char *cmd, *origcmd, *parm[MaxARGC];
423
424 // Allocate a buffer for the command as we will be modifying it
425 //
426 origcmd = cmd = (char *)malloc(strlen(theCmd)+1);
427 strcpy(cmd, theCmd);
428
429 // Construct the argv array based on passed command line.
430 //
431 for (j = 0; j < MaxARGC-1 && *cmd; j++)
432 {while(*cmd == ' ') cmd++;
433 if (!(*cmd)) break;
434 parm[j] = cmd;
435 while(*cmd && *cmd != ' ') cmd++;
436 if (*cmd) {*cmd = '\0'; cmd++;}
437 }
438 parm[j] = (char *)0;
439
440 // Continue with normal processing
441 //
442 j = Exec(parm, inrd, efd);
443 free(origcmd);
444 return j;
445}
446
447int XrdOucStream::Exec(char **parm, int inrd, int efd)
448{
449 int fildes[2], Child_in = -1, Child_out = -1, Child_log = -1;
450
451 // Create a pipe. Minimize file descriptor leaks.
452 //
453 if (inrd >= 0)
454 {if (pipe(fildes))
455 return Err(Exec, errno, "create input pipe for", parm[0]);
456 else {
457 fcntl(fildes[0], F_SETFD, FD_CLOEXEC);
458 Attach(fildes[0]); Child_out = fildes[1];
459 }
460
461 if (inrd)
462 {if (pipe(fildes))
463 return Err(Exec, errno, "create output pipe for", parm[0]);
464 else {
465 fcntl(fildes[1], F_SETFD, FD_CLOEXEC);
466 FE = fildes[1]; Child_in = fildes[0];
467 }
468 }
469 } else {Child_out = FD; Child_in = FE;}
470
471 // Handle the standard error file descriptor
472 //
473 if (!efd) Child_log = (Eroute ? dup(Eroute->logger()->originalFD()) : -1);
474 else if (efd > 0) Child_log = efd;
475 else if (efd == -2){Child_log = Child_out; Child_out = -1;}
476 else if (efd == -3) Child_log = Child_out;
477
478 // Fork a process first so we can pick up the next request. We also
479 // set the process group in case the child hasn't been able to do so.
480 // Make sure only one fork occurs at any one time (we are the only one).
481 //
482 forkMutex.Lock();
483 if ((child = fork()))
484 {if (child < 0)
485 {close(Child_in); close(Child_out); forkMutex.UnLock();
486 return Err(Exec, errno, "fork request process for", parm[0]);
487 }
488 close(Child_out);
489 if (inrd) close(Child_in );
490 if (!efd && Child_log >= 0) close(Child_log);
491 forkMutex.UnLock();
492 setpgid(child, child);
493 return 0;
494 }
495
496 /*****************************************************************/
497 /* C h i l d P r o c e s s */
498 /*****************************************************************/
499
500 // Redirect standard in if so requested
501 //
502 if (Child_in >= 0)
503 {if (inrd)
504 {if (dup2(Child_in, STDIN_FILENO) < 0)
505 {Erx(Exec, errno, "setting up standard in for " <<parm[0]);
506 _exit(255);
507 } else if (Child_in != Child_out) close(Child_in);
508 }
509 }
510
511 // Reassign the stream to be standard out to capture all of the output.
512 //
513 if (Child_out >= 0)
514 {if (dup2(Child_out, STDOUT_FILENO) < 0)
515 {Erx(Exec, errno, "setting up standard out for " <<parm[0]);
516 _exit(255);
517 } else if (Child_out != Child_log) close(Child_out);
518 }
519
520 // Redirect stderr of the stream if we can to avoid keeping the logfile open
521 //
522 if (Child_log >= 0)
523 {if (dup2(Child_log, STDERR_FILENO) < 0)
524 {Erx(Exec, errno, "set up standard err for " <<parm[0]);
525 _exit(255);
526 } else close(Child_log);
527 }
528
529 // Check if we need to set any envornment variables
530 //
531 if (myEnv)
532 {char **envP;
533 int i = 0;
534 if ((envP = (char **)myEnv->GetPtr("XrdEnvars**")))
535 while(envP[i]) {putenv(envP[i]); i++;}
536 }
537
538 // Set our process group (the parent should have done this by now) then
539 // invoke the command never to return
540 //
541 setpgid(0,0);
542 execv(parm[0], parm);
543 Erx(Exec, errno, "executing " <<parm[0]);
544 _exit(255);
545}
546
547/******************************************************************************/
548/* G e t L i n e */
549/******************************************************************************/
550
552{
553 int bcnt, retc;
554 char *bp;
555
556// Check if end of message has been reached.
557//
558 if (flags & XrdOucStream_EOM) return (char *)NULL;
559
560// Find the next record in the buffer
561//
562 if (bleft > 0)
563 {recp = bnext; bcnt = bleft;
564 for (bp = bnext; bcnt--; bp++)
565 if (!*bp || *bp == '\n')
566 {if (!*bp) flags |= XrdOucStream_EOM;
567 *bp = '\0';
568 bnext = ++bp;
569 bleft = bcnt;
570 token = recp;
571 return recp;
572 }
573 else if (notabs && *bp == '\t') *bp = ' ';
574
575 // There is no next record, so move up data in the buffer.
576 //
577 strncpy(buff, bnext, bleft);
578 bnext = buff + bleft;
579 }
580 else bnext = buff;
581
582// Prepare to read in more data.
583//
584 bcnt = bsize - (bnext - buff) -1;
585 bp = bnext;
586
587// Read up to the maximum number of bytes. Stop reading should we see a
588// new-line character or a null byte -- the end of a record.
589//
590 recp = token = buff; // This will always be true at this point
591 while(bcnt)
592 {do { retc = read(FD, (void *)bp, (size_t)bcnt); }
593 while (retc < 0 && errno == EINTR);
594
595 if (retc < 0) {Erp(GetLine,errno,"read request",0); return (char *)0;}
596 if (!retc)
597 {*bp = '\0';
598 flags |= XrdOucStream_EOM;
599 bnext = ++bp;
600 bleft = 0;
601 return buff;
602 }
603
604 bcnt -= retc;
605 while(retc--)
606 if (!*bp || *bp == '\n')
607 {if (!*bp) flags |= XrdOucStream_EOM;
608 else *bp = '\0';
609 bnext = ++bp;
610 bleft = retc;
611 return buff;
612 } else {
613 if (notabs && *bp == '\t') *bp = ' ';
614 bp++;
615 }
616 }
617
618// All done, force an end of record.
619//
620 Erp(GetLine, EMSGSIZE, "read full message", 0);
621 buff[bsize-1] = '\0';
622 return buff;
623}
624
625/******************************************************************************/
626/* G e t T o k e n */
627/******************************************************************************/
628
629char *XrdOucStream::GetToken(int lowcase) {
630 char *tpoint;
631
632 // Verify that we have a token to return;
633 //
634 if (!token) return (char *)NULL;
635
636 // Skip to the first non-blank character.
637 //
638 while (*token && *token == ' ') token ++;
639 if (!*token) {token = 0; return 0;}
640 tpoint = token;
641
642 // Find the end of the token.
643 //
644 if (lowcase) while (*token && *token != ' ')
645 {*token = (char)tolower((int)*token); token++;}
646 else while (*token && *token != ' ') {token++;}
647 if (*token) {*token = '\0'; token++;}
648
649 // All done here.
650 //
651 return tpoint;
652}
653
654char *XrdOucStream::GetToken(char **rest, int lowcase)
655{
656 char *tpoint;
657
658 // Get the next token
659 //
660 if (!(tpoint = GetToken(lowcase))) return tpoint;
661
662 // Skip to the first non-blank character.
663 //
664 while (*token && *token == ' ') token ++;
665 if (rest) *rest = token;
666
667
668 // All done.
669 //
670 return tpoint;
671}
672
673/******************************************************************************/
674/* G e t F i r s t W o r d */
675/******************************************************************************/
676
678{
679 // If in the middle of a line, flush to the end of the line. Suppress
680 // variable substitution when doing this to avoid errors.
681 //
682 if (xline)
683 {XrdOucEnv *oldEnv = SetEnv(0);
684 while(GetWord(lowcase)) {}
685 SetEnv(oldEnv);
686 }
687 return GetWord(lowcase);
688}
689
690/******************************************************************************/
691/* G e t M y F i r s t W o r d */
692/******************************************************************************/
693
695{
696 char *var;
697 int skip2fi = 0;
698
699 Echo();
700
701 if (!myInst)
702 {if (!myEnv) return add2llB(GetFirstWord(lowcase), 1);
703 else {while((var = GetFirstWord(lowcase)) && !isSet(var)) {}
704 return add2llB(var, 1);
705 }
706 }
707
708 do {if (!(var = GetFirstWord(lowcase)))
709 {if (sawif && !ecode)
710 {ecode = EINVAL;
711 if (Eroute) Eroute->Emsg("Stream", "Missing 'fi' for last 'if'.");
712 }
713 return add2llB(var, 1);
714 }
715
716 add2llB(var, 1);
717
718 if (!strcmp("continue", var))
719 {if (!docont()) return 0;
720 continue;
721 }
722
723 if ( !strcmp("if", var)) var = doif();
724 if (var && !strcmp("else", var)) var = doelse();
725 if (var && !strcmp("fi", var))
726 {if (sawif) sawif = skpel = skip2fi = 0;
727 else {if (Eroute)
728 Eroute->Emsg("Stream", "No preceding 'if' for 'fi'.");
729 ecode = EINVAL;
730 }
731 continue;
732 }
733 if (var && (!myEnv || !isSet(var))) return add2llB(var, 1);
734 } while (1);
735
736 return 0;
737}
738
739/******************************************************************************/
740/* G e t W o r d */
741/******************************************************************************/
742
743char *XrdOucStream::GetWord(int lowcase)
744{
745 char *wp, *ep;
746
747 // A call means the first token was acceptable and we continuing to
748 // parse, hence the line is echoable.
749 //
750 if (llBok == 1) llBok = 2;
751
752 // If we have a token, return it
753 //
754 xline = 1;
755 while((wp = GetToken(lowcase)))
756 {if (!myEnv) return add2llB(wp);
757 if ((wp = vSubs(wp)) && *wp) return add2llB(wp);
758 }
759
760 // If no continuation allowed, return a null (but only once)
761 //
762 if (!xcont) {xcont = 1; xline = 0; return (char *)0;}
763
764 // Find the next non-blank non-comment line
765 //
766do {while(GetLine())
767 {// Get the first token (none if it is a blank line)
768 //
769 if (!(wp = GetToken(lowcase))) continue;
770
771 // If token starts with a pound sign, skip the line
772 //
773 if (*wp == '#') continue;
774
775 // Process continuations (last non-blank character is a back-slash)
776 //
777 ep = bnext-2;
778 while (ep >= buff && *ep == ' ') ep--;
779 if (ep < buff) continue;
780 if (*ep == '\\') {xcont = 1; *ep = '\0';}
781 else xcont = 0;
782 return add2llB((myEnv ? vSubs(wp) : wp));
783 }
784
785 if (myInfo && myInfo->fcList)
786 {if (myInfo->itFC == myInfo->fcList->end())
787 {bleft = 0;
788 flags |= XrdOucStream_EOM;
789 break;
790 }
791 const char *path = (*(myInfo->itFC)).c_str();
792 myInfo->itFC++;
793 if (!docontF(path)) break;
794 bleft = 0;
795 flags &= ~XrdOucStream_EOM;
796 } else break;
797 } while(true);
798
799 xline = 0;
800 return (char *)0;
801}
802
803/******************************************************************************/
804/* G e t R e s t */
805/******************************************************************************/
806
807int XrdOucStream::GetRest(char *theBuff, int Blen, int lowcase)
808{
809 char *tp, *myBuff = theBuff;
810 int tlen;
811
812// Get remaining tokens
813//
814 theBuff[0] = '\0';
815 while ((tp = GetWord(lowcase)))
816 {tlen = strlen(tp);
817 if (tlen+1 >= Blen) return 0;
818 if (myBuff != theBuff) {*myBuff++ = ' '; Blen--;}
819 strcpy(myBuff, tp);
820 Blen -= tlen; myBuff += tlen;
821 }
822
823// All done
824//
825 add2llB(0);
826 return 1;
827}
828
829/******************************************************************************/
830/* R e t T o k e n */
831/******************************************************************************/
832
834{
835 // Check if we can back up
836 //
837 if (!token || token == recp) return;
838
839 // Find the null byte for the token and remove it, if possible
840 //
841 while(*token && token != recp) token--;
842 if (token != recp)
843 {if (token+1 != bnext) *token = ' ';
844 token--;
845 while(*token && *token != ' ' && token != recp) token--;
846 if (token != recp) token++;
847 }
848
849 // If saving line, we must do the same for the saved line
850 //
851 if (llBuff)
852 while(llBcur != llBuff && *llBcur != ' ') {llBcur--; llBleft++;}
853}
854
855/******************************************************************************/
856/* P u t */
857/******************************************************************************/
858
859int XrdOucStream::Put(const char *data, const int dlen) {
860 int dcnt = dlen, retc;
861
862 if (flags & XrdOucStream_BUSY) {ecode = ETXTBSY; return -1;}
863
864 while(dcnt)
865 {do { retc = write(FE, (const void *)data, (size_t)dlen);}
866 while (retc < 0 && errno == EINTR);
867 if (retc >= 0) dcnt -= retc;
868 else {flags |= XrdOucStream_BUSY;
869 Erp(Put, errno, "write to stream", 0);
870 flags &= ~XrdOucStream_BUSY;
871 return -1;
872 }
873 }
874 return 0;
875}
876
877int XrdOucStream::Put(const char *datavec[], const int dlenvec[]) {
878 int i, retc, dlen;
879 const char *data;
880
881 if (flags & XrdOucStream_BUSY) {ecode = ETXTBSY; return -1;}
882
883 for (i = 0; datavec[i]; i++)
884 {data = datavec[i]; dlen = dlenvec[i];
885 while(dlen)
886 {do { retc = write(FE, (const void *)data, (size_t)dlen);}
887 while (retc < 0 && errno == EINTR);
888 if (retc >= 0) {data += retc; dlen -= retc;}
889 else {flags |= XrdOucStream_BUSY;
890 Erp(Put, errno, "write to stream",0);
891 flags &= ~XrdOucStream_BUSY;
892 return -1;
893 }
894 }
895 }
896 return 0;
897}
898
899/******************************************************************************/
900/* P u t L i n e */
901/******************************************************************************/
902
903int XrdOucStream::PutLine(const char *data, int dlen)
904{
905 static const int plSize = 2048;
906
907// Allocate a buffer if one is not allocated
908//
909 if (!buff)
910 {if (!(buff = (char *)malloc(plSize)))
911 return Erq(Attach, errno, "allocate stream buffer");
912 bsize = plSize;
913 }
914
915// Adjust dlen
916//
917 if (dlen <= 0) dlen = strlen(data);
918 if (dlen >= bsize) dlen = bsize-1;
919
920// Simply insert the line into the buffer, truncating if need be
921//
922 bnext = recp = token = buff; // This will always be true at this point
923 if (dlen <= 0)
924 {*buff = '\0';
925 flags |= XrdOucStream_EOM;
926 bleft = 0;
927 } else {
928 strncpy(buff, data, dlen);
929 *(buff+dlen) = 0;
930 bleft = dlen+1;
931 }
932// All done
933//
934 return 0;
935}
936
937/******************************************************************************/
938/* W a i t 4 D a t a */
939/******************************************************************************/
940
942{
943 struct pollfd polltab = {FD, POLLIN|POLLRDNORM, 0};
944 int retc;
945
946// Wait until we can actually read something
947//
948 do {retc = poll(&polltab, 1, msMax);} while(retc < 0 && errno == EINTR);
949 if (retc != 1) return (retc ? errno : -1);
950
951// Return correct value
952//
953 return (polltab.revents & (POLLIN|POLLRDNORM) ? 0 : EIO);
954}
955
956/******************************************************************************/
957/* P r i v a t e M e t h o d s */
958/******************************************************************************/
959/******************************************************************************/
960/* a d d 2 C F G */
961/******************************************************************************/
962
963void XrdOucStream::add2CFG(const char *data, bool isCMT)
964{
965 if (isCMT) theCFG->append("# ");
966 theCFG->append(data);
967 theCFG->append('\n');
968}
969
970/******************************************************************************/
971/* a d d 2 l l B */
972/******************************************************************************/
973
974char *XrdOucStream::add2llB(char *tok, int reset)
975{
976 int tlen;
977
978// Return if not saving data
979//
980 if (!llBuff) return tok;
981
982// Check if we should flush the previous line
983//
984 if (reset)
985 {llBok = 1;
986 llBcur = llBuff;
987 llBleft= llBsz;
988 *llBuff = '\0';
989 } else if (!llBok) return tok;
990 else {llBok = 2;
991 if (llBleft >= 2)
992 {*llBcur++ = ' '; *llBcur = '\0'; llBleft--;}
993 }
994
995// Add in the new token
996//
997 if (tok)
998 {tlen = strlen(tok);
999 if (tlen < llBleft)
1000 {strcpy(llBcur, tok); llBcur += tlen; llBleft -= tlen;}
1001 }
1002 return tok;
1003}
1004
1005/******************************************************************************/
1006/* E c h o */
1007/******************************************************************************/
1008
1009bool XrdOucStream::Echo(int ec, const char *t1, const char *t2, const char *t3)
1010{
1011 if (Eroute)
1012 {if (t1) Eroute->Emsg("Stream", t1, t2, t3);
1013 if (llBok > 1 && Verbose && llBuff) Eroute->Say(llPrefix,llBuff);
1014 }
1015 ecode = ec;
1016 llBok = 0;
1017 return false;
1018}
1019
1020/******************************************************************************/
1021/* d o c o n t */
1022/******************************************************************************/
1023
1024bool XrdOucStream::docont()
1025{
1026 char *theWord;
1027
1028// A continue is not valid within the scope of an if
1029//
1030 if (sawif) return Echo(EINVAL, "'continue' invalid within 'if-fi'.");
1031
1032// Get the path (keep case), if none then ignore this continue
1033//
1034 theWord = GetWord();
1035 if (!theWord)
1036 {Echo();
1037 return true;
1038 }
1039
1040// Prepare to handle the directive
1041//
1042 contHandler cH;
1043 cH.path = strdup(theWord);
1044
1045// Grab additioal tokens which may be suffixes
1046//
1047 theWord = GetWord();
1048 while(theWord && *theWord == '*')
1049 {if (!*(theWord+1)) return Echo(EINVAL, "suffix missing after '*'.");
1050 cH.Add(theWord+1);
1051 theWord = GetWord();
1052 }
1053
1054// If we have a token, it better be an if
1055//
1056 if (theWord && strcmp(theWord, "if"))
1057 return Echo(EINVAL, "expecting 'if' but found", theWord);
1058
1059// Process the 'if'
1060//
1061 if (theWord && !XrdOucUtils::doIf(Eroute, *this, "continue directive",
1062 myInfo->myHost,myInfo->myName,myInfo->myExec))
1063 return true;
1064 Echo();
1065// if (Eroute) Eroute->Say(llPrefix, "continue ", path, " if true");
1066// if (Eroute) Eroute->Say(llPrefix, "continue ", bnext);
1067 return docont(cH.path, cH.tlP);
1068}
1069
1070/******************************************************************************/
1071
1072bool XrdOucStream::docont(const char *path, XrdOucTList *tlP)
1073{
1074 struct stat Stat;
1075 bool noentok;
1076
1077// A continue directive in the context of a continuation is illegal
1078//
1079 if ((myInfo && myInfo->fcList) || (flags & XrdOucStream_CONT) != 0)
1080 return Echo(EINVAL, "'continue' in a continuation is not allowed.");
1081
1082// Check if this file must exist (we also take care of empty paths)
1083//
1084 if ((noentok = (*path == '?')))
1085 {path++;
1086 if (!(*path)) return true;
1087 }
1088
1089// Check if this is a file or directory
1090//
1091 if (stat(path, &Stat))
1092 {if (errno == ENOENT && noentok) return true;
1093 if (Eroute)
1094 {Eroute->Emsg("Stream", errno, "open", path);
1095 ecode = ECANCELED;
1096 } else ecode = errno;
1097 return false;
1098 }
1099
1100// For directory continuation, there is much more to do (this can only happen
1101// once). Note that we used to allow a limited number of chained file
1102// continuations. No more, but we are still setup to easily do so.
1103//
1104 if ((Stat.st_mode & S_IFMT) == S_IFDIR)
1105 {if (!docontD(path, tlP)) return false;
1106 path = (*(myInfo->itFC)).c_str();
1107 myInfo->itFC++;
1108 } else flags |= XrdOucStream_CADD;
1109
1110// if ((flags & XrdOucStream_CONT) > XrdOucStream_CMAX)
1111// {if (Eroute)
1112// {Eroute->Emsg("Stream", EMLINK, "continue to", path);
1113// ecode = ECANCELED;
1114// } else ecode = EMLINK;
1115// return false;
1116// }
1117// }
1118
1119// Continue with the next file
1120//
1121 return docontF(path, noentok);
1122}
1123
1124/******************************************************************************/
1125/* d o c o n t D */
1126/******************************************************************************/
1127
1128bool XrdOucStream::docontD(const char *path, XrdOucTList *tlP)
1129{
1130 static const mode_t isXeq = S_IXUSR | S_IXGRP | S_IXOTH;
1131 XrdOucNSWalk nsWalk(Eroute, path, 0, XrdOucNSWalk::retFile);
1132 int rc;
1133
1134// Get all of the file entries in this directory
1135//
1136 XrdOucNSWalk::NSEnt *nsX, *nsP = nsWalk.Index(rc);
1137 if (rc)
1138 {if (Eroute) Eroute->Emsg("Stream", rc, "index config files in", path);
1139 ecode = ECANCELED;
1140 return false;
1141 }
1142
1143// Keep only files of interest
1144//
1145 myInfo->fcList = new std::set<std::string>;
1146 while((nsX = nsP))
1147 {nsP = nsP->Next;
1148 if ((nsX->Stat.st_mode & isXeq) == 0 && KeepFile(nsX->File, tlP))
1149 myInfo->fcList->insert(std::string(nsX->Path));
1150 delete nsX;
1151 }
1152
1153// Check if we have anything in the map
1154//
1155 if (myInfo->fcList->size() == 0)
1156 {delete myInfo->fcList;
1157 myInfo->fcList = 0;
1158 return false;
1159 }
1160
1161// All done
1162//
1163 myInfo->itFC = myInfo->fcList->begin();
1164 return true;
1165}
1166
1167/******************************************************************************/
1168/* c o n t F */
1169/******************************************************************************/
1170
1171bool XrdOucStream::docontF(const char *path, bool noentok)
1172{
1173 int cFD;
1174
1175// Open the file and handle any errors
1176//
1177 if ((cFD = XrdSysFD_Open(path, O_RDONLY)) < 0)
1178 {if (errno == ENOENT && noentok) return true;
1179 if (Eroute)
1180 {Eroute->Emsg("Stream", errno, "open", path);
1181 ecode = ECANCELED;
1182 } else ecode = errno;
1183 return false;
1184 }
1185
1186// Continue to the next file
1187//
1188 if (XrdSysFD_Dup2(cFD, FD) < 0)
1189 {if (Eroute)
1190 {Eroute->Emsg("Stream", ecode, "switch to", path);
1191 close(cFD);
1192 ecode = ECANCELED;
1193 } else ecode = errno;
1194 return false;
1195 }
1196
1197// Indicate we are switching to anther file
1198//
1199 if (Eroute) Eroute->Say("Config continuing with file ", path, " ...");
1200 bleft = 0;
1201 close(cFD);
1202 return true;
1203}
1204
1205/******************************************************************************/
1206/* d o e l s e */
1207/******************************************************************************/
1208
1209char *XrdOucStream::doelse()
1210{
1211 char *var;
1212
1213// An else must be preceeded by an if and not by a naked else
1214//
1215 if (!sawif || sawif == 2)
1216 {if (Eroute) Eroute->Emsg("Stream", "No preceding 'if' for 'else'.");
1217 ecode = EINVAL;
1218 return 0;
1219 }
1220
1221// If skipping all else caluses, skip all lines until we reach a fi
1222//
1223 if (skpel)
1224 {while((var = GetFirstWord()))
1225 {if (!strcmp("fi", var)) return var;}
1226 if (Eroute) Eroute->Emsg("Stream", "Missing 'fi' for last 'if'.");
1227 ecode = EINVAL;
1228 return 0;
1229 }
1230
1231// Elses are still possible then process one of them
1232//
1233 do {if (!(var = GetWord())) // A naked else will always succeed
1234 {sawif = 2;
1235 return 0;
1236 }
1237 if (strcmp("if", var)) // An else may only be followed by an if
1238 {Eroute->Emsg("Stream","'else",var,"' is invalid.");
1239 ecode = EINVAL;
1240 return 0;
1241 }
1242 sawif = 0;
1243 flags |= XrdOucStream_ELIF;
1244 var = doif();
1245 flags &= ~XrdOucStream_ELIF;
1246 } while(var && !strcmp("else", var));
1247 return var;
1248}
1249
1250/******************************************************************************/
1251/* d o i f */
1252/******************************************************************************/
1253
1254/* Function: doif
1255
1256 Purpose: To parse the directive: if [<hlist>] [exec <pgm>] [named <nlist>]
1257 fi
1258
1259 <hlist> Apply subsequent directives until the 'fi' if this host
1260 is one of the hosts in the blank separated list. Each
1261 host name may have a single asterisk somewhere in the
1262 name to indicate where arbitrry characters lie.
1263
1264 <pgm> Apply subsequent directives if this program is named <pgm>.
1265
1266 <nlist> Apply subsequent directives if this host instance name
1267 is in the list of blank separated names.
1268
1269 Notes: 1) At least one of hlist, pgm, or nlist must be specified.
1270 2) The combination of hlist, pgm, nlist must all be true.
1271
1272 Output: 0 upon success or !0 upon failure.
1273*/
1274
1275char *XrdOucStream::doif()
1276{
1277 char *var, ifLine[512];
1278 int rc;
1279
1280// Check if the previous if was properly closed
1281//
1282 if (sawif)
1283 {if (Eroute) Eroute->Emsg("Stream", "Missing 'fi' for last 'if'.");
1284 ecode = EINVAL;
1285 }
1286
1287// Save the line for context message should we get an error
1288//
1289 snprintf(ifLine, sizeof(ifLine), "%s", token);
1290
1291// Check if we should continue
1292//
1293 sawif = 1; skpel = 0;
1294 if ((rc = XrdOucUtils::doIf(Eroute,*this,"if directive",
1295 myInfo->myHost,myInfo->myName,myInfo->myExec)))
1296 {if (rc >= 0) skpel = 1;
1297 else {ecode = EINVAL;
1298 if(Eroute) Eroute->Say(llPrefix,
1299 (flags & XrdOucStream_ELIF ? "else " : 0),
1300 "if ", ifLine);
1301 }
1302 return 0;
1303 }
1304
1305// Skip all lines until we reach a fi or else
1306//
1307 while((var = GetFirstWord()))
1308 {if (!strcmp("fi", var)) return var;
1309 if (!strcmp("else", var)) return var;
1310 }
1311
1312// Make sure we have a fi
1313//
1314 if (!var)
1315 {if (Eroute) Eroute->Emsg("Stream", "Missing 'fi' for last 'if'.");
1316 ecode = EINVAL;
1317 }
1318 return 0;
1319}
1320
1321/******************************************************************************/
1322/* g e t V a l u e */
1323/******************************************************************************/
1324
1325int XrdOucStream::getValue(const char *path, char *vbuff, int vbsz)
1326{
1327 struct stat Stat;
1328 int n, rc = 0, vFD;
1329
1330// Make sure the file exists and it not too big
1331//
1332 if (stat(path, &Stat)) return errno;
1333 if (Stat.st_size >= vbsz) return EFBIG;
1334
1335// Open the file and read it in
1336//
1337 if ((vFD = XrdSysFD_Open(path, O_RDONLY)) < 0) return errno;
1338 if ((n = read(vFD, vbuff, vbsz-1)) >= 0) vbuff[n] = 0;
1339 else rc = errno;
1340
1341// All done
1342//
1343 close(vFD);
1344 return rc;
1345}
1346
1347/******************************************************************************/
1348/* i s S e t */
1349/******************************************************************************/
1350
1351int XrdOucStream::isSet(char *var)
1352{
1353 static const char *Mtxt1[2] = {"setenv", "set"};
1354 static const char *Mtxt2[2] = {"Setenv variable", "Set variable"};
1355 static const char *Mtxt3[2] = {"Variable", "Environmental variable"};
1356 char *tp, *vn, *vp, *pv, Vname[64], ec, Nil = 0, sawIT = 0;
1357 int Set = 1;
1358 char valBuff[1024];
1359
1360// Process set var = value | set -v | setenv = value
1361//
1362 if (!strcmp("setenv", var)) Set = 0;
1363 else if (strcmp("set", var)) return 0;
1364
1365// Now get the operand
1366//
1367 if (!(tp = GetToken()))
1368 return xMsg("Missing variable name after '",Mtxt1[Set],"'.");
1369
1370// Option flags only apply to set not setenv
1371//
1372 if (Set)
1373 {if (!strcmp(tp, "-q")) {if (llBuff) {free(llBuff); llBuff = 0;}; return 1;}
1374 if (!strcmp(tp, "-v") || !strcmp(tp, "-V"))
1375 {if (Eroute)
1376 {if (!llBuff) llBuff = (char *)malloc(llBsz);
1377 llBcur = llBuff; llBok = 0; llBleft = llBsz; *llBuff = '\0';
1378 Verbose = (strcmp(tp, "-V") ? 1 : 2);
1379 }
1380 return 1;
1381 }
1382 }
1383
1384// Next may be var= | var | var=val | var< | var<val
1385//
1386 if ((vp = index(tp, '=')) || (vp = index(tp, '<')))
1387 {sawIT = *vp; *vp = '\0'; vp++;}
1388 if (strlcpy(Vname, tp, sizeof(Vname)) >= sizeof(Vname))
1389 return xMsg(Mtxt2[Set],tp,"is too long.");
1390 if (!Set && !strncmp("XRD", Vname, 3))
1391 return xMsg("Setenv variable",tp,"may not start with 'XRD'.");
1392
1393// Verify that variable is only an alphanum
1394//
1395 tp = Vname;
1396 while (*tp && (*tp == '_' || isalnum(*tp))) tp++;
1397 if (*tp) return xMsg(Mtxt2[Set], Vname, "is non-alphanumeric");
1398
1399// Now look for the value
1400//
1401 if (sawIT) tp = vp;
1402 else if (!(tp = GetToken()) || (*tp != '=' && *tp != '<'))
1403 return xMsg("Missing '=' after", Mtxt1[Set], Vname);
1404 else {sawIT = *tp; tp++;}
1405 if (!*tp && !(tp = GetToken())) tp = (char *)"";
1406
1407// Handle reading value from a file
1408//
1409 if (sawIT == '<')
1410 {int rc;
1411 if (!*tp) return xMsg(Mtxt2[Set], Vname, "path to value not specified");
1412 if ((rc = getValue(tp, valBuff, sizeof(valBuff))))
1413 {char tbuff[512];
1414 snprintf(tbuff, sizeof(tbuff), "cannot be set via path %s; %s",
1415 tp, XrdSysE2T(rc));
1416 return xMsg(Mtxt2[Set], Vname, tbuff);
1417 }
1418 tp = valBuff;
1419 }
1420
1421// The value may be '$var', in which case we need to get it out of the env if
1422// this is a set or from our environment if this is a setenv
1423//
1424 if (*tp != '$') vp = tp;
1425 else {pv = tp+1;
1426 if (*pv == '(') ec = ')';
1427 else if (*pv == '{') ec = '}';
1428 else if (*pv == '[') ec = ']';
1429 else ec = 0;
1430 if (!ec) vn = tp+1;
1431 else {while(*pv && *pv != ec) pv++;
1432 if (*pv) *pv = '\0';
1433 else ec = 0;
1434 vn = tp+2;
1435 }
1436 if (!*vn) {*pv = ec; return xMsg("Variable", tp, "is malformed.");}
1437 if (!(vp = (Set ? getenv(vn) : myEnv->Get(vn))))
1438 {if (ec != ']')
1439 {xMsg(Mtxt3[Set],vn,"is undefined."); *pv = ec; return 1;}
1440 vp = &Nil;
1441 }
1442 *pv = ec;
1443 }
1444
1445// Make sure the value is not too long
1446//
1447 if ((int)strlen(vp) > maxVLen)
1448 return xMsg(Mtxt3[Set], Vname, "value is too long.");
1449
1450// Set the value
1451//
1452 if (Verbose == 2 && Eroute)
1453 if (!(pv = (Set ? myEnv->Get(Vname) : getenv(Vname))) || strcmp(vp, pv))
1454 {char vbuff[1024];
1455 strcpy(vbuff, Mtxt1[Set]); strcat(vbuff, " "); strcat(vbuff, Vname);
1456 Eroute->Say(vbuff, " = ", vp);
1457 }
1458 if (Set) myEnv->Put(Vname, vp);
1459 else if (!(pv = getenv(Vname)) || strcmp(vp,pv))
1460 XrdOucEnv::Export(Vname, vp);
1461 return 1;
1462}
1463
1464/******************************************************************************/
1465/* v S u b s */
1466/******************************************************************************/
1467
1468char *XrdOucStream::vSubs(char *Var)
1469{
1470 char *vp, *sp, *dp, *vnp, ec, bkp, valbuff[maxVLen], Nil = 0;
1471 int n;
1472
1473// Check for substitution
1474//
1475 if (!Var) return Var;
1476 sp = Var; dp = valbuff; n = maxVLen-1; *varVal = '\0';
1477
1478 while(*sp && n > 0)
1479 {if (*sp == '\\') {*dp++ = *(sp+1); sp +=2; n--; continue;}
1480 if (*sp != '$'
1481 || (!isalnum(*(sp+1)) && !index("({[", *(sp+1))))
1482 {*dp++ = *sp++; n--; continue;}
1483 sp++; vnp = sp;
1484 if (*sp == '(') ec = ')';
1485 else if (*sp == '{') ec = '}';
1486 else if (*sp == '[') ec = ']';
1487 else ec = 0;
1488 if (ec) {sp++; vnp++;}
1489 while(isalnum(*sp)) sp++;
1490 if (ec && *sp != ec)
1491 {xMsg("Variable", vnp-2, "is malformed."); return varVal;}
1492 bkp = *sp; *sp = '\0';
1493 if (!(vp = myEnv->Get(vnp)))
1494 {if (ec != ']') xMsg("Variable", vnp, "is undefined.");
1495 vp = &Nil;
1496 }
1497 while(n && *vp) {*dp++ = *vp++; n--;}
1498 if (*vp) break;
1499 if (ec) sp++;
1500 else *sp = bkp;
1501 }
1502
1503 if (*sp) xMsg("Substituted text too long using", Var);
1504 else {*dp = '\0'; strcpy(varVal, valbuff);}
1505 return varVal;
1506}
1507
1508/******************************************************************************/
1509/* x M s g */
1510/******************************************************************************/
1511
1512int XrdOucStream::xMsg(const char *txt1, const char *txt2, const char *txt3)
1513{
1514 if (Eroute) Eroute->Emsg("Stream", txt1, txt2, txt3);
1515 ecode = EINVAL;
1516 return 1;
1517}
struct stat Stat
Definition XrdCks.cc:49
#define Err(p, a, b, c)
#define XrdOucStream_EOM
#define XrdOucStream_BUSY
#define XrdOucStream_ELIF
#define XrdOucStream_CADD
#define Erx(p, a, b)
#define Erp(p, a, b, c)
#define XrdOucStream_CONT
#define MaxARGC
#define Erq(p, a, b)
int fcntl(int fd, int cmd,...)
#define close(a)
Definition XrdPosix.hh:48
#define write(a, b, c)
Definition XrdPosix.hh:115
#define stat(a, b)
Definition XrdPosix.hh:101
#define read(a, b, c)
Definition XrdPosix.hh:82
const char * XrdSysE2T(int errcode)
Definition XrdSysE2T.cc:104
size_t strlcpy(char *dst, const char *src, size_t sz)
static int Export(const char *Var, const char *Val)
Definition XrdOucEnv.cc:170
void * GetPtr(const char *varname)
Definition XrdOucEnv.cc:263
static const int retFile
XrdOucEnv * SetEnv(XrdOucEnv *newEnv)
XrdOucStream(XrdSysError *erobj=0, const char *ifname=0, XrdOucEnv *anEnv=0, const char *Pfx=0)
char * GetMyFirstWord(int lowcase=0)
int PutLine(const char *data, int dlen=0)
static XrdOucString * Capture()
char * GetLine()
char * GetFirstWord(int lowcase=0)
char * GetWord(int lowcase=0)
int Attach(int FileDescriptor, int bsz=2047)
int AttachIO(int infd, int outfd, int bsz=2047)
int Put(const char *data, const int dlen)
int Exec(const char *, int inrd=0, int efd=0)
int Wait4Data(int msMax=-1)
void Close(int hold=0)
char * GetToken(int lowcase=0)
int GetRest(char *theBuf, int Blen, int lowcase=0)
void append(const int i)
XrdOucTList * next
static int doIf(XrdSysError *eDest, XrdOucStream &Config, const char *what, const char *hname, const char *nname, const char *pname)
std::set< std::string > * fcList
std::set< std::string >::iterator itFC
struct NSEnt * Next