1 /***************************************************************************
2  * selection.c:
3  *
4  * Generic routines to manage selection lists.
5  *
6  * Written by Chad Trabant unless otherwise noted
7  *   IRIS Data Management Center
8  *
9  * modified: 2014.197
10  ***************************************************************************/
11 
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 
18 #include "libmseed.h"
19 
20 static int ms_globmatch (char *string, char *pattern);
21 
22 /***************************************************************************
23  * ms_matchselect:
24  *
25  * Test the specified parameters for a matching selection entry.  The
26  * srcname parameter may contain globbing characters.  The NULL value
27  * (matching any times) for the start and end times is HPTERROR.
28  *
29  * Return Selections pointer to matching entry on successful match and
30  * NULL for no match or error.
31  ***************************************************************************/
32 Selections *
ms_matchselect(Selections * selections,char * srcname,hptime_t starttime,hptime_t endtime,SelectTime ** ppselecttime)33 ms_matchselect (Selections *selections, char *srcname, hptime_t starttime,
34                 hptime_t endtime, SelectTime **ppselecttime)
35 {
36   Selections *findsl  = NULL;
37   SelectTime *findst  = NULL;
38   SelectTime *matchst = NULL;
39 
40   if (selections)
41   {
42     findsl = selections;
43     while (findsl)
44     {
45       if (ms_globmatch (srcname, findsl->srcname))
46       {
47         findst = findsl->timewindows;
48         while (findst)
49         {
50           if (starttime != HPTERROR && findst->starttime != HPTERROR &&
51               (starttime < findst->starttime && !(starttime <= findst->starttime && endtime >= findst->starttime)))
52           {
53             findst = findst->next;
54             continue;
55           }
56           else if (endtime != HPTERROR && findst->endtime != HPTERROR &&
57                    (endtime > findst->endtime && !(starttime <= findst->endtime && endtime >= findst->endtime)))
58           {
59             findst = findst->next;
60             continue;
61           }
62 
63           matchst = findst;
64           break;
65         }
66       }
67 
68       if (matchst)
69         break;
70       else
71         findsl = findsl->next;
72     }
73   }
74 
75   if (ppselecttime)
76     *ppselecttime = matchst;
77 
78   return (matchst) ? findsl : NULL;
79 } /* End of ms_matchselect() */
80 
81 /***************************************************************************
82  * msr_matchselect:
83  *
84  * A simple wrapper for calling ms_matchselect() using details from a
85  * MSRecord struct.
86  *
87  * Return Selections pointer to matching entry on successful match and
88  * NULL for no match or error.
89  ***************************************************************************/
90 Selections *
msr_matchselect(Selections * selections,MSRecord * msr,SelectTime ** ppselecttime)91 msr_matchselect (Selections *selections, MSRecord *msr, SelectTime **ppselecttime)
92 {
93   char srcname[50];
94   hptime_t endtime;
95 
96   if (!selections || !msr)
97     return NULL;
98 
99   msr_srcname (msr, srcname, 1);
100   endtime = msr_endtime (msr);
101 
102   return ms_matchselect (selections, srcname, msr->starttime, endtime,
103                          ppselecttime);
104 } /* End of msr_matchselect() */
105 
106 /***************************************************************************
107  * ms_addselect:
108  *
109  * Add select parameters to a specified selection list.  The srcname
110  * argument may contain globbing parameters.  The NULL value (matching
111  * any value) for the start and end times is HPTERROR.
112  *
113  * Return 0 on success and -1 on error.
114  ***************************************************************************/
115 int
ms_addselect(Selections ** ppselections,char * srcname,hptime_t starttime,hptime_t endtime)116 ms_addselect (Selections **ppselections, char *srcname,
117               hptime_t starttime, hptime_t endtime)
118 {
119   Selections *newsl = NULL;
120   SelectTime *newst = NULL;
121 
122   if (!ppselections || !srcname)
123     return -1;
124 
125   /* Allocate new SelectTime and populate */
126   if (!(newst = (SelectTime *)calloc (1, sizeof (SelectTime))))
127   {
128     ms_log (2, "Cannot allocate memory\n");
129     return -1;
130   }
131 
132   newst->starttime = starttime;
133   newst->endtime   = endtime;
134 
135   /* Add new Selections struct to begining of list */
136   if (!*ppselections)
137   {
138     /* Allocate new Selections and populate */
139     if (!(newsl = (Selections *)calloc (1, sizeof (Selections))))
140     {
141       ms_log (2, "Cannot allocate memory\n");
142       return -1;
143     }
144 
145     strncpy (newsl->srcname, srcname, sizeof (newsl->srcname));
146     newsl->srcname[sizeof (newsl->srcname) - 1] = '\0';
147 
148     /* Add new Selections struct as first in list */
149     *ppselections      = newsl;
150     newsl->timewindows = newst;
151   }
152   else
153   {
154     Selections *findsl  = *ppselections;
155     Selections *matchsl = 0;
156 
157     /* Search for matching Selectlink entry */
158     while (findsl)
159     {
160       if (!strcmp (findsl->srcname, srcname))
161       {
162         matchsl = findsl;
163         break;
164       }
165 
166       findsl = findsl->next;
167     }
168 
169     if (matchsl)
170     {
171       /* Add time window selection to beginning of window list */
172       newst->next          = matchsl->timewindows;
173       matchsl->timewindows = newst;
174     }
175     else
176     {
177       /* Allocate new Selections and populate */
178       if (!(newsl = (Selections *)calloc (1, sizeof (Selections))))
179       {
180         ms_log (2, "Cannot allocate memory\n");
181         return -1;
182       }
183 
184       strncpy (newsl->srcname, srcname, sizeof (newsl->srcname));
185       newsl->srcname[sizeof (newsl->srcname) - 1] = '\0';
186 
187       /* Add new Selections to beginning of list */
188       newsl->next        = *ppselections;
189       *ppselections      = newsl;
190       newsl->timewindows = newst;
191     }
192   }
193 
194   return 0;
195 } /* End of ms_addselect() */
196 
197 /***************************************************************************
198  * ms_addselect_comp:
199  *
200  * Add select parameters to a specified selection list based on
201  * separate name components.  The network, station, location, channel
202  * and quality arguments may contain globbing parameters.  The NULL
203  * value (matching any value) for the start and end times is HPTERROR.
204  *
205  * If any of the naming parameters are not supplied (pointer is NULL)
206  * a wildcard for all matches is substituted.  As a special case, if
207  * the location ID (loc) is set to "--" to match a space-space/blank
208  * ID it will be translated to an empty string to match libmseed's
209  * notation.
210  *
211  * Return 0 on success and -1 on error.
212  ***************************************************************************/
213 int
ms_addselect_comp(Selections ** ppselections,char * net,char * sta,char * loc,char * chan,char * qual,hptime_t starttime,hptime_t endtime)214 ms_addselect_comp (Selections **ppselections, char *net, char *sta, char *loc,
215                    char *chan, char *qual, hptime_t starttime, hptime_t endtime)
216 {
217   char srcname[100];
218   char selnet[20];
219   char selsta[20];
220   char selloc[20];
221   char selchan[20];
222   char selqual[20];
223 
224   if (!ppselections)
225     return -1;
226 
227   if (net)
228   {
229     strncpy (selnet, net, sizeof (selnet));
230     selnet[sizeof (selnet) - 1] = '\0';
231   }
232   else
233     strcpy (selnet, "*");
234 
235   if (sta)
236   {
237     strncpy (selsta, sta, sizeof (selsta));
238     selsta[sizeof (selsta) - 1] = '\0';
239   }
240   else
241     strcpy (selsta, "*");
242 
243   if (loc)
244   {
245     /* Test for special case blank location ID */
246     if (!strcmp (loc, "--"))
247       selloc[0] = '\0';
248     else
249     {
250       strncpy (selloc, loc, sizeof (selloc));
251       selloc[sizeof (selloc) - 1] = '\0';
252     }
253   }
254   else
255     strcpy (selloc, "*");
256 
257   if (chan)
258   {
259     strncpy (selchan, chan, sizeof (selchan));
260     selchan[sizeof (selchan) - 1] = '\0';
261   }
262   else
263     strcpy (selchan, "*");
264 
265   if (qual)
266   {
267     strncpy (selqual, qual, sizeof (selqual));
268     selqual[sizeof (selqual) - 1] = '\0';
269   }
270   else
271     strcpy (selqual, "?");
272 
273   /* Create the srcname globbing match for this entry */
274   snprintf (srcname, sizeof (srcname), "%s_%s_%s_%s_%s",
275             selnet, selsta, selloc, selchan, selqual);
276 
277   /* Add selection to list */
278   if (ms_addselect (ppselections, srcname, starttime, endtime))
279     return -1;
280 
281   return 0;
282 } /* End of ms_addselect_comp() */
283 
284 /***************************************************************************
285  * ms_readselectionsfile:
286  *
287  * Read a list of data selections from a file and them to the
288  * specified selections list.  On errors this routine will leave
289  * allocated memory unreachable (leaked), it is expected that this is
290  * a program failing condition.
291  *
292  * As a special case if the filename is "-", selection lines will be
293  * read from stdin.
294  *
295  * Returns count of selections added on success and -1 on error.
296  ***************************************************************************/
297 int
ms_readselectionsfile(Selections ** ppselections,char * filename)298 ms_readselectionsfile (Selections **ppselections, char *filename)
299 {
300   FILE *fp;
301   hptime_t starttime;
302   hptime_t endtime;
303   char selectline[200];
304   char *selnet;
305   char *selsta;
306   char *selloc;
307   char *selchan;
308   char *selqual;
309   char *selstart;
310   char *selend;
311   char *cp;
312   char next;
313   int selectcount = 0;
314   int linecount   = 0;
315 
316   if (!ppselections || !filename)
317     return -1;
318 
319   if (strcmp (filename, "-"))
320   {
321     if (!(fp = fopen (filename, "rb")))
322     {
323       ms_log (2, "Cannot open file %s: %s\n", filename, strerror (errno));
324       return -1;
325     }
326   }
327   else
328   {
329     /* Use stdin as special case */
330     fp = stdin;
331   }
332 
333   while (fgets (selectline, sizeof (selectline) - 1, fp))
334   {
335     selnet   = 0;
336     selsta   = 0;
337     selloc   = 0;
338     selchan  = 0;
339     selqual  = 0;
340     selstart = 0;
341     selend   = 0;
342 
343     linecount++;
344 
345     /* Guarantee termination */
346     selectline[sizeof (selectline) - 1] = '\0';
347 
348     /* End string at first newline character if any */
349     if ((cp = strchr (selectline, '\n')))
350       *cp = '\0';
351 
352     /* Skip empty lines */
353     if (!strlen (selectline))
354       continue;
355 
356     /* Skip comment lines */
357     if (*selectline == '#')
358       continue;
359 
360     /* Parse: identify components of selection and terminate */
361     cp   = selectline;
362     next = 1;
363     while (*cp)
364     {
365       if (*cp == ' ' || *cp == '\t')
366       {
367         *cp  = '\0';
368         next = 1;
369       }
370       else if (*cp == '#')
371       {
372         *cp = '\0';
373         break;
374       }
375       else if (next && !selnet)
376       {
377         selnet = cp;
378         next   = 0;
379       }
380       else if (next && !selsta)
381       {
382         selsta = cp;
383         next   = 0;
384       }
385       else if (next && !selloc)
386       {
387         selloc = cp;
388         next   = 0;
389       }
390       else if (next && !selchan)
391       {
392         selchan = cp;
393         next    = 0;
394       }
395       else if (next && !selqual)
396       {
397         selqual = cp;
398         next    = 0;
399       }
400       else if (next && !selstart)
401       {
402         selstart = cp;
403         next     = 0;
404       }
405       else if (next && !selend)
406       {
407         selend = cp;
408         next   = 0;
409       }
410       else if (next)
411       {
412         *cp = '\0';
413         break;
414       }
415       cp++;
416     }
417 
418     /* Skip line if network, station, location and channel are not defined */
419     if (!selnet || !selsta || !selloc || !selchan)
420     {
421       ms_log (2, "[%s] Skipping data selection line number %d\n", filename, linecount);
422       continue;
423     }
424 
425     if (selstart)
426     {
427       starttime = ms_seedtimestr2hptime (selstart);
428       if (starttime == HPTERROR)
429       {
430         ms_log (2, "Cannot convert data selection start time (line %d): %s\n", linecount, selstart);
431         return -1;
432       }
433     }
434     else
435     {
436       starttime = HPTERROR;
437     }
438 
439     if (selend)
440     {
441       endtime = ms_seedtimestr2hptime (selend);
442       if (endtime == HPTERROR)
443       {
444         ms_log (2, "Cannot convert data selection end time (line %d): %s\n", linecount, selend);
445         return -1;
446       }
447     }
448     else
449     {
450       endtime = HPTERROR;
451     }
452 
453     /* Add selection to list */
454     if (ms_addselect_comp (ppselections, selnet, selsta, selloc, selchan, selqual, starttime, endtime))
455     {
456       ms_log (2, "[%s] Error adding selection on line %d\n", filename, linecount);
457       return -1;
458     }
459 
460     selectcount++;
461   }
462 
463   if (fp != stdin)
464     fclose (fp);
465 
466   return selectcount;
467 } /* End of ms_readselectionsfile() */
468 
469 /***************************************************************************
470  * ms_freeselections:
471  *
472  * Free all memory associated with a Selections struct.
473  ***************************************************************************/
474 void
ms_freeselections(Selections * selections)475 ms_freeselections (Selections *selections)
476 {
477   Selections *select;
478   Selections *selectnext;
479   SelectTime *selecttime;
480   SelectTime *selecttimenext;
481 
482   if (selections)
483   {
484     select = selections;
485 
486     while (select)
487     {
488       selectnext = select->next;
489 
490       selecttime = select->timewindows;
491 
492       while (selecttime)
493       {
494         selecttimenext = selecttime->next;
495 
496         free (selecttime);
497 
498         selecttime = selecttimenext;
499       }
500 
501       free (select);
502 
503       select = selectnext;
504     }
505   }
506 
507 } /* End of ms_freeselections() */
508 
509 /***************************************************************************
510  * ms_printselections:
511  *
512  * Print the selections list using the ms_log() facility.
513  ***************************************************************************/
514 void
ms_printselections(Selections * selections)515 ms_printselections (Selections *selections)
516 {
517   Selections *select;
518   SelectTime *selecttime;
519   char starttime[50];
520   char endtime[50];
521 
522   if (!selections)
523     return;
524 
525   select = selections;
526   while (select)
527   {
528     ms_log (0, "Selection: %s\n", select->srcname);
529 
530     selecttime = select->timewindows;
531     while (selecttime)
532     {
533       if (selecttime->starttime != HPTERROR)
534         ms_hptime2seedtimestr (selecttime->starttime, starttime, 1);
535       else
536         strncpy (starttime, "No start time", sizeof (starttime) - 1);
537 
538       if (selecttime->endtime != HPTERROR)
539         ms_hptime2seedtimestr (selecttime->endtime, endtime, 1);
540       else
541         strncpy (endtime, "No end time", sizeof (endtime) - 1);
542 
543       ms_log (0, "  %30s  %30s\n", starttime, endtime);
544 
545       selecttime = selecttime->next;
546     }
547 
548     select = select->next;
549   }
550 } /* End of ms_printselections() */
551 
552 /***********************************************************************
553  * robust glob pattern matcher
554  * ozan s. yigit/dec 1994
555  * public domain
556  *
557  * glob patterns:
558  *	*	matches zero or more characters
559  *	?	matches any single character
560  *	[set]	matches any character in the set
561  *	[^set]	matches any character NOT in the set
562  *		where a set is a group of characters or ranges. a range
563  *		is written as two characters seperated with a hyphen: a-z denotes
564  *		all characters between a to z inclusive.
565  *	[-set]	set matches a literal hypen and any character in the set
566  *	[]set]	matches a literal close bracket and any character in the set
567  *
568  *	char	matches itself except where char is '*' or '?' or '['
569  *	\char	matches char, including any pattern character
570  *
571  * examples:
572  *	a*c		ac abc abbc ...
573  *	a?c		acc abc aXc ...
574  *	a[a-z]c		aac abc acc ...
575  *	a[-a-z]c	a-c aac abc ...
576  *
577  * Revision 1.4  2004/12/26  12:38:00  ct
578  * Changed function name (amatch -> globmatch), variables and
579  * formatting for clarity.  Also add matching header globmatch.h.
580  *
581  * Revision 1.3  1995/09/14  23:24:23  oz
582  * removed boring test/main code.
583  *
584  * Revision 1.2  94/12/11  10:38:15  oz
585  * charset code fixed. it is now robust and interprets all
586  * variations of charset [i think] correctly, including [z-a] etc.
587  *
588  * Revision 1.1  94/12/08  12:45:23  oz
589  * Initial revision
590  ***********************************************************************/
591 
592 #define GLOBMATCH_TRUE 1
593 #define GLOBMATCH_FALSE 0
594 #define GLOBMATCH_NEGATE '^' /* std char set negation char */
595 
596 /***********************************************************************
597  * ms_globmatch:
598  *
599  * Check if a string matches a globbing pattern.
600  *
601  * Return 0 if string does not match pattern and non-zero otherwise.
602  **********************************************************************/
603 static int
ms_globmatch(char * string,char * pattern)604 ms_globmatch (char *string, char *pattern)
605 {
606   int negate;
607   int match;
608   int c;
609 
610   while (*pattern)
611   {
612     if (!*string && *pattern != '*')
613       return GLOBMATCH_FALSE;
614 
615     switch (c = *pattern++)
616     {
617 
618     case '*':
619       while (*pattern == '*')
620         pattern++;
621 
622       if (!*pattern)
623         return GLOBMATCH_TRUE;
624 
625       if (*pattern != '?' && *pattern != '[' && *pattern != '\\')
626         while (*string && *pattern != *string)
627           string++;
628 
629       while (*string)
630       {
631         if (ms_globmatch (string, pattern))
632           return GLOBMATCH_TRUE;
633         string++;
634       }
635       return GLOBMATCH_FALSE;
636 
637     case '?':
638       if (*string)
639         break;
640       return GLOBMATCH_FALSE;
641 
642     /* set specification is inclusive, that is [a-z] is a, z and
643 	   * everything in between. this means [z-a] may be interpreted
644 	   * as a set that contains z, a and nothing in between.
645 	   */
646     case '[':
647       if (*pattern != GLOBMATCH_NEGATE)
648         negate = GLOBMATCH_FALSE;
649       else
650       {
651         negate = GLOBMATCH_TRUE;
652         pattern++;
653       }
654 
655       match = GLOBMATCH_FALSE;
656 
657       while (!match && (c = *pattern++))
658       {
659         if (!*pattern)
660           return GLOBMATCH_FALSE;
661 
662         if (*pattern == '-') /* c-c */
663         {
664           if (!*++pattern)
665             return GLOBMATCH_FALSE;
666           if (*pattern != ']')
667           {
668             if (*string == c || *string == *pattern ||
669                 (*string > c && *string < *pattern))
670               match = GLOBMATCH_TRUE;
671           }
672           else
673           { /* c-] */
674             if (*string >= c)
675               match = GLOBMATCH_TRUE;
676             break;
677           }
678         }
679         else /* cc or c] */
680         {
681           if (c == *string)
682             match = GLOBMATCH_TRUE;
683           if (*pattern != ']')
684           {
685             if (*pattern == *string)
686               match = GLOBMATCH_TRUE;
687           }
688           else
689             break;
690         }
691       }
692 
693       if (negate == match)
694         return GLOBMATCH_FALSE;
695 
696       /* If there is a match, skip past the charset and continue on */
697       while (*pattern && *pattern != ']')
698         pattern++;
699       if (!*pattern++) /* oops! */
700         return GLOBMATCH_FALSE;
701       break;
702 
703     case '\\':
704       if (*pattern)
705         c = *pattern++;
706     default:
707       if (c != *string)
708         return GLOBMATCH_FALSE;
709       break;
710     }
711 
712     string++;
713   }
714 
715   return !*string;
716 } /* End of ms_globmatch() */
717