Vstr is a string library designed to work easily and efficiently in network applications. This means that it is designed to perform IO operations in a non-blocking fashion, and allow the programer to take data from various places and join them in an IO operation without having to do any copies.
Often IO primitives are assumed to be blocking (Ie. all possible data will be done before IO operation completes). This does make "Hello World" type applications somewhat simpler, however networked applications have different requirements. However, don't be detered functions will be introduced for those simple applications to make them more readable.
Also note that all error checking is included in every example, it may make the examples somewhat easier to read if it wasn't included ... however including error checking is what the code must look like in a real application.
Here is about the most simple Vstr application, this is a single function that prints "Hello World" on a single line in a POSIX environment...
/* hello world - Self contained, using a single piece of data at * initialisation time (no data copying) */ #define VSTR_COMPILE_INCLUDE 1 #include <vstr.h> #include <errno.h> /* errno variable */ #include <err.h> /* BSD/Linux header see: man errx */ #include <unistd.h> /* for STDOUT_FILENO */ int main(void) { Vstr_base *s1 = NULL; if (!vstr_init()) /* initialize the library */ err(EXIT_FAILURE, "init"); /* create a string with data */ if (!(s1 = vstr_dup_cstr_buf(NULL, "Hello World\n"))) err(EXIT_FAILURE, "Create string"); /* output the data to the user -- assumes POSIX */ while (s1->len) if (!vstr_sc_write_fd(s1, 1, s1->len, STDOUT_FILENO, NULL)) { if ((errno != EAGAIN) && (errno != EINTR)) err(EXIT_FAILURE, "write"); } /* cleanup allocated resources */ vstr_free_base(s1); vstr_exit(); exit (EXIT_SUCCESS); }
...however this example is somewhat too simplistic because, normally, a Vstr string contains multiple nodes of informtion that are treated internally as a single entity.
So first we'll clean up the above example to move all the header inclusion into one file and create some helper functions to simplify the actually "hello world" code. Here's a quick overview of the functions moved into the header file...
/* ***************************************************************************** Beg of hello_world.h header file which will be used in the following examples ****************************************************************************/ #ifndef HELLO_WORLD_H #define HELLO_WORLD_H /* ************************************************************************** */ /* headers: Vstr (and all supporting system headers), plus extra ones we need */ /* ************************************************************************** */ #define VSTR_COMPILE_INCLUDE 1 /* make Vstr include it's system headers */ #include <vstr.h> #include <errno.h> #include <err.h> /* BSD/Linux header see: man errx */ #include <unistd.h> /* for STDOUT_FILENO */ /* ********************************* */ /* generic POSIX IO helper functions */ /* ********************************* */ #define IO_OK 0 /* the missing values will be explained later in the tutorial... */ #define IO_NONE 3 static int io_put(Vstr_base *io_w, int fd) { /* assumes POSIX */ if (!io_w->len) return (IO_NONE); if (!vstr_sc_write_fd(io_w, 1, io_w->len, fd, NULL)) { if (errno != EINTR) err(EXIT_FAILURE, "write"); } return (IO_OK); } /* ************************ */ /* generic helper functions */ /* ************************ */ /* hello world init function, init library and create a string */ static Vstr_base *hw_init(void) { Vstr_base *s1 = NULL; if (!vstr_init()) errno = ENOMEM, err(EXIT_FAILURE, "init"); if (!(s1 = vstr_make_base(NULL))) /* create an empty string */ errno = ENOMEM, err(EXIT_FAILURE, "Create string"); return (s1); } /* hello world exit function, cleanup what was allocated in hw_init() */ static int hw_exit(Vstr_base *s1) { vstr_free_base(s1); vstr_exit(); return (EXIT_SUCCESS); } #endif /* ***************************************************************************** End of hello_world.h header file which will be used in the following examples ****************************************************************************/
Now using the above header file, we can re-write the initial example in a much more readable form...
/* hello world - Using a single piece of data (no data copying) */ #include "ex_hello_world.h" /* helper functions */ int main(void) { Vstr_base *s1 = hw_init(); vstr_add_cstr_buf(s1, s1->len, "Hello World\n"); if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); while (io_put(s1, STDOUT_FILENO) != IO_NONE) {} exit (hw_exit(s1)); }
We can now alter the above to still print "Hello World" on a line, but have the data come from multiple sources which is then stored internally in mutliple nodes (remember there is no copying of data still). However can treat this all as a single string from our point of view. Although this example is obviously contrived, this is much more representative of what a networked application looks like...
/* hello world - multiple sources of data (still no data copying) */ #include "ex_hello_world.h" /* helper functions */ int main(void) { Vstr_base *s1 = hw_init(); vstr_add_cstr_ptr(s1, s1->len, "Hello"); vstr_add_cstr_ptr(s1, s1->len, " "); vstr_add_cstr_ptr(s1, s1->len, "World\n"); /* we are checking whether any of the above three functions failed here */ if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); /* loop until all data is output... */ while (io_put(s1, STDOUT_FILENO) != IO_NONE) {} exit (hw_exit(s1)); }
This is the final Hello World example, this is the first one that actually copies some of the data for the string. It also shows how you can add data at any point in the string, and substitute data within the string. Note that when giving the position to add data to the string you give the position before the position you wish the start of the data to be at, and when giving the position/length for a section (or sub-string) the position given is included within the section.
For people familiar with C++ this works out to be the same way that C++ std::string encodes positions for adding data (Ie. insert()), but not for getting sections (C++ often explains it as having a 0 index'd string and data is added before the point given, Vstr does uses a 1 poition for the first byte as that means that appending data is always done at the current length ... and a position of 0 can be used as invalid for searching etc.).
Description of operation | Position | Legnth |
Prepend data to X | 0 (Zero) | N/A |
Append data to X | X->len (Length of Vstr string) | N/A |
Last byte | X->len (Length of Vstr string) | 1 (One) |
First byte | 1 (One) | 1 (One) |
Entire Vstr string | 1 (One) | X->len (Length of Vstr string) |
All of X, but the first and last bytes | 2 (Two) | X->len - 2 (Length of Vstr string minus Two) |
/* hello world - multiple pieces of data, includes substitution and * inserting data into the middle of a string */ #include "ex_hello_world.h" /* helper functions */ int main(void) { Vstr_base *s1 = hw_init(); Vstr_ref *ref = NULL; vstr_add_cstr_ptr(s1, s1->len, "Hello"); vstr_add_rep_chr(s1, s1->len, 'W', 5); /* add "WWWWWW" */ if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); /* substitute an 'o' for a 'W' */ if (!vstr_sub_rep_chr(s1, strlen("HelloWW"), 1, 'o', 1)) errno = ENOMEM, err(EXIT_FAILURE, "Substitute string data"); /* substitute "WWW" for a "rld\n" -- */ if (!vstr_sub_cstr_buf(s1, strlen("HelloWoW"), strlen("WWW"), "rld\n")) errno = ENOMEM, err(EXIT_FAILURE, "Substitute string data"); if (!(ref = vstr_ref_make_ptr((char *)"XYZ ", vstr_ref_cb_free_ref))) errno = ENOMEM, err(EXIT_FAILURE, "Create data reference"); /* now ref->ptr is "XYZ " */ /* add space after "Hello", by skipping "XYZ" in reference */ vstr_add_ref(s1, strlen("Hello"), ref, strlen("XYZ"), strlen(" ")); vstr_ref_del(ref); /* delete our reference to the Vstr_ref */ if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); while (io_put(s1, STDOUT_FILENO) != IO_NONE) {} exit (hw_exit(s1)); }
This is a full header file needed to do simple non-blocking IO operations, it also puts into functions the common init and exit sections. This will be used by all of the following examples. The Simple GETOPT implementation isn't used for a while, so you can ignore that for now. A quick overview of the changes from the hello_world.h header file are...
And a quick review of the additions...
#ifndef EX_UTILS_H #define EX_UTILS_H 1 /* ************************************************************************** */ /* headers: Vstr (and all supporting system headers), plus extra ones we need */ /* ************************************************************************** */ #define VSTR_COMPILE_INCLUDE 1 /* make Vstr include it's system headers */ #include <vstr.h> #include <errno.h> #include <err.h> /* BSD/Linux header see: man errx */ #include <sys/poll.h> #include <sys/types.h> /* stat + open + STDXXX_FILENO */ #include <sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <string.h> /* ******************************** */ /* defines: TRUE/FALSE and assert() */ /* ******************************** */ #ifndef FALSE # define FALSE 0 #endif #ifndef TRUE # define TRUE 1 #endif #define assert(x) do { if (x) {} else errx(EXIT_FAILURE, "Assert=\"" #x "\", FAILED at line %u", __LINE__); } while (FALSE) #define ASSERT(x) do { if (x) {} else errx(EXIT_FAILURE, "Assert=\"" #x "\", FAILED at line %u", __LINE__); } while (FALSE) /* ********************************* */ /* generic POSIX IO helper functions */ /* ********************************* */ /* limits on amount of data we keep in core -- can be overridden */ /* Note that EX_UTILS_NO_USE_INPUT should be defined if Input IO isn't needed */ #ifndef EX_MAX_R_DATA_INCORE # define EX_MAX_R_DATA_INCORE (8 * 1024) #endif #ifndef EX_MAX_W_DATA_INCORE # define EX_MAX_W_DATA_INCORE (8 * 1024) #endif #define IO_OK 0 #define IO_BLOCK 1 #define IO_EOF 2 #define IO_NONE 3 /* block waiting for IO read, write or both... */ static void io_block(int io_r_fd, int io_w_fd) { struct pollfd ios_beg[2]; struct pollfd *ios = ios_beg; unsigned int num = 0; ios[0].revents = ios[1].revents = 0; if (io_r_fd == io_w_fd) { /* block on both read and write, same fds */ num = 1; ios[0].events = POLLIN | POLLOUT; ios[0].fd = io_w_fd; } else { /* block on read or write or both */ if (io_r_fd != -1) { ios->events = POLLIN; ios->fd = io_r_fd; ++num; ++ios; } if (io_w_fd != -1) { ios->events = POLLOUT; ios->fd = io_w_fd; ++num; ++ios; } } while (poll(ios_beg, num, -1) == -1) /* can't timeout */ { if (errno != EINTR) err(EXIT_FAILURE, "poll"); } } /* Try and move some data from Vstr string to fd */ static int io_put(Vstr_base *io_w, int fd) { if (!io_w->len) return (IO_NONE); if (!vstr_sc_write_fd(io_w, 1, io_w->len, fd, NULL)) { if (errno == EAGAIN) return (IO_BLOCK); err(EXIT_FAILURE, "write"); } return (IO_OK); } /* loop outputting data until empty, blocking when needed */ static void io_put_all(Vstr_base *io_w, int fd) { int state = IO_NONE; while ((state = io_put(io_w, fd)) != IO_NONE) { if (state == IO_BLOCK) io_block(-1, fd); } } #ifndef EX_UTILS_NO_USE_INPUT /* Try and move some data from fd to Vstr string */ static int io_get(Vstr_base *io_r, int fd) { if (io_r->len < EX_MAX_R_DATA_INCORE) { unsigned int ern = 0; vstr_sc_read_iov_fd(io_r, io_r->len, fd, 8, 16, &ern); if (ern == VSTR_TYPE_SC_READ_FD_ERR_EOF) return (IO_EOF); else if ((ern == VSTR_TYPE_SC_READ_FD_ERR_READ_ERRNO) && (errno == EAGAIN)) return (IO_BLOCK); else if (ern) err(EXIT_FAILURE, "read"); } return (IO_OK); } /* block read or writting, depending on limits */ static void io_limit(int io_r_state, int io_r_fd, int io_w_state, int io_w_fd, Vstr_base *s_w) { if (io_w_state == IO_BLOCK) /* allow 16k to build up */ { if (io_r_state == IO_BLOCK) /* block to either get or put some data */ io_block(io_r_fd, io_w_fd); else if (s_w->len > EX_MAX_W_DATA_INCORE) io_block(-1, io_w_fd); /* block to put more data */ } else if ((io_w_state == IO_NONE) && (io_r_state == IO_BLOCK)) io_block(io_r_fd, -1); /* block to get more data */ } #endif /* generic POSIX IO functions that _don't_ call Vstr functions */ static int io_fd_set_o_nonblock(int fd) { int flags = 0; /* see if the NONBLOCK flag is set... */ if ((flags = fcntl(fd, F_GETFL)) == -1) return (FALSE); /* if it isn't try and add it to the current flags */ if (!(flags & O_NONBLOCK) && (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)) return (FALSE); return (TRUE); } #ifndef VSTR_AUTOCONF_HAVE_OPEN64 # define open64 open #endif static int io_open(const char *filename) { int fd = open64(filename, O_RDONLY | O_NOCTTY); if (fd == -1) err(EXIT_FAILURE, "open(%s)", filename); /* When doing IO, it should always be non-blocking */ io_fd_set_o_nonblock(fd); return (fd); } /* ************************ */ /* generic helper functions */ /* ************************ */ /* Example init function */ static Vstr_base *ex_init(Vstr_base **s2) { Vstr_base *s1 = NULL; struct stat stat_buf; if (!vstr_init()) /* init the library */ errno = ENOMEM, err(EXIT_FAILURE, "init"); /* alter the node buffer size to be whatever the stdout block size is */ if (fstat(1, &stat_buf) == -1) warn("fstat(STDOUT)"); else { if (!stat_buf.st_blksize) /* this is allowed to be Zero */ stat_buf.st_blksize = 4096; if (!vstr_cntl_conf(NULL, VSTR_CNTL_CONF_SET_NUM_BUF_SZ, stat_buf.st_blksize / 8)) warnx("Couldn't alter node size to match block size"); } /* create strings... */ if (!(s1 = vstr_make_base(NULL)) || (s2 && !(*s2 = vstr_make_base(NULL)))) errno = ENOMEM, err(EXIT_FAILURE, "Create string"); /* create some data storage for _both_ of the above strings */ vstr_make_spare_nodes(NULL, VSTR_TYPE_NODE_BUF, 32); /* Try and make stdout non-blocking, if it is a file this won't do anything */ io_fd_set_o_nonblock(STDOUT_FILENO); return (s1); } /* Example exit function */ static int ex_exit(Vstr_base *s1, Vstr_base *s2) { /* These next calls are only really needed for debugging, * in that when they are done any memory leaks can be seen in debugging mode. */ /* As with the system free() both of these are ok if passed NULL */ /* free s1, our String object */ vstr_free_base(s1); /* free s2, our String object */ vstr_free_base(s2); /* "exit" Vstr, this free's all internal data and no library calls apart from * vstr_init() should be called after this. */ vstr_exit(); return (EXIT_SUCCESS); } #endif
This is the unix cat command, implemented with the help of the functions in the above ex_utils.h header file. This uses the same Vstr string for both input and output and uses non-blocking IO, both of which are done for efficiency.
If you have seen something like the "simple" cat in LAD this version looks much bigger. However one of the main reasons for this is that the LAD version has many bugs. The main problem is the lack of checking on the IO calls, this is most easily demonstrated by running it like so...
perl -e 'use Fcntl; fcntl(STDIN, F_SETFL, O_NONBLOCK); exec(@ARGV);' ./cat
...will cause the LAD version to exit immediatley due to an EAGAIN being returned from read. This problem also affects the LAD version being used in a blocking pipe due to the fact that write() isn't required to write() all it's data.
The LAD version also doesn't open() any files, which is significant functionality. So after fixing those bugs we get something that is much closer to the Vstr version and it still suffers from performance problems due to the need to block on input and output separately. It is possible to create a version using read, write and poll that would perform the same as the Vstr version ... however even the simplest method would have to implement it's own ring buffer which is very prone to error and would almost certainly make it bigger than ex_cat.c and ex_utils.h combined.
/* This is a _simple_ cat program, no command line options. And no use of mmap. * Reads from stdin if no args are given. * * This shows how to use the Vstr library at it's simpelest, * for easy and fast IO. Note however that all needed error detection is * included. * * This file is more commented than normal code, so as to make it easy to follow * while knowing almost nothing about Vstr or Linux IO programming. */ #include "ex_utils.h" /* Keep reading on the file descriptor until there is no more data (ERR_EOF) * abort if there is an error reading or writing */ static void ex_cat_read_fd_write_stdout(Vstr_base *s1, int fd) { while (TRUE) { int io_w_state = IO_OK; int io_r_state = io_get(s1, fd); if (io_r_state == IO_EOF) break; io_w_state = io_put(s1, 1); io_limit(io_r_state, fd, io_w_state, 1, s1); } } int main(int argc, char *argv[]) { /* This is "cat", without any command line options */ Vstr_base *s1 = ex_init(NULL); /* init the library etc. */ int count = 1; /* skip the program name */ /* if no arguments are given just do stdin to stdout */ if (count >= argc) { io_fd_set_o_nonblock(STDIN_FILENO); ex_cat_read_fd_write_stdout(s1, STDIN_FILENO); } /* loop through all arguments, open the file specified * and do the read/write loop */ while (count < argc) { int fd = io_open(argv[count]); ex_cat_read_fd_write_stdout(s1, fd); if (close(fd) == -1) warn("close(%s)", argv[count]); ++count; } /* output all remaining data */ io_put_all(s1, STDOUT_FILENO); exit (ex_exit(s1, NULL)); }
This is somewhat like the "nl" unix command, this is implemented in much the same way as the cat command. However the data has to have something added to the start of each line before it can be output, so we now have two string objects: One for input and one for output. Note that as the data is "moved" from the input to the output string object, it isn't copied instead a reference is created and shared between the two strings.
/* Unix nl command -- no arguments */ #include "ex_utils.h" static int ex_nl_process(Vstr_base *s1, Vstr_base *s2, int last) { static unsigned int count = 0; size_t pos = 0; /* we don't want to create more data, if we are over our limit */ if (s1->len > EX_MAX_W_DATA_INCORE) return (FALSE); if (!s2->len) return (TRUE); while ((pos = vstr_srch_chr_fwd(s2, 1, s2->len, '\n'))) { vstr_add_fmt(s1, s1->len, "% 6d\t", ++count); /* The flag turns _BUF nodes into sharable _REF nodes */ vstr_add_vstr(s1, s1->len, s2, 1, pos, VSTR_TYPE_ADD_BUF_REF); vstr_del(s2, 1, pos); if (s1->len > EX_MAX_W_DATA_INCORE) return (TRUE); } if (s2->len && last) { vstr_add_fmt(s1, s1->len, "% 6d\t", ++count); if (s2->len) vstr_mov(s1, s1->len, s2, 1, s2->len); vstr_add_cstr_buf(s1, s1->len, "\n"); } return (TRUE); } /* files are merged */ static void ex_nl_read_fd_write_stdout(Vstr_base *s1, Vstr_base *s2, int fd) { while (TRUE) { int io_w_state = IO_OK; int io_r_state = io_get(s2, fd); if (io_r_state == IO_EOF) break; ex_nl_process(s1, s2, FALSE); io_w_state = io_put(s1, 1); io_limit(io_r_state, fd, io_w_state, 1, s1); } } static void ex_nl_process_limit(Vstr_base *s1, Vstr_base *s2, unsigned int lim) { while (s2->len > lim) { int proc_data = ex_nl_process(s1, s2, !lim); if (!proc_data && (io_put(s1, STDOUT_FILENO) == IO_BLOCK)) io_block(-1, STDOUT_FILENO); } } int main(int argc, char *argv[]) { Vstr_base *s2 = NULL; Vstr_base *s1 = ex_init(&s2); int count = 1; /* if no arguments are given just do stdin to stdout */ if (count >= argc) { io_fd_set_o_nonblock(STDIN_FILENO); ex_nl_read_fd_write_stdout(s1, s2, STDIN_FILENO); } /* loop through all arguments, open the file specified * and do the read/write loop */ while (count < argc) { unsigned int ern = 0; if (s2->len <= EX_MAX_R_DATA_INCORE) vstr_sc_mmap_file(s2, s2->len, argv[count], 0, 0, &ern); if ((ern == VSTR_TYPE_SC_MMAP_FILE_ERR_FSTAT_ERRNO) || (ern == VSTR_TYPE_SC_MMAP_FILE_ERR_MMAP_ERRNO) || (ern == VSTR_TYPE_SC_MMAP_FILE_ERR_TOO_LARGE)) { int fd = io_open(argv[count]); ex_nl_read_fd_write_stdout(s1, s2, fd); if (close(fd) == -1) warn("close(%s)", argv[count]); } else if (ern && (ern != VSTR_TYPE_SC_MMAP_FILE_ERR_CLOSE_ERRNO)) err(EXIT_FAILURE, "add"); else ex_nl_process_limit(s1, s2, EX_MAX_R_DATA_INCORE); ++count; } ex_nl_process_limit(s1, s2, 0); io_put_all(s1, STDOUT_FILENO); exit (ex_exit(s1, s2)); }
This is somewhat like the "hexdump" unix command, it also uses the same simple IO model used in the cat and nl commands. However the data is now output twice once as hex values, and a second time as characters (converting unprintable characters into '.' characters). So again we have two string objects: One for input and one for output. To get two copies of the data, we initially export the data to a buffer and then convert that to hex via vstr_add_fmt() (the printf like function). We then convert the data that is still in the string object so it is printable and move it from the input to the output string objcet. Note that as the data is "moved" from the input to the output string object, it is always copied even if it was a reference on input (Ie. mmap()ed).
One other thing that is new in the hexdump command is the use of the VSTR_FLAGXX() macro fucntion, this is a convienience feature for when you need to specify multiple flags at once.
/* This is a fairly simple hexdump program, it has command line options to * enable printing of high latin symbols, and/or use mmap() to load the input * data. * * Reads from stdin if no args are given. * * This shows how to use the Vstr library for simple data convertion. * * This file is more commented than normal code, so as to make it easy to follow * while knowing almost nothing about Vstr or Linux IO programming. */ #include "ex_utils.h" /* hexdump in "readable" format ... note this is a bit more fleshed out than * some of the other examples mainly because I actually use it */ /* this is roughly equiv. to the Linux hexdump command... % rpm -qf /usr/bin/hexdump util-linux-2.11r-10 % hexdump -e '"%08_ax:" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X" " " 2/1 "%02X"' -e '" " 16 "%_p" "\n"' * ...except that it prints the address in big hex digits, and it doesn't take * you 30 minutes to remember how to type it out. * It also acts differently in that seperate files aren't merged * into one output line (Ie. in this version each file starts on a new line, * however the addresses are continuious). */ #define PRNT_NONE 0 #define PRNT_SPAC 1 #define PRNT_HIGH 2 /* number of characters we output per line (assumes 80 char width screen)... */ #define EX_HEXDUMP_CHRS_PER_LINE 16 /* configure what ASCII characters we print */ static unsigned int prnt_high_chars = PRNT_NONE; /* simple print of a number */ /* print the address */ # define EX_HEXDUMP_X8(s1, num) \ vstr_add_fmt(s1, (s1)->len, "0x%08X:", (num)) /* print a set of two bytes */ # define EX_HEXDUMP_X2X2(s1, num1, num2) \ vstr_add_fmt(s1, (s1)->len, " %02X%02X", (num1), (num2)) /* print a byte and spaces for the missing byte */ # define EX_HEXDUMP_X2__(s1, num1) \ vstr_add_fmt(s1, (s1)->len, " %02X ", (num1)) static int ex_hexdump_process(Vstr_base *s1, Vstr_base *s2, int last) { static unsigned int addr = 0; /* normal ASCII chars, just allow COMMA and DOT flags */ unsigned int flags = VSTR_FLAG02(CONV_UNPRINTABLE_ALLOW, COMMA, DOT); /* allow spaces, allow COMMA, DOT, underbar _, and space */ unsigned int flags_sp = VSTR_FLAG04(CONV_UNPRINTABLE_ALLOW, COMMA, DOT, _, SP); /* high ascii too, allow * COMMA, DOT, underbar _, space, high space and other high characters */ unsigned int flags_hsp = VSTR_FLAG06(CONV_UNPRINTABLE_ALLOW, COMMA, DOT, _, SP, HSP, HIGH); unsigned char buf[EX_HEXDUMP_CHRS_PER_LINE]; switch (prnt_high_chars) { case PRNT_HIGH: flags = flags_hsp; break; case PRNT_SPAC: flags = flags_sp; break; case PRNT_NONE: break; default: ASSERT(FALSE); break; } /* we don't want to create more data, if we are over our limit */ if (s1->len > EX_MAX_W_DATA_INCORE) return (FALSE); /* while we have a hexdump line ... */ while (s2->len >= EX_HEXDUMP_CHRS_PER_LINE) { unsigned int count = 0; /* get a hexdump line from the vstr */ vstr_export_buf(s2, 1, EX_HEXDUMP_CHRS_PER_LINE, buf, sizeof(buf)); /* write out a hexdump line address */ EX_HEXDUMP_X8(s1, addr); /* write out hex values */ while (count < EX_HEXDUMP_CHRS_PER_LINE) { EX_HEXDUMP_X2X2(s1, buf[count], buf[count + 1]); count += 2; } vstr_add_cstr_buf(s1, s1->len, " "); /* convert unprintable characters to the '.' character */ vstr_conv_unprintable_chr(s2, 1, EX_HEXDUMP_CHRS_PER_LINE, flags, '.'); /* write out characters, converting reference and pointer nodes to * _BUF nodes */ vstr_add_vstr(s1, s1->len, s2, 1, EX_HEXDUMP_CHRS_PER_LINE, VSTR_TYPE_ADD_ALL_BUF); vstr_add_rep_chr(s1, s1->len, '\n', 1); addr += EX_HEXDUMP_CHRS_PER_LINE; /* delete the line just processed */ vstr_del(s2, 1, EX_HEXDUMP_CHRS_PER_LINE); /* note that we don't want to create data indefinitely, so stop * according to in core configuration */ if (s1->len > EX_MAX_W_DATA_INCORE) return (TRUE); } if (last && s2->len) { /* do the same as above, but print the partial line for * the end of files */ size_t got = s2->len; size_t missing = EX_HEXDUMP_CHRS_PER_LINE - s2->len; const char *ptr = buf; missing -= (missing % 2); vstr_export_buf(s2, 1, s2->len, buf, sizeof(buf)); EX_HEXDUMP_X8(s1, addr); while (got >= 2) { EX_HEXDUMP_X2X2(s1, ptr[0], ptr[1]); got -= 2; ptr += 2; } if (got) { EX_HEXDUMP_X2__(s1, ptr[0]); got -= 2; } /* easy way to add X amount of ' ' characters */ vstr_add_rep_chr(s1, s1->len, ' ', (missing * 2) + (missing / 2) + 2); vstr_conv_unprintable_chr(s2, 1, s2->len, flags, '.'); vstr_add_vstr(s1, s1->len, s2, 1, s2->len, VSTR_TYPE_ADD_ALL_BUF); vstr_add_cstr_buf(s1, s1->len, "\n"); addr += s2->len; vstr_del(s2, 1, s2->len); return (TRUE); } /* if any of the above memory mgmt failed, error */ if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "adding data:"); return (FALSE); } static void ex_hexdump_process_limit(Vstr_base *s1, Vstr_base *s2, unsigned int lim) { while (s2->len > lim) { /* Finish processing read data (try writing if we need memory) */ int proc_data = ex_hexdump_process(s1, s2, !lim); if (!proc_data && (io_put(s1, STDOUT_FILENO) == IO_BLOCK)) io_block(-1, STDOUT_FILENO); } } /* we process an entire file at a time... */ static void ex_hexdump_read_fd_write_stdout(Vstr_base *s1, Vstr_base *s2, int fd) { /* read/process/write loop */ while (TRUE) { int io_w_state = IO_OK; int io_r_state = io_get(s2, fd); if (io_r_state == IO_EOF) break; ex_hexdump_process(s1, s2, FALSE); io_w_state = io_put(s1, 1); io_limit(io_r_state, fd, io_w_state, 1, s1); } /* write out all of the end of the file, * so the next file starts on a new line */ ex_hexdump_process_limit(s1, s2, 0); } int main(int argc, char *argv[]) { /* This is "hexdump", as it should be by default */ Vstr_base *s2 = NULL; Vstr_base *s1 = ex_init(&s2); /* init the library, and create two strings */ int count = 1; /* skip the program name */ unsigned int use_mmap = FALSE; /* parse command line arguments... */ while (count < argc) { /* quick hack getopt_long */ if (!strcmp("--", argv[count])) { ++count; break; } else if (!strcmp("--mmap", argv[count])) /* toggle use of mmap */ use_mmap = !use_mmap; else if (!strcmp("--none", argv[count])) /* choose what is displayed */ prnt_high_chars = PRNT_NONE; /* just simple 7 bit ASCII, no spaces */ else if (!strcmp("--space", argv[count])) prnt_high_chars = PRNT_SPAC; /* allow spaces */ else if (!strcmp("--high", argv[count])) prnt_high_chars = PRNT_HIGH; /* allow high bit characters */ else if (!strcmp("--version", argv[count])) { /* print version and exit */ vstr_add_fmt(s1, 0, "%s", "\ jhexdump 1.0.0\n\ Written by James Antill\n\ \n\ Uses Vstr string library.\n\ "); goto out; } else if (!strcmp("--help", argv[count])) { /* print version and exit */ vstr_add_fmt(s1, 0, "%s", "\ Usage: jhexdump [STRING]...\n\ or: jhexdump OPTION\n\ Repeatedly output a line with all specified STRING(s), or `y'.\n\ \n\ --help Display this help and exit\n\ --version Output version information and exit\n\ --high Allow space and high characters in ASCII output\n\ --none Allow only small amount of characters ASCII output (default)\n\ --space Allow space characters in ASCII output\n\ --mmap Toggle use of mmap() to load input files\n\ -- Treat rest of cmd line as input filenames\n\ \n\ Report bugs to James Antill <james@and.org>.\n\ "); goto out; } else break; ++count; } /* if no arguments are given just do stdin to stdout */ if (count >= argc) { io_fd_set_o_nonblock(STDIN_FILENO); ex_hexdump_read_fd_write_stdout(s1, s2, STDIN_FILENO); } /* loop through all arguments, open the file specified * and do the read/write loop */ while (count < argc) { unsigned int ern = 0; ASSERT(!s2->len); /* all input is fully processed before each new file */ /* try to mmap the file */ if (use_mmap) vstr_sc_mmap_file(s2, s2->len, argv[count], 0, 0, &ern); if (!use_mmap || (ern == VSTR_TYPE_SC_MMAP_FILE_ERR_FSTAT_ERRNO) || (ern == VSTR_TYPE_SC_MMAP_FILE_ERR_MMAP_ERRNO) || (ern == VSTR_TYPE_SC_MMAP_FILE_ERR_TOO_LARGE)) { /* if mmap didn't work ... do a read/alter/write loop */ int fd = io_open(argv[count]); ex_hexdump_read_fd_write_stdout(s1, s2, fd); if (close(fd) == -1) warn("close(%s)", argv[count]); } else if (ern && (ern != VSTR_TYPE_SC_MMAP_FILE_ERR_CLOSE_ERRNO)) err(EXIT_FAILURE, "add"); else /* mmap worked so processes the entire file at once */ ex_hexdump_process_limit(s1, s2, 0); ++count; } ASSERT(!s2->len); /* all input is fully processed before each new file */ /* Cleanup... */ out: io_put_all(s1, STDOUT_FILENO); exit (ex_exit(s1, s2)); }
Custom formatters are an exteremly useful feature of the Vstr string library, allowing you to safely implement one or more methods of printing a pointer to an arbitrary object. The most simple uses are to use the builtin custom formatters. This examples shows how you enable the IPv4 and Vstr custom formatters and then use how you can use them.
This example also includes vstr_sc_basename() which acts in a similar way to the POSIX basename function.
/* This is a _simple_ program to lookup a hostname via. gethostbyname(). * * This shows how easy it is to use custom format specifiers, to make your code * easier to write and maintain. * * This file is more commented than normal code, so as to make it easy to follow * while knowning almost nothing about Vstr or Linux IO programming. */ #include "ex_utils.h" #include <sys/socket.h> #include <netdb.h> int main(int argc, char *argv[]) { Vstr_base *s1 = ex_init(NULL); /* init the library, and create a string */ struct hostent *hp = NULL; /* data from the resolver library */ /* setup the pre-written custom format specifier for IPv4 addresses, */ vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$'); vstr_sc_fmt_add_ipv4_ptr(s1->conf, "{IPv4:%p}"); if (argc != 2) /* if a hostname isn't given output an message to stderr */ { size_t pos = 0; size_t len = 0; /* add another format specifier for printing Vstr strings */ vstr_sc_fmt_add_vstr(s1->conf, "{Vstr:%p%zu%zu%u}"); /* find the program name ... * putting it at the begining of the Vstr string */ vstr_add_cstr_ptr(s1, 0, argc ? argv[0] : "lookup_ip"); vstr_sc_basename(s1, 1, s1->len, &pos, &len); /* add a format line to the Vstr string, including the program name * which is at the begining of this Vstr string itself */ len = vstr_add_fmt(s1, s1->len, " %s ${Vstr:%p%zu%zu%u} %s\n", "Format:", s1, pos, len, 0, "<hostname>"); vstr_del(s1, 1, s1->len - len); /* delete the original program name */ io_put_all(s1, STDERR_FILENO); exit (EXIT_FAILURE); } /* call libc to lookup the hostname */ hp = gethostbyname(argv[1]); /* just print the relevant data.... Note that nothing complicated needs to * be done to print the IPv4 address, the custom formatter takes care of * it */ if (!hp) vstr_add_fmt(s1, 0, " Error retrieving hostname '%s': %s.\n", argv[1], hstrerror(h_errno)); else if (hp->h_addrtype == AF_INET) vstr_add_fmt(s1, 0, " The hostname '%s' has an " "IPv4 address of \"${IPv4:%p}\".\n", hp->h_name, hp->h_addr_list[0]); else vstr_add_fmt(s1, 0, " The hostname '%s' has an address type that " "isn't an IPv4 address.\n", hp->h_name); /* Cleanup... */ io_put_all(s1, STDOUT_FILENO); exit (ex_exit(s1, NULL)); }
So, printing ipv4 address is nice ... but the big benifit, with custom formatters, come with using them on types that you have to deal with a lot. This usually means types that you've defined yourself, or are defined in a library you are using. So this example will show you how to create your own custom formatters. I'll use the GNU multiple precision arithmetic library which is a well used library for creating arbitrary precision numbers (Ie. numbers that can represent any value). One of the annoying features of using this library is that it is non-trivial to easily turn these numbers into strings and/or output them as you would with a normal int/long/size_t/intmax_t/etc.
The GMP library has a set of printf like functions, and while you can create a limited length string, newly allocated string or create output to a file using these functions they fail the "Easy" test for a number of reasons...
It is not possible to use the grouping format specifier with the GNU MP variables. This means that when running under Linux/glibc, while...
int d; char *ret = NULL; /* ... */ gmp_asprintf(&ret, "%s is an num %'d.\n", "int", d);
...creates a C style string for the number in a readable format for the locale, the GNU MP variable equvilent...
mpz_t z; char *ret = NULL; /* ... */ gmp_asprintf(&ret, "%s is an bignum %'Zd.\n", "mpz_t", z);
...doesn't do anything other than create the number.
Any static printf like function format specifier checking has to be disabled for these functions due to the way the hard coded custom formaters are implemented. This means that although if you do...
int d; char *ret = NULL; /* ... */ asprintf(&ret, "%.*s is an num %d.\n", "int", d);
...gcc will tell you there is an error, when you do...
mpz_t z; char *ret = NULL; /* ... */ gmp_asprintf(&ret, "%.*s is an bignum %Zd.\n", "mpz_t", z);
...the code will happily compile, and then almost certainly crash when you run it.
You also lose all of the great benifits of using vstr_add_fmt(), the biggest problem here is that the gmp printf like functions pass all non gmp types directly to the underlying snprintf/etc. call. Although the extra speed/memory benifits of Vstr are nice too :).
Even if all of these problems were fixed in some future version, this still only solves the problem for GMP types. So if you have one or more other custom types that you need to format, you'd need yet another function.
The custom formatter for mpz_t is about 25 lines of code in this example, it could be a little less if you removed some features (Ie. supporting positive or negative values) or always got libgmp to allocate storage and then free it (or if some libgmp APIs were defined in a more user friendly manner).
However the actual complexity is pretty small, and this not only fully implements everything that gmp_printf() can do for that variable (safely), but also implements grouping.
/* Simple libgmp command, it prints the factorial of the first argument */ /* we only need output here, so turn off other IO functions */ #define EX_UTILS_NO_USE_INPUT 1 #define EX_UTILS_NO_USE_OPEN 1 #include "ex_utils.h" /* helper functions */ #include <limits.h> #include <gmp.h> #include <locale.h> /* if this is enabled we go through the factorials twice, which means we do * almost twice as much work ... the output is more readable for small values * though */ #define EX_GMP_FACT_USE_FIELDWIDTH 1 /* This is the custom formatter. * Note that this deals with grouping unlike the gmp_*printf() calls */ static int ex__usr_mpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec, /* gmp args, need to be in paramter list */ const mpz_t val) { int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NUM; /* it's a number */ size_t len = 0; int ret = FALSE; char ui_buf[sizeof(unsigned long) * CHAR_BIT]; char *buf = NULL; char *out_buf = ui_buf; if (mpz_sgn(val) == -1) /* it's a negative number */ flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NEG; if (mpz_fits_ulong_p(val)) /* it's a simple number */ len = vstr_sc_conv_num10_ulong(ui_buf, sizeof(ui_buf), mpz_get_ui(val)); else /* bignum, so get libgmp to export it as a string */ { len = mpz_sizeinbase(val, 10); /* doesn't include minus sign */ out_buf = buf = mpz_get_str(NULL, 10, val); /* dies on malloc error */ if (mpz_sgn(val) == -1) ++out_buf; /* skip the minus sign */ if (!out_buf[len - 1]) --len; /* see documentation for mpz_sizeinbase() */ } /* this deals with things like having the the zero flag (Ie. %0d), or the * plus flag (Ie. %+d) or right shifted field widths */ if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags)) goto mem_fail; if (spec->fmt_quote) /* add number including grouping */ ret = vstr_sc_add_grpnum_buf(base, pos, out_buf, len); else /* just add the number */ ret = vstr_add_buf(base, pos, out_buf, len); /* this deals with left shifted field widths */ if (!ret || !vstr_sc_fmt_cb_end(base, pos, spec, len)) goto mem_fail; free(buf); return (TRUE); mem_fail: free(buf); return (FALSE); } /* we need to jump though an extra function due to the way GMP defines the * mpz_t type */ static int ex_usr_mpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec) { void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0); return (ex__usr_mpz_cb(base, pos, spec, mpz)); } /* The code to calculate the factorial... */ static void ex_gmp_fact(mpz_t bignum_ret, mpz_t bignum_cnt, mpz_t bignum_for, int out, Vstr_base *s1, int ret_max_sz, int cnt_max_sz) { while (mpz_cmp(bignum_cnt, bignum_for) <= 0) { int w_state = IO_OK; mpz_mul(bignum_ret, bignum_ret, bignum_cnt); if (out) { /* output the current values */ vstr_add_fmt(s1, s1->len, "$'*<MPZ:%*p>%s %c $'*<MPZ:%*p>\n", cnt_max_sz, (void *)bignum_cnt, "!", '=', ret_max_sz, (void *)bignum_ret); if (s1->conf->malloc_bad) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); w_state = io_put(s1, STDOUT_FILENO); if ((w_state == IO_BLOCK) && (s1->len > EX_MAX_W_DATA_INCORE)) io_block(-1, STDOUT_FILENO); } mpz_add_ui(bignum_cnt, bignum_cnt, 1); } } int main(int argc, char *argv[]) { Vstr_base *s1 = ex_init(NULL); mpz_t bignum_ret; mpz_t bignum_for; mpz_t bignum_cnt; int cnt_max_sz = 1; int ret_max_sz = 1; const char *loc_num_name = NULL; if (argc < 2) errx(EXIT_FAILURE, "No count specified"); /* setup the custom format specifier for GMP ... see above */ vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$'); vstr_fmt_add(s1->conf, "<MPZ:%p>", ex_usr_mpz_cb, VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END); /* second version so we can give a field width */ vstr_fmt_add(s1->conf, "<MPZ:%*p>", ex_usr_mpz_cb, VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END); /* get the numeric locale name... */ setlocale(LC_ALL, ""); loc_num_name = setlocale(LC_NUMERIC, NULL); /* change grouping, from locale, to make numbers more readable */ if (!vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_LOC_CSTR_AUTO_NAME_NUMERIC, loc_num_name)) errx(EXIT_FAILURE, "Couldn't change numeric locale info"); mpz_init_set_str(bignum_for, argv[1], 0); mpz_init_set_str(bignum_ret, "1", 0); mpz_init_set_str(bignum_cnt, "1", 0); if (EX_GMP_FACT_USE_FIELDWIDTH) { /* find out the max length of the for values... */ /* value of the count... */ vstr_add_fmt(s1, s1->len, "$'<MPZ:%p>", (void *)bignum_for); if (s1->conf->malloc_bad) /* this checks a bunch of things above */ errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); cnt_max_sz = s1->len; vstr_del(s1, 1, s1->len); /* work out the result */ ex_gmp_fact(bignum_ret, bignum_cnt, bignum_for, FALSE, NULL, 0, 0); /* value of the result... */ if (!vstr_add_fmt(s1, s1->len, "$'<MPZ:%p>", (void *)bignum_ret)) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); ret_max_sz = s1->len; vstr_del(s1, 1, s1->len); /* reinit, so we can print everything... */ mpz_init_set_str(bignum_ret, "1", 0); mpz_init_set_str(bignum_cnt, "1", 0); } /* do the calculations... */ if (mpz_cmp_ui(bignum_for, 0) >= 0) /* special case 0! */ if (!vstr_add_fmt(s1, s1->len, "%*u%s %c %*u\n\n", cnt_max_sz, 0, "!", '=', ret_max_sz, 1)) errno = ENOMEM, err(EXIT_FAILURE, "Add string data"); ex_gmp_fact(bignum_ret, bignum_cnt, bignum_for, TRUE, s1, ret_max_sz, cnt_max_sz); io_put_all(s1, STDOUT_FILENO); exit (ex_exit(s1, NULL)); }
The above implements the equivalent of "%d", however with a couple of 1 line changes "%x" and "%o" can be done (and they can be signed, if desired). For instance, here is just the custom formatter callback code to implement all three...
#ifndef EX_GMP_NUMS #define EX_GMP_NUMS /* libgmp formatters need to include headers before this file */ /* if this is enabled we add the malloc()d string as a reference, * which saves doing an extra copy. */ #define EX_GMP_NUMS_USE_REFS 0 /* This is the custom formatter. * Note that this deals with grouping unlike the gmp_*printf() calls */ static int ex__usr_mpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec, char fmt, const mpz_t val) { unsigned int nb = 10; int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NUM; /* it's a number */ size_t len = 0; int ret = FALSE; char ui_buf[sizeof(unsigned long) * CHAR_BIT]; char *buf = NULL; char *out_buf = ui_buf; switch (fmt) { case 'd': break; case 'x': nb = 16; flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_HEXNUM_L; break; case 'o': nb = 8; flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_OCTNUM; break; default: ASSERT(FALSE); break; } if (mpz_sgn(val) == -1) flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NEG; if (mpz_fits_ulong_p(val)) len = vstr_sc_conv_num_ulong(ui_buf, sizeof(ui_buf), mpz_get_ui(val), "0123456789abcdef", nb); else { len = mpz_sizeinbase(val, nb); out_buf = buf = mpz_get_str(NULL, nb, val); /* dies on malloc error */ if (mpz_sgn(val) == -1) ++out_buf; if (!out_buf[len - 1]) --len; /* see documentation for mpz_sizeinbase() */ } ASSERT(strlen(out_buf) == len); if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags)) goto mem_fail; if (spec->fmt_quote) /* add number including grouping */ ret = vstr_sc_add_grpnum_buf(base, pos, out_buf, len); else if (!EX_GMP_NUMS_USE_REFS || !buf) /* just add the number */ ret = vstr_add_buf(base, pos, out_buf, len); else { /* assumes mp_set_memory_functions() hasn't been called */ Vstr_ref *ref = vstr_ref_make_ptr(buf, vstr_ref_cb_free_ptr_ref); if (!ref) goto mem_fail; ret = vstr_add_ref(base, pos, ref, out_buf - buf, len); buf = NULL; /* memory is free'd when the reference is used up */ /* if !ret then this will free buf */ vstr_ref_del(ref); } if (!ret || !vstr_sc_fmt_cb_end(base, pos, spec, len)) goto mem_fail; free(buf); return (TRUE); mem_fail: free(buf); return (FALSE); } static int ex_usr_dmpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec) { void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0); return (ex__usr_mpz_cb(base, pos, spec, 'd', mpz)); } static int ex_usr_ompz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec) { void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0); return (ex__usr_mpz_cb(base, pos, spec, 'o', mpz)); } static int ex_usr_xmpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec) { void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0); return (ex__usr_mpz_cb(base, pos, spec, 'x', mpz)); } #endif
This is a mapping from std. C functions operating on C-style strings to Vstr string functions...
Common C string functions | Vstr string functions |
memcpy | vstr_add_buf |
memmove | vstr_add_vstr |
memmove | vstr_sub_vstr |
memmove | vstr_mov |
strcpy | vstr_add_cstr_buf |
strncpy | vstr_add_cstr_buf |
strcat | vstr_add_cstr_buf |
strncat | vstr_add_cstr_buf |
memcmp | vstr_cmp_buf |
strcmp | vstr_cmp_cstr_buf |
strcmp | vstr_cmp |
strncmp | vstr_cmp_buf |
strncmp | vstr_cmp |
strcasecmp | vstr_cmp_case_cstr_buf |
strcasecmp | vstr_cmp_case |
strncasecmp | vstr_cmp_case_buf |
strncasecmp | vstr_cmp_case |
strncmp | vstr_cmp_cstr_buf |
strcoll | N/A |
strxfrm | N/A |
memchr | vstr_srch_chr_fwd |
strchr | vstr_srch_chr_fwd |
strnchr | vstr_srch_chr_fwd |
strrchr | vstr_srch_chr_rev |
strtok | vstr_split_chrs |
memset | vstr_add_rep_chr |
memmem | vstr_srch_buf_fwd |
strstr | vstr_srch_cstr_buf_fwd |
strspn | vstr_spn_chrs_buf_fwd |
strcspn | vstr_cspn_chrs_buf_fwd |
sprintf | vstr_add_sysfmt |
strlen | ->len (member variable, also always passed to functions) |
This is a mapping from C++ std::string functions to Vstr string functions...
Common C++ string functions | Vstr string functions |
append | vstr_add_buf |
append | vstr_add_rep_chr |
append | vstr_add_vstr |
insert | vstr_add_buf |
insert | vstr_add_rep_chr |
insert | vstr_add_vstr |
replace | vstr_sub_buf |
replace | vstr_sub_rep_chr |
replace | vstr_sub_vstr |
substr | Fundamental part of Vstr |
find | vstr_srch_cstr_buf_fwd |
find | vstr_srch_buf_fwd |
find | vstr_srch_chr_fwd |
find | vstr_srch_vstr_fwd |
find_first_of | vstr_srch_cstr_chrs_fwd |
find_last_of | vstr_srch_cstr_chrs_rev |
find_first_not_of | vstr_csrch_cstr_chrs_fwd |
find_last_not_of | vstr_csrch_cstr_chrs_rev |
rfind | vstr_srch_cstr_buf_rev |
rfind | vstr_srch_buf_rev |
rfind | vstr_srch_chr_rev |
rfind | vstr_srch_vstr_rev |
erase | vstr_del |
resize | vstr_sc_reduce |
compare | vstr_cmp |
compare | vstr_cmp_cstr |
swap | vstr_mov |
== | vstr_cmp_eq |
== | vstr_cmp_cstr_eq |
c_str | vstr_export_cstr_ptr |
data | vstr_export_cstr_ref |
reserve | vstr_make_spare_nodes |
assign | vstr_sub_vstr |
copy | vstr_export_cstr_buf |
copy | vstr_export_cstr_ptr |
copy | vstr_export_cstr_malloc |
copy | vstr_dup_vstr |
length | ->len (member variable, also always passed to functions) |