LP#1709710: Make chunk sizing smart about XML quoting
[opensrf-equinox.git] / src / libopensrf / utils.c
index 987a527..1c049c0 100644 (file)
@@ -12,32 +12,85 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 */
 
+/**
+       @file utils.c
+       
+       @brief A collection of various low-level utility functions.
+       
+       About half of these functions concern the growing_buffer structure,
+       a sort of poor man's string class that allocates more space for
+       itself as needed.
+*/
 #include <opensrf/utils.h>
 #include <opensrf/log.h>
 #include <errno.h>
 
+/**
+       @brief A thin wrapper for malloc().
+       
+       @param size How many bytes to allocate.
+       @return a pointer to the allocated memory.
+
+       If the allocation fails, safe_malloc calls exit().  Consequently the
+       calling code doesn't have to check for NULL.
+
+       Currently safe_malloc() initializes the allocated buffer to all-bits-zero.
+       However the calling code should not rely on this behavior, because it is
+       likely to change.  If you need your buffer filled with all-bits-zero, then
+       call safe_calloc() instead, or fill it yourself by calling memset().
+*/
 inline void* safe_malloc( int size ) {
        void* ptr = (void*) malloc( size );
        if( ptr == NULL ) {
                osrfLogError( OSRF_LOG_MARK, "Out of Memory" );
                exit(99);
        }
-       memset( ptr, 0, size );
+       memset( ptr, 0, size ); // remove this after safe_calloc transition
        return ptr;
 }
 
-/****************
- The following static variables, and the following two functions,
- overwrite the argv array passed to main().  The purpose is to
- change the program name as reported by ps and similar utilities.
+/**
+       @brief A thin wrapper for calloc().
+       
+       @param size How many bytes to allocate.
+       @return a pointer to the allocated memory.
+
+       If the allocation fails, safe_calloc calls exit().  Consequently the
+       calling code doesn't have to check for NULL.
+ */
+inline void* safe_calloc( int size ) {
+       void* ptr = (void*) calloc( 1, size );
+       if( ptr == NULL ) {
+               osrfLogError( OSRF_LOG_MARK, "Out of Memory" );
+               exit(99);
+       }
+       return ptr;
+}
 
- Warning: this code makes the non-portable assumption that the
- strings to which argv[] points are contiguous in memory.  The
- C Standard makes no such guarantee.
- ****************/
+/**
+       @brief Saves a pointer to the beginning of the argv[] array from main().  See init_proc_title().
+*/
 static char** global_argv = NULL;
+/**
+       @brief Saves the length of the argv[] array from main().  See init_proc_title().
+ */
 static int global_argv_size = 0;
 
+/**
+       @brief Save the size and location of the argv[] array.
+       @param argc The argc parameter to main().
+       @param argv The argv parameter to main().
+       @return zero.
+
+       Save a pointer to the argv[] array in the local static variable
+       global_argv.  Add up the lengths of the strings in the argv[]
+       array, subtract 2, and store the result in the local variable
+       global_argv_size.
+
+       The return value, being invariant, is useless.
+
+       This function prepares for a subsequent call to set_proc_title().
+*/
 int init_proc_title( int argc, char* argv[] ) {
 
        global_argv = argv;
@@ -45,7 +98,7 @@ int init_proc_title( int argc, char* argv[] ) {
        int i = 0;
        while( i < argc ) {
                int len = strlen( global_argv[i]);
-               memset( global_argv[i], 0, len);
+               osrf_clearbuf( global_argv[i], len );
                global_argv_size += len;
                i++;
        }
@@ -54,15 +107,40 @@ int init_proc_title( int argc, char* argv[] ) {
        return 0;
 }
 
-int set_proc_title( char* format, ... ) {
+/**
+       @brief Replace the name of the running executable.
+       @param format A printf-style format string.  Subsequent parameters, if any,
+               provide values to be formatted and inserted into the format string.
+       @return Length of the resulting string (or what the length would be if the
+               receiving buffer were big enough to hold it), or -1 in case of an encoding
+               error.  Note: because some older versions of snprintf() don't work correctly,
+               this function may return -1 if the string is truncated for lack of space.
+
+       Formats a string as directed, and uses it to replace the name of the
+       currently running executable.  This replacement string is what will
+       be seen and reported by utilities such as ps and top.
+
+       The replacement string goes into a location identified by a previous call
+       to init_proc_title().
+
+       WARNING: this function makes assumptions about the memory layout of
+       the argv[] array.  ANSI C does not guarantee that these assumptions
+       are correct.
+*/
+int set_proc_title( const char* format, ... ) {
        VA_LIST_TO_STRING(format);
-       memset( *(global_argv), 0, global_argv_size);
+       osrf_clearbuf( *(global_argv), global_argv_size);
        return snprintf( *(global_argv), global_argv_size, VA_BUF );
 }
 
+/**
+       @brief Determine current date and time to high precision.
+       @return Current date and time as seconds since the Epoch.
 
-/* utility method for profiling */
-double get_timestamp_millis() {
+       Used for profiling.  The time resolution is system-dependent but is no finer
+       than microseconds.
+*/
+double get_timestamp_millis( void ) {
        struct timeval tv;
        gettimeofday(&tv, NULL);
        double time     = (int)tv.tv_sec        + ( ((double)tv.tv_usec / 1000000) );
@@ -70,7 +148,18 @@ double get_timestamp_millis() {
 }
 
 
-/* setting/clearing file flags */
+/**
+       @brief Set designated file status flags for an open file descriptor.
+       @param fd The file descriptor to be tweaked.
+       @param flags A set of bitflags.
+       @return 0 if successful, -1 or if not.
+
+       Whatever bits are set in the flags parameter become set in the file status flags of
+       the file descriptor -- subject to the limitation that the only bits affected (at
+       least on Linux) are O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK.
+
+       See also clr_fl().
+*/
 int set_fl( int fd, int flags ) {
        
        int val;
@@ -86,6 +175,18 @@ int set_fl( int fd, int flags ) {
        return 0;
 }
        
+/**
+       @brief Clear designated file status flags for an open file descriptor.
+       @param fd The file descriptor to be tweaked.
+       @param flags A set of bitflags.
+       @return 0 if successful, or -1 if not.
+
+       Whatever bits are set in the flags parameter become cleared in the file status flags
+       of the file descriptor -- subject to the limitation that the only bits affected (at
+       least on Linux) are O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK.
+
+       See also set_fl().
+ */
 int clr_fl( int fd, int flags ) {
        
        int val;
@@ -101,15 +202,33 @@ int clr_fl( int fd, int flags ) {
        return 0;
 }
 
+/**
+       @brief Determine how lohg a string will be after printf-style formatting.
+       @param format The format string.
+       @param args The variable-length list of arguments
+       @return If successful: the length of the string that would be created, plus 1 for
+               a terminal nul, plus another 1, presumably to be on the safe side.  If unsuccessful
+               due to a formatting error: 1.
+
+       WARNINGS: the first parameter is not checked for NULL.  The return value in case of an
+       error is not obviously sensible.
+*/
 long va_list_size(const char* format, va_list args) {
        int len = 0;
        len = vsnprintf(NULL, 0, format, args);
        va_end(args);
-       len += 2;
-       return len;
+       return len + 2;
 }
 
 
+/**
+       @brief Format a printf-style string into a newly allocated buffer.
+       @param format The format string.  Subsequent parameters, if any, will be
+               formatted and inserted into the resulting string.
+       @return A pointer to the string so created.
+
+       The calling code is responsible for freeing the string.
+*/
 char* va_list_to_string(const char* format, ...) {
 
        long len = 0;
@@ -117,23 +236,34 @@ char* va_list_to_string(const char* format, ...) {
        va_list a_copy;
 
        va_copy(a_copy, args);
-
        va_start(args, format);
-       len = va_list_size(format, args);
 
-       char buf[len];
-       memset(buf, 0, sizeof(buf));
+       char* buf = safe_malloc( va_list_size(format, args) );
+       *buf = '\0';
 
        va_start(a_copy, format);
        vsnprintf(buf, len - 1, format, a_copy);
        va_end(a_copy);
-       return strdup(buf);
+       return buf;
 }
 
 // ---------------------------------------------------------------------------------
 // Flesh out a ubiqitous growing string buffer
 // ---------------------------------------------------------------------------------
 
+/**
+       @brief Create a growing_buffer containing an empty string.
+       @param num_initial_bytes The initial size of the internal buffer, not counting the
+               terminal nul.
+       @return A pointer to the newly created growing_buffer.
+
+       The value of num_initial_bytes should typically be a plausible guess of how big
+       the string will ever be.  However the guess doesn't have to accurate, because more
+       memory will be allocated as needed.
+
+       The calling code is responsible for freeing the growing_buffer by calling buffer_free()
+       or buffer_release().
+*/
 growing_buffer* buffer_init(int num_initial_bytes) {
 
        if( num_initial_bytes > BUFFER_MAX_SIZE ) return NULL;
@@ -151,9 +281,66 @@ growing_buffer* buffer_init(int num_initial_bytes) {
 }
 
 
+/**
+       @brief Allocate more memory for a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @param total_len How much total memory we need for the buffer.
+       @return 0 if successful, or 1 if not.
+
+       This function fails if it is asked to allocate BUFFER_MAX_SIZE
+       or more bytes.
+*/
+static int buffer_expand( growing_buffer* gb, size_t total_len ) {
+
+       // We do not check to see if the buffer is already big enough.  It is the
+       //responsibility of the calling function to call this only when necessary.
+
+       // Make sure the request is not excessive
+       
+       if( total_len >= BUFFER_MAX_SIZE ) {
+               fprintf(stderr, "Buffer reached MAX_SIZE of %lu",
+                               (unsigned long) BUFFER_MAX_SIZE );
+               buffer_free( gb );
+               return 1;
+       }
+
+       // Pick a big enough buffer size, but don't exceed a maximum
+       
+       while( total_len >= gb->size ) {
+               gb->size *= 2;
+       }
+
+       if( gb->size > BUFFER_MAX_SIZE )
+               gb->size = BUFFER_MAX_SIZE;
+
+       // Allocate and populate the new buffer
+       
+       char* new_data;
+       OSRF_MALLOC( new_data, gb->size );
+       memcpy( new_data, gb->buf, gb->n_used );
+       new_data[ gb->n_used ] = '\0';
+
+       // Replace the old buffer
+       
+       free( gb->buf );
+       gb->buf = new_data;
+       return 0;
+}
+
+
+/**
+       @brief Append a formatted string to a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @param format A printf-style format string.  Subsequent parameters, if any, will be
+               formatted and inserted into the resulting string.
+       @return If successful,the length of the resulting string; otherwise -1.
+
+       This function fails if either of the first two parameters is NULL,
+       or if the resulting string requires BUFFER_MAX_SIZE or more bytes.
+*/
 int buffer_fadd(growing_buffer* gb, const char* format, ... ) {
 
-       if(!gb || !format) return 0; 
+       if(!gb || !format) return -1; 
 
        long len = 0;
        va_list args;
@@ -165,62 +352,94 @@ int buffer_fadd(growing_buffer* gb, const char* format, ... ) {
        len = va_list_size(format, args);
 
        char buf[len];
-       memset(buf, 0, sizeof(buf));
+       osrf_clearbuf(buf, sizeof(buf));
 
        va_start(a_copy, format);
        vsnprintf(buf, len - 1, format, a_copy);
        va_end(a_copy);
 
        return buffer_add(gb, buf);
-
 }
 
 
-int buffer_add(growing_buffer* gb, char* data) {
-       if(!(gb && data)) return 0;
+/**
+       @brief Appends a string to a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @param data A pointer to the string to be appended.
+       @return If successful, the length of the resulting string; or if not, -1.
+*/
+int buffer_add(growing_buffer* gb, const char* data) {
+       if(!(gb && data)) return -1;
 
        int data_len = strlen( data );
 
-       if(data_len == 0) return 0;
+       if(data_len == 0) return gb->n_used;
 
        int total_len = data_len + gb->n_used;
 
        if( total_len >= gb->size ) {
-               while( total_len >= gb->size ) {
-                       gb->size *= 2;
-               }
-       
-               if( gb->size > BUFFER_MAX_SIZE ) {
-                       osrfLogError( OSRF_LOG_MARK, "Buffer reached MAX_SIZE of %d", BUFFER_MAX_SIZE );
-                       buffer_free( gb );
-                       return 0;
-               }
-       
-               char* new_data;
-               OSRF_MALLOC(new_data, gb->size );
-       
-               strcpy( new_data, gb->buf );
-               free( gb->buf );
-               gb->buf = new_data;
+               if( buffer_expand( gb, total_len ) )
+                       return -1;
        }
 
-       strcat( gb->buf, data );
+       strcpy( gb->buf + gb->n_used, data );
        gb->n_used = total_len;
        return total_len;
 }
 
+/**
+       @brief Append a specified number of characters to a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @param data A pointer to the characters to be appended.
+       @param n How many characters to be append.
+       @return If sccessful, the length of the resulting string; or if not, -1.
+
+       If the characters to be appended include an embedded nul byte, it will be appended
+       along with the others.  The results are likely to be unpleasant.
+*/
+int buffer_add_n(growing_buffer* gb, const char* data, size_t n) {
+       if(!(gb && data)) return -1;
+
+       if(n == 0) return 0;
 
+       int total_len = n + gb->n_used;
+
+       if( total_len >= gb->size ) {
+               if( buffer_expand( gb, total_len ) )
+                       return -1;
+       }
+
+       memcpy( gb->buf + gb->n_used, data, n );
+       gb->buf[total_len] = '\0';
+       gb->n_used = total_len;
+       return total_len;
+}
+
+
+/**
+       @brief Reset a growing_buffer so that it contains an empty string.
+       @param gb A pointer to the growing_buffer.
+       @return 0 if successful, -1 if not.
+*/
 int buffer_reset( growing_buffer *gb){
-       if( gb == NULL ) { return 0; }
-       if( gb->buf == NULL ) { return 0; }
-       memset( gb->buf, 0, sizeof(gb->buf) );
+       if( gb == NULL ) { return -1; }
+       if( gb->buf == NULL ) { return -1; }
+       osrf_clearbuf( gb->buf, gb->size );
        gb->n_used = 0;
-       return 1;
+       gb->buf[ 0 ] = '\0';
+       return gb->n_used;
 }
 
-/* Return a pointer to the text within a growing_buffer, */
-/* while destroying the growing_buffer itself.           */
+/**
+       @brief Free a growing_buffer and return a pointer to the string inside.
+       @param gb A pointer to the growing_buffer.
+       @return A pointer to the string previously contained by the growing buffer.
+
+       The calling code is responsible for freeing the string.
 
+       This function is equivalent to buffer_data() followed by buffer_free().  However
+       it is more efficient, because it avoids calls to strudup and free().
+*/
 char* buffer_release( growing_buffer* gb) {
        char* s = gb->buf;
        s[gb->n_used] = '\0';
@@ -228,45 +447,103 @@ char* buffer_release( growing_buffer* gb) {
        return s;
 }
 
-/* Destroy a growing_buffer and the text it contains */
-
+/**
+       @brief Free a growing_buffer and its contents.
+       @param gb A pointer to the growing_buffer.
+       @return 1 if successful, or 0 if not (because the input parameter is NULL).
+*/
 int buffer_free( growing_buffer* gb ) {
-       if( gb == NULL ) 
+       if( gb == NULL )
                return 0;
        free( gb->buf );
        free( gb );
        return 1;
 }
 
-char* buffer_data( growing_buffer *gb) {
+/**
+       @brief Create a copy of the string inside a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @return A pointer to the newly created copy.
+
+       The growing_buffer itself is not affected.
+
+       The calling code is responsible for freeing the string.
+*/
+char* buffer_data( const growing_buffer *gb) {
        return strdup( gb->buf );
 }
 
+/**
+       @brief Remove the last character from a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @return The character removed (or a nul byte if the string is already empty).
+*/
+char buffer_chomp(growing_buffer* gb) {
+       char c = '\0';
+    if(gb && gb->n_used > 0) {
+           gb->n_used--;
+               c = gb->buf[gb->n_used];
+           gb->buf[gb->n_used] = '\0';
+    }
+    return c;
+}
+
 
-/*
-#define OSRF_BUFFER_ADD_CHAR(gb, c)\
-       do {\
-               if(gb) {\
-                       if(gb->n_used < gb->size - 1)\
-                               gb->buf[gb->n_used++] = c;\
-                       else\
-                               buffer_add_char(gb, c);\
-               }\
-       }while(0)
-       */
-
-int buffer_add_char(growing_buffer* gb, char c) {
-       char buf[2];
-       buf[0] = c;
-       buf[1] = '\0';
-       buffer_add(gb, buf);
-       return 1;
+/**
+       @brief Append a single character to a growing_buffer.
+       @param gb A pointer to the growing_buffer.
+       @param c The character to be appended.
+       @return The length of the resulting string.
+
+       If the character appended is a nul byte it will still be appended as if
+       it were a normal character.  The results are likely to be unpleasant.
+*/
+int buffer_add_char(growing_buffer* gb, char c ) {
+       if(gb && gb->buf) {
+
+               int total_len = gb->n_used + 1;
+
+               if( total_len >= gb->size ) {
+                       if( buffer_expand( gb, total_len ) )
+                               return -1;
+               }
+       
+               gb->buf[ gb->n_used ]   = c;
+               gb->buf[ ++gb->n_used ] = '\0';
+               return gb->n_used;
+       } else
+               return 0;
 }
 
 
+/**
+       @brief Translate a UTF8 string into escaped ASCII, suitable for JSON.
+       @param string The input string to be translated.
+       @param size The length of the input string (need not be accurate).
+       @param full_escape Boolean; true turns on the escaping of certain
+               special characters.
+       @return A pointer to the translated version of the string.
+
+       Deprecated.  Use buffer_append_utf8() instead.
+
+       If full_escape is non-zero, the translation will escape certain
+       certain characters with a backslash, according to the conventions
+       used in C and other languages: quotation marks, bell characters,
+       form feeds, horizontal tabs, carriage returns, line feeds, and
+       backslashes.  A character with a numerical value less than 32, and
+       not one of the special characters mentioned above, will be
+       translated to a backslash followed by four hexadecimal characters.
 
+       If full_escape is zero, the translation will (incorrectly) leave
+       these characters unescaped and unchanged.
+
+       The calling code is responsible for freeing the returned string.
+*/
 char* uescape( const char* string, int size, int full_escape ) {
 
+       if( NULL == string )
+               return NULL;
+       
        growing_buffer* buf = buffer_init(size + 64);
        int clen = 0;
        int idx = 0;
@@ -362,14 +639,20 @@ char* uescape( const char* string, int size, int full_escape ) {
                idx++;
        }
 
-       char* d = buffer_data(buf);
-       buffer_free(buf);
-       return d;
+       return buffer_release(buf);
 }
 
 
-// A function to turn a process into a daemon 
-int daemonize() {
+/**
+       @brief Become a proper daemon.
+       @param callback Ptr to a function.  From parent, called with child PID as first argument, and the next argument to *this* function as the second argument.  From child, called with -1, -1 (yes pid_t is signed)
+       @param callback_arg An integer argument passed as the second argument to the callback function from the parent process post-fork()
+       @return 0 if successful, or -1 if not.
+
+       Call fork().  The parent exits.  The child moves to the root directory, detaches from
+       the terminal, and redirects the standard streams (stdin, stdout, stderr) to /dev/null.
+*/
+int daemonizeWithCallback( void (*callback)(pid_t, int), int callback_arg ) {
        pid_t f = fork();
 
        if (f == -1) {
@@ -378,6 +661,9 @@ int daemonize() {
 
        } else if (f == 0) { // We're in the child now...
                
+               if( callback )
+                       callback( -1, -1 );
+
                // Change directories.  Otherwise whatever directory
                // we're in couldn't be deleted until the program
                // terminated -- possibly causing some inconvenience.
@@ -395,15 +681,36 @@ int daemonize() {
                return 0;
 
        } else { // We're in the parent...
+               if( callback )
+                       callback( f, callback_arg );
+
                _exit(0);
        }
 }
 
+/**
+       @brief Become a proper daemon.
+       @return 0 if successful, or -1 if not.
 
-/* Return 1 if the string represents an integer,  */
-/* as recognized by strtol(); Otherwise return 0. */
+       See daemonizeWithCallback() for details.
+*/
+int daemonize( void ) {
+       return daemonizeWithCallback( NULL, 0 );
+}
+
+
+/**
+       @brief Determine whether a string represents a decimal integer.
+       @param s A pointer to the string.
+       @return 1 if the string represents a decimal integer, or 0 if it
+       doesn't.
 
-int stringisnum(char* s) {
+       To qualify as a decimal integer, the string must consist entirely
+       of optional leading white space, an optional leading sign, and
+       one or more decimal digits.  In addition, the number must be
+       representable as a long.
+*/
+int stringisnum(const char* s) {
        char* w;
        strtol(s, &w, 10);
        return *w ? 0 : 1;
@@ -411,35 +718,18 @@ int stringisnum(char* s) {
        
 
 
-char* file_to_string(const char* filename) {
-
-       if(!filename) return NULL;
-
-       int len = 1024;
-       char buf[len];
-       memset(buf, 0, sizeof(buf));
-       growing_buffer* gb = buffer_init(len);
-
-       FILE* file = fopen(filename, "r");
-       if(!file) {
-               osrfLogError( OSRF_LOG_MARK, "Unable to open file [%s]", filename );
-               return NULL;
-       }
-
-       while(fgets(buf, sizeof(buf), file)) {
-               buffer_add(gb, buf);
-               memset(buf, 0, sizeof(buf));
-       }
-
-       fclose(file);
-
-       char* data = buffer_data(gb);
-       buffer_free(gb);
-       return data;
-}
+/**
+       @brief Translate a printf-style formatted string into an MD5 message digest.
+       @param text The format string.  Subsequent parameters, if any, provide values to be
+               formatted and inserted into the format string.
+       @return A pointer to a string of 32 hexadecimal characters.
 
+       The calling code is responsible for freeing the returned string.
 
-char* md5sum( char* text, ... ) {
+       This function is a wrapper for some public domain routines written by David Madore,
+       Ron Rivest, and Colin Plumb.
+*/
+char* md5sum( const char* text, ... ) {
 
        struct md5_ctx ctx;
        unsigned char digest[16];
@@ -455,8 +745,8 @@ char* md5sum( char* text, ... ) {
        MD5_stop (&ctx, digest);
 
        char buf[16];
-       char final[256];
-       memset(final, 0, sizeof(final));
+       char final[ 1 + 2 * sizeof( digest ) ];
+       final[0] = '\0';
 
        for ( i=0 ; i<16 ; i++ ) {
                snprintf(buf, sizeof(buf), "%02x", digest[i]);
@@ -467,6 +757,13 @@ char* md5sum( char* text, ... ) {
 
 }
 
+/**
+       @brief Determine whether a given file descriptor is valid.
+       @param fd The file descriptor to be checked.
+       @return 0 if the file descriptor is valid, or -1 if it isn't.
+
+       The most likely reason a file descriptor would be invalid is if it isn't open.
+*/
 int osrfUtilsCheckFileDescriptor( int fd ) {
 
        fd_set tmpset;
@@ -484,3 +781,26 @@ int osrfUtilsCheckFileDescriptor( int fd ) {
        return 0;
 }
 
+size_t osrfXmlEscapingLength ( const char* str ) {
+       int extra = 0;
+       const char* s;
+       for (s = str; *s; ++s) {
+               switch (*s) {
+                       case '>':
+                       case '<':
+                               extra += 3;
+                               break;
+                       case '&':
+                               extra += 4;
+                               break;
+                       case '"':
+                               extra += 11;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       return extra;
+}
+