/**************************************************************************** * Routines to manage files of miniSEED. * * This file is part of the miniSEED Library. * * Copyright (c) 2023 Chad Trabant, EarthScope Data Services * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ #include #include #include #include #include #include #include #include "libmseed.h" #include "msio.h" /* Skip length in bytes when skipping non-data */ #define SKIPLEN 1 /* Initialize the global file reading parameters */ MS3FileParam gMS3FileParam = MS3FileParam_INITIALIZER; /* Stream state flags */ #define MSFP_RANGEAPPLIED 0x0001 //!< Byte ranging has been applied static char *parse_pathname_range (const char *string, int64_t *start, int64_t *end); /*****************************************************************/ /** * @brief Run-time test for URL support in libmseed. * * @returns 0 when no URL suported is included, non-zero otherwise. *********************************************************************/ int libmseed_url_support (void) { #if defined(LIBMSEED_URL) return 1; #else return 0; #endif } /* End of libmseed_url_support() */ /*****************************************************************/ /** * @brief Read miniSEED records from a file or URL * * This routine is a wrapper for ms3_readmsr_selection() that uses the * global file reading parameters. This routine is _not_ thread safe * and cannot be used to read more than one stream at a time. * * See ms3_readmsr_selection() for a further description of arguments. * * @returns Return value from ms3_readmsr_selection() * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_readmsr (MS3Record **ppmsr, const char *mspath, uint32_t flags, int8_t verbose) { MS3FileParam *msfp = &gMS3FileParam; return ms3_readmsr_selection (&msfp, ppmsr, mspath, flags, NULL, verbose); } /* End of ms3_readmsr() */ /*****************************************************************/ /** * @brief Read miniSEED records from a file or URL in a thread-safe way * * This routine is a wrapper for ms3_readmsr_selection() that uses the * re-entrant capabilities. This routine is thread safe and can be * used to read more than one stream at a time as long as separate * MS3FileParam containers are used for each stream. * * A ::MS3FileParam container will be allocated if \c *ppmsfp is \c * NULL. * * See ms3_readmsr_selection() for a further description of arguments. * * @returns Return value from ms3_readmsr_selection() * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_readmsr_r (MS3FileParam **ppmsfp, MS3Record **ppmsr, const char *mspath, uint32_t flags, int8_t verbose) { return ms3_readmsr_selection (ppmsfp, ppmsr, mspath, flags, NULL, verbose); } /* End of ms_readmsr_r() */ /********************************************************************** * * A helper routine to shift (remove bytes from the beginning of) the * stream reading buffer for a MSFP. The buffer length, reading offset * and stream position indicators are all updated as necessary. * * \ref MessageOnError - this function logs a message on error *********************************************************************/ static void ms3_shift_msfp (MS3FileParam *msfp, int shift) { if (!msfp) { ms_log (2, "Required argument not defined: 'msfp'\n"); return; } if (shift <= 0 && shift > msfp->readlength) { ms_log (2, "Cannot shift buffer, shift: %d, readlength: %d, readoffset: %d\n", shift, msfp->readlength, msfp->readoffset); return; } memmove (msfp->readbuffer, msfp->readbuffer + shift, msfp->readlength - shift); msfp->readlength -= shift; if (shift < msfp->readoffset) { msfp->readoffset -= shift; } else { msfp->streampos += (shift - msfp->readoffset); msfp->readoffset = 0; } return; } /* End of ms3_shift_msfp() */ /* Macro to calculate length of unprocessed buffer */ #define MSFPBUFLEN(MSFP) (MSFP->readlength - MSFP->readoffset) /* Macro to return current reading position */ #define MSFPREADPTR(MSFP) (MSFP->readbuffer + MSFP->readoffset) /*****************************************************************/ /** * @brief Read miniSEED records from a file or URL with optional selection * * This routine will open and read, with subsequent calls, all * miniSEED records in specified stream (file or URL). * * All stream reading parameters are stored in a ::MS3FileParam * container and returned (via a pointer to a pointer) for the calling * routine to use in subsequent calls. A ::MS3FileParam container * will be allocated if \c *ppmsfp is \c NULL. This routine is thread * safe and can be used to read multiple streams in parallel as long as * the stream reading parameters are managed appropriately. * * The \a flags argument are bit flags used to control the reading * process. The following flags are supported: * - ::MSF_SKIPNOTDATA - skip input that cannot be identified as miniSEED * - ::MSF_UNPACKDATA data samples will be unpacked * - ::MSF_VALIDATECRC Validate CRC (if present in format) * - ::MSF_PNAMERANGE Parse byte range suffix from \a mspath * * If ::MSF_PNAMERANGE is set in \a flags, the \a mspath will be * searched for start and end byte offsets for the file or URL in the * following format: '\c PATH@@\c START-\c END', where \c START and \c * END are both optional and specified in bytes. * * If \a selections is not NULL, the ::MS3Selections will be used to * limit what is returned to the caller. Any data not matching the * selections will be skipped. * * After reading all the records in a stream the calling program should * call this routine a final time with \a mspath set to NULL. This * will close the input stream and free allocated memory. * * @param[out] ppmsfp Pointer-to-pointer of an ::MS3FileParam, which * contains the state of stream reading across iterative calls of this * function. * * @param[out] ppmsr Pointer-to-pointer of an ::MS3Record, which will * contain a parsed record on success. * * @param[in] mspath File or URL to read * * @param[in] flags Flags used to control parsing, see @ref * control-flags * * @param[in] selections Specify limits to which data should be * returned, see @ref data-selections * * @param[in] verbose Controls verbosity, 0 means no diagnostic output * * @returns ::MS_NOERROR and populates an ::MS3Record struct, at \a * *ppmsr, on successful read. On error, a (negative) libmseed error * code is returned and *ppmsr is set to NULL. * @retval ::MS_ENDOFFILE on reaching the end of a stream * * \sa @ref data-selections * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_readmsr_selection (MS3FileParam **ppmsfp, MS3Record **ppmsr, const char *mspath, uint32_t flags, const MS3Selections *selections, int8_t verbose) { MS3FileParam *msfp; uint32_t pflags = flags; char *pathname_range = NULL; int parseval = 0; int readsize = 0; int readcount = 0; int retcode = MS_NOERROR; if (!ppmsr || !ppmsfp) { ms_log (2, "Required argument not defined: 'ppmsr' or 'ppmsfp'\n"); return MS_GENERROR; } msfp = *ppmsfp; /* Initialize the read parameters if needed */ if (!msfp) { msfp = (MS3FileParam *)libmseed_memory.malloc (sizeof (MS3FileParam)); if (msfp == NULL) { ms_log (2, "Cannot allocate memory for MSFP\n"); return MS_GENERROR; } *msfp = (MS3FileParam) MS3FileParam_INITIALIZER; /* Redirect the supplied pointer to the allocated params */ *ppmsfp = msfp; } /* When cleanup is requested */ if (mspath == NULL) { msr3_free (ppmsr); if (msfp->input.handle != NULL) msio_fclose (&msfp->input); if (msfp->readbuffer != NULL) libmseed_memory.free (msfp->readbuffer); /* If the parameters are the global parameters reset them */ if (*ppmsfp == &gMS3FileParam) { gMS3FileParam = (struct MS3FileParam) MS3FileParam_INITIALIZER; } /* Otherwise free the MS3FileParam */ else { libmseed_memory.free (*ppmsfp); *ppmsfp = NULL; } return MS_NOERROR; } /* Allocate reading buffer */ if (msfp->readbuffer == NULL) { if (!(msfp->readbuffer = (char *)libmseed_memory.malloc (MAXRECLEN))) { ms_log (2, "Cannot allocate memory for read buffer\n"); return MS_GENERROR; } } /* Open the stream if needed, use stdin if path is "-" */ if (msfp->input.handle == NULL) { /* Parse and set byte range from path name suffix */ if (flags & MSF_PNAMERANGE) { pathname_range = parse_pathname_range (mspath, &msfp->startoffset, &msfp->endoffset); } /* Store the path */ strncpy (msfp->path, mspath, sizeof (msfp->path) - 1); /* Truncate to remove byte range suffix if present or maximum range */ if (pathname_range) { msfp->path[pathname_range - mspath] = '\0'; } else { msfp->path[sizeof (msfp->path) - 1] = '\0'; } if (strcmp (mspath, "-") == 0) { msfp->input.type = LMIO_FILE; msfp->input.handle = stdin; } else { if (msio_fopen(&msfp->input, msfp->path, "rb", &msfp->startoffset, &msfp->endoffset)) { msr3_free (ppmsr); return MS_GENERROR; } /* Set stream position to start offset */ if (msfp->startoffset > 0) { msfp->streampos = msfp->startoffset; } } } /* Defer data unpacking if selections are used by unsetting MSF_UNPACKDATA */ if ((flags & MSF_UNPACKDATA) && selections) pflags &= ~(MSF_UNPACKDATA); /* Read data and search for records until input stream ends or end offset is reached */ for (;;) { /* Finished when within MINRECLEN from known end offset in stream */ if (msfp->endoffset && (msfp->endoffset + 1 - msfp->streampos) < MINRECLEN) { retcode = MS_ENDOFFILE; break; } /* Read more data into buffer if not at EOF and buffer has less than MINRECLEN * or more data is needed for the current record detected in buffer. */ if (!msio_feof (&msfp->input) && (MSFPBUFLEN (msfp) < MINRECLEN || parseval > 0)) { /* Reset offsets if no unprocessed data in buffer */ if (MSFPBUFLEN (msfp) <= 0) { msfp->readlength = 0; msfp->readoffset = 0; } /* Otherwise shift existing data to beginning of buffer */ else if (msfp->readoffset > 0) { ms3_shift_msfp (msfp, msfp->readoffset); } /* Determine read size */ readsize = (MAXRECLEN - msfp->readlength); /* Read data into record buffer */ readcount = (int)msio_fread (&msfp->input, msfp->readbuffer + msfp->readlength, readsize); if (readcount <= 0 && !msio_feof (&msfp->input)) { ms_log (2, "Error reading %s at offset %" PRId64 "\n", msfp->path, msfp->streampos); retcode = MS_GENERROR; break; } /* Update read buffer length */ msfp->readlength += readcount; } /* Attempt to parse record from buffer */ if (MSFPBUFLEN (msfp) >= MINRECLEN) { /* Set end of file flag if at EOF */ if (msio_feof (&msfp->input)) pflags |= MSF_ATENDOFFILE; parseval = msr3_parse (MSFPREADPTR (msfp), MSFPBUFLEN (msfp), ppmsr, pflags, verbose); /* Record detected and parsed */ if (parseval == 0) { /* Test against selections if supplied */ if (selections && !ms3_matchselect (selections, (*ppmsr)->sid, (*ppmsr)->starttime, msr3_endtime (*ppmsr), (*ppmsr)->pubversion, NULL)) { if (verbose > 1) { ms_log (0, "Skipping (selection) record for %s (%d bytes) starting at offset %" PRId64 "\n", (*ppmsr)->sid, (*ppmsr)->reclen, msfp->streampos); } /* Skip record length bytes, update reading offset and file position */ msfp->readoffset += (*ppmsr)->reclen; msfp->streampos += (*ppmsr)->reclen; } else { /* Unpack data samples if this has been deferred */ if (!(pflags & MSF_UNPACKDATA) && (flags & MSF_UNPACKDATA) && (*ppmsr)->samplecnt > 0) { if (msr3_unpack_data ((*ppmsr), verbose) != (*ppmsr)->samplecnt) { ms_log (2, "Cannot unpack data samples for record at byte offset %" PRId64 ": %s\n", msfp->streampos, msfp->path); retcode = MS_GENERROR; break; } } if (verbose > 1) ms_log (0, "Read record length of %d bytes\n", (*ppmsr)->reclen); /* Update reading offset, stream position and record count */ msfp->readoffset += (*ppmsr)->reclen; msfp->streampos += (*ppmsr)->reclen; msfp->recordcount++; retcode = MS_NOERROR; break; } } else if (parseval < 0) { /* Skip non-data if requested */ if (flags & MSF_SKIPNOTDATA) { if (verbose > 1) { ms_log (0, "Skipped %d bytes of non-data record at byte offset %" PRId64 "\n", SKIPLEN, msfp->streampos); } /* Skip SKIPLEN bytes, update reading offset and file position */ msfp->readoffset += SKIPLEN; msfp->streampos += SKIPLEN; } /* Parsing errors */ else if (parseval == MS_NOTSEED) { ms_log (2, "No miniSEED data detected in %s (starting at byte offset %" PRId64 ")\n", msfp->path, msfp->streampos); retcode = parseval; break; } else if (parseval == MS_OUTOFRANGE) { ms_log (2, "miniSEED record length out of supported range in %s (at byte offset %" PRId64 ")\n", msfp->path, msfp->streampos); retcode = parseval; break; } else { retcode = parseval; break; } } else /* parseval > 0 (found record but need more data) */ { /* Check for parse hints that are larger than MAXRECLEN */ if ((MSFPBUFLEN (msfp) + parseval) > MAXRECLEN) { if (flags & MSF_SKIPNOTDATA) { /* Skip SKIPLEN bytes, update reading offset and file position */ msfp->readoffset += SKIPLEN; msfp->streampos += SKIPLEN; } else { ms_log (2, "miniSEED record length out of supported range in %s (at byte offset %" PRId64 ")\n", msfp->path, msfp->streampos); retcode = MS_OUTOFRANGE; break; } } /* End of file check */ else if (msio_feof (&msfp->input)) { if (verbose) ms_log (0, "Truncated record at byte offset %" PRId64 ", end offset %" PRId64 ": %s\n", msfp->streampos, msfp->endoffset, msfp->path); retcode = MS_ENDOFFILE; break; } } } /* End of record detection */ /* Finished when at end-of-stream and buffer contains less than MINRECLEN */ if (msio_feof (&msfp->input) && MSFPBUFLEN (msfp) < MINRECLEN) { if (msfp->recordcount == 0) { ms_log (2, "%s: No data records read, not SEED?\n", msfp->path); retcode = MS_NOTSEED; } else { retcode = MS_ENDOFFILE; } break; } } /* End of reading, record detection and parsing loop */ /* Cleanup target MS3Record if returning an error */ if (retcode != MS_NOERROR) { msr3_free (ppmsr); } return retcode; } /* End of ms3_readmsr_selection() */ /****************************************************************/ /** * @brief Read miniSEED from a file into a trace list * * This is a simple wrapper for ms3_readtracelist_selection() that * uses no selections. * * See ms3_readtracelist_selection() for a further description of * arguments. * * @returns Return value from ms3_readtracelist_selection() * * \ref MessageOnError - this function logs a message on error * * \sa @ref trace-list *********************************************************************/ int ms3_readtracelist (MS3TraceList **ppmstl, const char *mspath, const MS3Tolerance *tolerance, int8_t splitversion, uint32_t flags, int8_t verbose) { return ms3_readtracelist_selection (ppmstl, mspath, tolerance, NULL, splitversion, flags, verbose); } /* End of ms3_readtracelist() */ /****************************************************************/ /** * @brief Read miniSEED from a file into a trace list, with time range * selection * * This is a wrapper for ms3_readtraces_selection() that creates a * simple selection for a specified time window. * * See ms3_readtracelist_selection() for a further description of * arguments. * * @returns Return value from ms3_readtracelist_selection() * * \ref MessageOnError - this function logs a message on error * * \sa @ref trace-list *********************************************************************/ int ms3_readtracelist_timewin (MS3TraceList **ppmstl, const char *mspath, const MS3Tolerance *tolerance, nstime_t starttime, nstime_t endtime, int8_t splitversion, uint32_t flags, int8_t verbose) { MS3Selections selection; MS3SelectTime selecttime; selection.sidpattern[0] = '*'; selection.sidpattern[1] = '\0'; selection.timewindows = &selecttime; selection.pubversion = 0; selection.next = NULL; selecttime.starttime = starttime; selecttime.endtime = endtime; selecttime.next = NULL; return ms3_readtracelist_selection (ppmstl, mspath, tolerance, &selection, splitversion, flags, verbose); } /* End of ms3_readtracelist_timewin() */ /****************************************************************/ /** * @brief Read miniSEED from a file into a trace list, with selection * filtering * * This routine will open and read all miniSEED records in specified * file and populate a ::MS3TraceList, allocating this struture if * needed. This routine is thread safe. * * If \a selections is not NULL, the ::MS3Selections will be used to * limit which records are added to the trace list. Any data not * matching the selections will be skipped. * * As this routine reads miniSEED records it attempts to construct * continuous time series, merging segments when possible. See * mstl3_addmsr() for details of \a tolerance. * * The \a splitversion flag controls whether data are grouped * according to data publication version (or quality for miniSEED * 2.x). See mstl3_addmsr() for full details. * * If the ::MSF_RECORDLIST flag is set in \a flags, a ::MS3RecordList * will be built for each ::MS3TraceSeg. The ::MS3RecordPtr entries * contain the location of the data record, bit flags, extra headers, etc. * * @param[out] ppmstl Pointer-to-pointer to a ::MS3TraceList to populate * @param[in] mspath File to read * @param[in] tolerance Tolerance function pointers as ::MS3Tolerance * @param[in] selections Pointer to ::MS3Selections for limiting data * @param[in] splitversion Flag to control splitting of version/quality * @param[in] flags Flags to control reading, see ms3_readmsr_selection() * @param[in] verbose Controls verbosity, 0 means no diagnostic output * * @returns ::MS_NOERROR and populates an ::MS3TraceList struct at *ppmstl * on success, otherwise returns a (negative) libmseed error code. * * \ref MessageOnError - this function logs a message on error * * \sa @ref trace-list * \sa @ref data-selections *********************************************************************/ int ms3_readtracelist_selection (MS3TraceList **ppmstl, const char *mspath, const MS3Tolerance *tolerance, const MS3Selections *selections, int8_t splitversion, uint32_t flags, int8_t verbose) { MS3Record *msr = NULL; MS3FileParam *msfp = NULL; MS3TraceSeg *seg = NULL; MS3RecordPtr *recordptr = NULL; uint32_t dataoffset; uint32_t datasize; int retcode; if (!ppmstl) { ms_log (2, "Required argument not defined: 'ppmstl'\n"); return MS_GENERROR; } /* Initialize MS3TraceList if needed */ if (!*ppmstl) { *ppmstl = mstl3_init (*ppmstl); if (!*ppmstl) { ms_log (2, "Cannot allocate memory\n"); return MS_GENERROR; } } /* Loop over the input file and add each record to trace list */ while ((retcode = ms3_readmsr_selection (&msfp, &msr, mspath, flags, selections, verbose)) == MS_NOERROR) { seg = mstl3_addmsr_recordptr (*ppmstl, msr, (flags & MSF_RECORDLIST) ? &recordptr : NULL, splitversion, 1, flags, tolerance); if (seg == NULL) { ms_log (2, "%s: Cannot add record to trace list\n", msr->sid); retcode = MS_GENERROR; break; } /* Populate remaining fields of record pointer */ if (recordptr) { /* Determine offset to data and length of data payload */ if (msr3_data_bounds (msr, &dataoffset, &datasize)) { retcode = MS_GENERROR; break; } recordptr->bufferptr = NULL; recordptr->fileptr = NULL; recordptr->filename = mspath; recordptr->fileoffset = msfp->streampos - msr->reclen; recordptr->dataoffset = dataoffset; recordptr->prvtptr = NULL; } } /* Reset return code to MS_NOERROR on successful read by ms_readmsr_selection() */ if (retcode == MS_ENDOFFILE) retcode = MS_NOERROR; ms3_readmsr_selection (&msfp, &msr, NULL, 0, NULL, 0); return retcode; } /* End of ms3_readtracelist_selection() */ /*****************************************************************/ /** * @brief Set User-Agent header for URL-based requests. * * Configure global User-Agent header for URL-based requests * generated by the library. The \a program and \a version values * will be combined into the form "program/version" along with * declarations of the library and URL-supporting dependency versions. * * An error will be returned when the library was not compiled with * URL support. * * @param[in] program Name of calling program * @param[in] version Version of calling program * * @returns 0 on succes and a negative library error code on error. * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_url_useragent (const char *program, const char *version) { #if !defined(LIBMSEED_URL) ms_log (2, "URL support not included in library\n"); return -1; #else return msio_url_useragent (program, version); #endif } /* End of ms3_url_useragent() */ /*****************************************************************/ /** * @brief Set authentication credentials for URL-based requests. * * Sets global user and password for authentication for URL-based * requests generated by the library. The expected format of the * credentials is: "[user name]:[password]" (without the square * brackets). * * An error will be returned when the library was not compiled with * URL support. * * @param[in] userpassword User and password as user:password * * @returns 0 on succes and a negative library error code on error. * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_url_userpassword (const char *userpassword) { #if !defined(LIBMSEED_URL) ms_log (2, "URL support not included in library\n"); return -1; #else return msio_url_userpassword (userpassword); #endif } /* End of ms3_url_userpassword() */ /*****************************************************************/ /** * @brief Add header to any URL-based requests. * * Sets global header to be included in URL-based requests generated * by the library. * * An error will be returned when the library was not compiled with * URL support. * * @sa ms3_url_freeheaders() * * @param[in] header Header in "key: value" format * * @returns 0 on succes and a negative library error code on error. * * \ref MessageOnError - this function logs a message on error *********************************************************************/ int ms3_url_addheader (const char *header) { #if !defined(LIBMSEED_URL) ms_log (2, "URL support not included in library\n"); return -1; #else return msio_url_addheader (header); #endif } /* End of ms3_url_addheader() */ /*****************************************************************/ /** * @brief Free all set headers for URL-based requests. * * Free all global headers for URL-based requests as set by * ms3_url_addheader(). * * @sa ms3_url_addheader() *********************************************************************/ void ms3_url_freeheaders (void) { #if !defined(LIBMSEED_URL) ms_log (2, "URL support not included in library\n"); return; #else msio_url_freeheaders (); #endif } /* End of ms3_url_freeheaders() */ /*************************************************************************** * * Internal record handler. The handler data should be a pointer to * an open file descriptor to which records will be written. * ***************************************************************************/ static void ms_record_handler_int (char *record, int reclen, void *ofp) { if (fwrite (record, reclen, 1, (FILE *)ofp) != 1) { ms_log (2, "Error writing to output file\n"); } } /* End of ms_record_handler_int() */ /**********************************************************************/ /** * @brief Write miniSEED from an ::MS3Record container to a file * * Pack ::MS3Record data into miniSEED record(s) by calling * msr3_pack() and write to a specified file. The ::MS3Record * container is used as a template for record(s) written to the file. * * The \a overwrite flag controls whether a existing file is * overwritten or not. If true (non-zero) any existing file will be * replaced. If false (zero) new records will be appended to an * existing file. In either case, new files will be created if they * do not yet exist. * * @param[in,out] msr ::MS3Record containing data to write * @param[in] mspath File for output records * @param[in] overwrite Flag to control overwriting versus appending * @param[in] flags Flags controlling data packing, see msr3_pack() * @param[in] verbose Controls verbosity, 0 means no diagnostic output * * @returns the number of records written on success and -1 on error. * * \ref MessageOnError - this function logs a message on error * * \sa msr3_pack() ***************************************************************************/ int64_t msr3_writemseed (MS3Record *msr, const char *mspath, int8_t overwrite, uint32_t flags, int8_t verbose) { FILE *ofp; const char *perms = (overwrite) ? "wb" : "ab"; int64_t packedrecords = 0; if (!msr || !mspath) { ms_log (2, "Required argument not defined: 'msr' or 'mspath'\n"); return -1; } /* Open output file or use stdout */ if (strcmp (mspath, "-") == 0) { ofp = stdout; } else if ((ofp = fopen (mspath, perms)) == NULL) { ms_log (2, "Cannot open output file %s: %s\n", mspath, strerror (errno)); return -1; } /* Pack the MS3Record */ packedrecords = msr3_pack (msr, &ms_record_handler_int, ofp, NULL, flags, verbose - 1); /* Close file and return record count */ fclose (ofp); return packedrecords; } /* End of msr3_writemseed() */ /**********************************************************************/ /** * @brief Write miniSEED from an ::MS3TraceList container to a file * * Pack ::MS3TraceList data into miniSEED record(s) by calling * mstl3_pack() and write to a specified file. * * The \a overwrite flag controls whether a existing file is * overwritten or not. If true (non-zero) any existing file will be * replaced. If false (zero) new records will be appended to an * existing file. In either case, new files will be created if they * do not yet exist. * * @param[in,out] mstl ::MS3TraceList containing data to write * @param[in] mspath File for output records * @param[in] overwrite Flag to control overwriting versus appending * @param[in] maxreclen The maximum record length to create * @param[in] encoding encoding Encoding for data samples, see msr3_pack() * @param[in] flags Flags controlling data packing, see mstl3_pack() and msr3_pack() * @param[in] verbose Controls verbosity, 0 means no diagnostic output * * @returns the number of records written on success and -1 on error. * * \ref MessageOnError - this function logs a message on error * * \sa mstl3_pack() * \sa msr3_pack() ***************************************************************************/ int64_t mstl3_writemseed (MS3TraceList *mstl, const char *mspath, int8_t overwrite, int maxreclen, int8_t encoding, uint32_t flags, int8_t verbose) { FILE *ofp; const char *perms = (overwrite) ? "wb" : "ab"; int64_t packedrecords = 0; if (!mstl || !mspath) { ms_log (2, "Required argument not defined: 'msr' or 'mspath'\n"); return -1; } /* Open output file or use stdout */ if (strcmp (mspath, "-") == 0) { ofp = stdout; } else if ((ofp = fopen (mspath, perms)) == NULL) { ms_log (2, "Cannot open output file %s: %s\n", mspath, strerror (errno)); return -1; } /* Do not modify the trace list during packing */ flags |= MSF_MAINTAINMSTL; /* Pack all data */ flags |= MSF_FLUSHDATA; packedrecords = mstl3_pack (mstl, &ms_record_handler_int, ofp, maxreclen, encoding, NULL, flags, verbose, NULL); /* Close file and return record count */ fclose (ofp); return packedrecords; } /* End of mstl3_writemseed() */ /*****************************************************************/ /** * Parse a range from the end of a string. * * Expected format is: 'PATH@START-END' * where START and END are optional but the dash must be included * for an END to be present. The START and END values must contain * 20 or fewer digits (0-9). * * Expected variations: '@START', '@START-END', '@-END' * * @returns Pointer to '@' starting valid range on success, otherwise NULL. *********************************************************************/ char * parse_pathname_range (const char *string, int64_t *start, int64_t *end) { char startstr[21] = {0}; /* Maximum of 20 digit value */ char endstr[21] = {0}; /* Maximum of 20 digit value */ int startdigits = 0; int enddigits = 0; char *dash = NULL; char *at = NULL; char *ptr; if (!string || (!start || !end)) return NULL; /* Find last '@' */ if ((at = strrchr (string, '@')) != NULL) { /* Walk the characters in the string following '@'. * Fail as soon as a non-conforming pattern is determined. */ ptr = at; while (*(++ptr) != '\0') { /* If a digit before dash, part of start */ if (isdigit((int)*ptr) && dash == NULL) startstr[startdigits++] = *ptr; /* If a digit after dash, part of end */ else if (isdigit((int)*ptr) && dash != NULL) endstr[enddigits++] = *ptr; /* If a dash after a dash, not a valid range */ else if (*ptr == '-' && dash != NULL) return NULL; /* If first dash found, store pointer */ else if (*ptr == '-' && dash == NULL) dash = ptr; /* Nothing else is acceptable, not a valid range */ else return NULL; /* If digit sequences have exceeded limits, not a valid range */ if (startdigits >= sizeof(startstr) || enddigits >= sizeof(endstr)) return NULL; } /* Convert start and end values to numbers if non-zero length */ if (start && startdigits) *start = (int64_t) strtoull (startstr, NULL, 10); if (end && enddigits) *end = (int64_t) strtoull (endstr, NULL, 10); } return at; } /* End of parse_pathname_range() */