| iMatix home page
| << | < | > | >>
SFL Logo SFL
Version 2.1

The Standard Function Library

The SFL (Standard Function Library) from iMatix is a portable function library for C/C++ programs. The SFL is the result of many years' development, and is provided as free software for the benefit of the Internet community.

You may want to go straight to the Table of Contents.

The SFL is written in ANSI C and has been ported to MS-DOS, Windows, UNIX systems (Linux, IBM AIX, SunOS, HP/UX, Solaris, NetBSD, FreeBSD, SCO OpenServer, Digital UNIX) and Digital OpenVMS. It comes with complete sources and documentation in HTML.

The SFL provides about 300 functions that cover these areas:

The SFL is free software that you may use and distribute for private or commercial purposes according to the SFL License Agreement.  

Table of Contents

  • What Is The SFL
  • Origins
  • Objectives
  • Portability
  • Installing The SFL
  • Availability and Distribution
  • Installation for UNIX Systems
  • Installation for Digital VMS Systems
  • Installation for Windows
  • Installation for MS-DOS
  • Using The SFL
  • The Universal Header File - prelude.h
  • The Library Header File - sfl.h
  • System Notes
  • To Do List
  • Contributors and References
  • The SFL License Agreement
  • Universal Header File for C programming
  • Define SFL version
  • Large bitstring manipulation functions
  • bits_init
  • bits_term
  • bits_create
  • bits_destroy
  • bits_set
  • bits_clear
  • bits_test
  • bits_fput
  • bits_fget
  • Compression functions
  • compress_block
  • expand_block
  • compress_rle
  • expand_rle
  • compress_nulls
  • expand_nulls
  • compress_bits
  • expand_bits
  • Console output functions
  • console_send
  • console_enable
  • console_disable
  • console_set_mode
  • console_capture
  • coprintf
  • coputs
  • coputc
  • Conversion functions
  • conv_bool_str
  • conv_date_pict
  • conv_date_str
  • conv_number_str
  • conv_str_bool
  • conv_str_date
  • conv_str_day
  • conv_str_number
  • conv_str_time
  • conv_time_pict
  • conv_time_str
  • Encryption and decryption functions
  • crypt_encode
  • crypt_decode
  • calculate_crc
  • Date and time functions
  • date_now
  • time_now
  • leap_year
  • julian_date
  • day_of_week
  • next_weekday
  • prev_weekday
  • week_of_year
  • year_quarter
  • default_century
  • pack_date
  • pack_time
  • unpack_date
  • unpack_time
  • date_to_days
  • days_to_date
  • date_to_timer
  • timer_to_date
  • timer_to_time
  • timer_to_gmdate
  • timer_to_gmtime
  • time_to_csecs
  • csecs_to_time
  • future_date
  • past_date
  • date_diff
  • valid_date
  • valid_time
  • date_is_future
  • date_is_past
  • timezone_string
  • local_to_gmt
  • gmt_to_local
  • External data representation functions
  • exdr_write
  • exdr_writed
  • exdr_read
  • Fast string searching functions
  • strfind
  • strfind_r
  • strfind_rb
  • memfind
  • memfind_r
  • memfind_rb
  • txtfind
  • File-access functions
  • file_open
  • file_locate
  • file_close
  • file_read
  • file_readn
  • file_write
  • file_copy
  • file_concat
  • file_rename
  • file_delete
  • file_exists
  • file_where
  • file_cycle
  • file_cycle_needed
  • file_has_changed
  • safe_to_extend
  • default_extension
  • fixed_extension
  • strip_extension
  • strip_file_path
  • strip_file_name
  • file_is_readable
  • file_is_writeable
  • file_is_executable
  • file_is_program
  • file_is_directory
  • file_is_legal
  • file_exec_name
  • get_file_size
  • get_file_time
  • get_file_lines
  • file_slurp
  • file_slurpl
  • file_set_eoln
  • get_tmp_file_name
  • Initialisation file access functions
  • ini_find_section
  • ini_scan_section
  • ini_dyn_load
  • ini_dyn_save
  • ini_dyn_changed
  • ini_dyn_refresh
  • ini_dyn_value
  • ini_dyn_values
  • Multilanguage support
  • set_userlang
  • set_userlang_str
  • get_userlang
  • get_userlang_str
  • set_accents
  • get_accents
  • get_units_name
  • get_tens_name
  • get_day_name
  • get_day_abbrev
  • get_month_name
  • get_month_abbrev
  • timestamp_string
  • Line buffering functions
  • linebuf_create
  • linebuf_destroy
  • linebuf_reset
  • linebuf_append
  • linebuf_first
  • linebuf_next
  • linebuf_last
  • linebuf_prev
  • Linked-list functions
  • list_unlink
  • list_relink
  • list_add
  • list_remove
  • list_sort
  • SMTP mailer function
  • smtp_send_mail_ex
  • smtp_send_mail
  • Mathematic functions
  • point_in_rect
  • point_in_circle
  • point_in_poly
  • Message-file access functions
  • open_message_file
  • close_message_file
  • print_message
  • message_text
  • Memory allocation functions
  • mem_alloc_
  • mem_realloc_
  • mem_strdup_
  • mem_strfree_
  • mem_free_
  • mem_assert_
  • mem_checkall_
  • mem_check_
  • mem_descr_
  • mem_new_trans_
  • mem_commit_
  • mem_rollback_
  • mem_size_
  • mem_used
  • mem_allocs
  • mem_frees
  • mem_display
  • mem_scavenger
  • MIME support functions
  • encode_base64
  • decode_base64
  • decode_mime_time
  • encode_mime_time
  • Linked-list functions
  • node_create
  • node_destroy
  • node_relink_after
  • node_relink_before
  • node_unlink
  • node_relink
  • Directory access functions
  • open_dir
  • read_dir
  • close_dir
  • format_dir
  • fix_dir
  • free_dir
  • load_dir_list
  • free_dir_list
  • sort_dir_list
  • add_dir_list
  • resolve_path
  • locate_path
  • clean_path
  • get_curdir
  • set_curdir
  • file_matches
  • make_dir
  • remove_dir
  • Process control functions
  • process_create
  • process_status
  • process_kill
  • process_close
  • process_server
  • process_alarm
  • process_esc
  • process_unesc
  • process_priority
  • Time-slot functions
  • year_range_empty
  • year_range_fill
  • year_slot_clear
  • year_slot_set
  • year_slot_filled
  • day_range_empty
  • day_range_fill
  • day_slot_clear
  • day_slot_set
  • day_slot_filled
  • date_to_day
  • time_to_min
  • String-handling functions
  • strdupl
  • strfree
  • strskp
  • strcset
  • strpad
  • strlwc
  • strupc
  • strcrop
  • stropen
  • strclose
  • strunique
  • strmatch
  • strprefixed
  • strprefix
  • strdefix
  • strhash
  • strconvch
  • xstrcat
  • xstrcpy
  • lexcmp
  • lexncmp
  • lexwcmp
  • soundex
  • soundexn
  • strt2descr
  • descr2strt
  • strtfree
  • removechars
  • replacechrswith
  • insertstring
  • insertchar
  • leftfill
  • rightfill
  • trim
  • ltrim
  • searchreplace
  • deletestring
  • getstrfld
  • setstrfld
  • getstrfldlen
  • findstrinfile
  • getequval
  • matchtable
  • stringreplace
  • wordwrapstr
  • stricstr
  • strtempcmp
  • istoken
  • eatstr
  • eatstrpast
  • movestrpast
  • eatchar
  • isoneoftokens
  • TCP/IP, UDP/IP socket functions
  • sock_init
  • sock_term
  • passive_TCP
  • passive_UDP
  • passive_socket
  • create_socket
  • connect_TCP
  • connect_UDP
  • connect_TCP_fast
  • connect_UDP_fast
  • connect_socket
  • connect_to_peer
  • address_end_point
  • build_sockaddr
  • socket_localaddr
  • socket_peeraddr
  • socket_nodelay
  • socket_is_alive
  • socket_error
  • accept_socket
  • connect_error
  • get_sock_addr
  • get_peer_addr
  • read_TCP
  • write_TCP
  • read_UDP
  • write_UDP
  • close_socket
  • sock_select
  • get_hostname
  • get_hostaddr
  • get_hostaddrs
  • sock_ntoa
  • sockmsg
  • winsock_last_error
  • socket_is_permitted
  • get_host_file
  • get_name_server
  • Symbol-table functions
  • sym_create_table_
  • sym_delete_table
  • sym_empty_table
  • sym_merge_tables
  • sym_lookup_symbol
  • sym_create_symbol_
  • sym_assume_symbol_
  • sym_delete_symbol
  • sym_exec_all
  • sym_hash
  • sym_get_value
  • sym_get_number
  • sym_get_boolean
  • sym_set_value
  • sym_sort_table
  • symb2strt_
  • strt2symb_
  • symb2descr_
  • descr2symb_
  • System-level functions (assertions,...)
  • sys_assert
  • sys_name
  • HTTP and CGI Support functions
  • http_escape
  • http_escape_size
  • http_unescape
  • http_query2strt
  • http_query2symb
  • http_query2descr
  • http_encode_meta
  • cgi_parse_query_vars
  • cgi_parse_file_vars
  • http_multipart_decode
  • is_full_url
  • cgi_get_input
  • cgi_fld_by_name
  • cgi_fld_by_index
  • cgi_fld_len_by_index
  • displayform
  • Environment variable functions
  • env_get_string
  • env_get_number
  • env_get_boolean
  • env2descr
  • descr2env
  • env2symb
  • symb2env
  • String token manipulation functions.
  • tok_split
  • tok_split_rich
  • tok_free
  • tok_push
  • tok_size
  • tok_text_size
  • tok_subst
  • Linked-list functions
  • tree_init
  • tree_insert
  • tree_delete
  • tree_find
  • tree_traverse
  • tree_first
  • tree_last
  • tree_next
  • tree_prev
  • Tracing functions
  • enable_trace
  • disable_trace
  • push_trace
  • pop_trace
  • set_trace_file
  • trace
  • Process user id (uid) and group id (gid) functions
  • get_uid_name
  • get_gid_name
  • set_uid_user
  • set_uid_root
  • set_gid_user
  • set_gid_root
  • set_uid_gid
  • get_login
  • XML (Extensible Markup Language) access functions
  • xml_new
  • xml_modify_value
  • xml_item_name
  • xml_item_value
  • xml_free
  • xml_first_child
  • xml_next_sibling
  • xml_parent
  • xml_put_attr
  • xml_attr
  • xml_attr_name
  • xml_attr_value
  • xml_get_attr
  • xml_free_attr
  • xml_first_attr
  • xml_next_attr
  • xml_changed
  • xml_refresh
  • xml_save
  • xml_error
  • xml_load
  • What Is The SFL

    Origins

    At iMatix we develop portable free and commercial software. We work in ANSI C to cover the widest range possible. A major part of our toolkit has always been our subroutine library. This was initially written for MS-DOS in 1991 but has developed into a more ambitious project since then.

    From the outset, we ignored commercial libraries. Our software is usually free - as in 'liberated' rather than 'gratis' - and using a commercial library would have been intolerable. We looked for free libraries, but found only specialised and mostly non-portable collections of functions. So, we built our own. We hope you like it. We certainly use it all the time.

    Objectives

    When we designed the SFL, we had certain things in mind:

    Portability

    The SFL is in use on these systems:

    Some recent functions may not have been tested or implemented across all platforms. Some functions are empty on some platforms. Since the SFL is continually improving and enlarging, there are always newer functions that are less tested, and possibly less than 100% portable. Our intention is that the transparency of the SFL makes these functions easy to test and improve.

    Installing The SFL

    Availability and Distribution

    We supply the SFL as two archives: a source kit and a documentation kit (in HTML). These files are available for download by HTTP from our website on a permanent basis. You need to recompile the SFL for your specific system, using an ANSI C compiler. We don't provide binary kits (as yet) for several reasons:

    Getting The Source Archive

    The SFL source archive is supplied as a zip file and a GNU gzip+tar file. These are the files in the /pub/sfl/src directory:

    sflsrc21.tgz   308722 98/04/25 10:17:16  Gzip/tar archive
    sflsrc21.zip   385323 98/07/25 23:28:32  ZIP archive
    

    If you have trouble accessing the iMatix site, send us an e-mail and we'll send you the SFL archives by return e-mail, using the uuencode format.

    Getting The The Documentation Kit

    The SFL documentation is supplied as HTML files, available on-line or off-line as a single .zip file that you can install on a hard disk for rapid access, and also as a GNU gzipped file. These are the files in the /pub/sfl/doc directory:

    sflbig21.tgz   184689 98/04/25 10:17:22  Gzip/tar archive
    sflbig21.zip   190376 98/07/25 23:28:36  ZIP archive
    sfldoc21.tgz   217445 98/04/25 10:17:18  Gzip/tar archive
    sfldoc21.zip   571527 98/07/25 23:28:36  ZIP archive
    

    We recommend that you unzip or gunzip/detar the archive into a subdirectory. Point your browser at the index.htm file. We use relative addressing in all HTML documents, so that links work just as well on a local hard-disk as on-line on our website. In a windowing environment is it easy and useful to create an icon that runs a Web browser on this file.


    Installation for UNIX Systems

    To install the SFL on a UNIX system you need to:

    1. Download the source archive and decompress it.
    2. Run the 'build' script to compile the SFL sources and build the libsfl.a archive file.
    3. Optionally, install the libsfl.a file in the /usr/lib directory.
    4. Optionally, install the SFL header file(s) in the /usr/include directory.

    Decompressing The Source Archive

    To unzip the source .zip file, you need the Infozip unzip tool:

    $ mkdir temp
    $ mv sflsrc21.zip temp
    $ cd temp
    $ unzip -a sflsrc21
    

    To decompress the source .tgz file you need GNU gzip/gunzip and tar:

    $ mkdir temp
    $ mv sflsrc21.tgz temp
    $ cd temp
    $ gzip -d sflsrc21.tgz
      or
    $ gunzip sflsrc21.tgz
    $ tar -xvf sflsrc21.tar
    

    You can also, in extreme cases, unzip the files on a PC and transfer the individual files to the UNIX system.

    Compiling The SFL Sources

    The SFL source archive includes a script, c, that you can (and should) use to compile the SFL sources. This script invokes the ANSI C compiler to produce an object code file. It detects the platform and invokes the compiler with the necessary switches for ANSI C compilation. On some systems this is the normal behaviour for the cc command. On other systems it is not normal. You should make the c script executable, (preferrably) install it in a shared directory like /usr/local/bin, and try it out:

    $ chmod a+rx c
    $ mv c /usr/local/bin
    $ c
    

    To compile the SFL sources, use this command:

    $ chmod +x build
    $ build
    

    If you get warnings or error messages, this is usually a bad sign. Some compilers issue warnings just because you ask for ANSI compilation. If you get any other error messages, please let us know.

    You can use individual SFL files simply by specifying them on the command line when you compile and link a program. However, this is usually a pain. Therefore, the build script creates a library file called libsfl.a. The linker can automatically search this file for the SFL functions. To install libsfl.a in the /usr/lib directory, do this:

    $ mv libsfl.a /usr/lib
    

    Using The SFL In Your Applications

    To use an SFL function in your applications you must include a header file that defines the structures, prototypes, and types for the function API. The SFL provides three types of header files:

    1. One header file per function group. These are provided for reference; you will normally not use these directly.
    2. A Universal Header File which encapsulates and replaces all local header files. Again, this is provided for reference; you do not normally use this directly.
    3. A Library Header File that contains the Universal Header File and all the SFL header files in one go. This is meant to simplify application programming and installation.

    We recommend that you install the sfl.h file in /usr/include. A typical application program starts like this:

    #include <sfl.h>
    

    To link an application program, use the c -l command. This assumes that libsfl.a is installed /usr/lib.


    Installation for Digital VMS Systems

    To install the SFL on a Digital VMS system you need to:

    1. Download the source archive and decompress it.
    2. Run the 'build.txt' command file to build the libsfl.olb library file.
    3. Optionally, install the libsfl.olb file in the SYS$LIBRARY directory.
    4. Optionally, install the SFL header file(s) in the SYS$LIBRARY directory.

    Decompressing The Source Archive

    To unzip the source .zip file, you need the Infozip unzip tool (note that you need the -a switch):

    $ create/dir [.temp]
    $ ren sflsrc21.zip [.temp]
    $ set def [.temp]
    $ unzip -a sflsrc21
    

    You can also, in extreme cases, unzip the files on a PC and transfer the individual files to the VMS system.

    Compiling The SFL Sources

    To compile the SFL sources, use this command:

    $ @build.txt
    

    If you get warnings or error messages, this is a bad sign - please let us know.

    The build.txt command file creates a library file called libsfl.olb. You can install this in a central directory like SYS$LIBRARY if you wish. You'll need system privileges to do this.

    Using The SFL In Your Applications

    To use an SFL function in your applications you must include a header file that defines the structures, prototypes, and types for the function API. The SFL provides three types of header files:

    1. One header file per function group. These are provided for reference; you will normally not use these directly.
    2. A Universal Header File which encapsulates and replaces all local header files. Again, this is provided for reference; you do not normally use this directly.
    3. A Library Header File that contains the Universal Header File and all the SFL header files in one go. This is meant to simplify application programming and installation.

    We recommend that you install the sfl.h file in SYS$LIBRARY. A typical application program starts like this:

    #include <sfl.h>
    

    Installation for Windows

    Briefly, either create a static library, and include that in your project; create a .DLL and call that, or add the files you want to use to your project and compile them as part of the application.

    With MSVC 4.0, we find it useful to create a main project for the application in hand, and a subproject for the SFL. We build the SFL as a static library. If you use MFC, you must compile everything (including MFC) in single-threaded mode, and use libd.

    Under MSVC 1.5x, there is a bug in the project manager that generates invalid make files: the SFL prelude.h file refers to various non-Windows include files, within #if statements. The MSVC 1.5x project manager includes these in the make file; you must manually remove them. One solution is to edit prelude.h; another is to use a Perl or Awk script to edit the make file each time you change the project. You could also move to a different 16-bit compiler. Finally, you can create the make files as empty files in the C include directory.


    Installation for MS-DOS

    To install the SFL on a MS-DOS system you need to:

    1. Download the source archive and decompress it.
    2. Run build.bat to build the libsfl.lib library file.
    3. Optionally, install the libsfl.lib file in central directory.
    4. Optionally, install the SFL header file(s) in a central directory.

    Decompressing The Source Archive

    To unzip the source .zip file, you need the Infozip unzip tool, or PKzip version 2.04g or later, or a compatible unzip program.:

    C:\DOWNLOAD> md temp
    C:\DOWNLOAD> copy sflsrc21.zip temp
    C:\DOWNLOAD> del sflsrc21.zip
    C:\DOWNLOAD> cd temp
    C:\DOWNLOAD> unzip sflsrc21
    

    Compiling The SFL Sources

    These build scripts are provided for MS-DOS:

    The build scripts create a library file called libsfl.lib. You can install this, and sfl.h, in a central directory if you wish.

    Using The SFL In Your Applications

    To use an SFL function in your applications you must include a header file that defines the structures, prototypes, and types for the function API. The SFL provides three types of header files:

    1. One header file per function group. These are provided for reference; you will normally not use these directly.
    2. A Universal Header File which encapsulates and replaces all local header files. Again, this is provided for reference; you do not normally use this directly.
    3. A Library Header File that contains the Universal Header File and all the SFL header files in one go. This is meant to simplify application programming and installation.

    We recommend that you install the sfl.h file in the /include directory of your compiler. A typical application program starts like this:

    #include <sfl.h>
    

    Using The SFL

     

    The Universal Header File - prelude.h

    What and Why?

    The Universal Header File a technology that we have developed to make C applications more easily portable with less effort. One of the big difficulties in compiling C code on different platforms is that header files change their names, locations, and internal functions from system to system, even on one system over time.

    Typically, you may see C programs that start with a rash of #ifdef's mixed with #include's depending on the system, compiler, and specific needs of the program.

    Since we are basically really lazy, all this unnecessary work is intolerable. We would much rather make the compiler work harder. The systems we develop on (typically MS-DOS with Turbo-C) are so fast that we can afford to take a really lazy approach.

    So, what we do is this: we include every 'useful' and 'standard' header file that we can think of. We then include every 'useful' non-portable file that we've ever needed, in a clean way, so that application code does not need to 'know' how it was done.

    At the same time we define lots of things that make life easier. Generally we don't like macros, since these create 'pseudo languages' that are just more work to learn. However, some things (like #define FOREVER for (;;)) are so useful and pretty comonplace, so we stick them in too.

    Lastly, we flatten-out the problem called 'what system am I running on', by providing a set of definitions like __UNIX__ and __UTYPE_SUNOS that code can use if it has to. Again, it can be quite messy to figure-out that we're compiling on a Brand X, so we need this-and-that header file. We hide this so that we can forget about it.

    Okay, those are the benefits of this approach. What are the costs? We typically hear these criticisms:

    We use the Universal Header File in all C projects (not just those based on the SFL). If it was not for the simple fact that it has helped us a lot, we'd probably not make it available.

    Using the Universal Header File

    You should probably read through the prelude.h file to best understand it. Our usual habit is to comment the code first, so that it's self-explanatory. The SFL documentation has a section on the Universal Header File. This section is generated from the code.

    When you use the SFL Library Header File (sfl.h), you don't need to include prelude.h, since it's already embedded in sfl.h. This makes application programming easier (just one header file to include). If you need to change prelude.h, you can either change sfl.h as well, or rebuild sfl.h using the build script. Better still, tell us what you want to change, so that we can maintain a single version of the file.  

    The Library Header File - sfl.h

    Each module in the SFL consists of a header file and one or more C source files. You can choose to include the header files that you want (this is what the SFL source code does), but this can quickly become burdensome. To simplify matters, a single header file sfl.h contains all the individual header files. It also contains the Universal Header File.

    System Notes

    MS-DOS

    Most of the SFL is portable to MS-DOS. Exceptions are: the socket i/o functions (sflsock), the user/group ID functions (sfluid) and the server process functions (sflserv). These are all null; you can call any of the functions, but they will return either an okay feedback (in most cases) or an error feedback (for the socket functions). The SFL compiles cleanly with Borland Turbo C/C++ 1.0 and Microsoft VC++ 4.0; it has not been tested with Borland C/C++.

    MS-Windows

    Most of the SFL is portable to Windows 3.x, Windows 95, and Windows NT. Exceptions are: the user/group ID functions (sfluid) and the server process functions (sflserv). These are all null; you can call any of the functions, but they will return an okay feedback. The SFL compiles cleanly with Microsoft VC++ 4.0; it has not been tested with Borland C/C++.

    Digital VAX

    The SFL is portable to Digital VMS except for the directory access functions (sfldir), user/group functions (sfluid) and the server process functions (sflserv). The sfldir functions will be implemented at a later date. The other functions are null; you can call any of the functions, but they will return an okay feedback. The SFL compiles cleanly with Vax C and Dec C and has been tested with various releases of these compilers.

    Linux

    The SFL is fully portable to Linux and has been tested with GNU C. It should give no compiler warning errors.

    Sun OS and Sun Sparc

    The SFL is fully portable to Sun OS. You may have trouble finding an ANSI C compiler, especially on Sparc systems. People sometimes install GNU C, using the Sun header files and libraries. This should work, although we have not tested it recently. Sometimes the Sun ANSI C compiler is called acc, not cc. You can use the CCNAME environment variable to point to the right compiler name. Some Sun C compilers give warnings when you use the ANSI compile mode. You can ignore these warnings.

    See also the warning about 'Other UNIX Systems'; some SunOS installations show this symptom.

    HP/UX

    The SFL is fully portable to HP/UX. It should give no compiler warning errors.

    IBM AIX

    The SFL is fully portable to IBM/AIX. It should give no compiler warning errors.

    Digital UNIX

    The SFL is fully portable to Digital UNIX. It should give no compiler warning errors. When compiling on an Alpha system, the word size is 64 bits.

    Other UNIX Systems

    The directory functions can fail on SVr4 if the <dirent.h> file does not match the C library. Recompile with CCDEFINES set to the value "-D _USE_BSD_DIRENT" and they should work a bit better. Under Solaris with GCC, you should not define this macro.

    OS/2

    The SFL was ported to OS/2 by Ewen McNiell around New Year's Eve 1996. It runs under EMX. The SFL distribution kit includes an OS/2 build script. The 'c' script runs under OS/2 as well as UNIX.

    To Do List

    Contributors and References

     

    The SFL License Agreement

    This license agreement covers your use of the iMatix STANDARD FUNCTION LIBRARY (SFL), its source code, documentation, and executable files, hereinafter referred to as "the Product".

    The Product is Copyright © 1991-98 iMatix. You may use it and distribute it according to this following License Agreement. If you do not agree with these terms, please remove the Product from your system. By incorporating the Product in your work or distributing the Product to others you implicitly agree to these license terms.

    Statement Of Copyright

    The Product is, and remains, Copyright © 1991-98 iMatix, with exception of specific copyrights as noted in the individual source files.

    Conditions Of Use

    You do not need to provide the source code for the Product as part of your product. However, you must do one of these things to comply with the Product License Agreement:

    1. Provide the source code for Product modules that you use, or
    2. Make your product freely available according to a license similar to the GNU General Public License, or the Perl Artistic License, or
    3. Add this phrase to the documentation for your product: "This product uses parts of the iMatix SFL, Copyright © 1991-98 iMatix <http://www.imatix.com>".

    Rights Of Usage

    You may freely and at no cost use the Product in any project, commercial, academic, military, or private, so long as you respect the License Agreement. The License Agreement does not affect any software except the Product. In particular, any application that uses the Product does not itself fall under the License Agreement.

    You may modify any part of the Product, including sources and documentation, except this License Agreement, which you may not modify.

    You must clearly indicate any modifications at the start of each source file. The user of any modified Product code must know that the source file is not original.

    At your discretion, you may rewrite or reuse any part of the Product so that your derived code is not obviously part of the Product. This derived code does not fall under the Product License Agreement directly, but you must include a credit at the start of each source file indicating the original authorship and source of the code, and a statement of copyright as follows:
    "Parts copyright (c) 1991-98 iMatix."

    Rights Of Distribution

    You may freely distribute the Product, or any subset of the Product, by any means. The License, in the form of the file called "LICENSE.TXT" must accompany any such distribution.

    You may charge a fee for distributing the Product, for providing a warranty on the Product, for making modifications to the Product, or for any other service provided in relation to the Product. You are not required to ask our permission for any of these activities.

    At no time will iMatix associate itself with any distribution of the Product except that supplied from the Internet site http://www.imatix.com.

    Disclaimer Of Warranty

    The Product is provided as free software, in the hope that it will be useful. It is provided "as-is", without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the Product is with you. Should the Product prove defective, the full cost of repair, servicing, or correction lies with you.

    Universal Header File for C programming

    Filename: prelude.h
    Package: Standard Function Library (SFL)
    Written: 93/03/29 iMatix SFL project team sfl@imatix.com
    Revised: 98/06/27
    Copyright: Copyright (c) 1991-98 iMatix
    Version: 1.92
    1.90 PH Released with SFL 1.90
    1.91 PH 98/04/27 Added DRDOS LFN and DJGPP support - rj
    1.92 PH 98/05/18 Added QNX support (provided by Alessandro Sala)

    Synopsis

    This header file encapsulates many generally-useful include files and defines lots of good stuff. The intention of this header file is to hide the messy #ifdef's that you typically need to make real programs compile & run. To use, specify as the first include file in your program. The main contributors to this file were:
    PH Pieter Hintjens <ph@imatix.com>
    EDM Ewen McNeill <ewen@imatix.com>
    PA Pascal Antonnaux <pascal@imatix.com>
    BW Bruce Walter <walter@fortean.com>
    RJ Rob Judd <judd@alphalink.com.au>

    List of Functions

    List of Symbol Definitions

    prelude.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    ASSERT(f) (various)
    DEBUG TRUE
    DOES_SNPRINTF (various)
    DOES_SOCKETS /* System supports BSD sockets */
    DOES_UID /* System supports uid functions */
    EXIT_FAILURE 1 /* GCC, sometimes. */
    EXIT_SUCCESS 0 /* but not defined on SunOs with */
    FALSE 0
    FOREVER for (;;) /* FOREVER { ... } */
    FORK_CHILD 0
    FORK_ERROR -1 /* Return codes from fork() */
    LINE_MAX 255 /* if not previously #define'd */
    MSDOS_FILESYSTEM (various)
    NAMEFOLD (various)
    O_BINARY 0
    O_NDELAY 0
    O_NONBLOCK (various)
    PATHEND (various)
    PATHFOLD (various)
    PATHSEP (various)
    PATH_MAX 2048 /* if not previously #define'd */
    SIGABRT 22 /* Termination by abort() */
    SIGALRM (various)
    SIGILL 4 /* Illegal instruction */
    SIGINT 2 /* Ctrl-C sequence */
    SIGSEGV 11 /* Segment violation */
    SIGTERM 15 /* Kill signal */
    TIMEZONE (various)
    TRUE 1 /* ANSI standard */
    _INCLUDE_HPUX_SOURCE TRUE
    _INCLUDE_POSIX_SOURCE TRUE
    _INCLUDE_XOPEN_SOURCE TRUE
    _PRELUDE_INCLUDED TRUE
    __IS_32BIT__ /* Else assume 32-bit OS/compiler */
    __IS_64BIT__ (various)
    __MSDOS__ (various)
    __OS2__ TRUE
    __STRICT_ANSI__ TRUE
    __UNIX__ (various)
    __UTYPE_AUX TRUE
    __UTYPE_BSDOS TRUE
    __UTYPE_DECALPHA TRUE
    __UTYPE_FREEBSD TRUE
    __UTYPE_GENERIC TRUE
    __UTYPE_HPUX TRUE
    __UTYPE_IBMAIX TRUE
    __UTYPE_IRIX TRUE
    __UTYPE_LINUX TRUE
    __UTYPE_MIPS TRUE
    __UTYPE_NETBSD TRUE
    __UTYPE_NEXT TRUE
    __UTYPE_QNX TRUE
    __UTYPE_SCO TRUE
    __UTYPE_SINIX TRUE
    __UTYPE_SUNOS TRUE
    __UTYPE_SUNSOLARIS TRUE
    __UTYPE_UNIXWARE TRUE
    __VMS_XOPEN TRUE
    __VMS__ TRUE
    __WINDOWS__ (various)
    bit_clr(x,bit) ((x) &= ~bit_msk (bit))
    bit_msk(bit) (1 << (bit))
    bit_set(x,bit) ((x) |= bit_msk (bit))
    bit_tst(x,bit) ((x) & bit_msk (bit))
    environ _environ
    local static void /* Shorthand for local functions */
    max(a,b) (((a) > (b))? (a): (b))
    memmove(d,s,l) bcopy (s,d,l)
    min(a,b) (((a) < (b))? (a): (b))
    random(num) (various)
    randomize() srand ((unsigned) time (NULL))
    sleep(a) (various)
    snprintf _snprintf
    strclr(s) (*(s) = 0)
    streq(s1,s2) (!strcmp ((s1), (s2)))
    strerror(n) sys_errlist [n]
    strlast(s) ((s) [strlen (s) - 1])
    strneq(s1,s2) (strcmp ((s1), (s2)))
    strnull(s) (*(s) == 0)
    strterm(s) ((s) [strlen (s)])
    strused(s) (*(s) != 0)
    tbllast(x) (x [tblsize (x) - 1])
    tblsize(x) (sizeof (x) / sizeof ((x) [0]))
    until(expr) while (!(expr)) /* do { ... } until (expr) */
    vsnprintf _vsnprintf

    List of Type Definitions

    Type name: Defined as:
    Bool unsigned short
    byte unsigned char
    dbyte unsigned short
    dword unsigned long
    function void (*) (void)
    gid_t int
    qbyte (various)
    uid_t int
    word unsigned short

    Define SFL version

    Filename: sflvers.h
    Package: Standard Function Library (SFL)
    Written: 96/11/21 iMatix SFL project team sfl@imatix.com
    Revised: 98/10/02
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Defines the SFL_VERSION constant.

    List of Symbol Definitions

    sflvers.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    SFL_VERSION "2.01" /* Main SFL version */
    _SFLVERS_INCLUDED TRUE

    Large bitstring manipulation functions

    Filename: sflbits.h
    Package: Standard Function Library (SFL)
    Written: 96/05/14 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides operations to manipulate large bitstrings. The bitstrings are compressed. Intended for bit-based index techniques, where bitstrings can be millions of bits long. These functions are still in development; this is an early version that provides basic functionality. Simple tests on large bitmaps with random filling show a cost of about 3 bytes per bit, after compression. This includes all the indexing information.

    List of Functions

    List of Symbol Definitions

    sflbits.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    BIT_DATASIZE 500 /* Size of block data part */
    BIT_INDEXSIZE BIT_DATASIZE/2 /* Size of block index part */
    BIT_MAXBITS 16384000L /* Max. possible bit number */
    BIT_MAXBLOCKS 1024 /* Max. size of bitstring */
    BIT_SECTSIZE 8192 /* Size of one bitstring section */
    _SFLBITS_INCLUDED TRUE
     

    bits_init

    #include "sflbits.h"
    int
    bits_init (void)
    

    Synopsis

    Initialises bitstring functions. You must call this before using any other bitstring functions. Returns 0 if okay, -1 if there was an error.

    Source Code - (sflbits.c)

    {
        ASSERT (comp_zero == NULL);
    
        comp_zero = mem_alloc (BIT_SECTSIZE + 1);
        if (!comp_zero)
            return (-1);                    /*  Could not allocate new block     */
    
        memset (compressed, BIT_SECTSIZE, 0x00);
        comp_zero_size = compress bits (compressed, comp_zero, BIT_SECTSIZE);
        comp_zero      = mem_realloc (comp_zero, comp_zero_size);
    
        comp_ones = mem_alloc (BIT_SECTSIZE + 1);
        if (!comp_ones)
          {
            mem_free (comp_ones);
            return (-1);                    /*  Could not allocate new block     */
          }
        memset (compressed, BIT_SECTSIZE, 0xFF);
        comp_ones_size = compress bits (compressed, comp_ones, BIT_SECTSIZE);
        comp_ones      = mem_realloc (comp_ones, comp_ones_size);
    
        return (0);
    }
    
     

    bits_term

    #include "sflbits.h"
    int
    bits_term (void)
    

    Synopsis

    Terminates bitstring functions. You must call this when you are finished using the bitstring functions. Returns 0 if okay, -1 if there was an error.

    Source Code - (sflbits.c)

    {
        mem_free (comp_zero);
        mem_free (comp_ones);
        return (0);
    }
    
     

    bits_create

    #include "sflbits.h"
    BITS *
    bits_create (void)
    

    Synopsis

    Creates a new bitstring and initialises all bits to zero. Returns a BITS handle which you should use in all further references to the bitstring.

    Source Code - (sflbits.c)

    {
        BITS
            *bits;                          /*  Newly-created bitstring          */
        BITBLOCK
            *index;                         /*  Newly-created index block        */
    
        bits = mem_alloc (sizeof (BITS));
        if (bits)
          {
            memset (bits, 0, sizeof (BITS));
            index = mem_alloc (sizeof (BITBLOCK));
            if (index)
              {
                /*  Set all index fields to 0: bitstring is all zeroes           */
                memset (index, 0, sizeof (BITBLOCK));
                index-> left       = 0;
                index-> right      = 0;
                index-> size       = BIT_DATASIZE;
                bits-> block [0]   = index;
                bits-> block_count = 1;
                bits-> free_list   = 0;     /*  No blocks in free list           */
              }
            else
              {
                mem_free (bits);
                bits = NULL;
              }
          }
        return (bits);
    }
    
     

    bits_destroy

    #include "sflbits.h"
    void
    bits_destroy (
        BITS *bits)
    

    Synopsis

    Releases all memory used by a bitstring and deletes the bitstring. Do not refer to the bitstring after calling this function.

    Source Code - (sflbits.c)

    {
        int
            block_nbr;                      /*  Bitstring block number           */
    
        ASSERT (bits);
    
        /*  Free all blocks allocated to bitmap                                  */
        for (block_nbr = 0; block_nbr < bits-> block_count; block_nbr++)
            mem_free (bits-> block [block_nbr]);
    
        mem_free (bits);
    }
    
     

    bits_set

    #include "sflbits.h"
    int
    bits_set (
        BITS *bits,
        long bit)
    

    Synopsis

    Sets the specified bit in the bitmap. Returns ?

    Source Code - (sflbits.c)

    {
        int
            index,                          /*  Number of index block            */
            section;                        /*  Number of section in index       */
        dbyte
            bit_nbr;                        /*  Number of bit in section         */
    
        ASSERT (bits);
    
        locate_bit  (bits, bit, &index, &section, &bit_nbr);
        get_section (bits, index, section, section_data, TRUE);
        section_data [bit_nbr / 8] |= 1 << (bit_nbr % 8);
        put_section (bits, index, section, section_data);
    
        return 0;
    }
    
     

    bits_clear

    #include "sflbits.h"
    int
    bits_clear (
        BITS *bits,
        long bit)
    

    Synopsis

    Clears the specified bit in the bitmap. Returns ?

    Source Code - (sflbits.c)

    {
        int
            index,                          /*  Number of index block            */
            section;                        /*  Number of section in index       */
        dbyte
            bit_nbr;                        /*  Number of bit in section         */
    
        ASSERT (bits);
    
        locate_bit  (bits, bit, &index, &section, &bit_nbr);
        get_section (bits, index, section, section_data, TRUE);
        section_data [bit_nbr / 8] &= 255 - (1 << (bit_nbr % 8));
        put_section (bits, index, section, section_data);
    
        return 0;
    }
    
     

    bits_test

    #include "sflbits.h"
    int
    bits_test (
        const BITS *bits,
        long bit)
    

    Synopsis

    Tests the specified bit in the bitmap. Returns 1 or 0.

    Source Code - (sflbits.c)

    {
        int
            index,                          /*  Number of index block            */
            section;                        /*  Number of section in index       */
        dbyte
            bit_nbr;                        /*  Number of bit in section         */
    
        ASSERT (bits);
    
        locate_bit  (bits, bit, &index, &section, &bit_nbr);
        get_section ((BITS *) bits, index, section, section_data, FALSE);
        if ((section_data [bit_nbr / 8]) & (1 << (bit_nbr % 8)))
            return (1);
        else
            return (0);
    }
    
     

    bits_fput

    #include "sflbits.h"
    int
    bits_fput (FILE *file,
        const BITS *bits)
    

    Synopsis

    Writes the bitstring to the specified file stream. To read the bitstring, use the bits fget() function. The structure of the bitstring is:

    Source Code - (sflbits.c)

    {
        int
            block_nbr;                      /*  Bitstring block number           */
        word
            comp_size;                      /*  Size of compressed block         */
        BITBLOCK
            *block_ptr;                     /*  Points to bitstring block        */
    
        ASSERT (bits);
        ASSERT (file);
    
        /*  Write bitstring header to file                                       */
        fwrite (&bits-> block_count, sizeof (bits-> block_count), 1, file);
        fwrite (&bits-> free_list,   sizeof (bits-> free_list),   1, file);
    
        /*  Write bitstring blocks to file                                       */
        for (block_nbr = 0; block_nbr < bits-> block_count; block_nbr++)
          {
            block_ptr = bits-> block [block_nbr];
            comp_size = compress block ((byte *) block_ptr,
                                        compressed, (word) block_ptr-> size);
    
            fwrite (&comp_size, sizeof (comp_size), 1, file);
            fwrite (compressed, comp_size,          1, file);
          }
        return 0;
    }
    
     

    bits_fget

    #include "sflbits.h"
    BITS *
    bits_fget (FILE *file)
    

    Synopsis

    Reads a bitstring from the specified file stream. You must have previously written the bitstring using bit_fput (). Returns a newly-created bitmap, or NULL if there was insufficient memory.

    Source Code - (sflbits.c)

    {
        int
            block_nbr;                      /*  Bitstring block number           */
        word
            comp_size;                      /*  Size of compressed block         */
        BITBLOCK
            *block_ptr;                     /*  Points to bitstring block        */
        BITS
            *bits;
    
        ASSERT (file);
    
        bits = bits create ();              /*  Create a new, empty bitmap       */
    
        /*  Read bitstring header from file                                      */
        fread (&bits-> block_count, sizeof (bits-> block_count), 1, file);
        fread (&bits-> free_list,   sizeof (bits-> free_list),   1, file);
    
        /*  Read bitstring blocks from file                                      */
        for (block_nbr = 0; block_nbr < bits-> block_count; block_nbr++)
          {
            block_nbr = alloc_block (bits);
            if (block_nbr == 0)
              {
                bits destroy (bits);
                return (NULL);
              }
            fread (&comp_size, sizeof (comp_size), 1, file);
            fread (compressed, comp_size,          1, file);
            block_ptr        = bits-> block [block_nbr];
            block_ptr-> size = expand block (compressed, (byte *) block_ptr,
                                             comp_size);
          }
        return (bits);
    }
    

    Compression functions

    Filename: sflcomp.h
    Package: Standard Function Library (SFL)
    Written: 91/05/20 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Various compression/decompression functions. The LZ-type algorith (LZRW1/KH) was originally written by Kurt Haenen <ghgaea8@blekul11> and made portable by P. Hintjens. This is a reasonable LZ/RLE algorithm, very fast, but about 30% less efficient than a ZIP-type algorithm in terms of space. The RLE algorithms are better suited to compressing sparse data. The nulls variant is specifically tuned to data that consists mostly of binary zeroes. The bits variant is tuned for compressing sparse bitmaps.

    List of Functions

    List of Symbol Definitions

    sflcomp.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLCOMP_INCLUDED TRUE
     

    compress_block

    #include "sflcomp.h"
    word
    compress_block (
        const byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Takes up to 64Kb of uncompressed data in Source, compresses it using a fast LZ/RLE algorithm and places the result in Dest. The compression technique is comparable to that used by Zip and such tools, but less agressive. It is, however, fast enough to use in realtime. Returns the size of the compressed data. To decompress the data, use the expand block() function.

    Source Code - (sflcomp.c)

    {
        static short
              Hash [4096];
        short SymbolAddress;
        word  Key;
        word  Size;
        byte  Bit = 0;
        word  Command = 0;
        word  src_index = 0;
        word  dst_size = 3;
        word  HeaderIndex = 1;
    
        dst [0] = FLAG_COMPRESS;
        for (Key = 0; Key < 4096; Key++)
            Hash [Key] = -1;
    
        while ((src_index < src_size) && (dst_size <= src_size))
          {
            if (Bit > 15)
              {
                dst [HeaderIndex]     = (byte) ((Command >> 8) & 0x00ff);
                dst [HeaderIndex + 1] = (byte) ( Command       & 0x00ff);
                HeaderIndex = dst_size;
                dst_size += 2;
                Bit = 0;
              }
            for (Size = 1;; Size++)
                if ((word) (src_index + Size) >= src_size
                || (src [src_index] != src [src_index + Size])
                || (Size >= 0x0fff))
                    break;
    
            if (Size >= 16)
              {
                dst [dst_size++] = 0;
                dst [dst_size++] = (byte) (((word) (Size - 16) >> 8) & 0x00ff);
                dst [dst_size++] = (byte) ((Size - 16) & 0x00ff);
                dst [dst_size++] = src [src_index];
                src_index += Size;
                Command = (Command << 1) + 1;
              }
            else
            if (get_match (src, src_index, src_size,
                           Hash, &Size, &SymbolAddress) != 0)
              {
                Key = ((src_index - SymbolAddress) << 4) + (Size - 3);
                dst [dst_size++] = (byte) ((Key >> 8) & 0x00ff);
                dst [dst_size++] = (byte) (Key & 0x00ff);
                src_index += Size;
                Command = (Command << 1) + 1;
              }
            else
              {
                dst [dst_size++] = src [src_index++];
                Command = (Command << 1);
              }
            Bit++;
          }
        Command <<= (16 - Bit);
        dst [HeaderIndex]     = (byte) ((Command >> 8) & 0x00ff);
        dst [HeaderIndex + 1] = (byte) ( Command       & 0x00ff);
    
         if (dst_size > src_size)
          {
             for (dst_size = 0; dst_size < src_size; dst_size++)
                 dst [dst_size + 1] = src [dst_size];
             dst [0] = FLAG_COPY;
             return (src_size + 1);
           }
         return (dst_size);
    }
    
     

    expand_block

    #include "sflcomp.h"
    word
    expand_block (
        const byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Expands a block of data previously compressed using the compress block() function. The compressed block is passed in src; the expanded result in dst. dst must be large enough to accomodate the largest possible decompressed block. Returns the size of the uncompressed data.

    Source Code - (sflcomp.c)

    {
        word SymbolAddress;
        word ChunkSize;
        word Counter;
        word Command = 0;
        word src_index = 1;
        word dst_size = 0;
        byte Bit = 0;
    
        if (src [0] == FLAG_COPY)
          {
            for (dst_size = 1; dst_size < src_size; dst_size++)
                dst [dst_size - 1] = src [dst_size];
            return (src_size - 1);
          }
        while (src_index < src_size)
          {
            if (Bit == 0)
              {
                Command  = src [src_index++] << 8;
                Command += src [src_index++];
                Bit = 16;
              }
            if (Command & 0x8000)
              {
                SymbolAddress =  (word) (src [src_index++] << 4);
                SymbolAddress += (word) (src [src_index] >> 4);
                if (SymbolAddress)
                  {
                    ChunkSize = (word) (src [src_index++] & 0x0f) + 3;
                    SymbolAddress = dst_size - SymbolAddress;
                    for (Counter = 0; Counter < ChunkSize; Counter++)
                        dst [dst_size++] = dst [SymbolAddress++];
                  }
                else
                  {
                    ChunkSize  = (word) (src [src_index++] << 8);
                    ChunkSize += (word) (src [src_index++] + 16);
                    for (Counter = 0; Counter < ChunkSize; Counter++)
                        dst [dst_size++] = src [src_index];
                    src_index++;
                  }
              }
            else
                dst [dst_size++] = src [src_index++];
    
            Command <<= 1;
            Bit--;
          }
        return (dst_size);
    }
    
     

    compress_rle

    #include "sflcomp.h"
    word
    compress_rle (
        byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Takes a block of uncompressed data in src, compresses it using a RLE algorithm and places the result in dst. To decompress the data, use the expand rle () function. Returns the size of the compressed data. The dst buffer should be 10% larger than the src buffer. The src buffer must be at least src_size + 1 bytes long. It may be modified. The compressed data contains these strings:
    [01-7F][data...] String of uncompressed data, 1 to 127 bytes.
    [83-FF][byte] Run of 3 to 127 identical bytes.
    [80][len][byte] Run of 128 to 255 identical bytes.
    [81][lo][hi][byte] Run of 256 to 2^16 identical bytes.
    [82][len] Run of 3 to 255 spaces.
    [00][len] Run of 3 to 255 binary zeroes.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of compressed data          */
            src_scan,                       /*  Scan through source data         */
            run_end,                        /*  Points to end of run of bytes    */
            length = 0;                     /*  Size of the run or string        */
        byte
            cur_byte,                       /*  Next byte to process             */
            *header;                        /*  Header of unpacked string        */
        Bool
            have_run;                       /*  TRUE when we have a run          */
    
        src_scan = 0;                       /*  Start at beginning of source     */
        dst_size = 0;                       /*  No output yet                    */
        header   = NULL;                    /*  No open unpacked string          */
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
            have_run = FALSE;               /*  Unless we find a run             */
    
            /*  Three identical bytes signals the start of a run                 */
            if (cur_byte == src [src_scan]
            &&  cur_byte == src [src_scan + 1]
            && (src_scan + 1 < src_size))
              {
                /*  Stick-in a sentinel character to ensure that the run ends    */
                src [src_size] = !cur_byte;
                run_end = src_scan;         /*  src_scan <= src_size             */
                while (src [run_end] == cur_byte)
                    run_end++;
    
                have_run = TRUE;
                if (header)                 /*  If we have a previous unpacked   */
                  {                         /*    string, close it               */
                    *header = (byte) length;
                    header  = NULL;
                  }
                length = run_end - src_scan + 1;
                src_scan = run_end;
              }
            if (have_run)
              {
                /*  We compress short runs of spaces and nulls separately        */
                if (length < 256 && cur_byte == 0)
                  {
                    dst [dst_size++] = 0x00;
                    dst [dst_size++] = (byte) length;
                  }
                else
                if (length < 256 && cur_byte == ' ')
                  {
                    dst [dst_size++] = 0x82;
                    dst [dst_size++] = (byte) length;
                  }
                else
                if (length < 128)
                  {
                    dst [dst_size++] = (byte) length | 0x80;
                    dst [dst_size++] = cur_byte;
                  }
                else
                if (length < 256)           /*  Short run 128-255 bytes          */
                  {
                    dst [dst_size++] = 0x80;
                    dst [dst_size++] = (byte) length;
                    dst [dst_size++] = cur_byte;
                  }
                else                        /*  Long run 256-2^16 bytes          */
                  {
                    dst [dst_size++] = 0x81;
                    dst [dst_size++] = (byte) (length & 0xff);
                    dst [dst_size++] = (byte) (length >> 8);
                    dst [dst_size++] = cur_byte;
                  }
              }
            else
              {
                if (!header)                /*  Start new unpacked string if     */
                  {                         /*    necessary                      */
                    header = &dst [dst_size++];
                    length = 0;
                  }
                dst [dst_size++] = cur_byte;
                if (++length == 127)        /*  Each string can be up to 127     */
                  {                         /*    bytes long (high bit cleared)  */
                    *header = (byte) length;
                    header  = NULL;
                  }
              }
          }
        if (header)                         /*  If we have a previous unpacked   */
          {                                 /*    string, close it               */
            *header = (byte) length;
            header  = NULL;
          }
        return (dst_size);                  /*  Return compressed data size      */
    }
    
     

    expand_rle

    #include "sflcomp.h"
    word
    expand_rle (
        const byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Expands a block of data previously compressed using the compress rle() function. The compressed block is passed in src; the expanded result in dst. Dst must be large enough to accomodate the largest possible decompressed block. Returns the size of the expanded data.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of expanded data            */
            src_scan,                       /*  Scan through source data         */
            length;                         /*  Size of the run or string        */
        byte
            cur_byte;                       /*  Next byte to process             */
    
        src_scan = 0;
        dst_size = 0;
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
    
            /*  1 to 127 is uncompressed string of 1 to 127 bytes                */
            if (cur_byte > 0 && cur_byte < 128)
              {
                length = (word) cur_byte;
                memcpy (dst + dst_size, src + src_scan, length);
                src_scan += length;
                dst_size += length;
              }
            else                            /*  Run of 3 or more bytes           */
              {
                switch (cur_byte)
                  {
                    case 0x00:              /*  Run of 3-255 zeroes              */
                        length   = src [src_scan++];
                        cur_byte = 0;
                        break;
                    case 0x82:              /*  Run of 3-255 spaces              */
                        length   = src [src_scan++];
                        cur_byte = ' ';
                        break;
                    case 0x80:              /*  Short run 128-255 bytes          */
                        length   = src [src_scan++];
                        cur_byte = src [src_scan++];
                        break;
                    case 0x81:              /*  Long run 256-2^16 bytes          */
                        length   = src [src_scan++];
                        length  += src [src_scan++] << 8;
                        cur_byte = src [src_scan++];
                        break;
                    default:                /*  Run of 3 to 127 bytes            */
                        length = cur_byte & 127;
                        cur_byte = src [src_scan++];
                  }
                memset (dst + dst_size, cur_byte, length);
                dst_size += length;
              }
          }
        return (dst_size);                  /*  Return expanded data size        */
    }
    
     

    compress_nulls

    #include "sflcomp.h"
    word
    compress_nulls (
        byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Similar to compress rle(), but optimised towards compression of binary zeroes. Use this when you are certain that the sparse areas are set to binary zeroes. You must use expand nulls () to decompress a block compressed with this function. Returns the size of the compressed data. The dst buffer should be 10% larger than the src buffer. The src buffer must be at least src_size + 1 bytes long. It may be modified. The compressed data contains these strings:
    [01-7F][data...] String of uncompressed data, 1 to 127 bytes.
    [82-FF] Run of 2 to 127 binary zeroes.
    [81][80-FF] Run of 128 to 255 binary zeroes.
    [80][lo][hi] Run of 256 to 2^16 binary zeroes.
    [00][len][byte] Run of 4 to 255 identical bytes.
    [00][00][lo][hi][byte] Run of 256 to 2^16 identical bytes.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of compressed data          */
            src_scan,                       /*  Scan through source data         */
            run_end,                        /*  Points to end of run of bytes    */
            length = 0;                     /*  Size of the run or string        */
        byte
            cur_byte,                       /*  Next byte to process             */
            *header;                        /*  Header of unpacked string        */
        Bool
            have_run;                       /*  TRUE when we have a run          */
    
        src_scan = 0;                       /*  Start at beginning of source     */
        dst_size = 0;                       /*  No output yet                    */
        header   = NULL;                    /*  No open unpacked string          */
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
            have_run = FALSE;               /*  Unless we find a run             */
    
            /*  Two identical bytes may signal the start of a run                */
            if (cur_byte == src [src_scan]
            &&  src_scan < src_size)
              {
                /*  Stick-in a sentinel character to ensure that the run ends    */
                src [src_size] = !cur_byte;
                run_end = src_scan;         /*  src_scan <= src_size             */
                while (src [run_end] == cur_byte)
                    run_end++;
    
                /*  A run is 4+ identical bytes or 2+ nulls                      */
                if ((run_end - src_scan > 2) || cur_byte == 0)
                  {
                    have_run = TRUE;
                    if (header)             /*  If we have a previous unpacked   */
                      {                     /*    string, close it               */
                        *header = (byte) length;
                        header  = NULL;
                      }
                    length = run_end - src_scan + 1;
                    src_scan = run_end;
                  }
              }
            if (have_run)
              {
                if (cur_byte == 0)
                  {
                    if (length < 128)       /*  2-127 binary zeroes              */
                        dst [dst_size++] = (byte) (length | 0x80);
                    else
                    if (length < 256)       /*  128-256 binary zeroes            */
                      {
                        dst [dst_size++] = 0x81;
                        dst [dst_size++] = (byte) length;
                      }
                    else                    /*  256-2^15 binary zeroes           */
                      {
                        dst [dst_size++] = 0x80;
                        dst [dst_size++] = (byte) (length & 0xff);
                        dst [dst_size++] = (byte) (length >> 8);
                      }
                  }
                else
                if (length < 256)           /*  Short run 4-255 bytes            */
                  {
                    dst [dst_size++] = 0x00;
                    dst [dst_size++] = (byte) length;
                    dst [dst_size++] = cur_byte;
                  }
                else                        /*  Long run 256-2^16 bytes          */
                  {
                    dst [dst_size++] = 0x00;
                    dst [dst_size++] = 0x00;
                    dst [dst_size++] = (byte) (length & 0xff);
                    dst [dst_size++] = (byte) (length >> 8);
                    dst [dst_size++] = cur_byte;
                  }
              }
            else
              {
                if (!header)                /*  Start new unpacked string if     */
                  {                         /*    necessary                      */
                    header = &dst [dst_size++];
                    length = 0;
                  }
                dst [dst_size++] = cur_byte;
                if (++length == 127)        /*  Each string can be up to 127     */
                  {                         /*    bytes long (high bit cleared)  */
                    *header = (byte) length;
                    header  = NULL;
                  }
              }
          }
        if (header)                         /*  If we have a previous unpacked   */
          {                                 /*    string, close it               */
            *header = (byte) length;
            header  = NULL;
          }
        return (dst_size);                  /*  Return compressed data size      */
    }
    
     

    expand_nulls

    #include "sflcomp.h"
    word
    expand_nulls (
        const byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Expands a block of data previously compressed using the compress nulls() function. The compressed block is passed in src; the expanded result in dst. Dst must be large enough to accomodate the largest possible decompressed block. Returns the size of the expanded data.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of expanded data            */
            src_scan,                       /*  Scan through source data         */
            length;                         /*  Size of the run or string        */
        byte
            cur_byte;                       /*  Next byte to process             */
    
        src_scan = 0;
        dst_size = 0;
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
    
            /*  1 to 127 is uncompressed string of 1 to 127 bytes                */
            if (cur_byte > 0 && cur_byte < 128)
              {
                length = (word) cur_byte;
                memcpy (dst + dst_size, src + src_scan, length);
                src_scan += length;
                dst_size += length;
              }
            else                            /*  Run of 2 or more bytes           */
              {
                switch (cur_byte)
                  {
                    case 0x00:              /*  Run of non-zero bytes            */
                        length = src [src_scan++];
                        if (length == 0)    /*  Stored as double-byte            */
                          {
                            length   = src [src_scan++];
                            length  += src [src_scan++] << 8;
                          }
                        cur_byte = src [src_scan++];
                        break;
                    case 0x80:              /*  256-2^16 zeroes                  */
                        length   = src [src_scan++];
                        length  += src [src_scan++] << 8;
                        cur_byte = 0;
                        break;
                    case 0x81:              /*  128 to 255 zeroes                */
                        length   = src [src_scan++];
                        cur_byte = 0;
                        break;
                    default:                /*  2 to 127 zeroes                  */
                        length   = cur_byte & 127;
                        cur_byte = 0;
                  }
                memset (dst + dst_size, cur_byte, length);
                dst_size += length;
              }
          }
        return (dst_size);                  /*  Return expanded data size        */
    }
    
     

    compress_bits

    #include "sflcomp.h"
    word
    compress_bits (
        byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Similar to compress rle(), but optimised towards compression of sparse bitmaps. Use this when you are playing with large, sparse bitmaps. You must use expand bits () to decompress a block compressed with this function. Returns the size of the compressed data. The dst buffer should be 10% larger than the src buffer for worst cases. The src buffer must be at least src_size + 1 bytes long. It may be modified. The compressed data contains these strings:
    [00-07] Single byte containing a bit in position 0 to 7.
    [08-7F][data...] String of uncompressed data, 1 to 120 bytes.
    [82-FF] Run of 1 to 126 binary zeroes.
    [81][00-FD] Run of 127 to 380 binary zeroes.
    [81][FE][len][byte] Run of 4 to 255 identical bytes.
    [81][FF][lo][hi][byte] Run of 256 to 2^16 identical bytes.
    [80][lo][hi] Run of 381 to 2^16 binary zeroes.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of compressed data          */
            src_scan,                       /*  Scan through source data         */
            run_end,                        /*  Points to end of run of bytes    */
            length = 0;                     /*  Size of the run or string        */
        byte
            cur_byte,                       /*  Next byte to process             */
            *header;                        /*  Header of unpacked string        */
        static byte
            single_bits [256];              /*  Bytes with one bit set           */
        static Bool
            initialised = FALSE;            /*  First time flag                  */
    
        /*  The single_bits table provides a fast lookup for bytes with          */
        /*  one bit set.  The 'interesting' bytes are non-zero in the table      */
        /*  where their value is the output code value (0-7) + 1.                */
        if (!initialised)                   /*  First time?  Initialise          */
          {
            memset (single_bits, 0, 256);
            single_bits [1]   = 1;
            single_bits [2]   = 2;
            single_bits [4]   = 3;
            single_bits [8]   = 4;
            single_bits [16]  = 5;
            single_bits [32]  = 6;
            single_bits [64]  = 7;
            single_bits [128] = 8;
          }
    
        src_scan = 0;                       /*  Start at beginning of source     */
        dst_size = 0;                       /*  No output yet                    */
        header   = NULL;                    /*  No open unpacked string          */
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
    
            /*- Look for 1 or more binary zeroes, and compress into a run -------*/
    
            if (cur_byte == 0)
              {
                src [src_size] = 0xff;      /*  Stop with a sentinel             */
                run_end = src_scan;         /*  src_scan <= src_size             */
                while (src [run_end] == 0)
                    run_end++;
    
                if (header)                 /*  If we have a previous unpacked   */
                  {                         /*    string, close it               */
                    *header = (byte) length + 7;
                    header  = NULL;
                  }
                length = run_end - src_scan + 1;
                src_scan = run_end;
                if (length < 127)           /*  1-126 binary zeroes              */
                    dst [dst_size++] = (byte) (++length | 0x80);
                else
                if (length < 381)           /*  127-380 binary zeroes            */
                  {
                    dst [dst_size++] = 0x81;
                    dst [dst_size++] = (byte) length - 127;
                  }
                else                        /*  381-2^16 binary zeroes           */
                  {
                    dst [dst_size++] = 0x80;
                    dst [dst_size++] = (byte) (length & 0xff);
                    dst [dst_size++] = (byte) (length >> 8);
                  }
              }
            else
    
            /*- Next, look for bytes with 1 bit set; we encode these as 1 byte --*/
    
            if (single_bits [cur_byte])     /*  Single bit value?                */
              {
                dst [dst_size++] = single_bits [cur_byte] - 1;
                if (header)                 /*  If we have a previous unpacked   */
                  {                         /*    string, close it               */
                    *header = (byte) length + 7;
                    header  = NULL;
                  }
              }
            else
    
            /*- Next, look for a run of 4 or more identical (non-zero) bytes ----*/
    
            if (cur_byte == src [src_scan]
            &&  cur_byte == src [src_scan + 1]
            &&  cur_byte == src [src_scan + 2]
            && (src_scan < src_size - 2))
              {
                src [src_size] = !cur_byte; /*  Stick in a sentinel byte         */
                run_end = src_scan;         /*  src_scan <= src_size             */
                while (src [run_end] == cur_byte)
                    run_end++;
    
                if (header)                 /*  If we have a previous unpacked   */
                  {                         /*    string, close it               */
                    *header = (byte) length + 7;
                    header  = NULL;
                  }
                length = run_end - src_scan + 1;
                src_scan = run_end;
    
                if (length < 256)           /*  Short run 4-255 bytes            */
                  {
                    dst [dst_size++] = 0x81;
                    dst [dst_size++] = 0xFE;
                    dst [dst_size++] = (byte) length;
                    dst [dst_size++] = cur_byte;
                  }
                else                        /*  Long run 256-2^16 bytes          */
                  {
                    dst [dst_size++] = 0x81;
                    dst [dst_size++] = 0xFF;
                    dst [dst_size++] = (byte) (length & 0xff);
                    dst [dst_size++] = (byte) (length >> 8);
                    dst [dst_size++] = cur_byte;
                  }
              }
            else
    
            /*- Lastly, compress unpackable strings into chunks of 120 bytes ----*/
    
              {
                if (!header)                /*  Start new unpacked string if     */
                  {                         /*    necessary                      */
                    header = &dst [dst_size++];
                    length = 0;
                  }
                dst [dst_size++] = cur_byte;
                if (++length == 120)        /*  Each string can be up to 120     */
                  {                         /*    bytes long (high bit cleared)  */
                    *header = (byte) length + 7;
                    header  = NULL;
                  }
              }
          }
        if (header)                         /*  If we have a previous unpacked   */
          {                                 /*    string, close it               */
            *header = (byte) length + 7;
            header  = NULL;
          }
        return (dst_size);                  /*  Return compressed data size      */
    }
    
     

    expand_bits

    #include "sflcomp.h"
    word
    expand_bits (
        const byte *src,
        byte *dst,
        word src_size)
    

    Synopsis

    Expands a block of data previously compressed using the compress bits() function. The compressed block is passed in src; the expanded result in dst. Dst must be large enough to accomodate the largest possible decompressed block. Returns the size of the expanded data.

    Source Code - (sflcomp.c)

    {
        word
            dst_size,                       /*  Size of expanded data            */
            src_scan,                       /*  Scan through source data         */
            length;                         /*  Size of the run or string        */
        byte
            cur_byte;                       /*  Next byte to process             */
    
        src_scan = 0;
        dst_size = 0;
        while (src_scan < src_size)
          {
            cur_byte = src [src_scan++];
    
            if (cur_byte < 8)               /*  Single bit in position 0 to 7    */
                dst [dst_size++] = 1 << cur_byte;
            else
            if (cur_byte < 128)             /*  String of 1 to 120 bytes         */
              {
                length = (word) cur_byte - 7;
                memcpy (dst + dst_size, src + src_scan, length);
                src_scan += length;
                dst_size += length;
              }
            else                            /*  Run of 1 or more bytes           */
              {
                switch (cur_byte)
                  {
                    case 0x80:              /*  381-2^16 binary zeroes           */
                        length   = src [src_scan++];
                        length  += src [src_scan++] << 8;
                        cur_byte = 0;
                        break;
                    case 0x81:
                        length = src [src_scan++];
                        if (length == 0xFE) /*  4-255 non-zero bytes             */
                          {
                            length   = src [src_scan++];
                            cur_byte = src [src_scan++];
                          }
                        else
                        if (length == 0xFF) /*  Run of 256-2^15 non-zero bytes   */
                          {
                            length   = src [src_scan++];
                            length  += src [src_scan++] << 8;
                            cur_byte = src [src_scan++];
                          }
                        else
                          {
                            length  += 127;
                            cur_byte = 0;   /*  127 to 380 zeroes                */
                          }
                        break;
                    default:                /*  1 to 126 zeroes                  */
                        length   = (cur_byte - 1) & 127;
                        cur_byte = 0;
                  }
                memset (dst + dst_size, cur_byte, length);
                dst_size += length;
              }
          }
        return (dst_size);                  /*  Return expanded data size        */
    }
    

    Console output functions

    Filename: sflcons.h
    Package: Standard Function Library (SFL)
    Written: 97/05/22 iMatix SFL project team sfl@imatix.com
    Revised: 98/02/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides redirectable console output: use the coprintf() and coputs() calls instead of printf() and puts() in a real-time application. Then, you can call console send() to send all console output to a specified function. This is a useful way to get output into -- for example -- a GUI window.

    List of Functions

    List of Symbol Definitions

    sflcons.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLCONS_INCLUDED TRUE

    List of Type Definitions

    Type name: Defined as:
    CONSOLE_FCT void () (const char *)
     

    console_send

    #include "sflcons.h"
    void
    console_send (CONSOLE_FCT *new_console_fct, Bool echo)
    

    Synopsis

    Redirects console output to a specified CONSOLE_FCT function. If the specified address is NULL, redirects back to the stdout stream. This is independent of any console capturing in progress. If the echo argument is TRUE, console output is also sent to stdout.

    Source Code - (sflcons.c)

    {
        console_fct  = new_console_fct;
        console_echo = echo;                /*  Copy to stdout                   */
    }
    
     

    console_enable

    #include "sflcons.h"
    void
    console_enable (void)
    

    Synopsis

    Enables console output. Use together with console disable() to stop and start console output.

    Source Code - (sflcons.c)

    {
        console_active = TRUE;
    }
    
     

    console_disable

    #include "sflcons.h"
    void
    console_disable (void)
    

    Synopsis

    Disables console output. Use together with console enable() to stop and start console output.

    Source Code - (sflcons.c)

    {
        console_active = FALSE;
    }
    
     

    console_set_mode

    #include "sflcons.h"
    void
    console_set_mode (int mode)
    

    Synopsis

    Sets console display mode; the argument can be one of:
    CONSOLE PLAIN Output text exactly as specified
    CONSOLE DATETIME Prefix text by "yy/mm/dd hh:mm:ss "
    CONSOLE TIME Prefix text by "hh:mm:ss "
    The default is plain output.

    Source Code - (sflcons.c)

    {
        ASSERT (mode == CONSOLE_PLAIN
             || mode == CONSOLE_DATETIME
             || mode == CONSOLE_TIME);
    
        console_mode = mode;
    }
    
     

    console_capture

    #include "sflcons.h"
    int
    console_capture (const char *filename, char mode)
    

    Synopsis

    Starts capturing console output to the specified file. If the mode is 'w', creates an empty capture file. If the mode is 'a', appends to any existing data. Returns 0 if okay, -1 if there was an error - in this case you can test the value of errno. If the filename is NULL or an empty string, closes any current capture file.

    Source Code - (sflcons.c)

    {
        if (console_file)
          {
            file close (console_file);
            console_file = NULL;
          }
        if (filename && *filename)
          {
            ASSERT (mode == 'w' || mode == 'a');
            console_file = file open (filename, mode);
            if (console_file == NULL)
                return (-1);
          }
        return (0);
    }
    
     

    coprintf

    #include "sflcons.h"
    int
    coprintf (const char *format, ...)
    

    Synopsis

    As printf() but sends output to the current console. This is by default the stdout device, unless you used console send() to direct console output to some function. A newline is added automatically.

    Source Code - (sflcons.c)

    {
        static char
            formatted [LINE_MAX];
        va_list
            argptr;                         /*  Argument list pointer            */
        int
            fmtsize = 0;                    /*  Size of formatted line           */
        char
            *prefixed = NULL;               /*  Prefixed formatted line          */
    
        if (console_active)
          {
            va_start (argptr, format);      /*  Start variable args processing   */
    #if (defined (DOES_SNPRINTF))
            fmtsize = vsnprintf (formatted, LINE_MAX, format, argptr);
    #else
            fmtsize = vsprintf  (formatted, format, argptr);
    #endif
            va_end (argptr);                /*  End variable args processing     */
            ASSERT (fmtsize < LINE_MAX);
    
            switch (console_mode)
              {
                case CONSOLE_DATETIME:
                    prefixed = xstrcpy (NULL, date_str (), " ", time_str (), ": ",
                                        formatted, NULL);
                    break;
                case CONSOLE_TIME:
                    prefixed = xstrcpy (NULL, time_str (), ": ", formatted, NULL);
                    break;
              }
            if (console_file)
              {
                file write (console_file, prefixed? prefixed: formatted);
                fflush (console_file);
              }
            if (console_fct)
                (console_fct) (prefixed? prefixed: formatted);
    
            if (console_echo)
              {
                fprintf (stdout, prefixed? prefixed: formatted);
                fprintf (stdout, "\n");
                fflush  (stdout);
              }
            if (prefixed)
              {
                fmtsize = strlen (prefixed);
                mem_free (prefixed);
              }
          }
        return (fmtsize);
    }
    
     

    coputs

    #include "sflcons.h"
    int
    coputs (const char *string)
    

    Synopsis

    As puts() but sends output to the current console. This is by default the stdout device, unless you used console send() to direct console output to some function.

    Source Code - (sflcons.c)

    {
        coprintf (string);
        return (1);
    }
    
     

    coputc

    #include "sflcons.h"
    int
    coputc (int character)
    

    Synopsis

    As putc() but sends output to the current console. This is by default the stdout device, unless you used console send() to direct console output to some function.

    Source Code - (sflcons.c)

    {
        char
            buffer [2];
    
        if (console_active)
          {
            if (console_file)
              {
                putc (character, console_file);
                fflush (console_file);
              }
            if (console_fct)
              {
                buffer [0] = (char) character;
                buffer [1] = '\0';
                (console_fct) (buffer);
              }
            if (console_echo)
              {
                putc (character, stdout);
                fflush  (stdout);
              }
          }
        return (character);
    }
    

    Conversion functions

    Filename: sflconv.h
    Package: Standard Function Library (SFL)
    Written: 95/12/17 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    These functions provide conversion between a set of datatypes (dates, times, numbers, Booleans) and external strings that represent the values. The objective is to format datatypes for display or printing, and to validate and convert strings supplied by the user. Conversion is controlled by a set of options specific to each datatype. Additionally, dates and times may be formatted using picture strings. The functions were written for use in an interactive 'forms' environment.

    List of Functions

    List of Symbol Definitions

    sflconv.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    BOOL_1_0 4
    BOOL_TRUE_FALSE 2
    BOOL_T_F 3
    BOOL_YES_NO 0 /* Boolean field formatting */
    BOOL_Y_N 1
    CONV_ERR_BAD_MONTH 8 /* Unknown month name */
    CONV_ERR_DATE_OVERFLOW 5 /* Result too large for output */
    CONV_ERR_DATE_SIZE 6 /* Too few or too many digits */
    CONV_ERR_DECS_HIDDEN 18 /* Decimals not allowed if hidden */
    CONV_ERR_DECS_MISSING 11 /* Not enough decimals supplied */
    CONV_ERR_DECS_OVERFLOW 19 /* Too many decimal positions */
    CONV_ERR_DECS_REJECTED 17 /* Decimals not allowed if integer */
    CONV_ERR_INVALID_INPUT 1 /* Unrecognised char in input */
    CONV_ERR_MULTIPLE_AM 4 /* More than one 'am' or 'pm' */
    CONV_ERR_MULTIPLE_DELIM 7 /* Too many delimiters */
    CONV_ERR_MULTIPLE_MONTH 10 /* More than one month name */
    CONV_ERR_MULTIPLE_POINT 16 /* More than one decimal point */
    CONV_ERR_MULTIPLE_SIGN 13 /* More than one sign character */
    CONV_ERR_NOT_BOOLEAN 3 /* Not a yes/no or true/false value */
    CONV_ERR_NUM_OVERFLOW 12 /* Result too large for output */
    CONV_ERR_OUT_OF_RANGE 2 /* Value out of valid range */
    CONV_ERR_REJECT_3_5 9 /* 3/5 digits in a row not allowed */
    CONV_ERR_SIGN_BAD_FIN 15 /* Malformed financial negative */
    CONV_ERR_SIGN_REJECTED 14 /* Sign not allowed if unsigned */
    CONV_ERR_TOO_MANY_DIGITS 20 /* Too many digits for number */
    CONV_MAX_DECS 100 /* Up to 100 decimal positions */
    CONV_NO_ERRORS 0 /* No errors */
    DATE_MD_COMPACT 7
    DATE_MD_DELIM 8
    DATE_MD_SPACE 9
    DATE_ORDER_DMY 2
    DATE_ORDER_MDY 3
    DATE_ORDER_YMD 1
    DATE_YMD_COMMA 3
    DATE_YMD_COMPACT 0
    DATE_YMD_DELIM 1
    DATE_YMD_SPACE 2
    DATE_YM_COMPACT 4
    DATE_YM_DELIM 5
    DATE_YM_SPACE 6
    DECS_DROP_ZEROS 2
    DECS_HIDE_ALL 3
    DECS_SCIENTIFIC 4
    DECS_SHOW_ALL 1
    FLAG_D_CENTURY 8
    FLAG_D_DD_AS_D 1 /* Date field flags */
    FLAG_D_MM_AS_M 2
    FLAG_D_MONTH_ABC 4
    FLAG_D_ORDER_DMY 64
    FLAG_D_ORDER_MDY 128
    FLAG_D_ORDER_YMD 32
    FLAG_D_UPPER 16
    FLAG_N_DECIMALS 2
    FLAG_N_LEFT 4
    FLAG_N_SIGNED 1 /* Number field flags */
    FLAG_N_THOUSANDS 32
    FLAG_N_ZERO_BLANK 16
    FLAG_N_ZERO_FILL 8
    FLAG_T_12_HOUR 32
    FLAG_T_CC_AS_C 8
    FLAG_T_COMPACT 16
    FLAG_T_HH_AS_H 1 /* Time field flags */
    FLAG_T_MM_AS_M 2
    FLAG_T_SS_AS_S 4
    FORMAT_MAX 80 /* Max. size of formatted field */
    SIGN_ALL_LEAD 4
    SIGN_ALL_TRAIL 2
    SIGN_FINANCIAL 5
    SIGN_NEG_LEAD 3
    SIGN_NEG_TRAIL 1 /* Number field formatting */
    _DATE_FORMAT_FIRST 0 /* Date field formatting */
    _DATE_FORMAT_LAST 9
    _DATE_MD_LAST 9
    _DATE_ORDER_FIRST 1 /* Values for date_order */
    _DATE_ORDER_LAST 3
    _DATE_YMD_LAST 3
    _DATE_YM_LAST 6
    _SFLCONV_INCLUDED TRUE
     

    conv_bool_str

    #include "sflconv.h"
    char *
    conv_bool_str (
        Bool boolean,
        int  format)
    

    Synopsis

    Converts a Bool value to a string according to the specified format: 0 = Yes|No; 1 = Y|N, 2 = True|False, 3 = T|F, 4 = 1|0. Returns a pointer to a static string that is overwritten by each call.

    Source Code - (sflcvbs.c)

    {
        static char *bool_name [] =
          {
            "Yes",  "No",
            "Y",    "N",
            "True", "False",
            "T",    "F",
            "1",    "0"
          };
    
        conv_reason = 0;                    /*  No conversion errors so far      */
        return (bool_name [format * 2 + (boolean? 0: 1)]);
    }
    
     

    conv_date_pict

    #include "sflconv.h"
    char *
    conv_date_pict (
        long date,
        const char *picture)
    

    Synopsis

    Converts a date to a string using a picture. The picture is composed of any combination of these formats:
    cc century 2 digits, 01-99
    y day of year, 1-366
    yy year 2 digits, 00-99
    yyyy year 4 digits, 100-9999
    m month, 1-12
    mm month, 01-12
    mmm month, 3 letters
    mmmm month, full name
    MMM month, 3 letters, ucase
    MMMM month, full name, ucase
    d day, 1-31
    dd day, 01-31
    ddd day of week, Sun-Sat
    dddd day of week, Sunday- Saturday
    DDD day of week, SUN-SAT
    DDDD day of week, SUNDAY-SATURDAY
    w day of week, 1-7 (1=Sunday)
    ww week of year, 1-53
    q year quarter, 1-4
    \x literal character x
    other literal character
    Returns the formatted result. This is a static string, of at most 80 characters, that is overwritten by each call. If date is zero, returns an empty string. The 'm' and 'd' formats output a leading space when used at the start of the picture. This is to improve alignment of columns of dates. The 'm' and 'd' formats also output a space when the previous character was a digit; otherwise the date components stick together and are illegible.

    Examples

        puts (conv_date_pict (19951202, "mm d, yy"));
            Dec 2, 95
        puts (conv_date_pict (19951202, "d mmm, yy"));
            2 Dec, 95
    

    Source Code - (sflcvdp.c)

    {
        static char
            *month_name [] =
              {
                "January", "February", "March", "April", "May", "June", "July",
                "August", "September", "October", "November", "December"
              },
            *day_name [] =
              {
                "Sunday", "Monday", "Tuesday", "Wednesday",
                "Thursday", "Friday", "Saturday"
              },
            formatted [FORMAT_MAX + 1];     /*  Formatted return string          */
        int
            century,                        /*  Century component of date        */
            year,                           /*  Year component of date           */
            month,                          /*  Month component of date          */
            day,                            /*  Day component of date            */
            cursize;                        /*  Size of current component        */
        char
           *dest,                           /*  Store formatted data here        */
            ch,                             /*  Next character in picture        */
            lastch = '0';                   /*  Last character we output         */
        long
            full_date = date;
    
        conv_reason = 0;                    /*  No conversion errors so far      */
    
        /*  Zero date is returned as empty string                                */
        if (date == 0)
          {
            strclr (formatted);
            return (formatted);
          }
    
        default century (&full_date);
        century = GET_CENTURY (full_date);
        year    = GET_YEAR    (full_date);
        month   = GET_MONTH   (full_date);
        day     = GET_DAY     (full_date);
    
        ASSERT (month > 0 && month <= 12);
        ASSERT (day   > 0 && day   <= 31);
    
        /*  Scan through picture, converting each component                      */
        dest = formatted;
        *dest = 0;                          /*  string is empty                  */
        while (*picture)
          {
            /*  Get character and count number of occurences                     */
            ch = *picture++;
            for (cursize = 1; *picture == ch; cursize++)
                picture++;
    
            switch (ch)
              {
                /*  cc        century 2 digits, 01-99                            */
                case 'c':
                    if (cursize == 2)
                        sprintf (dest, "%02d", century);
                    break;
    
                /*  y         day of year, 1-366                                 */
                /*  yy        year 2 digits, 00-99                               */
                /*  yyyy      year 4 digits, 0100-9999                           */
                case 'y':                   /*  y = day of year                  */
                    if (cursize == 1)
                        sprintf (dest, "%d", julian date (full_date));
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", year);
                    else
                    if (cursize == 4)
                        sprintf (dest, "%02d%02d", century, year);
                    break;
    
                /*  m         month, 1-12                                        */
                /*  mm        month, 01-12                                       */
                /*  mmm       month, 3 letters                                   */
                /*  mmmm      month, full name                                   */
                case 'm':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), month);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", month);
                    else
                    if (cursize == 3)
                      {
                        memcpy (dest, month_name [month - 1], 3);
                        dest [3] = 0;
                      }
                    else
                    if (cursize == 4)
                        strcpy (dest, month_name [month - 1]);
                    break;
    
                /*  MMM       month, 3-letters, ucase                            */
                /*  MMMM      month, full name, ucase                            */
                case 'M':
                    if (cursize == 3)
                      {
                        memcpy (dest, month_name [month - 1], 3);
                        dest [3] = 0;
                        strupc (dest);
                      }
                    else
                    if (cursize == 4)
                      {
                        strcpy (dest, month_name [month - 1]);
                        strupc (dest);
                      }
                    break;
    
                /*  d         day, 1-31                                          */
                /*  dd        day, 01-31                                         */
                /*  ddd       day of week, Sun-Sat                               */
                /*  dddd      day of week, Sunday-Saturday                       */
                case 'd':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), day);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", day);
                    else
                    if (cursize == 3)
                      {
                        memcpy (dest, day_name [day of week (full_date)], 3);
                        dest [3] = 0;
                      }
                    else
                    if (cursize == 4)
                        strcpy (dest, day_name [day of week (full_date)]);
                    break;
    
                /*  DDD       day of week, SUN-SAT                               */
                /*  DDDD      day of week, SUNDAY-SATURDAY                       */
                case 'D':
                    if (cursize == 3)
                      {
                        memcpy (dest, day_name [day of week (full_date)], 3);
                        dest [3] = 0;
                        strupc (dest);
                      }
                    else
                    if (cursize == 4)
                      {
                        strcpy (dest, day_name [day of week (full_date)]);
                        strupc (dest);
                      }
                    break;
    
                /*  w         day of week, 1-7 (1=Sunday)                        */
                /*  ww        week of year, 1-53                                 */
                case 'w':
                    if (cursize == 1)
                        sprintf (dest, "%d", day of week (full_date) + 1);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%d", week of year (full_date));
                    break;
    
                /*  q         year quarter, 1-4                                  */
                case 'q':
                    if (cursize == 1)
                        sprintf (dest, "%d", year quarter (full_date));
                    break;
    
                /*  \x        literal character x                                */
                case '\\':
                    ch = *picture++;
            }
            if (*dest)                      /*  If something was output,         */
                while (*dest)               /*    skip to end of string          */
                    dest++;
            else
                while (cursize--)           /*  Else output ch once or more      */
                    *dest++ = ch;           /*    and bump dest pointer          */
    
            lastch = *(dest - 1);           /*  Get previous character           */
            *dest = 0;                      /*  Terminate the string nicely      */
        }
        return (formatted);
    }
    
     

    conv_date_str

    #include "sflconv.h"
    char *
    conv_date_str (
        long date,
        int  flags,
        int  format,
        int  order,
        char datesep,
        int  width)
    

    Synopsis

    Converts a date to a string. The format argument defines how the date is shown:
    DATE YMD COMPACT ddmmyy
    DATE YMD SLASH dd/mm/yy
    DATE YMD SPACE dd mm yy
    DATE YMD COMMA dd mm, yy (DM,Y or MD,Y or Y,MD)
    DATE YM COMPACT mmyy
    DATE YM SLASH mm/yy
    DATE YM SPACE mm yy
    DATE MD COMPACT ddmm
    DATE MD SLASH dd/mm
    DATE MD SPACE dd mm
    The date order (year/month/day) is normally supplied in the order argument. However, the date flags can override this. The flags are:
    FLAG D DD AS D Show day without leading zero
    FLAG D MM AS M Show month without leading zero
    FLAG D MONTH ABC Show month as letters (fullname if width > 16)
    FLAG D CENTURY Show year as four digits
    FLAG D UPPERCASE Month name in uppercase
    FLAG D ORDER DMY Order is DMY for this date
    FLAG D ORDER MDY Order is MDY for this date
    FLAG D ORDER YMD Order is YMD for this date
    Returns a pointer to a static area holding the string, or NULL if there was an error (for instance, formatted date greater than width).

    Source Code - (sflcvds.c)

    {
        static char *format_table [] = {
            "ymd",    "dmy",    "mdy",      /*  DATE_YMD_COMPACT                 */
            "y/m/d",  "d/m/y",  "m/d/y",    /*  DATE_YMD_DELIM                   */
            "y m d",  "d m y",  "m d y",    /*  DATE_YMD_SPACE                   */
            "y, m d", "d m, y", "m d, y",   /*  DATE_YMD_COMMA                   */
            "ym",     "my",     "my",       /*  DATE_YM_COMPACT                  */
            "y/m",    "m/y",    "m/y",      /*  DATE_YM_DELIM                    */
            "y m",    "m y",    "m y",      /*  DATE_YM_SPACE                    */
            "md",     "dm",     "md",       /*  DATE_MD_COMPACT                  */
            "m/d",    "d/m",    "m/d",      /*  DATE_MD_DELIM                    */
            "m d",    "d m",    "m d"       /*  DATE_MD_SPACE                    */
        };
        char
            *format_ptr,                    /*  Scan through format string       */
            delim [2],                      /*  Delimiter character              */
            picture [14],                   /*  Largest picture: dd mmmm, yyyy   */
            ch;                             /*  Next char in format string       */
        int
            index,
            date_order = order;             /*  Order to use                     */
    
        ASSERT (format >= _DATE_FORMAT_FIRST && format <= _DATE_FORMAT_LAST);
        ASSERT (order  >= _DATE_ORDER_FIRST  && order  <= _DATE_ORDER_LAST);
    
        conv_reason = 0;                    /*  No conversion errors so far      */
    
        if (flags & FLAG_D_ORDER_YMD)
            date_order = DATE_ORDER_YMD;
        else
        if (flags & FLAG_D_ORDER_DMY)
            date_order = DATE_ORDER_DMY;
        else
        if (flags & FLAG_D_ORDER_MDY)
            date_order = DATE_ORDER_MDY;
    
        /*  Get index into table                                                 */
        index = format * 3 + date_order - 1;
    
        /*  Now build-up picture according to format string                      */
        strclr (picture);
        for (format_ptr = format_table [index]; *format_ptr; format_ptr++)
          {
            ch = *format_ptr;
            switch (ch)
              {
                case 'y':
                    strcat (picture, flags & FLAG_D_CENTURY? "yyyy": "yy");
                    break;
    
                case 'm':
                    if (flags & FLAG_D_MONTH_ABC)
                        if (width > 16)
                            strcat (picture, flags & FLAG_D_UPPER? "MMMM": "mmmm");
                        else
                            strcat (picture, flags & FLAG_D_UPPER? "MMM": "mmm");
                    else
                        strcat (picture, flags & FLAG_D_MM_AS_M? "m": "mm");
                    break;
    
                case 'd':
                    strcat (picture, flags & FLAG_D_DD_AS_D? "d": "dd");
                    break;
    
                case '/':
                    ch = datesep;           /*  Use supplied date separator      */
                default:
                    delim [0] = ch;
                    delim [1] = 0;
                    strcat (picture, delim);
              }
          }
        format_ptr = conv date pict (date, picture);
        if (strlen (format_ptr) > (unsigned) width)
          {
            conv_reason = CONV_ERR_DATE_OVERFLOW;
            return (NULL);
          }
        else
            return (format_ptr);
    }
    
     

    conv_number_str

    #include "sflconv.h"
    char *
    conv_number_str (
        const char *number,                 /*  Number to convert                */
        int   flags,                        /*  Number formatting flags          */
        char  dec_point,                    /*  Decimal point: '.' or ','        */
        int   decimals,                     /*  Number of decimals, or 0         */
        int   dec_format,                   /*  How are decimals shown?          */
        int   width,                        /*  Output field width, or 0         */
        int   sign_format                   /*  How are negatives shown?         */
    )
    

    Synopsis

    Converts a number to a string. The number format is defined largely by the flags argument, which can specify various values defined in sflconv.h:
    FLAG N SIGNED Show signed number, using sign_format argument.
    FLAG N DECIMALS Show decimals, using dec_format argument.
    FLAG N LEFT Left-justify number; no effect if width is 0.
    FLAG N ZERO FILL Right-justfified, with leading zeroes.
    FLAG N ZERO BLANK Show zero as empty string or spaces (width > 0).
    FLAG N THOUSANDS Show number with thousands separators.
    Sign formats:
    SIGN NEG TRAIL Negative numbers only: 123-
    SIGN ALL TRAIL All non-zero numbers: 123- 123+
    SIGN NEG LEAD Negative numbers only: -123
    SIGN ALL LEAD All non-zero numbers: -123 +123
    SIGN FINANCIAL Negative numbers only: (123)
    Decimal formats:
    DECS SHOW ALL 123.10, 123.00, 0.95
    DECS DROP ZEROS 123.1, 123, 0.95
    DECS HIDE ALL 123, 123, 0
    DECS PERCENTAGE 12300, 12300, 95
    DECS SCIENTIFIC 1.231e2, 1.23e2, 9.5e-1
    The input number string may contain leading zeros and a leading sign character (space, '+', '-') if signed. These are examples of valid 8-digit numbers: "1234" "00001234" "12345678" "+12345678". If the flag FLAG_N_DECIMALS is set, the last X digits are taken to be decimals, where X is the value of the decimals argument. If the number contains a decimal point (always '.'), this is taken to indicate the start of the decimal part. The formatted number is placed within a field of specified width. If the number is right-justfied, this means it may have leading spaces. If the field width is 0, the number will never have leading spaces. Returns a pointer to the formatted string, or NULL if the specified width is too small for formatted number or the supplied number does not contain enough digits.

    Source Code - (sflcvns.c)

    {
        static char
            formatted [FORMAT_MAX + 1],     /*  Formatted return string          */
            zero [CONV_MAX_DECS + 2];       /*  Default value if needed          */
        int
            sep_stop,                       /*  Where we put next sep_char       */
            dec_stop,                       /*  Where we put decimal point       */
            decs_wanted = decimals,         /*  Number of decimals wanted        */
            decs_seen,                      /*  Number of decimals output        */
            sign_pos,                       /*  Where we put sign, if any        */
            digits;                         /*  Number of digits read so far     */
        char
           *dest,                           /*  Store formatted number here      */
            sign_char,                      /*  Number's sign: ' ', '+', '-'     */
            sep_char,                       /*  Thousands separator '.' or ','   */
            drop_zero,                      /*  We suppress this char            */
            ch;                             /*  Next character in picture        */
        Bool
            have_zero;                      /*  TRUE if whole number is zero     */
    
        ASSERT (width <= FORMAT_MAX);
        ASSERT (dec_point == '.' || dec_point == ',');
    
        conv_reason = 0;                    /*  No conversion errors so far      */
    
        /*  ---------------------------------   Prepare to copy digits  ---------*/
    
        if (decs_wanted > CONV_MAX_DECS)
          {
            conv_reason = CONV_ERR_DECS_OVERFLOW;
            return (NULL);                  /*  Error - too many decimals        */
          }
        /*  If value is empty, use "0" with enough decimals as default value     */
        /*  We allow one whole digit and as many decimals as needed.             */
        if (strnull (number))
          {
            strpad (zero, '0', decs_wanted + 1);
            number = zero;
          }
    
        /*  Pick-up sign character if present                                    */
        if (*number == ' ' || *number == '+' || *number == '-')
            sign_char = *number++;
        else
            sign_char = ' ';
    
        /*  While leading zero is '0' we blank-out zeros in the number           */
        drop_zero = (char) (flags & FLAG_N_ZERO_FILL? ' ': '0');
    
        /*  Prepare for decimals                                                 */
        if ((flags & FLAG_N_DECIMALS) == 0)
            decs_wanted = 0;
    
        if (strchr (number, '.'))
            dec_stop = (int) (strchr (number, '.') - (char *) number);
        else
            dec_stop = strlen (number) - decs_wanted;
    
        if (dec_stop < 1)
          {
            conv_reason = CONV_ERR_DECS_MISSING;
            return (NULL);                  /*  Error - too few decimals         */
          }
    
        /*  Prepare for thousands-separators if FLAG_N_THOUSANDS                 */
        if ((flags & FLAG_N_THOUSANDS) && !(flags & FLAG_N_ZERO_FILL))
          {
            /*  Get number of whole digits, allowing for decimals & dec sign     */
            sep_char = (char) (dec_point == '.'? ',': '.');
            sep_stop = (dec_stop - (decs_wanted? decs_wanted + 1: 0)) % 3;
            if (sep_stop == 0)
                sep_stop = 3;               /*  Get into range 1..3              */
          }
        else
          {
            sep_char = ' ';
            sep_stop = 0;                   /*  No thousands separators          */
          }
    
        /*  ---------------------------------   Copy the digits  ----------------*/
    
        digits    = 0;                      /*  No digits loaded yet             */
        decs_seen = 0;                      /*  No decimals output yet           */
        have_zero = TRUE;                   /*  Assume number is zero            */
        dest      = formatted;              /*  Format number                    */
        while (*number)                     /*    until we hit the terminator    */
          {
            ch = *number++;
            if (ch == '.')
                continue;                   /*  Ignore '.' in number             */
    
            digits++;
    
            if (ch == drop_zero && digits < dec_stop)
                ch = ' ';
            else
            if (isdigit (ch))
              {
                drop_zero = ' ';
                if (ch > '0')
                    have_zero = FALSE;
              }
            if (ch != ' ' || (width > 0 && !(flags & FLAG_N_LEFT)))
              {
                *dest++ = ch;               /*  Output this digit                */
                if (digits > dec_stop)
                    decs_seen++;            /*  Count the decimal digit          */
                else
                if (digits == dec_stop)     /*  Handle decimal stop              */
                  {                         /*    with optional point            */
                    if (flags & FLAG_N_DECIMALS)
                        *dest++ = dec_point;
                    sep_stop = 0;           /*  And kill further thousand seps   */
                  }
              }
            /*  Output thousands separator unless we are in blank area           */
            if (digits == sep_stop)
              {
                if (ch != ' ')
                    *dest++ = sep_char;
                sep_stop += 3;
              }
          }
        *dest = 0;                          /*  Terminate the string nicely      */
        /*  ---------------------------------   Post-format the result  ---------*/
    
        if (decs_wanted > 0)
          {
            /*  Output trailing decimal zeroes if not supplied                   */
            if (decs_seen == 0)
                *dest++ = dec_point;
            while (decs_seen < decs_wanted)
              {
                *dest++ = '0';
                decs_seen++;
              }
            /*  Drop all decimals if format is DEC_HIDE_ALL                      */
            if (dec_format == DECS_HIDE_ALL)
                while (*dest != dec_point)
                    dest--;                 /*  Drop-off trailing zero           */
            else
            /*  Drop trailing decimal zeroes if format is DEC_DROP_ZEROS         */
            if (dec_format == DECS_DROP_ZEROS)
                while (*dest != dec_point)
                    if (*(dest - 1) > '0')
                        break;
                    else
                        dest--;             /*  Drop-off trailing zero           */
    
            *dest = 0;                      /*  Terminate the string nicely      */
          }
    
        /*  Justify within width if width > 0                                    */
        sign_pos = 0;                       /*  Sign normally comes at start     */
        digits   = strlen (formatted);
        if (flags & FLAG_N_SIGNED)
          {
            digits++;                       /*  Allow for eventual sign          */
            if (sign_format == SIGN_FINANCIAL)
                digits++;                   /*  Sign shown like (123)            */
          }
        while (digits < width)
          {
            if (flags & FLAG_N_LEFT && !(flags & FLAG_N_ZERO_FILL))
                strcat (formatted, " ");
            else
              {
                stropen (formatted, FALSE); /*  Insert blank at start of string  */
                if (flags & FLAG_N_ZERO_FILL)
                    formatted [0] = '0';
                else
                    sign_pos++;             /*  Skip leading space               */
              }
            digits++;
          }
    
        /*  Format sign if FLAG_N_SIGNED                                         */
        if (flags & FLAG_N_SIGNED)
          {
            if (sign_format == SIGN_NEG_LEAD
            ||  sign_format == SIGN_ALL_LEAD
            ||  sign_format == SIGN_FINANCIAL)
                stropen (formatted, FALSE);
    
            if (sign_format == SIGN_NEG_TRAIL
            ||  sign_format == SIGN_ALL_TRAIL
            ||  sign_format == SIGN_FINANCIAL)
                strcat (formatted, " ");
    
            if (!have_zero)                 /*  Zero has no sign                 */
                switch (sign_format)
                  {
                    case SIGN_NEG_LEAD:
                        if (sign_char != '-')
                            break;          /*  Fall through if negative sign    */
                    case SIGN_ALL_LEAD:
                        formatted [sign_pos] = sign_char;
                        break;
    
                    case SIGN_NEG_TRAIL:
                        if (sign_char != '-')
                            break;          /*  Fall through if negative sign    */
                    case SIGN_ALL_TRAIL:
                        strlast (formatted) = sign_char;
                        break;
    
                    case SIGN_FINANCIAL:
                        if (sign_char == '-')
                          {
                            formatted [0]       = '(';
                            strlast (formatted) = ')';
                          }
                        break;
                  }
          }
    
        /*  If all zeroes, return a blank string if FLAG_N_ZERO_BLANK            */
        if ((flags & FLAG_N_ZERO_BLANK) && have_zero)
          {
            memset (formatted, ' ', width);
            formatted [width] = 0;
          }
    
        if (width > 0 && (strlen (formatted) > (size_t) width))
          {
            conv_reason = CONV_ERR_NUM_OVERFLOW;
            return (NULL);                  /*  Overflow -- number too large     */
          }
        else
            return (formatted);
    }
    
     

    conv_str_bool

    #include "sflconv.h"
    int
    conv_str_bool (
        const char *string)
    

    Synopsis

    Converts a string to a Bool. Accepts T/Y/1 as TRUE, F/N/0 as FALSE, ignoring case. Looks only at the first letter of the string. Returns 1 for TRUE, 0 for FALSE, -1 if the string was not valid.

    Source Code - (sflcvsb.c)

    {
        char
            ch = tolower (string [0]);
    
        conv_reason = 0;                    /*  No conversion errors so far      */
        if (ch == 'y' || ch == 't' || ch == '1')
            return (1);
        else
        if (ch == 'n' || ch == 'f' || ch == '0')
            return (0);
        else
          {
            conv_reason = CONV_ERR_NOT_BOOLEAN;
            return (-1);
          }
    }
    
     

    conv_str_date

    #include "sflconv.h"
    long
    conv_str_date (
        const char *string,
        int  flags,
        int  format,
        int  order)
    

    Synopsis

    Converts a string to a date. The supposed format of the date is defined by the format argument, which can be one of:
    DATE YMD COMPACT Year month day.
    DATE YMD DELIM Year month day.
    DATE YMD SPACE Year month day.
    DATE YMD COMMA Year month day.
    DATE YM COMPACT Year and month only; day is zero.
    DATE YM DELIM Year and month only; day is zero.
    DATE YM SPACE Year and month only; day is zero.
    DATE MD COMPACT Month and day only; year is zero.
    DATE MD DELIM Month and day only; year is zero.
    DATE MD SPACE Month and day only; year is zero.
    The date order must be one of:
    DATE ORDER YMD Year month day.
    DATE ORDER DMY Day month year.
    DATE ORDER MDY Month day year.
    You can override the order using these flags:
    FLAG D ORDER YMD Year month day.
    FLAG D ORDER DMY Day month year.
    FLAG D ORDER MDY Month day year.
    Returns the date as a long integer, YYYYMMDD. The conversion is pretty relaxed and allows strings like: 010195, 1-1-95, 1-Jan- 95, 1jan95, 01jan95, 1 1 95, etc. The input string must be null-terminated. Returns -1 in case of an invalid date or format. If the short formats (DATE_DM_..., DATE_YM_...) are used, the missing field is always set to 0 and cannot be supplied in the string. If the date was empty, i.e. contains no usable digits, returns 0.

    Source Code - (sflcvsd.c)

    {
        static
            char *month_name [] =
              {
                "jan", "feb", "mar", "apr", "may", "jun",
                "jul", "aug", "sep", "oct", "nov", "dec"
              };
    
        static byte
            month_days [] =
              {
                31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
              },
            order_ptr [][3] =               /*  Year, Month, Day pointers        */
              {                             /*    for various orders & formats   */
              /*  YY MM DD   YYYY MM DD     YY MM       YYYY MM            MM DD */
                { 0, 2, 4 }, { 0, 4, 6 }, { 0, 2, 99 }, { 0, 4, 99 }, { 99, 0, 2 },
                { 4, 2, 0 }, { 4, 2, 0 }, { 2, 0, 99 }, { 2, 0, 99 }, { 99, 2, 0 },
                { 4, 0, 2 }, { 4, 0, 2 }, { 2, 0, 99 }, { 2, 0, 99 }, { 99, 0, 2 },
              };
    
        char
            date_digits   [9],              /*  8 digits of date                 */
            month_letters [4] = "???",      /*  3 characters of month            */
            ch;                             /*  Next character in date           */
        long
            feedback;                       /*  Returned date; -1 = error        */
        int
            digits,                         /*  Number of digits in string       */
            delimiters,                     /*  Number of delimiters in date     */
            digitseq,                       /*  Number of digits in sequence     */
            count,                          /*  Number of month letters          */
            year,                           /*  Date year value                  */
            month,                          /*  Date month value                 */
            day,                            /*  Date day value                   */
            order_index,                    /*  Index into order table           */
            y_ptr,                          /*  Where is year in date?           */
            m_ptr,                          /*  Where is month in date?          */
            d_ptr,                          /*  Where is day in date?            */
            date_order = order;             /*  Actual date order                */
        Bool
            had_month;                      /*  Did we already get a month?      */
    
        ASSERT (format >= _DATE_FORMAT_FIRST && format <= _DATE_FORMAT_LAST);
        ASSERT (order  >= _DATE_ORDER_FIRST  && order  <= _DATE_ORDER_LAST);
    
        conv_reason = 0;                    /*  No conversion errors so far      */
        if (flags & FLAG_D_ORDER_YMD)
            date_order = DATE_ORDER_YMD;
        else
        if (flags & FLAG_D_ORDER_DMY)
            date_order = DATE_ORDER_DMY;
        else
        if (flags & FLAG_D_ORDER_MDY)
            date_order = DATE_ORDER_MDY;
    
        /*  Collect date digits                                                  */
        digits     = 0;                     /*  Nothing collected so far         */
        digitseq   = 0;                     /*  No digits in sequence            */
        feedback   = 0;                     /*  No errors so far                 */
        delimiters = 0;                     /*  We allow up to 2 delimiters      */
        had_month  = FALSE;                 /*  True after 3-letter month seen   */
    
        do
          {
            ch = *string++;
            if (isdigit (ch))
              {
                if (digits < 8)
                  {
                    digitseq++;
                    date_digits [digits++] = ch;
                  }
                else
                  {
                    conv_reason = CONV_ERR_DATE_SIZE;
                    feedback = -1;          /*  Too many digits                  */
                  }
              }
            else
              {
                /*  Fill-up to even number of digits                             */
                if (digits > (digits / 2) * 2)
                  {
                    date_digits [digits] = date_digits [digits - 1];
                    date_digits [digits - 1] = '0';
                    digits++;
                  }
                /*  3 or 5 in a row is not allowed                               */
                if (digitseq == 3 || digitseq == 5)
                  {
                    conv_reason = CONV_ERR_REJECT_3_5;
                    feedback = -1;
                  }
                digitseq = 0;
    
                /*  If a letter, try to match against a month                    */
                if (isalpha (ch))
                  {
                    if (had_month)
                      {
                        conv_reason = CONV_ERR_MULTIPLE_MONTH;
                        feedback = -1;
                      }
                    else
                      {
                        for (count = 0; isalpha (ch); )
                          {
                            if (count < 3)
                                month_letters [count++] = (char) tolower (ch);
                            ch = *string++;
                          }
                        string--;           /*  Move back to char after month    */
                        if (count < 3)
                          {
                            conv_reason = CONV_ERR_BAD_MONTH;
                            feedback = -1;  /*  Too few letters                  */
                          }
                        month_letters [3] = 0;
                        for (count = 0; count < 12; count++)
                            if (streq (month_letters, month_name [count]))
                              {
                                count++;
                                date_digits [digits++] = (char) (count / 10 + '0');
                                date_digits [digits++] = (char) (count % 10 + '0');
                                had_month = TRUE;
                                break;
                              }
                        if (!had_month)
                          {
                            conv_reason = CONV_ERR_BAD_MONTH;
                            feedback = -1;  /*  Month not found                  */
                          }
                      }
                  }
                else
                if (ispunct (ch))           /*  Skip any delimiter               */
                    if ((++delimiters > 2)
                    ||  (format > _DATE_YMD_LAST && delimiters > 1))
                      {
                        conv_reason = CONV_ERR_MULTIPLE_DELIM;
                        feedback = -1;      /*  Multiple delimiters              */
                      }
              }
          }
        until (ch == 0);
    
        /*  Return zero date if empty                                            */
        if (digits == 0)
            return (feedback);
    
        /*  Calculate offset in date_digits for various date order & formats     */
        order_index = (date_order - 1) * 5; /*  Each row has 5 items             */
    
        if (format <= _DATE_YMD_LAST)
          {
            if (digits == 6)
                ;   /* nothing */
            else
            if (digits == 8)
                order_index += 1;
          }
        else
        if (format <= _DATE_YM_LAST)
          {
            date_digits [digits++] = '0';
            date_digits [digits++] = '0';
            if (digits == 6)
                order_index += 2;
            else
            if (digits == 8)
                order_index += 3;
            else
              {
                conv_reason = CONV_ERR_DATE_SIZE;
                return (-1);                /*  Error - bad date size            */
              }
          }
        else
        if (format <= _DATE_MD_LAST)
          {
            date_digits [digits++] = '0';
            date_digits [digits++] = '0';
            if (digits == 6)
                order_index += 4;
            else
              {
                conv_reason = CONV_ERR_DATE_SIZE;
                return (-1);                /*  Error - bad date size            */
              }
          }
    
        /*  Decode order to pick-up offset of day/month/year in date_digits      */
        y_ptr = order_ptr [order_index][0];
        m_ptr = order_ptr [order_index][1];
        d_ptr = order_ptr [order_index][2];
    
    #   define DIGIT(x) (date_digits [(x)] - '0')
        if (y_ptr != 99)
          {
            year = DIGIT (y_ptr) * 10 + DIGIT (y_ptr + 1);
            if (digits == 8)
                year = DIGIT (y_ptr + 2) * 10 + DIGIT (y_ptr + 3) + year * 100;
    
            if (year < 50)
                year += 2000;
            else
            if (year < 100)
                year += 1900;
          }
        else
            year = 0;
    
        if (m_ptr != 99)
          {
            month = DIGIT (m_ptr) * 10 + DIGIT (m_ptr + 1);
            if (month == 0 || month > 12)
              {
                conv_reason = CONV_ERR_OUT_OF_RANGE;
                feedback = -1;
              }
          }
        else
            month = 0;
    
        if (d_ptr != 99)
          {
            day = DIGIT (d_ptr) * 10 + DIGIT (d_ptr + 1);
            if ((day == 0 || day > (int) month_days [month - 1])
            ||  (month == 2 && day == 29 && !leap year (year)))
              {
                conv_reason = CONV_ERR_OUT_OF_RANGE;
                feedback = -1;
              }
          }
        else
            day = 0;
    
        if (feedback == 0)
            feedback = year * 10000L + month * 100 + day;
    
        return (feedback);
    }
    
     

    conv_str_day

    #include "sflconv.h"
    int
    conv_str_day (
        const char *string)
    

    Synopsis

    Converts a string to a day. The string contains a day name in English. Only the first three letters of the name are significant. Returns a value 0..6 for Sunday to Saturday, or -1 if the name is not a valid day.

    Source Code - (sflcvsd.c)

    {
        static
            char *day_name [] =
              { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
        int
            day;
        char
            day_comp [4];
    
        /*  Get up to three letters of day name and convert to lower case        */
        strncpy (day_comp, string, 3);
        day_comp [3] = '\0';
        strlwc (day_comp);
    
        /*  I don't like magic constants, but "7" is pretty safe for now         */
        for (day = 0; day < 7; day++)
            if (streq (day_name [day], day_comp))
                break;
    
        return (day >= 7? -1: day);
    }
    
     

    conv_str_number

    #include "sflconv.h"
    char *
    conv_str_number (
        const char *string,                 /*  String to convert                */
        int   flags,                        /*  Number field flags               */
        char  dec_point,                    /*  Decimal point: '.' or ','        */
        int   decimals,                     /*  Number of decimals, or 0         */
        int   dec_format,                   /*  How are decimals shown           */
        int   width                         /*  Output field width, > 0          */
    )
    

    Synopsis

    Converts a string to a number. The number format is defined by one or more of these flags (you add them to get a flags argument):
    FLAG N SIGNED Number is signed.
    FLAG N DECIMALS Number has decimals.
    FLAG N ZERO FILL Number has leading zeros.
    FLAG N THOUSANDS Number has thousands-separators.
    The input string may contain digits, decimal point, thousand separators and a sign character or indicator. Formatting characters are only accepted if they correspond to the number format. A blank string is accepted as zero. A space following digits ends the number; anything further is ignored. Returns a string of width digits, including leading sign if that is required. Zeroes are signed with a space. Width must be at least 1. If the flag FLAG_N_DECIMALS is set, the last X digits are decimals, where X is the value of the decimals argument. Decimals are then accepted or rejected depending on the dec_format:
    DECS SHOW ALL Accept decimals.
    DECS DROP ZEROS Accept decimals.
    DECS HIDE ALL Reject decimals.
    DECS SCIENTIFIC Accept decimals.
    If the flag FLAG_N_SIGNED is set, accepts a leading or trailing sign, or a financial negative like this: (123). Returns a pointer to the formatted string, or null if the string was rejected. These are the possible reasons for rejection:

    Source Code - (sflcvsn.c)

    {
        static char
            number [FORMAT_MAX + 1];        /*  Cleaned-up return string         */
        int
            digits,                         /*  Number of digits read so far     */
            decs_wanted = decimals;         /*  Number of decimals wanted        */
        char
           *dest,                           /*  Store formatted number here      */
            sign_char,                      /*  Number's sign: ' ', '+', '-'     */
            sep_char,                       /*  Thousands separator '.' or ','   */
            decs_seen,                      /*  Number of decimals output        */
            ch;                             /*  Next character in picture        */
        Bool
            have_point,                     /*  Have we seen a decimal point     */
            have_zero,                      /*  TRUE if number is all zero       */
            end_loop;                       /*  Flag to break out of scan loop   */
    
        ASSERT (width <= FORMAT_MAX);
        ASSERT (width > 0);
        ASSERT (dec_point == '.' || dec_point == ',');
    
        conv_reason = 0;                    /*  No conversion errors so far      */
    
        /*  ---------------------------------   Prepare to copy digits  ---------*/
    
        if ((flags & FLAG_N_THOUSANDS) && !(flags & FLAG_N_ZERO_FILL))
            sep_char = dec_point == '.'? ',': '.';
        else
            sep_char = ' ';                 /*  Reject any thousands separator   */
    
        /*  ---------------------------------   Copy the digits  ----------------*/
    
        digits     = 0;                     /*  No digits loaded yet             */
        decs_seen  = 0;                     /*  No decimals output yet           */
        sign_char  = ' ';                   /*  Final sign character '+' or '-'  */
        end_loop   = FALSE;                 /*  Flag to break out of scan loop   */
        have_point = FALSE;                 /*  No decimal point seen            */
        have_zero  = TRUE;                  /*  So far, it's zero                */
    
        dest = number;                      /*  Scan through number              */
        while (*string)
          {
            ch = *string++;
            switch (ch)
              {
                case '9':
                case '8':
                case '7':
                case '6':
                case '5':
                case '4':
                case '3':
                case '2':
                case '1':
                    have_zero = FALSE;
                case '0':
                    digits++;
                    *dest++ = ch;
                    if (have_point)
                        ++decs_seen;
                    break;
    
                case '-':
                case '+':
                case '(':
                    if (sign_char != ' ')
                      {
                        conv_reason = CONV_ERR_MULTIPLE_SIGN;
                        return (NULL);      /*  More than one sign char          */
                      }
                    else
                    if (flags & FLAG_N_SIGNED)
                        sign_char = ch;
                    else
                      {
                        conv_reason = CONV_ERR_SIGN_REJECTED;
                        return (NULL);      /*  Number may not be signed         */
                      }
                    break;
    
                case ')':
                    if (sign_char == '(')
                        sign_char = '-';
                    else
                      {
                        conv_reason = CONV_ERR_SIGN_BAD_FIN;
                        return (NULL);      /*  Malformed financial negative     */
                      }
                    break;
    
                case ' ':                   /*  Space ends number after digits   */
                    end_loop = (digits > 0);
                    break;
    
                default:
                    if (ch == dec_point)
                      {
                        if (have_point)
                          {
                            conv_reason = CONV_ERR_MULTIPLE_POINT;
                            return (NULL);  /*  More than one decimal point      */
                          }
                        else
                        if (flags & FLAG_N_DECIMALS)
                            have_point = TRUE;
                        else
                          {
                            conv_reason = CONV_ERR_DECS_REJECTED;
                            return (NULL);  /*  No decimals are allowed          */
                          }
                      }
                    else
                    if (ch != sep_char)     /*  We allow sep chars anywhere      */
                      {
                        conv_reason = CONV_ERR_INVALID_INPUT;
                        return (NULL);      /*    else we have junk              */
                      }
              }
            if (end_loop)
                break;
          }
    
        /*  ---------------------------------   Post-format the result  ---------*/
    
        if (flags & FLAG_N_DECIMALS)
          {
            ASSERT (width > decs_wanted);   /*  At least decimals + 1 digit      */
    
            if (dec_format == DECS_HIDE_ALL)
              {
                if (have_point)
                  {
                    conv_reason = CONV_ERR_DECS_HIDDEN;
                    return (NULL);          /*  No decimals are allowed          */
                  }
              }
            while (decs_seen < decs_wanted) /*  Supply missing decimals          */
              {
                digits++;
                *dest++ = '0';
                decs_seen++;
              }
            if (decs_seen > decs_wanted)
              {
                conv_reason = CONV_ERR_DECS_OVERFLOW;
                return (NULL);              /*  More decimals than allowed       */
              }
          }
        else
            decs_wanted = 0;
    
        *dest = 0;                          /*  Terminate the string nicely      */
        if (digits > width)
          {
            conv_reason = CONV_ERR_TOO_MANY_DIGITS;
            return (NULL);                  /*  Overflow -- number too large     */
          }
    
        /*  Supply leading zeroes                                                */
        if (digits < width)
          {
            /*  Shift number and null to right of field                          */
            memmove (number + (width - digits), number, digits + 1);
            memset  (number, '0', width - digits);
          }
    
        /*  Store sign if necessary                                              */
        if (flags & FLAG_N_SIGNED)
          {
            ASSERT (width > 1);             /*  At least sign + 1 digit          */
            if (number [0] != '0')
              {
                conv_reason = CONV_ERR_TOO_MANY_DIGITS;
                return (NULL);              /*  Overflow -- no room for sign     */
              }
            if (sign_char == '(')
              {
                conv_reason = CONV_ERR_SIGN_BAD_FIN;
                return (NULL);              /*  Malformed financial negative     */
              }
            else
            if (sign_char == ' ')
                sign_char = '+';
    
            if (have_zero)
                number [0] = ' ';           /*  Store sign                       */
            else
                number [0] = sign_char;
          }
    
        return (number);
    }
    
     

    conv_str_time

    #include "sflconv.h"
    /*  We supply the dialog file as the source
    long
    conv_str_time (const char *p_string)
    

    Synopsis

    Converts a string to a time. The string must have this format: hour[<delim>minute[<delim>second[<delim>centi ]]][a[m]|p[m]] Any non-digit is accepted as delimiter. Each component may be one or two digits. The input string must be null-terminated. Returns -1 in case of an invalid date or format. If the string was empty (contains no usable digits, returns 0. The am/pm indicator can occur one anywhere in the string.

    Source Code - (sflcvst.c)

    {
    
    After-Init:
        (--) Ok                                 -> Expect-Hour
              + Get-Next-Component
    
    Expect-Hour:
        (--) Number                             -> Expect-Minute
              + Have-Hour
              + Get-Next-Component
        (--) Am-Pm                              -> Expect-Hour
              + Have-Am-Pm-Indicator
              + Get-Next-Component
    
    Expect-Minute:
        (--) Number                             -> Expect-Second
              + Have-Minute
              + Get-Next-Component
        (--) Am-Pm                              -> Expect-Minute
              + Have-Am-Pm-Indicator
              + Get-Next-Component
    
    Expect-Second:
        (--) Number                             -> Expect-Centisecond
              + Have-Second
              + Get-Next-Component
        (--) Am-Pm                              -> Expect-Second
              + Have-Am-Pm-Indicator
              + Get-Next-Component
    
    Expect-Centisecond:
        (--) Number                             -> Allow-Am-Pm
              + Have-Centisecond
              + Get-Next-Component
        (--) Am-Pm                              -> Expect-Centisecond
              + Have-Am-Pm-Indicator
              + Get-Next-Component
    
    Allow-Am-Pm:
        (--) Am-Pm                              -> Expect-Finished
              + Have-Am-Pm-Indicator
              + Get-Next-Component
    
    Expect-Finished:
        (--) Finished                           ->
              + Have-Complete-Time
              + Terminate-The-Program
    
    Defaults:
        (--) Number                             ->
              + Have-Invalid-Time
              + Terminate-The-Program
        (--) Am-Pm                              ->
              + Have-Invalid-Time
              + Terminate-The-Program
        (--) Finished                           ->
              + Have-Complete-Time
              + Terminate-The-Program
        (--) Delimiter                          ->
              + Have-Delimiter
              + Get-Next-Component
        (--) Error                              ->
              + Have-Invalid-Time
              + Terminate-The-Program
    }
    
     

    conv_time_pict

    #include "sflconv.h"
    char *
    conv_time_pict (
        long time,
        const char *picture)
    

    Synopsis

    Converts a time to a string using a picture. The picture is composed of any combination of these formats:
    h hour, 0-23
    hh hour, 00- 23
    m minute, 0-59
    mm minute, 00-59
    s second, 0-59
    ss second, 00-59
    c centisecond, 0- 99
    cc centisecond, 00-99
    a a/p indicator - use 12-hour clock
    aa am/pm indicator - use 12-hour clock
    A A/P indicator - use 12-hour clock
    AA AM/PM indicator - use 12-hour clock
    \x literal character x
    other literal character
    Returns the formatted result. This is a static string, of at most 80 characters, that is overwritten by each call. If time is zero, returns an empty string. The 'h', 'm', 's', and 'c' formats output a leading space when used at the start of the picture. This is to improve the alignment of a column of times. If the previous character was a digit, these formats also output a space in place of the leading zero.

    Source Code - (sflcvtp.c)

    {
        static char
            formatted [FORMAT_MAX + 1];     /*  Formatted return string          */
        int
            hour,                           /*  Hour component of time           */
            minute,                         /*  Minute component of time         */
            second,                         /*  Second component of time         */
            centi,                          /*  1/100 sec component of time      */
            cursize;                        /*  Size of current component        */
        char
           *dest,                           /*  Store formatted data here        */
            ch,                             /*  Next character in picture        */
            lastch = '0';                   /*  Last character we output         */
        Bool
            pm;                             /*  TRUE when hour >= 12             */
    
        conv_reason = 0;                    /*  No conversion errors so far      */
    
        /*  Zero time is returned as empty string                                */
        if (time == 0)
          {
            strclr (formatted);
            return (formatted);
          }
    
        hour    = GET_HOUR   (time);
        minute  = GET_MINUTE (time);
        second  = GET_SECOND (time);
        centi   = GET_CENTI  (time);
    
        /*  If am/pm component specified, use 12-hour clock                      */
        if (hour >= 12)
          {
            pm = TRUE;
            if (strpbrk (picture, "aA") && hour > 12)
                hour = 12;
          }
        else
            pm = FALSE;
    
        ASSERT (hour   >= 0 && hour   < 24);
        ASSERT (minute >= 0 && minute < 60);
        ASSERT (second >= 0 && second < 60);
    
        /*  Scan through picture, converting each component                      */
        dest = formatted;
        *dest = 0;                          /*  string is empty                  */
        while (*picture)
          {
            /*  Get character and count number of occurences                     */
            ch = *picture++;
            for (cursize = 1; *picture == ch; cursize++)
                picture++;
    
            switch (ch)
              {
                /*  h         hour,  0-23                                        */
                /*  hh        hour, 00-23                                        */
                case 'h':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), hour);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", hour);
                    break;
    
                /*  m         minute,  0-59                                      */
                /*  mm        minute, 00-59                                      */
                case 'm':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), minute);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", minute);
                    break;
    
                /*  s         second,  0-59                                      */
                /*  ss        second, 00-59                                      */
                case 's':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), second);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", second);
                    break;
    
                /*  c         centisecond,  0-99                                 */
                /*  cc        centisecond, 00-99                                 */
                case 'c':
                    if (cursize == 1)
                        sprintf (dest, (isdigit (lastch)? "%2d": "%d"), centi);
                    else
                    if (cursize == 2)
                        sprintf (dest, "%02d", centi);
                    break;
    
                /*  a         a/p indicator                                      */
                /*  aa        am/pm indicator                                    */
                case 'a':
                    strncat (dest, (pm? "pm": "am"), cursize);
                    dest [cursize] = 0;
                    break;
    
                /*  A         A/P indicator                                      */
                /*  AA        AM/PM indicator                                    */
                case 'A':
                    strncat (dest, (pm? "PM": "AM"), cursize);
                    dest [cursize] = 0;
                    break;
    
                /*  \x        literal character x                                */
                case '\\':
                    ch = *picture++;
            }
            if (*dest)                      /*  If something was output,         */
                while (*dest)               /*    skip to end of string          */
                    dest++;
            else
                while (cursize--)           /*  Else output ch once or more      */
                    *dest++ = ch;           /*    and bump dest pointer          */
    
            lastch = *(dest - 1);           /*  Get previous character           */
            *dest = 0;                      /*  Terminate the string nicely      */
        }
        return (formatted);
    }
    
     

    conv_time_str

    #include "sflconv.h"
    char *
    conv_time_str (
        long time,
        int  flags,
        char timesep,
        int  width)
    

    Synopsis

    Converts a time to a string. The flags and width control the resulting string. You can use one or more of these flags added together:
    FLAG T HH AS H Suppress leading zeroes on the hours.
    FLAG T MM AS M Suppress leading zeroes on the minutes.
    FLAG T SS AS S Suppress leading zeroes on the seconds.
    FLAG T CC AS C Suppress leading zeroes on the centiseconds.
    FLAG T COMPACT Show without delimiters.
    FLAG T 12 HOUR Append am/pm indicator.
    Returns a pointer to a static area holding the string, or NULL if there was an error. If no flags are used, the width argument gives these results (shown as a picture, which is how conv_time_str works - see conv_time_pict):
    4 or less Error.
    5 to 7 "hh:mm"
    8 to 10 "hh:mm:ss"
    11 or more "hh:mm:ss:cc"
    If FLAG_T_COMPACT is used, width gives these results:
    3 or less Error.
    4 to 5 "hhmm"
    6 to 7 "hhmmss"
    8 or more "hhmmsscc"
    If FLAG_T_12_HOUR is used, width gives these results:
    5 or less Error.
    6 to 8 "hh:mma"
    9 to 11 "hh:mm:ssa"
    12 or more "hh:mm:ss:cca"
    If FLAG_T_COMPACT and FLAG_T_12_HOUR are used, width gives these results:
    4 or less Error.
    5 to 6 "hhmma"
    7 to 8 "hhmmssa"
    9 or more "hhmmsscca"

    Source Code - (sflcvts.c)

    {
        char
            delim [2],                      /*  Delimiter string, ":" or ""      */
            picture [13];                   /*  Largest picture: hh:mm:ss:cca    */
        int
            delim_len;
        int
            space_left = width;
    
        conv_reason = 0;                    /*  No conversion errors so far      */
        if (flags & FLAG_T_COMPACT)
          {
            delim [0] = 0;
            delim_len = 0;
          }
        else
          {
            delim [0] = timesep;
            delim [1] = 0;
            delim_len = 1;
          }
        if (flags & FLAG_T_12_HOUR)
            space_left--;                   /*  Subtract 1 if eventual "a"       */
    
        /*  Build-up date picture components until we run out of space           */
        strcpy (picture, (flags & FLAG_T_HH_AS_H? "h": "hh"));
        space_left -= 2;
    
        if (space_left >= delim_len + 2)
          {
            strcat (picture, delim);
            strcat (picture, (flags & FLAG_T_MM_AS_M? "m": "mm"));
            space_left -= delim_len + 2;
          }
        else
            return (NULL);                  /*  Error - space_left is too small  */
    
        if (space_left >= delim_len + 2)
          {
            strcat (picture, delim);
            strcat (picture, (flags & FLAG_T_SS_AS_S? "s": "ss"));
            space_left -= delim_len + 2;
          }
    
        if (space_left >= delim_len + 2)
          {
            strcat (picture, delim);
            strcat (picture, (flags & FLAG_T_CC_AS_C? "c": "cc"));
            space_left -= delim_len + 2;
          }
    
        /*  Append "a" (or "aa" if space) if 12-hour clock wanted                */
        if (flags & FLAG_T_12_HOUR)
            strcat (picture, (space_left == 0? "a": "aa"));
    
        return (conv time pict (time, picture));
    }
    

    Encryption and decryption functions

    Filename: sflcryp.h
    Package: Standard Function Library (SFL)
    Written: 96/01/23 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    The encryption/decryption functions were based on the cryptosystem library by Andrew Brown asb@cs.nott.ac.uk, cleaned- up for portability. Thanks for a great package. IDEA is registered as the international patent WO 91/18459 "Device for Converting a Digital Block and the Use thereof". For commercial use of IDEA, you should contact: ASCOM TECH AG Freiburgstrasse 370 CH-3018 Bern, Switzerland

    Notes

        Description of IDEA cipher
        --------------------------
        The IDEA cipher operates on 64 bit (8 byte) blocks, using a 128 bit (16
        byte) key. IDEA has found itself famous through its inclusion in the
        well-known PGP package. The following is from the introduction to chapter
        3 of the thesis that presented the cipher.
    
        The block cipher IDEA (International Data Encryption Algorithm) is based
        on the new design concept of "mixing operations from different algebraic
        groups". The required "confusion" was achieved by successively using three
        "incompatible" group operations on pairs of 16-bit subblocks and the cipher
        structure was chosen to provide the necessary "diffusion". The cipher
        structure was further chosen to facilitate both hardware and software
        implementations. The IDEA cipher is an improved version of PES and was
        developed to increase security against differential cryptanalysis.
    
        Description of MDC cipher
        -------------------------
        This is a method for turning a hash function, here MD5, into a fast
        secret-key encryption. Based on a suggestion by Phil Karn in sci.crypt, 13
        Feb 1992. See also his comments from sci.crypt, 23 Mar 1992. The method is
        a variant of that described in Zheng, Matsumoto and Imai, Crypto 89. See
        also, "A New Class of Cryptosystems Based on Interconnection Networks" by
        michaelp@terpsichore.informatic.rwth-aachen.de
    
        Description of DES cipher
        -------------------------
        DES is the well known U.S. Data Encryption Standard cipher.
        DES encrypts data in 64 bit blocks, using a 64 bit key -- of which
        56 bits are used in the encipherment process.
    

    List of Functions

    List of Symbol Definitions

    sflcryp.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    CRYPT_DES 2 /* DES algorithm */
    CRYPT_IDEA 0 /* IDEA algorithm */
    CRYPT_MAX_BLOCK_SIZE 32 /* Largest block size, in bytes */
    CRYPT_MDC 1 /* MDC algorithm */
    CRYPT_TOP 4 /* We support 4 algorithms */
    CRYPT_XOR 3 /* A basic XOR algorithm */
    _SFLCRYP_INCLUDED TRUE
     

    crypt_encode

    #include "sflcryp.h"
    Bool
    crypt_encode (
        byte *buffer,                       /*  Data to encrypt, in-place        */
        word  buffer_size,                  /*  Amount of data to encrypt        */
        int   algorithm,                    /*  What type of encryption          */
        const byte *key)                    /*  Encryption key                   */
    

    Synopsis

    Encrypt a buffer with the specified algorithm and specified key. Returns TRUE if the buffer is encrypted sucessfully. The buffer is encrypted in-place. The algorithm can be one of:
    CRYPT IDEA Use IDEA encryption.
    CRYPT MDC Use MDC encryption.
    CRYPT DES Use DES encryption.
    CRYPT XOR Use XOR encryption.
    The minimum buffer size, and key size depends on the algorithm:
    CRYPT IDEA Buffer is at least 8 bytes long, key is 16 bytes.
    CRYPT MDC Buffer is at least 8 bytes long, key is 8 bytes.
    CRYPT DES Buffer is at least 32 bytes long, key is 16 bytes.
    CRYPT XOR Buffer is at least 16 bytes long, key is 16 bytes.
    Use crypt decode() with the same algorithm and key to decrypt the buffer. The buffer size must be a multiple of the minimum size shown above. If you specify a buffer size of zero the function does nothing but returns TRUE. For portability, the buffer size is limited to 64k.

    Source Code - (sflcryp.c)

    {
        return (crypt_data (buffer, buffer_size, algorithm, key, TRUE));
    }
    
     

    crypt_decode

    #include "sflcryp.h"
    Bool
    crypt_decode (
        byte *buffer,                       /*  Data to decrypt, in-place        */
        word  buffer_size,                  /*  Amount of data to decrypt        */
        int   algorithm,                    /*  What type of decryption          */
        const byte *key)                    /*  Decryption key                   */
    

    Synopsis

    Decrypt a buffer that was produced by crypt encode(). You must (obviously, I reckon) use the same algorithm and key as was used to encrypt the data. Returns TRUE if the buffer is decrypted okay. The buffer is encrypted in-place. The algorithm can be one of:
    CRYPT IDEA Use IDEA encryption.
    CRYPT MDC Use MDC encryption.
    CRYPT DES Use DES encryption.
    CRYPT XOR Use XOR encryption.
    The minimum buffer size, and key size depends on the algorithm:
    CRYPT IDEA Buffer is at least 8 bytes long, key is 16 bytes.
    CRYPT MDC Buffer is at least 8 bytes long, key is 8 bytes.
    CRYPT DES Buffer is at least 32 bytes long, key is 16 bytes.
    CRYPT XOR Buffer is at least 16 bytes long, key is 16 bytes.
    The buffer size must be a multiple of the minimum size shown above. If you specify a buffer size of zero the function does nothing but returns TRUE.

    Source Code - (sflcryp.c)

    {
        return (crypt_data (buffer, buffer_size, algorithm, key, FALSE));
    }
    
     

    calculate_crc

    #include "sflcryp.h"
    qbyte
    calculate_crc (byte *block, size_t length)
    

    Synopsis

    Calculates the 32-bit CCITT CRC for a memory block. The CRC calculation is rapid, since the function uses a pre-calculated table. Returns the 32-bit CRC.

    Source Code - (sflcryp.c)

    {
        size_t
            offset;
        word
            this_word;
        qbyte
            crc_value;                      /*  Running CRC value                */
    
        crc_value = 0xFFFFFFFFL;
        for (offset = 0; offset < length; offset++)
        {
            this_word = block [offset];
            this_word = this_word ^ (dbyte) (crc_value & 255);
            crc_value = (crc_value >> 8) ^ crc_table [this_word];
        }
        return (crc_value ^ 0xFFFFFFFFL);
    }
    

    Date and time functions

    Filename: sfldate.h
    Package: Standard Function Library (SFL)
    Written: 96/01/05 iMatix SFL project team sfl@imatix.com
    Revised: 98/08/05
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Includes functions to get the current date/time, calculate the day or week, week of year and leap year. Dates and times are each stored in a 32-bit long value of 8 digits: dates are CCYYMMDD; times are HHMMSSCC. You can compare dates and times directly - e.g. if (date_wanted >= date_now).

    List of Functions

    List of Symbol Definitions

    sfldate.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    DAY_FRIDAY 5
    DAY_MONDAY 1
    DAY_SATURDAY 6
    DAY_SUNDAY 0
    DAY_THURSDAY 4
    DAY_TUESDAY 2
    DAY_WEDNESDAY 3
    GET_CCYEAR(d) (int) ( (d) / 10000L)
    GET_CENTI(t) (int) ( (t) % 100)
    GET_CENTURY(d) (int) ( (d) / 1000000L)
    GET_DAY(d) (int) ( (d) % 100)
    GET_HOUR(t) (int) ( (t) / 1000000L)
    GET_MINUTE(t) (int) (((t) % 1000000L) / 10000L)
    GET_MONTH(d) (int) (((d) % 10000L) / 100)
    GET_SECOND(t) (int) (((t) % 10000L) / 100)
    GET_YEAR(d) (int) (((d) % 1000000L) / 10000L)
    INTERVAL_CENTI 1
    INTERVAL_DAY 8640000L
    INTERVAL_HOUR 360000L
    INTERVAL_MIN 6000
    INTERVAL_SEC 100
    MAKE_DATE(c,y,m,d) (long) (c) * 1000000L +
    MAKE_TIME(h,m,s,c) (long) (h) * 1000000L +
    _SFLDATE_INCLUDED TRUE
    timeeq(d1,t1,d2,t2) ((d1) == (d2) && (t1) == (t2))
    timege(d1,t1,d2,t2) ((d1) > (d2) || ((d1) == (d2) && (t1) >= (t2)))
    timegt(d1,t1,d2,t2) ((d1) > (d2) || ((d1) == (d2) && (t1) > (t2)))
    timele(d1,t1,d2,t2) ((d1) < (d2) || ((d1) == (d2) && (t1) <= (t2)))
    timelt(d1,t1,d2,t2) ((d1) < (d2) || ((d1) == (d2) && (t1) < (t2)))
    timeneq(d1,t1,d2,t2) ((d1) != (d2) || (t1) != (t2))
     

    date_now

    #include "sfldate.h"
    long
    date_now (void)
    

    Synopsis

    Returns the current date as a long value (CCYYMMDD). Since most system clocks do not return a century, this function assumes that all years 80 and above are in the 20th century, and all years 00 to 79 are in the 21st century. For best results, consume before 1 Jan 2080.

    Source Code - (sfldate.c)

    {
        return (timer to date (time (NULL)));
    }
    
     

    time_now

    #include "sfldate.h"
    long
    time_now (void)
    

    Synopsis

    Returns the current time as a long value (HHMMSSCC). If the system clock does not return centiseconds, these are set to zero.

    Source Code - (sfldate.c)

    {
    #if (defined (__TURBOC__))
        /*  The Turbo-C gettime() function returns just what we want             */
        struct time
            time_struct;
    
        gettime (&time_struct);
        return (MAKE_TIME (time_struct.ti_hour,
                           time_struct.ti_min,
                           time_struct.ti_sec,
                           time_struct.ti_hund));
    
    #elif (defined (__UTYPE_FREEBSD__))
        return (timer to time (time (NULL)));
    
    #elif (defined (__UNIX__) || defined (__VMS_XOPEN))
        /*  The BSD gettimeofday function returns seconds and microseconds       */
        struct timeval
            time_struct;
    
        gettimeofday (&time_struct, 0);
        return (timer to time (time_struct.tv_sec)
                             + time_struct.tv_usec / 10000);
    
    #elif (defined (WIN32))
        /*  The Win32 GetLocalTime function returns just what we want            */
        SYSTEMTIME
            time_struct;
    
        GetLocalTime (&time_struct);
        return (MAKE_TIME (time_struct.wHour,
                           time_struct.wMinute,
                           time_struct.wSecond,
                           time_struct.wMilliseconds / 10));
    
    #else
        /*  Otherwise, just get the time without milliseconds                    */
        return (timer to time (time (NULL)));
    #endif
    }
    
     

    leap_year

    #include "sfldate.h"
    Bool
    leap_year (int year)
    

    Synopsis

    Returns TRUE if the year is a leap year. You must supply a 4- digit value for the year: 90 is taken to mean 90 ad. Handles leap centuries correctly.

    Source Code - (sfldate.c)

    {
        return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
    }
    
     

    julian_date

    #include "sfldate.h"
    int
    julian_date (long date)
    

    Synopsis

    Returns the number of days since 31 December last year. The Julian date of 1 January is 1.

    Source Code - (sfldate.c)

    {
        static int
            days [12] = {
            0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
        };
        int
            julian;
    
        julian = days [GET_MONTH (date) - 1] + GET_DAY (date);
        if (GET_MONTH (date) > 2 && leap year (GET_YEAR (date)))
            julian++;
    
        return (julian);
    }
    
     

    day_of_week

    #include "sfldate.h"
    int
    day_of_week (long date)
    

    Synopsis

    Returns the day of the week where 0 is Sunday, 1 is Monday, ... 6 is Saturday. Uses Zeller's Congurence algorithm.

    Source Code - (sfldate.c)

    {
        int
            year  = GET_CCYEAR (date),
            month = GET_MONTH  (date),
            day   = GET_DAY    (date);
    
        if (month > 2)
            month -= 2;
        else
          {
            month += 10;
            year--;
          }
        day = ((13 * month - 1) / 5) + day + (year % 100) +
              ((year % 100) / 4) + ((year / 100) / 4) - 2 *
               (year / 100) + 77;
    
        return (day - 7 * (day / 7));
    }
    
     

    next_weekday

    #include "sfldate.h"
    long
    next_weekday (long date)
    

    Synopsis

    Returns the date of the next weekday, skipping from Friday to Monday.

    Source Code - (sfldate.c)

    {
        long
            days = date to days (date);
    
        if (day of week (date) == 5)        /*  Friday                           */
            days += 3;
        else
        if (day of week (date) == 6)        /*  Saturday                         */
            days += 2;
        else
            days += 1;                      /*  Sunday to Thursday               */
    
        return (days to date (days));
    }
    
     

    prev_weekday

    #include "sfldate.h"
    long
    prev_weekday (long date)
    

    Synopsis

    Returns the date of the previous weekday, skipping from Monday to Friday.

    Source Code - (sfldate.c)

    {
        long
            days = date to days (date);
    
        if (day of week (date) == 1)        /*  Monday                           */
            days -= 3;
        else
        if (day of week (date) == 0)        /*  Sunday                           */
            days -= 2;
        else
            days -= 1;                      /*  Tuesday to Saturday              */
    
        return (days to date (days));
    }
    
     

    week_of_year

    #include "sfldate.h"
    int
    week_of_year (long date)
    

    Synopsis

    Returns the week of the year, where 1 is the first full week. Week 0 may or may not exist in any year. Uses a Lillian date algorithm to calculate the week of the year.

    Source Code - (sfldate.c)

    {
        long
            year = GET_CCYEAR (date) - 1501,
            day  = year * 365 + year / 4 - 29872L + 1
                 - year / 100 + (year - 300) / 400;
    
        return ((julian date (date) + (int) ((day + 4) % 7)) / 7);
    }
    
     

    year_quarter

    #include "sfldate.h"
    int
    year_quarter (long date)
    

    Synopsis

    Returns the year quarter, 1 to 4, depending on the month specified.

    Source Code - (sfldate.c)

    {
        return ((GET_MONTH (date) - 1) / 3 + 1);
    }
    
     

    default_century

    #include "sfldate.h"
    long
    default_century (long *date)
    

    Synopsis

    Supplies a default century for the year if necessary. If the year is 51 to 99, the century is set to 19. If the year is 0 to 50, the century is set to 20. Returns the adjusted date.

    Source Code - (sfldate.c)

    {
        if (GET_CENTURY (*date) == 0)
            *date += (GET_YEAR (*date) > 50? 19000000L: 20000000L);
        return (*date);
    }
    
     

    pack_date

    #include "sfldate.h"
    word
    pack_date (long date)
    

    Synopsis

    Packs the date into a single unsigned short word. Use this function to store dates when memory space is at a premium. The packed date can be used correctly in comparisons. Returns the packed date. The date must be later than 31 December 1979.

    Source Code - (sfldate.c)

    {
        return (word) (((GET_CCYEAR (date) - 1980) << 9) +
                        (GET_MONTH  (date) << 5) +
                         GET_DAY    (date));
    }
    
     

    pack_time

    #include "sfldate.h"
    word
    pack_time (long time)
    

    Synopsis

    Packs the time into a single unsigned short word. Use this function to store times when memory space is at a premium. The packed time can be used correctly in comparisons. Returns the packed time. Seconds are stored with 2-second accuracy and centiseconds are lost.

    Source Code - (sfldate.c)

    {
        return (word) ((GET_HOUR   (time) << 11) +
                       (GET_MINUTE (time) << 5)  +
                       (GET_SECOND (time) >> 1));
    }
    
     

    unpack_date

    #include "sfldate.h"
    long
    unpack_date (word packdate)
    

    Synopsis

    Converts a packed date back into a long value.

    Source Code - (sfldate.c)

    {
        int year;
    
        year = ((word) (packdate & 0xfe00) >> 9) + 80;
        return (MAKE_DATE (year > 80? 19: 20,
                           year,
                           (word) (packdate & 0x01e0) >> 5,
                           (word) (packdate & 0x001f)));
    }
    
     

    unpack_time

    #include "sfldate.h"
    long
    unpack_time (word packtime)
    

    Synopsis

    Converts a packed time back into a long value.

    Source Code - (sfldate.c)

    {
        return (MAKE_TIME ((word) (packtime & 0xf800) >> 11,
                           (word) (packtime & 0x07e0) >> 5,
                           (word) (packtime & 0x001f) << 1, 0));
    }
    
     

    date_to_days

    #include "sfldate.h"
    long
    date_to_days (long date)
    

    Synopsis

    Converts the date into a number of days since a distant but unspecified epoch. You can use this function to calculate differences between dates, and forward dates. Use days to date() to calculate the reverse function. Author: Robert G. Tantzen, translated from the Algol original in Collected Algorithms of the CACM (algorithm 199). Original translation into C by Nat Howard, posted to Usenet 5 Jul 1985.

    Source Code - (sfldate.c)

    {
        long
            year    = GET_YEAR    (date),
            century = GET_CENTURY (date),
            month   = GET_MONTH   (date),
            day     = GET_DAY     (date);
    
        if (month > 2)
            month -= 3;
        else
          {
            month += 9;
            if (year)
                year--;
            else
              {
                year = 99;
                century--;
              }
          }
        return ((146097L * century)    / 4L +
                (1461L   * year)       / 4L +
                (153L    * month + 2L) / 5L +
                           day   + 1721119L);
    }
    
     

    days_to_date

    #include "sfldate.h"
    long
    days_to_date (long days)
    

    Synopsis

    Converts a number of days since some distant but unspecified epoch into a date. You can use this function to calculate differences between dates, and forward dates. Use date to days() to calculate the reverse function. Author: Robert G. Tantzen, translated from the Algol original in Collected Algorithms of the CACM (algorithm 199). Original translation into C by Nat Howard, posted to Usenet 5 Jul 1985.

    Source Code - (sfldate.c)

    {
        long
            century,
            year,
            month,
            day;
    
        days   -= 1721119L;
        century = (4L * days - 1L) / 146097L;
        days    =  4L * days - 1L  - 146097L * century;
        day     =  days / 4L;
    
        year    = (4L * day + 3L) / 1461L;
        day     =  4L * day + 3L  - 1461L * year;
        day     = (day + 4L) / 4L;
    
        month   = (5L * day - 3L) / 153L;
        day     =  5L * day - 3   - 153L * month;
        day     = (day + 5L) / 5L;
    
        if (month < 10)
            month += 3;
        else
          {
            month -= 9;
            if (year++ == 99)
              {
                year = 0;
                century++;
              }
          }
        return (MAKE_DATE (century, year, month, day));
    }
    
     

    date_to_timer

    #include "sfldate.h"
    time_t
    date_to_timer (long date, long time)
    

    Synopsis

    Converts the supplied date and time into a time_t timer value. This is the number of non-leap seconds since 00:00:00 GMT Jan 1, 1970. Function was rewritten by Bruce Walter walter@fortean.com.

    Source Code - (sfldate.c)

    {
        struct tm
            time_struct;
    
        time_struct.tm_sec   = GET_SECOND (time);
        time_struct.tm_min   = GET_MINUTE (time);
        time_struct.tm_hour  = GET_HOUR   (time);
        time_struct.tm_mday  = GET_DAY    (date);
        time_struct.tm_mon   = GET_MONTH  (date) - 1;
        time_struct.tm_year  = GET_CCYEAR (date) - 1900;
        time_struct.tm_isdst = -1;
        return (mktime (&time_struct));
    }
    
     

    timer_to_date

    #include "sfldate.h"
    long
    timer_to_date (time_t time_secs)
    

    Synopsis

    Converts the supplied timer value into a long date value. Dates are stored as long values: CCYYMMDD. If the supplied value is zero, returns zero. The timer value is assumed to be UTC (GMT).

    Source Code - (sfldate.c)

    {
        struct tm
            *time_struct;
    
        if (time_secs == 0)
            return (0);
        else
          {
            /*  Convert into a long value CCYYMMDD                               */
            time_struct = localtime (&time_secs);
            time_struct-> tm_year += 1900;
            return (MAKE_DATE (time_struct-> tm_year / 100,
                               time_struct-> tm_year % 100,
                               time_struct-> tm_mon + 1,
                               time_struct-> tm_mday));
          }
    }
    
     

    timer_to_time

    #include "sfldate.h"
    long
    timer_to_time (time_t time_secs)
    

    Synopsis

    Converts the supplied timer value into a long time value. Times are stored as long values: HHMMSS00. Since the timer value does not hold centiseconds, these are set to zero. If the supplied value was zero, returns zero. The timer value is assumed to be UTC (GMT).

    Source Code - (sfldate.c)

    {
        struct tm
            *time_struct;
    
        if (time_secs == 0)
            return (0);
        else
          {
            /*  Convert into a long value HHMMSS00                               */
            time_struct = localtime (&time_secs);
            return (MAKE_TIME (time_struct-> tm_hour,
                               time_struct-> tm_min,
                               time_struct-> tm_sec,
                               0));
          }
    }
    
     

    timer_to_gmdate

    #include "sfldate.h"
    long
    timer_to_gmdate (time_t time_secs)
    

    Synopsis

    Converts the supplied timer value into a long date value in Greenwich Mean Time (GMT). Dates are stored as long values: CCYYMMDD. If the supplied value is zero, returns zero.

    Source Code - (sfldate.c)

    {
        struct tm
            *time_struct;
    
        if (time_secs == 0)
            return (0);
        else
          {
            /*  Convert into a long value CCYYMMDD                               */
            time_struct = gmtime (&time_secs);
            if (time_struct == NULL)        /*  If gmtime is not implemented     */
                time_struct = localtime (&time_secs);
    
            ASSERT (time_struct);
            time_struct-> tm_year += 1900;
            return (MAKE_DATE (time_struct-> tm_year / 100,
                               time_struct-> tm_year % 100,
                               time_struct-> tm_mon + 1,
                               time_struct-> tm_mday));
          }
    }
    
     

    timer_to_gmtime

    #include "sfldate.h"
    long
    timer_to_gmtime (time_t time_secs)
    

    Synopsis

    Converts the supplied timer value into a long time value in Greenwich Mean Time (GMT). Times are stored as long values: HHMMSS00. On most systems the clock does not return centiseconds, so these are set to zero. If the supplied value is zero, returns zero.

    Source Code - (sfldate.c)

    {
        struct tm
            *time_struct;
    
        if (time_secs == 0)
            return (0);
        else
          {
            /*  Convert into a long value HHMMSS00                               */
            time_struct = gmtime (&time_secs);
            if (time_struct == NULL)        /*  If gmtime is not implemented     */
                time_struct = localtime (&time_secs);
    
            ASSERT (time_struct);
            return (MAKE_TIME (time_struct-> tm_hour,
                               time_struct-> tm_min,
                               time_struct-> tm_sec,
                               0));
          }
    }
    
     

    time_to_csecs

    #include "sfldate.h"
    long
    time_to_csecs (long time)
    

    Synopsis

    Converts a time (HHMMSSCC) into a number of centiseconds.

    Source Code - (sfldate.c)

    {
        return ((long) (GET_HOUR   (time) * (long) INTERVAL_HOUR)
              + (long) (GET_MINUTE (time) * (long) INTERVAL_MIN)
              + (long) (GET_SECOND (time) * (long) INTERVAL_SEC)
              + (long) (GET_CENTI  (time)));
    }
    
     

    csecs_to_time

    #include "sfldate.h"
    long
    csecs_to_time (long csecs)
    

    Synopsis

    Converts a number of centiseconds (< INTERVAL_DAY) into a time value (HHMMSSCC).

    Source Code - (sfldate.c)

    {
        long
            hour,
            min,
            sec;
    
        ASSERT (csecs < INTERVAL_DAY);
    
        hour  = csecs / INTERVAL_HOUR;
        csecs = csecs % INTERVAL_HOUR;
        min   = csecs / INTERVAL_MIN;
        csecs = csecs % INTERVAL_MIN;
        sec   = csecs / INTERVAL_SEC;
        csecs = csecs % INTERVAL_SEC;
        return (MAKE_TIME (hour, min, sec, csecs));
    }
    
     

    future_date

    #include "sfldate.h"
    void
    future_date (long *date, long *time, long days, long csecs)
    

    Synopsis

    Calculates a future date and time from the date and time specified, plus an interval specified in days and 1/100th seconds. The date can be any date since some distant epoch (around 1600). If the date and time arguments are both zero, the current date and time are used. Either date and time arguments may be null.

    Source Code - (sfldate.c)

    {
        long
            dummy_date = 0,
            dummy_time = 0;
    
        if (date == NULL)
            date = &dummy_date;
        if (time == NULL)
            time = &dummy_time;
    
        /*  Set date and time to NOW if necessary                                */
        if (*date == 0 && *time == 0)
          {
            *date = date now ();
            *time = time now ();
          }
    
        /*  Get future date in days and centiseconds                             */
        days  = date to days  (*date) + days;
        csecs = time to csecs (*time) + csecs;
    
        /*  Normalise overflow in centiseconds                                   */
        while (csecs >= INTERVAL_DAY)
          {
            days++;
            csecs -= INTERVAL_DAY;
          }
    
        /*  Convert date and time back into organised values                     */
        *date = days to date  (days);
        *time = csecs to time (csecs);
    }
    
     

    past_date

    #include "sfldate.h"
    void
    past_date (long *date, long *time, long days, long csecs)
    

    Synopsis

    Calculates a past date and time from the date and time specified, minus an interval specified in days and 1/100th seconds. The date can be any date since some distant epoch (around 1600). If the date and time arguments are both zero, the current date and time are used. Either date and time arguments may be null.

    Source Code - (sfldate.c)

    {
        long
            dummy_date = 0,
            dummy_time = 0;
    
        if (date == NULL)
            date = &dummy_date;
        if (time == NULL)
            time = &dummy_time;
    
        /*  Set date and time to NOW if necessary                                */
        if (*date == 0 && *time == 0)
          {
            *date = date now ();
            *time = time now ();
          }
        /*  Get past date in days and centiseconds                               */
        days  = date to days  (*date) - days;
        csecs = time to csecs (*time) - csecs;
    
        /*  Normalise underflow in centiseconds                                  */
        while (csecs < 0)
          {
            days--;
            csecs += INTERVAL_DAY;
          }
    
        /*  Convert date and time back into organised values                     */
        *date = days to date  (days);
        *time = csecs to time (csecs);
    }
    
     

    date_diff

    #include "sfldate.h"
    void
    date_diff (
        long date1, long time1,             /*  Date and time                    */
        long date2, long time2,             /*    minus this date and time       */
        long *days, long *csecs             /*  Gives these values               */
    )
    

    Synopsis

    Calculates the difference between two date/time values, and returns the difference as a number of days and a number of centiseconds. The date can be any date since some distant epoch (around 1600). The calculation is date1:time1 - date2:time2. The returned values may be negative.

    Source Code - (sfldate.c)

    {
        *days  = date to days  (date1) - date to days  (date2);
        *csecs = time to csecs (time1) - time to csecs (time2);
    }
    
     

    valid_date

    #include "sfldate.h"
    Bool
    valid_date (long date)
    

    Synopsis

    Returns TRUE if the date is valid or zero; returns FALSE if the date is not valid.

    Source Code - (sfldate.c)

    {
        int
            month,
            day;
        Bool
            feedback;
        static byte
            month_days [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    
        month = GET_MONTH (date);
        day   = GET_DAY   (date);
    
        if (date == 0)
            feedback = TRUE;                /*  Zero date is okay                */
        else
        if (month < 1 || month > 12)
            feedback = FALSE;               /*  Month out of range               */
        else
        if ((day < 1 || day > month_days [month - 1])
        ||  (month == 2 && day == 29 && !leap year (GET_YEAR (date))))
            feedback = FALSE;               /*  Day out of range                 */
        else
            feedback = TRUE;                /*  Zero date is okay                */
    
        return (feedback);
    }
    
     

    valid_time

    #include "sfldate.h"
    Bool
    valid_time (long time)
    

    Synopsis

    Returns TRUE if the time is valid or zero; returns FALSE if the time is not valid.

    Source Code - (sfldate.c)

    {
        return (GET_SECOND (time) < 60
            &&  GET_MINUTE (time) < 60
            &&  GET_HOUR   (time) < 24);
    }
    
     

    date_is_future

    #include "sfldate.h"
    Bool
    date_is_future (long date, long time)
    

    Synopsis

    Returns TRUE if the specified date and time are in the future. Returns FALSE if the date and time are in the past, or the present (which will be the past by the time you've read this). Date is specified as a YYYYMMDD value; time as HHMMSSCC.

    Source Code - (sfldate.c)

    {
        return (date  > date now ()
            || (date == date now () && time > time now ()));
    }
    
     

    date_is_past

    #include "sfldate.h"
    Bool
    date_is_past (long date, long time)
    

    Synopsis

    Returns TRUE if the specified date and time are in the past. Returns FALSE if the date and time are in the future or present (which despite any assertion to the contrary, is not the past. Although that may change soon). Date is specified as YYYYMMDD; time as HHMMSSCC.

    Source Code - (sfldate.c)

    {
        return (date  < date now ()
            || (date == date now () && time < time now ()));
    }
    
     

    timezone_string

    #include "sfldate.h"
    char *
    timezone_string (void)
    

    Synopsis

    Returns a static string containing the time zone as a 4-digit number, with a leading '+' or '-' character. GMT is represented as "+0000"; Central European Time is "+1000". If the system does not support the timezone, returns "+0000".

    Source Code - (sfldate.c)

    {
    #if (defined (TIMEZONE))
        static char
            formatted_string [6];           /*  -nnnn plus null                  */
        int
            minutes;                        /*  TIMEZONE is in seconds           */
    
        minutes = (int) (TIMEZONE / 60);
        sprintf (formatted_string, "%03d%02d", 0 - minutes / 60, minutes % 60);
        if (*formatted_string == '0')
            *formatted_string = '+';
        return  (formatted_string);
    #else
        return ("+0000");
    #endif
    }
    
     

    local_to_gmt

    #include "sfldate.h"
    void
    local_to_gmt (long date, long time, long *gmt_date, long *gmt_time)
    

    Synopsis

    Converts the specified date and time to GMT. Returns the GMT date and time in two arguments.

    Source Code - (sfldate.c)

    {
        time_t
            time_value;
    
        time_value = date to timer   (date, time);
        *gmt_date  = timer to gmdate (time_value);
        *gmt_time  = timer to gmtime (time_value);
    }
    
     

    gmt_to_local

    #include "sfldate.h"
    void
    gmt_to_local (long gmt_date, long gmt_time, long *date, long *time)
    

    Synopsis

    Converts the specified GMT date and time to the local time. Returns the local date and time in two arguments.

    Source Code - (sfldate.c)

    {
        time_t
            time_value;
        struct tm
            *time_struct;
    
        /*  Convert from GMT                                                     */
        time_value  = date to timer (gmt_date, gmt_time) - TIMEZONE;
        time_struct = localtime (&time_value);
        if (time_struct-> tm_isdst)
          {
            time_value  += 3600;            /*  Adjust for daylight savings      */
            time_struct = localtime (&time_value);
          }
        time_struct-> tm_year += 1900;
        *date = MAKE_DATE (time_struct-> tm_year / 100,
                           time_struct-> tm_year % 100,
                           time_struct-> tm_mon + 1,
                           time_struct-> tm_mday);
    
        *time = MAKE_TIME (time_struct-> tm_hour,
                           time_struct-> tm_min,
                           time_struct-> tm_sec,
                           0);
    }
    

    External data representation functions

    Filename: sflexdr.h
    Package: Standard Function Library (SFL)
    Written: 96/06/25 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to read and write data in a portable format that is suitable for transmission to other systems. The principle is similar to the ONC XDR standard used in RPC, but somewhat simpler. The streams produced by these functions are not compatible with ONC XDR.

    List of Functions

    List of Symbol Definitions

    sflexdr.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLEXDR_INCLUDED TRUE
     

    exdr_write

    #include "sflexdr.h"
    int
    exdr_write (byte *buffer, const char *format, ...)
    

    Synopsis

    Accepts a list of data items, prepares them according to the format string, and stores the result in the buffer. The buffer may be transmitted to another system, then decoded using exdr_read. Assumes nothing about system word sizes, etc. However, does assume that both systems use ASCII. If the buffer address is NULL, does not store the data items, but counts the effective size and returns that. The null-terminated format string can contain these sequences:
    c Single character value (may be multibyte)
    b Single byte value
    w,d Double byte value (16-bit short integer)
    q,l Quad-byte value (32-bit long integer)
    s Null-terminated string (address of string), or NULL
    B Bool value (16-bit short integer)
    m Memory descriptor size (16-bit integer), >= 0
    M Memory descriptor body (pointer to block), or NULL
    h Huge memory descriptor size (31-bit integer), >= 0
    H Huge memory descriptor body (pointer), or NULL
    Each format sequence corresponds to one item in the list. The buffer must be large enough to hold the formatted result. Returns the size of the formatted data. Ignores invalid format characters; you can insert hyphens or spaces freely. Strings may be specified as (void *) NULL - they are stored as empty strings. Memory blocks may be specified as 0 and (void *) NULL together. Note that if you do not use the (void *) typecast when calling exdr write(), your code will fail on systems where an int is not the same size as a void *. Huge memory blocks cannot be more than 2^31 bytes large (2Gb) or 2^16 bytes if size_t is 16 bits large.

    Source Code - (sflexdr.c)

    {
        va_list
            argptr;                         /*  Argument list pointer            */
        byte
            byte_value,                     /*  Byte value from arguments        */
            *target,                        /*  Pointer into target buffer       */
            *block;                         /*  Source block for 'M' type        */
        char
            *string;                        /*  Source string for 's' type       */
        dbyte
            dbyte_value;                    /*  Network format dbyte value       */
        qbyte
            memory_size = 0,                /*  Memory descriptor size value     */
            qbyte_value;                    /*  Network format qbyte value       */
    
        ASSERT (format);
        va_start (argptr, format);          /*  Start variable arguments list    */
        target = buffer;
    
        while (*format)
          {
            switch (*format++)
              {
                case 'c':                   /*  Character                        */
                case 'b':                   /*  Single byte                      */
                    byte_value = (byte) va_arg (argptr, int);
                    if (buffer)
                        *(byte *) target = byte_value;
                    target += 1;
                    break;
    
                case 'd':                   /*  Signed short integer             */
                case 'w':                   /*  Unsigned short integer           */
                case 'B':                   /*  Bool                             */
                    dbyte_value = htons ((short) va_arg (argptr, int));
                    if (buffer)
                      {
                        *(byte *) target++ = *((byte *) &dbyte_value);
                        *(byte *) target++ = *((byte *) &dbyte_value + 1);
                      }
                    else
                        target += 2;
                    break;
    
                case 'l':                   /*  Signed long (32-bit)             */
                case 'q':                   /*  4-byte unsigned value            */
                    qbyte_value = htonl (va_arg (argptr, qbyte));
                    if (buffer)
                      {
                        *(byte *) target++ = *((byte *) &qbyte_value);
                        *(byte *) target++ = *((byte *) &qbyte_value + 1);
                        *(byte *) target++ = *((byte *) &qbyte_value + 2);
                        *(byte *) target++ = *((byte *) &qbyte_value + 3);
                      }
                    else
                        target += 4;
                    break;
    
                case 's':                   /*  Null-terminated string           */
                    string = va_arg (argptr, char *);
                    if (string)
                      {
                        if (buffer)
                            strcpy ((char *) target, string);
                        target += strlen (string) + 1;
                      }
                    else                    /*  Store NULL as single null byte   */
                      {
                        if (buffer)
                            *(byte *) target++ = 0;
                        else
                            target += 1;
                      }
                    break;
    
                case 'm':                   /*  Memory descriptor size           */
                    memory_size = va_arg (argptr, int);
                    dbyte_value = htons ((dbyte) memory_size);
                    if (buffer)
                      {
                        *(byte *) target++ = *((byte *) &dbyte_value);
                        *(byte *) target++ = *((byte *) &dbyte_value + 1);
                      }
                    else
                        target += 2;
                    break;
                case 'M':                   /*  Memory descriptor body           */
                    block = va_arg (argptr, byte *);
                    if (block)
                      {
                        if (buffer)
                            memcpy (target, block, (size_t) memory_size);
                        target += (size_t) memory_size;
                      }
                    else
                        ASSERT (memory_size == 0);
                    break;
    
                case 'h':                   /*  Huge memory descriptor size       */
                    memory_size = va_arg (argptr, qbyte);
                    qbyte_value = htonl (memory_size);
                    if (buffer)
                      {
                        *(byte *) target++ = *((byte *) &qbyte_value);
                        *(byte *) target++ = *((byte *) &qbyte_value + 1);
                        *(byte *) target++ = *((byte *) &qbyte_value + 2);
                        *(byte *) target++ = *((byte *) &qbyte_value + 3);
                      }
                    else
                        target += 4;
                    break;
                case 'H':                   /*  Huge memory descriptor body       */
                    block = va_arg (argptr, byte *);
                    if (block)
                      {
                        if (buffer)
                            memcpy (target, block, (size_t) memory_size);
                        target += (size_t) memory_size;
                      }
                    else
                        ASSERT (memory_size == 0);
                    break;
              }
          }
        va_end (argptr);                    /*  End variable arguments list      */
        return ((int) (target - buffer));
    }
    
     

    exdr_writed

    #include "sflexdr.h"
    int
    exdr_writed (DESCR *buffer, const char *format, ...)
    

    Synopsis

    As exdr write(), but accepts a DESCR buffer. This is more secure. Aborts with an error if the formatted data would be too long for the buffer, if compiled with DEBUG. The buffer address cannot be NULL.

    Source Code - (sflexdr.c)

    {
        va_list
            argptr;                         /*  Argument list pointer            */
        byte
            *target,                        /*  Pointer into target buffer       */
            *block;                         /*  Source block for 'M' type        */
        char
            *string;                        /*  Source string for 's' type       */
        dbyte
            dbyte_value;                    /*  Network format dbyte value       */
        qbyte
            memory_size = 0,                /*  Memory descriptor size value     */
            qbyte_value;                    /*  Network format qbyte value       */
        size_t
            used_size;                      /*  Current buffer data size         */
    
        ASSERT (buffer);
        ASSERT (format);
        va_start (argptr, format);          /*  Start variable arguments list    */
        target = buffer-> data;
    
        while (*format)
          {
            used_size = (size_t) (target - buffer-> data);
            switch (*format++)
              {
                case 'c':                   /*  Character                        */
                case 'b':                   /*  Single byte                      */
                    *(byte *) target = (byte) va_arg (argptr, int);
                    ASSERT (used_size + 1 <= buffer-> size);
                    target += 1;
                    break;
    
                case 'd':                   /*  Signed short integer             */
                case 'w':                   /*  Unsigned short integer           */
                case 'B':                   /*  Bool                             */
                    dbyte_value = htons ((short) va_arg (argptr, int));
                    ASSERT (used_size + 2 <= buffer-> size);
                    *(byte *) target++ = *((byte *) &dbyte_value);
                    *(byte *) target++ = *((byte *) &dbyte_value + 1);
                    break;
    
                case 'l':                   /*  Signed long (32-bit)             */
                case 'q':                   /*  4-byte unsigned value            */
                    qbyte_value = htonl (va_arg (argptr, qbyte));
                    ASSERT (used_size + 4 <= buffer-> size);
                    *(byte *) target++ = *((byte *) &qbyte_value);
                    *(byte *) target++ = *((byte *) &qbyte_value + 1);
                    *(byte *) target++ = *((byte *) &qbyte_value + 2);
                    *(byte *) target++ = *((byte *) &qbyte_value + 3);
                    break;
    
                case 's':                   /*  Null-terminated string           */
                    string = va_arg (argptr, char *);
                    if (string)
                      {
                        ASSERT (used_size + strlen (string) + 1 <= buffer-> size);
                        strcpy ((char *) target, string);
                        target += strlen (string) + 1;
                      }
                    else                    /*  Store NULL as single null byte   */
                      {
                        ASSERT (used_size + 1 <= buffer-> size);
                        *(byte *) target++ = 0;
                      }
                    break;
    
                case 'm':                   /*  Memory descriptor size           */
                    memory_size = va_arg (argptr, int);
                    ASSERT (used_size + 2 + memory_size <= buffer-> size);
                    dbyte_value = htons ((dbyte) memory_size);
                    *(byte *) target++ = *((byte *) &dbyte_value);
                    *(byte *) target++ = *((byte *) &dbyte_value + 1);
                    break;
                case 'M':                   /*  Memory descriptor body           */
                    block = va_arg (argptr, byte *);
                    if (block)
                      {
                        memcpy (target, block, (size_t) memory_size);
                        target += (size_t) memory_size;
                      }
                    else
                        ASSERT (memory_size == 0);
                    break;
    
                case 'h':                   /*  Huge memory descriptor size      */
                    memory_size = va_arg (argptr, qbyte);
                    ASSERT (used_size + 4 + memory_size <= buffer-> size);
                    qbyte_value = htonl (memory_size);
                    *(byte *) target++ = *((byte *) &qbyte_value);
                    *(byte *) target++ = *((byte *) &qbyte_value + 1);
                    *(byte *) target++ = *((byte *) &qbyte_value + 2);
                    *(byte *) target++ = *((byte *) &qbyte_value + 3);
                    break;
                case 'H':                   /*  Huge memory descriptor body      */
                    block = va_arg (argptr, byte *);
                    if (block)
                      {
                        memcpy (target, block, (size_t) memory_size);
                        target += (size_t) memory_size;
                      }
                    else
                        ASSERT (memory_size == 0);
                    break;
              }
          }
        va_end (argptr);                    /*  End variable arguments list      */
        return ((int) (target - buffer-> data));
    }
    
     

    exdr_read

    #include "sflexdr.h"
    int
    exdr_read (const byte *buffer, const char *format, ...)
    

    Synopsis

    Unpacks a buffer prepared by exdr write() into as set of data items as specified by a format string. See exdr write() for the syntax of the format string. Each format sequence corresponds to one item in in the list which must be specified as an address. Target strings and memory blocks must be large enough to hold the returned data: target strings and blocks can also be null, in which case the function calls mem_alloc to allocate heap memory. Note that you must supply a pointer to the string or memory block address, not the address itself. It is a common error to pass the address of a static block - see the example below for the *right* way to do it. Any of the argument addresses can be NULL, in which case that field is ignored. This is useful to get a few selected fields out of a message. Errors in the argument list can cause memory corruption and unpredictable program results. If a memory allocation fails, all previous memory allocations are "rolled back" and the function returns the value -1. Return codes: 0 - normal -1 - memory allocation failed

    Examples

        char *string = NULL;
        byte buffer [1000];
        byte *buffaddr = buffer;
        int value, length;
        exdr_read (buffer, "qdsmM", NULL, &value, &string, &length, &buffaddr);
    

    Source Code - (sflexdr.c)

    {
        MEMTRN
            *memtrn;                        /*  Memory transaction               */
        va_list
            argptr;                         /*  Argument list pointer            */
        void
            *target;                        /*  Source data item address         */
        dbyte
            string_size,                    /*  String size                      */
            dbyte_value;                    /*  Network format dbyte value       */
        qbyte
            memory_size = 0,                /*  Memory descriptor size value     */
            qbyte_value;                    /*  Network format qbyte value       */
    
        ASSERT (buffer);
        ASSERT (format);
        memtrn = mem new trans ();
    
        va_start (argptr, format);          /*  Start variable arguments list    */
        while (*format)
          {
            target = va_arg (argptr, void *);
            switch (*format++)
              {
                case 'c':                   /*  Character                        */
                case 'b':                   /*  Single byte                      */
                    if (target)
                        *(byte *) target = *(byte *) buffer;
                    buffer += 1;
                    break;
    
                case 'd':                   /*  Signed short integer             */
                case 'w':                   /*  Unsigned short integer           */
                case 'B':                   /*  Bool                             */
                    *((byte *) &dbyte_value)     = *(byte *) buffer++;
                    *((byte *) &dbyte_value + 1) = *(byte *) buffer++;
                    if (target)
                        *(dbyte *) target = ntohs (dbyte_value);
                    break;
    
                case 'l':                   /*  Signed long (32-bit)             */
                case 'q':                   /*  4-byte unsigned value            */
                    *((byte *) &qbyte_value)     = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 1) = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 2) = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 3) = *(byte *) buffer++;
                    if (target)
                        *(qbyte *) target = ntohl (qbyte_value);
                    break;
    
                case 's':                   /*  Null-terminated string           */
                    string_size = strlen ((char *) buffer) + 1;
                    if (target)
                      {
                        if (*(byte **) target == NULL)
                            *(byte **) target = mem_alloc (string_size);
                        if (*(byte **) target)
                            memcpy (*(byte **) target, buffer, string_size);
                        else
                          {
                            mem rollback (memtrn);
                            return (-1);
                          }
                      }
                    buffer += string_size;
                    break;
    
                case 'm':                   /*  Memory descriptor size           */
                    *((byte *) &dbyte_value)     = *(byte *) buffer++;
                    *((byte *) &dbyte_value + 1) = *(byte *) buffer++;
                    memory_size = ntohs (dbyte_value);
                    if (target)
                        *(dbyte *) target = (dbyte) memory_size;
                    break;
                case 'M':                   /*  Memory descriptor body           */
                    if (target && memory_size > 0)
                      {
                        if (*(byte **) target == NULL)
                            *(byte **) target = mem_alloc ((size_t) memory_size);
                        if (*(byte **) target)
                            memcpy (*(byte **) target, buffer,
                                     (size_t) memory_size);
                        else
                          {
                            mem rollback (memtrn);
                            return (-1);
                          }
                      }
                    buffer += (size_t) memory_size;
                    break;
    
                case 'h':                   /*  Huge memory descriptor size      */
                    *((byte *) &qbyte_value)     = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 1) = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 2) = *(byte *) buffer++;
                    *((byte *) &qbyte_value + 3) = *(byte *) buffer++;
                    memory_size = ntohl (qbyte_value);
                    if (target)
                        *(qbyte *) target = memory_size;
                    break;
                case 'H':                   /*  Huge memory descriptor body      */
                    if (target && memory_size > 0)
                      {
                        if (*(byte **) target == NULL)
                            *(byte **) target = mem_alloc ((size_t) memory_size);
                        if (*(byte **) target)
                            memcpy (*(byte **) target, buffer,
                                     (size_t) memory_size);
                        else
                          {
                            mem rollback (memtrn);
                            return (-1);
                          }
                      }
                    buffer += (size_t) memory_size;
                    break;
              }
          }
        va_end (argptr);                    /*  End variable arguments list      */
        mem commit (memtrn);
        return (0);
    }
    

    Fast string searching functions

    Filename: sflfind.h
    Package: Standard Function Library (SFL)
    Written: 96/04/24 iMatix SFL project team sfl@imatix.com
    Revised: 98/05/03
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Searches for a pattern within a string or block of memory using a variant of the Boyer-Moore algorithm (improved by Horspool and Sunday). As fast or faster than the normal Boyer-Moore algorithm for most search strings, and much simpler. Includes a basic function for searching blocks of memory with known sizes, plus an envelope that searches null-delimited strings. Provides the option of repeatedly searching for the same pattern without re-parsing the pattern each time. Original algorithm published by BOYER, R., and S. MOORE 1977, "A Fast String Searching Algorithm." CACM, 20, 762-72. Simplifications by HORSPOOL, R. N. 1980, "Practical Fast Searching in Strings." Software - Practice and Experience, 10, 501-06. More improvements by HUME, A., and D. M. SUNDAY 1991, "Fast String Searching." AT&T Bell Labs Computing Science Technical Report No. 156. Implemented in C by P. Hintjens. strfind r() and memfind r(), are reentrant versions of strfind() and memfind() for single searches, and strfind rb() and memfind rb() are reentrant versions of strfind() and memfind() supporting repeat searches against the same pattern.

    List of Functions

    List of Symbol Definitions

    sflfind.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLFIND_INCLUDED TRUE
     

    strfind

    #include "sflfind.h"
    char *
    strfind (const char *string,            /*  String containing data           */
             const char *pattern,           /*  Pattern to search for            */
             Bool repeat_find)              /*  Same pattern as last time        */
    

    Synopsis

    Searches for a pattern in a string using the Boyer-Moore- Horspool-Sunday algorithm. The string and pattern are null- terminated strings. Returns a pointer to the pattern if found within the string, or NULL if the pattern was not found. If you repeatedly scan for the same pattern, use the repeat_find argument. If this is TRUE, the function does not re-parse the pattern. You must of course call the function with repeat_find equal to FALSE the first time. This function is meant to handle character data, and is most effective when you work with large strings. To search binary data use memfind(). Will not work on multibyte characters.

    Examples

        char *result;
    
        result = strfind ("abracadabra", "cad", FALSE);
        if (result)
            puts (result);
    

    Source Code - (sflfind.c)

    {
        static size_t
            searchbuf [256];                /*  Fixed search buffer              */
    
        ASSERT (string);                    /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  fall through if not debugging    */
    
        return (char *) memfind rb (string,    strlen (string),
                                    pattern,   strlen (pattern),
                                    searchbuf, &repeat_find);
    }
    
     

    strfind_r

    #include "sflfind.h"
    char *
    strfind_r (const char *string,          /*  String containing data           */
               const char *pattern)         /*  Pattern to search for            */
    

    Synopsis

    Searches for a pattern in a string using the Boyer-Moore- Horspool-Sunday algorithm. The string and pattern are null- terminated strings. Returns a pointer to the pattern if found within the string, or NULL if the pattern was not found. This function is meant to handle character data, and is most effective when you work with large strings. To search binary data use memfind(). Will not work on multibyte characters. Reentrant.

    Examples

        char *result;
    
        result = strfind_r ("abracadabra", "cad");
        if (result)
            puts (result);
    

    Source Code - (sflfind.c)

    {
        size_t
            searchbuf [256];                /*  One-time search buffer           */
        Bool
            secondtime = FALSE;             /*  Search buffer init needed        */
    
        ASSERT (string);                    /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  fall through if not debugging    */
    
        return (char *) memfind rb (string,    strlen (string),
                                    pattern,   strlen (pattern),
                                    searchbuf, &secondtime);
    }
    
     

    strfind_rb

    #include "sflfind.h"
    char *
    strfind_rb (const char *string,         /*  String containing data           */
                const char *pattern,        /*  Pattern to search for            */
                size_t     *shift,          /*  Working buffer between searches  */
                Bool       *repeat_find)    /*  Flag for first/later search      */
    

    Synopsis

    Searches for a pattern in a string using the Boyer-Moore- Horspool-Sunday algorithm. The string and pattern are null- terminated strings. Returns a pointer to the pattern if found within the string, or NULL if the pattern was not found. Supports more efficient repeat searches (for the same pattern), through a supplied search buffer. The search buffer must be long enough to contain 256 (2**8) size_t entries. On the first call repeat_find must be set to FALSE. After the search buffer has been initialised, repeat_find will be set to TRUE by the function, avoiding the search buffer initialisation on later calls. This function is most effective when repeated searches are made for the same pattern in one or more strings. This function is meant to handle character data, and is most effective when you work with large strings. To search binary data use memfind(). Will not work on multibyte characters. Reentrant.

    Examples

        char   *result;
        Bool   repeat_search = FALSE;
        size_t searchbuf[256];
    
        result = strfind_rb ("abracadabra", "cad", searchbuf, &repeat_search);
        if (result)
          {
            puts (result);
            result = strfind_rb ("cad/cam", "cad", searchbuf, &repeat_search);
            if (result)
                puts (result);
          }
    

    Source Code - (sflfind.c)

    {
        ASSERT (string);                    /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  fall through if not debugging    */
        ASSERT (shift);
        ASSERT (repeat_find);
    
        return (char *) memfind rb (string,    strlen (string),
                                    pattern,   strlen (pattern),
                                    shift,     repeat_find);
    }
    
     

    memfind

    #include "sflfind.h"
    void *
    memfind (const void  *block,            /*  Block containing data            */
             size_t       block_size,       /*  Size of block in bytes           */
             const void  *pattern,          /*  Pattern to search for            */
             size_t       pattern_size,     /*  Size of pattern block            */
             Bool         repeat_find)      /*  Same pattern as last time        */
    

    Synopsis

    Searches for a pattern in a block of memory using the Boyer- Moore-Horspool-Sunday algorithm. The block and pattern may contain any values; you must explicitly provide their lengths. Returns a pointer to the pattern if found within the block, or NULL if the pattern was not found. If you repeatedly scan for the same pattern, use the repeat_find argument. If this is TRUE, the function does not re-parse the pattern. This function is meant to handle binary data. If you need to search strings, use the strfind_r or strfind rb() functions. Non-Reentrant.

    Source Code - (sflfind.c)

    {
        static size_t
            searchbuf [256];                /*  Static shared search buffer      */
    
        ASSERT (block);                     /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  full through if not debugging    */
    
        return memfind rb (block, block_size, pattern, pattern_size,
                           searchbuf, &repeat_find);
    }
    
     

    memfind_r

    #include "sflfind.h"
    void *
    memfind_r (const void  *block,          /*  Block containing data            */
               size_t       block_size,     /*  Size of block in bytes           */
               const void  *pattern,        /*  Pattern to search for            */
               size_t       pattern_size)   /*  Size of pattern block            */
    

    Synopsis

    Searches for a pattern in a block of memory using the Boyer- Moore-Horspool-Sunday algorithm. The block and pattern may contain any values; you must explicitly provide their lengths. Returns a pointer to the pattern if found within the block, or NULL if the pattern was not found. This function is meant to handle binary data, for a single search for a given pattern. If you need to search strings, use the strfind r() or strfind rb() functions. If you want to do efficient repeated searches for one pattern, use memfind rb(). Reentrant.

    Source Code - (sflfind.c)

    {
        size_t
            searchbuf [256];                /*  One-time search buffer           */
        Bool
            secondtime  = FALSE;
    
        ASSERT (block);                     /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  full through if not debugging    */
    
        return memfind rb (block, block_size, pattern, pattern_size,
                           searchbuf, &secondtime);
    }
    
     

    memfind_rb

    #include "sflfind.h"
    void *
    memfind_rb (const void  *in_block,      /*  Block containing data            */
                size_t       block_size,    /*  Size of block in bytes           */
                const void  *in_pattern,    /*  Pattern to search for            */
                size_t       pattern_size,  /*  Size of pattern block            */
                size_t      *shift,         /*  Shift table (search buffer)      */
                Bool        *repeat_find)   /*  TRUE: search buffer already init */
    

    Synopsis

    Searches for a pattern in a block of memory using the Boyer- Moore-Horspool-Sunday algorithm. The block and pattern may contain any values; you must explicitly provide their lengths. Returns a pointer to the pattern if found within the block, or NULL if the pattern was not found. On the first search with a given pattern, *repeat_find should be FALSE. It will be set to TRUE after the shift table is initialised, allowing the initialisation phase to be skipped on subsequent searches. shift must point to an array big enough to hold 256 (8**2) size_t values. This function is meant to handle binary data, for repeated searches for the same pattern. If you need to search strings, use the strfind r() or strfind rb() functions. If you wish to search for a pattern only once consider using memfind r(). Reentrant.

    Source Code - (sflfind.c)

    {
        size_t
            byte_nbr,                       /*  Distance through block           */
            match_size;                     /*  Size of matched part             */
        const byte
            *match_base = NULL,             /*  Base of match of pattern         */
            *match_ptr  = NULL,             /*  Point within current match       */
            *limit      = NULL;             /*  Last potiental match point       */
        const byte
            *block   = (byte *) in_block,   /*  Concrete pointer to block data   */
            *pattern = (byte *) in_pattern; /*  Concrete pointer to search value */
    
        ASSERT (block);                     /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  fail gracefully if not debugging */
        ASSERT (shift);                     /*  NULL repeat_find => is false     */
        if (block == NULL || pattern == NULL || shift == NULL)
            return (NULL);
    
        /*  Pattern must be smaller or equal in size to string                   */
        if (block_size < pattern_size)
            return (NULL);                  /*  Otherwise it's not found         */
    
        if (pattern_size == 0)              /*  Empty patterns match at start    */
            return ((void *)block);
    
        /*  Build the shift table unless we're continuing a previous search      */
    
        /*  The shift table determines how far to shift before trying to match   */
        /*  again, if a match at this point fails.  If the byte after where the  */
        /*  end of our pattern falls is not in our pattern, then we start to     */
        /*  match again after that byte; otherwise we line up the last occurence */
        /*  of that byte in our pattern under that byte, and try match again.    */
    
        if (!repeat_find || !*repeat_find)
          {
            for (byte_nbr = 0; byte_nbr < 256; byte_nbr++)
                shift [byte_nbr] = pattern_size + 1;
            for (byte_nbr = 0; byte_nbr < pattern_size; byte_nbr++)
                shift [(byte) pattern [byte_nbr]] = pattern_size - byte_nbr;
    
            if (repeat_find)
                *repeat_find = TRUE;
          }
    
        /*  Search for the block, each time jumping up by the amount             */
        /*  computed in the shift table                                          */
    
        limit = block + (block_size - pattern_size + 1);
        ASSERT (limit > block);
    
        for (match_base = block;
             match_base < limit;
             match_base += shift [*(match_base + pattern_size)])
          {
            match_ptr  = match_base;
            match_size = 0;
    
            /*  Compare pattern until it all matches, or we find a difference    */
            while (*match_ptr++ == pattern [match_size++])
              {
                ASSERT (match_size <= pattern_size &&
                        match_ptr == (match_base + match_size));
    
                /*  If we found a match, return the start address                */
                if (match_size >= pattern_size)
                  return ((void*)(match_base));
    
              }
          }
        return (NULL);                      /*  Found nothing                    */
    }
    
     

    txtfind

    #include "sflfind.h"
    char *
    txtfind (const char *string,            /*  String containing data           */
             const char *pattern)           /*  Pattern to search for            */
    

    Synopsis

    Searches for a case-insensitive text pattern in a string using the Boyer-Moore-Horspool-Sunday algorithm. The string and pattern are null-terminated strings. Returns a pointer to the pattern if found within the string, or NULL if the pattern was not found. Will match strings irrespective of case. To match exact strings, use strfind(). Will not work on multibyte characters.

    Examples

        char *result;
    
        result = txtfind ("AbracaDabra", "cad");
        if (result)
            puts (result);
    

    Source Code - (sflfind.c)

    {
        size_t
            shift [256];                    /*  Shift distance for each value    */
        size_t
            string_size,
            pattern_size,
            byte_nbr,                       /*  Index into byte array            */
            match_size;                     /*  Size of matched part             */
        const char
            *match_base = NULL,             /*  Base of match of pattern         */
            *match_ptr  = NULL,             /*  Point within current match       */
            *limit      = NULL;             /*  Last potiental match point       */
    
        ASSERT (string);                    /*  Expect non-NULL pointers, but    */
        ASSERT (pattern);                   /*  fail gracefully if not debugging */
        if (string == NULL || pattern == NULL)
            return (NULL);
    
        string_size  = strlen (string);
        pattern_size = strlen (pattern);
    
        /*  Pattern must be smaller or equal in size to string                   */
        if (string_size < pattern_size)
            return (NULL);                  /*  Otherwise it cannot be found     */
    
        if (pattern_size == 0)              /*  Empty string matches at start    */
            return (char *) string;
    
        /*  Build the shift table                                                */
    
        /*  The shift table determines how far to shift before trying to match   */
        /*  again, if a match at this point fails.  If the byte after where the  */
        /*  end of our pattern falls is not in our pattern, then we start to     */
        /*  match again after that byte; otherwise we line up the last occurence */
        /*  of that byte in our pattern under that byte, and try match again.    */
    
        for (byte_nbr = 0; byte_nbr < 256; byte_nbr++)
            shift [byte_nbr] = pattern_size + 1;
    
        for (byte_nbr = 0; byte_nbr < pattern_size; byte_nbr++)
            shift [(byte) tolower (pattern [byte_nbr])] = pattern_size - byte_nbr;
    
        /*  Search for the string.  If we don't find a match, move up by the     */
        /*  amount we computed in the shift table above, to find location of     */
        /*  the next potiental match.                                            */
    
        limit = string + (string_size - pattern_size + 1);
        ASSERT (limit > string);
    
        for (match_base = string;
             match_base < limit;
             match_base += shift [(byte) tolower (*(match_base + pattern_size))])
          {
            match_ptr  = match_base;
            match_size = 0;
    
            /*  Compare pattern until it all matches, or we find a difference    */
            while (tolower (*match_ptr++) == tolower (pattern [match_size++]))
              {
                ASSERT (match_size <= pattern_size &&
                        match_ptr == (match_base + match_size));
    
                /*  If we found a match, return the start address                */
                if (match_size >= pattern_size)
                    return ((char *)(match_base));
              }
          }
        return (NULL);                      /*  Found nothing                    */
    }
    

    File-access functions

    Filename: sflfile.h
    Package: Standard Function Library (SFL)
    Written: 92/10/25 iMatix SFL project team sfl@imatix.com
    Revised: 98/08/30
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to read and write files with explicit new- line/carriage-return control; to find files on a path; to copy files, check files' protection, etc.

    List of Functions

    List of Symbol Definitions

    sflfile.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    FILE_DIR_MAX 64 /* Max size of directory name */
    FILE_NAME_MAX 160 /* Max size of filename */
    FOPEN_APPEND_BINARY (various)
    FOPEN_APPEND_TEXT (various)
    FOPEN_READ_BINARY (various)
    FOPEN_READ_TEXT (various)
    FOPEN_WRITE_BINARY (various)
    FOPEN_WRITE_TEXT (various)
    _SFLFILE_INCLUDED TRUE
    file_lines(f) get_file_lines(f) /* Changed 98/07/23 */
     

    file_open

    #include "sflfile.h"
    FILE *
    file_open (
        const char *filename,               /*  Name of file to open             */
        char mode)                          /*  'r', 'w', or 'a'                 */
    

    Synopsis

    opens a text file for reading or writing. Use in combination with the file read() and file write() functions. These functions handle end-of-line sequences using a heuristic that works as follows. ... (at this point the author went for a pint of beer and has not been seen since. We're hoping that the old version - following - is ok.)Opens the specified file for input or output. If you use the file_read / file_write functions you must open the file using this function. This set of functions lets you read files without concern for the line format (CRLF or LF). Mode should be one of 'r' 'w' 'a'. Returns a FILE pointer if the file is opened correctly; else NULL. Sets the global variable file_crlf to FALSE on all systems except MS-DOS (and Windows by inheritence) where it is set to TRUE by default. When opening a file in append mode, automatically removes any Ctrl-Z character under MS-DOS or OS/2.

    Source Code - (sflfile.c)

    {
        ASSERT (filename);
    
    #   if (defined (MSDOS_FILESYSTEM))
        file_crlf = TRUE;
    #   else
        file_crlf = FALSE;
    #   endif
    
        if (mode == 'r')
            return (fopen (filename, FOPEN_READ_BINARY));
        else
        if (mode == 'w')
            return (fopen (filename, FOPEN_WRITE_BINARY));
        else
        if (mode == 'a'
        &&  safe to extend (filename))
            return (fopen (filename, FOPEN_APPEND_BINARY));
        else
            return (NULL);                  /*  Invalid mode                     */
    }
    
     

    file_locate

    #include "sflfile.h"
    FILE *
    file_locate (
        const char *path,
        const char *name,
        const char *ext)
    

    Synopsis

    Combines the functions of file where() and file_open when you want to read a file. Searches for a file on a specified path, opens the file if found, and returns a FILE * for the open file. Returns NULL if the file was not found or could not be opened for reading.

    Source Code - (sflfile.c)

    {
        char
            *filename;
    
        ASSERT (name);
        filename = file where ('r', path, name, ext);
        if (filename)
            return (file open (filename, 'r'));
        else
            return (NULL);
    }
    
     

    file_close

    #include "sflfile.h"
    int
    file_close (
        FILE *stream)
    

    Synopsis

    Closes an open file stream. Returns 0 if okay, -1 if there was an error. For now, equivalent to fclose, and supplied because it looks nice when you use file open() and file close() together.

    Source Code - (sflfile.c)

    {
        ASSERT (stream);
        return (fclose (stream));
    }
    
     

    file_read

    #include "sflfile.h"
    Bool
    file_read (
        FILE *stream,
        char *string)
    

    Synopsis

    Reads a line of text delimited by newline from the stream. The string must be LINE_MAX + 1 long. Places a null byte in place of the newline character. Expands tab characters to every 8th column. Returns TRUE when there is more input waiting; FALSE when the last line of the file has been read. Sets the global variable file_crlf to TRUE if CR was found in the file. This variable is by default FALSE. It is also used by file_write.

    Source Code - (sflfile.c)

    {
        int
            ch,                             /*  Character read from file         */
            cnbr;                           /*  Index into returned string       */
    
        ASSERT (stream);
        ASSERT (string);
    
        cnbr = 0;                           /*  Start at the beginning...        */
        memset (string, ' ', LINE_MAX);     /*    and prepare entire line        */
        for (;;)
          {
            ch = fgetc (stream);            /*  Get next character from file     */
            if (ch == '\t')                 /*  Jump if tab                      */
                cnbr = ((cnbr >> 3) << 3) + 8;
            else
            if (ch == '\r')                 /*  Found carriage-return            */
                file_crlf = TRUE;           /*    Set flag and ignore CR         */
            else
            if ((ch == '\n')                /*  Have end of line                 */
            ||  (ch == EOF)                 /*    or end of file                 */
            ||  (ch == 26))                 /*    or MS-DOS Ctrl-Z               */
              {
                string [cnbr] = '\0';       /*  Terminate string                 */
                return (ch == '\n' || cnbr);    /*  and return TRUE/FALSE        */
              }
            else
            if (cnbr < LINE_MAX)
                string [cnbr++] = (char) ch;    /*  Else add char to string      */
    
            if (cnbr >= LINE_MAX)           /*  Return in any case if line is    */
              {                             /*    too long - the line will be    */
                string [LINE_MAX] = '\0';   /*    cut into pieces                */
                return (TRUE);
              }
          }
    }
    
     

    file_readn

    #include "sflfile.h"
    Bool
    file_readn (
        FILE *stream,
        char *string,
        int   line_max)
    

    Synopsis

    Works as file read() but with a maximum line-length specified by the caller. The supplied buffer must be at least as large as the specified line_max + 1.

    Source Code - (sflfile.c)

    {
        int
            ch,                             /*  Character read from file         */
            cnbr;                           /*  Index into returned string       */
    
        ASSERT (stream);
        ASSERT (string);
    
        cnbr = 0;                           /*  Start at the beginning...        */
        memset (string, ' ', line_max);     /*    and prepare entire line        */
        for (;;)
          {
            ch = fgetc (stream);            /*  Get next character from file     */
            if (ch == '\t')                 /*  Jump if tab                      */
                cnbr = ((cnbr >> 3) << 3) + 8;
            else
            if (ch == '\r')                 /*  Found carriage-return            */
                file_crlf = TRUE;           /*    Set flag and ignore CR         */
            else
            if ((ch == '\n')                /*  Have end of line                 */
            ||  (ch == EOF)                 /*    or end of file                 */
            ||  (ch == 26))                 /*    or MS-DOS Ctrl-Z               */
              {
                string [cnbr] = '\0';       /*  Terminate string                 */
                return (ch == '\n' || cnbr);    /*  and return TRUE/FALSE        */
              }
            else
            if (cnbr < line_max)
                string [cnbr++] = (char) ch;    /*  Else add char to string      */
    
            if (cnbr >= line_max)           /*  Return in any case if line is    */
              {                             /*    too long - the line will be    */
                string [line_max] = '\0';   /*    cut into pieces                */
                return (TRUE);
              }
          }
    }
    
     

    file_write

    #include "sflfile.h"
    char *
    file_write (
        FILE *stream,
        const char *string)
    

    Synopsis

    Writes a line of text to the specified output stream. If the variable file_crlf is TRUE, adds a carriage-return to the line being written to the output stream. This variable is supplied so that you can either ignore crlf issues (do nothing), or handle them explicitly (play with file_crlf). Returns the string written, or NULL if no data could be written to the file.

    Source Code - (sflfile.c)

    {
        ASSERT (stream);
        ASSERT (string);
    
        fputs (string, stream);
        if (file_crlf)
            fputc ('\r', stream);
    
        if (fputc ('\n', stream) == EOF)
            return (NULL);
        else
            return ((char *) string);
    }
    
     

    file_copy

    #include "sflfile.h"
    int
    file_copy (
        const char *dest,
        const char *src,
        char mode)
    

    Synopsis

    Copies a file called src to one called dest. The dest file may not already exist. If mode is 'b', copies a binary file; if mode is 't', copies a text file. This distinction only applies to MS-DOS file systems; on other platforms the two modes are equivalent. Returns 0 if no problems occurred, -1 if an error occurred, 1 if the destination file already exists.

    Source Code - (sflfile.c)

    {
        FILE *inf, *outf;
        char *buffer,
             openmode [3] = "??";
        size_t chars_read;                  /*  Amount read from stream          */
        int  feedback = 0;
    
        ASSERT (dest);
        ASSERT (src);
        if (file exists (dest))
            return (1);                     /*  Cancel: dest already exists      */
    
    #   if (defined (MSDOS_FILESYSTEM))
        openmode [1] = mode;
    #   else
        openmode [1] = 0;
    #   endif
        openmode [0] = 'r';
        if ((inf = fopen (src, openmode)) == NULL)
            return (-1);                    /*  Input file not found             */
    
        if ((buffer = mem_alloc (SHRT_MAX)) == NULL)
            feedback = -1;                  /*  Insufficient memory for buffer   */
        else
          {
            openmode [0] = 'w';
            if ((outf = fopen (dest, openmode)) == NULL)
              {
                mem_free (buffer);
                return (-1);                /*  Could not create output file     */
              }
            while ((chars_read = fread (buffer, 1, SHRT_MAX, inf)) != 0)
                if (fwrite (buffer, 1, chars_read, outf) != chars_read)
                  {
                    feedback = -1;
                    break;
                  }
            fclose (outf);
            mem_free (buffer);
          }
        fclose (inf);
        return (feedback);
    }
    
     

    file_concat

    #include "sflfile.h"
    int
    file_concat (
        const char *dest,
        const char *src)
    

    Synopsis

    Copies the contents of dest onto src. If src does not exist, it is created. Returns 0 if the concatenation operation succeeded, or -1 if some error occurred.

    Source Code - (sflfile.c)

    {
        FILE *inf, *outf;
        char *buffer;
        size_t chars_read;                  /*  Amount read from stream          */
        int  feedback = 0;
    
        ASSERT (dest);
        ASSERT (src);
    
        if ((inf = fopen (src, FOPEN_READ_BINARY)) == NULL)
            return (-1);                    /*  Input file not found             */
    
        if ((buffer = mem_alloc (SHRT_MAX)) == NULL)
            feedback = -1;                  /*  Insufficient memory for buffer   */
        else
          {
            if ((outf = fopen (dest, FOPEN_APPEND_BINARY)) == NULL)
              {
                mem_free (buffer);
                return (-1);                /*  Could not create output file     */
              }
            while ((chars_read = fread (buffer, 1, SHRT_MAX, inf)) != 0)
                if (fwrite (buffer, 1, chars_read, outf) != chars_read)
                  {
                    feedback = -1;
                    break;
                  }
            fclose (outf);
            mem_free (buffer);
          }
        fclose (inf);
        return (feedback);
    }
    
     

    file_rename

    #include "sflfile.h"
    int
    file_rename (
        const char *oldname,
        const char *newname)
    

    Synopsis

    Renames a file from oldname to newname. Returns 0 if okay, or - 1 if there was an error. Does not overwrite existing files.

    Source Code - (sflfile.c)

    {
    #   if (defined (MSDOS_FILESYSTEM))
        char *dos_newname;
        int   feedback;
    
        ASSERT (oldname);
        ASSERT (newname);
    
        dos_newname = mem_strdup (newname);
        strconvch (dos_newname, '/', '\\');
        feedback = rename (oldname, dos_newname);
        mem_free (dos_newname);
        return (feedback);
    
    #   else
        ASSERT (oldname);
        ASSERT (newname);
    
        return (rename (oldname, newname));
    #   endif
    }
    
     

    file_delete

    #include "sflfile.h"
    int
    file_delete (
        const char *filename)
    

    Synopsis

    Deletes the specified file. Returns 0 if okay, -1 in case of an error.

    Source Code - (sflfile.c)

    {
    #if (defined (__VMS__))
        ASSERT (filename);
        return (remove (filename));
    
    #elif (defined (WIN32))
        int
            rc;
    
        ASSERT (filename);
        rc = !DeleteFile (filename);
        if (rc && errno == EACCES)
          {
            /*  Under WinNT and Win95, a delete of a freshly-created file can
             *  sometimes fail with a permission error which passes after a
             *  short delay.  Ugly but it seems to work.
             */
            Sleep (200);
            rc = !DeleteFile (filename);
          }
        return (rc);
    #else
    
        ASSERT (filename);
        return (unlink (filename));
    #endif
    }
    
     

    file_exists

    #include "sflfile.h"
    Bool
    file_exists (
        const char *filename)
    

    Synopsis

    Returns TRUE if the file exists, or FALSE if it does not.

    Source Code - (sflfile.c)

    {
        ASSERT (filename);
        return (file_mode (filename) > 0);
    }
    
     

    file_where

    #include "sflfile.h"
    char *
    file_where (
        char mode,
        const char *path,
        const char *name,
        const char *ext)
    

    Synopsis

    Scans a user-specified path symbol for a specific file, and returns the fully-specified filename. Also adds an extension if this is required. The mode argument can be one of: r, w, a, or s for read, write, append, or static. The function tries to locate existing files somewhere on the path. New files are always created in the current directory. Static files are created in the first directory on the path. The path argument is only used when more is r, a, or s. If the path is NULL or empty, it is ignored. Otherwise, the path is translated as an environment variable, and cut into a list of directory names. The path is cut up as follows:
    MS-DOS directory names separated by ';'. ;; means current.
    OS/2 directory names separated by ';'. ;; means current.
    Unix directory names separated by ':'. :: means current.
    VMS directory names separated by ','. " ", means current.
    Other single directory name.
    When the mode is 'r' or 'a', searches the current directory before considering the path value. When the path cannot be translated, and is not null or empty, it is used as a literal value. The name argument is the filename with or without extension. It will be prefixed by the path and suffixed by the extension, if required. The ext argument is a default or mandatory extension. If ext starts with a dot, it is mandatory and always used. Otherwise it is used only if the name does not already have an extension. If ext is NULL or empty, it is ignored. The total length of a name including path, name, extension, and any delimiters is FILE_NAME_MAX. Names are truncated if too long. The maximum size of one directory component is FILE_DIR_MAX chars. All parameters are case- sensitive; the precise effect of this depends on the system. On MS-DOS, filenames are always folded to uppercase, but the path must be supplied in uppercase correctly. On UNIX, all parameters are case sensitive. On VMS, path and filenames are folded into uppercase. Returns a pointer to a static character array containing the filename; if mode is 'r' and the file does not exist, returns NULL. If the mode is 'w', 'a', or 's', always returns a valid filename. Under VMS, all filenames are handled in POSIX mode, i.e. /disk/path/file instead of $disk:[path]file.

    Source Code - (sflfile.c)

    {
        const char
            *pathptr;                       /*  End of directory in path         */
        char
            *curdir;
        Bool
            search_curdir = TRUE;           /*  Look in current directory?       */
    
        ASSERT (name);
        if (ext != NULL && *ext)            /*  Append extension if not null     */
          {                                 /*    to get name + ext into         */
            if (ext [0] == '.')             /*    work_name.                     */
                fixed extension (work_name, name, ext);
            else
                default extension (work_name, name, ext);
          }
        else
            strcpy (work_name, name);
    #if (NAMEFOLD == TRUE)
        strupc (work_name);                 /*  Fold to uppercase if needed      */
    #endif
    
        if (path != NULL && *path)          /*  Get value of path, or NULL       */
          {
            pathptr = getenv (path);        /*  Translate path symbol            */
            if (pathptr == NULL)
              {
                pathptr = path;             /*  If not found, use literally      */
                search_curdir = FALSE;      /*  Path now takes priority          */
              }
    #if (PATHFOLD == TRUE)                  /*  Fold to uppercase if necessary   */
            if (pathptr)
              {
                ASSERT (strlen (pathptr) < PATH_MAX);
                strcpy (path_name, pathptr);
                strupc (path_name);
                pathptr = path_name;        /*  Redirect to uppercase version    */
              }
    #endif
          }
        else
            pathptr = NULL;
    
    #if (defined (MSDOS_FILESYSTEM))
        /*  Normalise the path value by changing any slashes to backslashes      */
        if (pathptr)
          {
            if (pathptr != path_name)
              {
                strcpy (path_name, pathptr);
                pathptr = path_name;
              }
            strconvch (path_name, '/', '\\');
          }
    #endif
    
        /*  Take care of 'w' and 's' options first                               */
        if (mode == 'w')                    /*  Create output file locally       */
            return (work_name);
    
        if (mode == 's')                    /*  Get specific directory name      */
          {
            if (pathptr && file is directory (pathptr))
                build_next_path (full_name, pathptr, work_name);
            else
                build_next_path (full_name, ".\\", work_name);
            return (full_name);
          }
    
        /*  If file exists as defined, prefix with current directory if not an   */
        /*  absolute filename, then return the resulting filename                */
        if (search_curdir && file exists (work_name))
          {
    #if (defined (MSDOS_FILESYSTEM))
            /*  Under MSDOS we have a full path if we have any of:
             *     /directory/directory/filename
             *     D:/directory/directory/filename
             *     the variations of those with backslashes.
             */
            if (work_name [0] == '\\'   || work_name [0] == '/' ||
               (isalpha (work_name [0]) && work_name [1] == ':' &&
               (work_name [2] == '\\'   || work_name [2] == '/')))
    
    #else
            /*  Under UNIX, VMS, or OS/2, we have a full path if the path starts
             *  with the directory marker
             */
            if (work_name [0] == PATHEND)
    #endif
                strcpy (full_name, work_name);
            else
              {
                curdir = get curdir ();
                sprintf (full_name, "%s%c%s", curdir, PATHEND, work_name);
                mem_free (curdir);
              }
    #if (defined (MSDOS_FILESYSTEM))
            strconvch (full_name, '/', '\\');
    #endif
            return (full_name);             /*  Then return path + name + ext    */
          }
        if (!pathptr)                       /*  Now we need a path               */
            return (NULL);                  /*   - if none defined, give up      */
    
        for (;;)                            /*  Try each path component          */
          {
            pathptr = build_next_path (full_name, pathptr, work_name);
            if (file exists (full_name))
                return (full_name);         /*  Until we find one,               */
    
            if (*pathptr == '\0')           /*    or we come to the end of       */
              {                             /*    the path                       */
                if (mode == 'r')
                    return (NULL);          /*  Input file was not found...      */
                else
                    return (full_name);
              }
          }
    }
    
     

    file_cycle

    #include "sflfile.h"
    Bool
    file_cycle (
        const char *filename,
        int how)
    

    Synopsis

    Cycles the file: if the file already exists, renames the existing file. This function tries to rename the file using the date of creation of the file; if this fails because an existing file had the same name, generates a guaranteed unique file name. Returns TRUE if the cycle operation succeeded, or FALSE if it failed (e.g. due to a protection problem). The how argument must be one of:
    CYCLE ALWAYS Cycle file unconditionally
    CYCLE HOURLY Cycle file if hour has changed
    CYCLE DAILY Cycle file if day has changed
    CYCLE WEEKLY Cycle file if week has changed
    CYCLE MONTHLY Cycle file if month has changed
    CYCLE NEVER Don't cycle the file

    Source Code - (sflfile.c)

    {
        long
            file_date;                      /*  Datestamp of file                */
        char
            *point,
            *insert_at;                     /*  Where we start messing name      */
        int
            unique_nbr;                     /*  To generate a unique name        */
    
        ASSERT (filename);
    
        /*  If no cycling needed, do nothing                                     */
        if (!file cycle needed (filename, how))
            return (TRUE);                  /*  No errors, nothing in fact       */
    
        file_date = timer to date (get file time (filename));
        strcpy (full_name, filename);
        point = strrchr (full_name, '.');
        if (point)
          {
            strcpy (work_name, point);      /*  Save extension, if any           */
            *point = '\0';                  /*    and truncate original name     */
          }
        else
            strclr (work_name);
    
        /*  We leave up to 2 original letters of the filename, then stick-in     */
        /*  the 6-digit timestamp.                                               */
        insert_at = strrchr (full_name, '/');
    #if (defined (MSDOS_FILESYSTEM))
        if (!insert_at)
            insert_at = strrchr (full_name, '\\');
    #endif
        if (insert_at)
            insert_at++;                    /*  Bump past slash, if found        */
        else
            insert_at = full_name;
    
        if (*insert_at)                     /*  Bump insert_at twice, to leave   */
            insert_at++;                    /*    up to 2 letters before we      */
        if (*insert_at)                     /*    stick-in the date stamp        */
            insert_at++;
    
        /*  Format new name for file and make sure it does not already exist     */
        sprintf (insert_at, "%06d", (int) (file_date % 1000000L));
        strcat  (insert_at, work_name);
        if (file exists (full_name))
          {
            point = strrchr (full_name, '.') + 1;
            for (unique_nbr = 0; unique_nbr < 1000; unique_nbr++)
              {
                sprintf (point, "%03d", unique_nbr);
                if (!file exists (full_name))
                    break;
              }
          }
        if (file exists (full_name))
            return (FALSE);                 /*  We give up!                      */
    
        if (file rename (filename, full_name))
            return (FALSE);                 /*  No permission                    */
        else
            return (TRUE);                  /*  Okay, it worked                  */
    }
    
     

    file_cycle_needed

    #include "sflfile.h"
    Bool
    file_cycle_needed (
        const char *filename,
        int how)
    

    Synopsis

    Checks whether the file should be cycled or not. Returns TRUE if the file needs to be cycled, FALSE if not. The how argument must be one of:
    CYCLE ALWAYS Cycle file unconditionally
    CYCLE HOURLY Cycle file if hour has changed
    CYCLE DAILY Cycle file if day has changed
    CYCLE WEEKLY Cycle file if week has changed
    CYCLE MONTHLY Cycle file if month has changed
    CYCLE NEVER Don't cycle the file
    If the specified file does not exist or is not accessible, returns FALSE.

    Source Code - (sflfile.c)

    {
        long
            curr_time,                      /*  Current time                     */
            curr_date,                      /*  Current date                     */
            file_date,                      /*  Timestamp of file                */
            file_time;                      /*  Datestamp of file                */
        Bool
            cycle;                          /*  Do we want to cycle the file?    */
    
        ASSERT (filename);
        if (!file exists (filename))        /*  Not found - nothing more to do   */
            return (FALSE);
    
        file_time = timer to time (get file time (filename));
        file_date = timer to date (get file time (filename));
        curr_time = time now ();
        curr_date = date now ();
    
        switch (how)
          {
            case CYCLE_ALWAYS:
                cycle = TRUE;
                break;
            case CYCLE_HOURLY:
                cycle = GET_HOUR (file_time) != GET_HOUR (curr_time);
                break;
            case CYCLE_DAILY:
                cycle = GET_DAY (file_date) != GET_DAY (curr_date);
                break;
            case CYCLE_WEEKLY:
                cycle = week of year (file_date) != week of year (curr_date);
                break;
            case CYCLE_MONTHLY:
                cycle = GET_MONTH (file_date) != GET_MONTH (curr_date);
                break;
            case CYCLE_NEVER:
                cycle = FALSE;
                break;
            default:
                cycle = FALSE;
          }
        return (cycle);
    }
    
     

    file_has_changed

    #include "sflfile.h"
    Bool
    file_has_changed (
        const char *filename,
        long old_date,
        long old_time)
    

    Synopsis

    Returns TRUE if the file has changed since it was last read. The calling program must supply the date and time of the file as it was read. If the file is not present or accessible, returns FALSE.

    Source Code - (sflfile.c)

    {
        long
            file_date,                      /*  Timestamp of file                */
            file_time;                      /*  Datestamp of file                */
    
        ASSERT (filename);
        if (!file exists (filename))        /*  Not found - nothing more to do   */
            return (FALSE);
    
        file_time = timer to time (get file time (filename));
        file_date = timer to date (get file time (filename));
        if (file_date  > old_date
        || (file_date == old_date && file_time > old_time))
            return (TRUE);
        else
            return (FALSE);
    }
    
     

    safe_to_extend

    #include "sflfile.h"
    Bool
    safe_to_extend (
        const char *filename)
    

    Synopsis

    Handles system-specific case of extending a file that may not be in a valid state for such an operation. Returns TRUE if the extend can go ahead; returns FALSE if the extend cannot be permitted. Under MS-DOS and Windows, if the last byte in the file is Ctrl-Z (26) the file is truncated by 1 position to remove this character.

    Source Code - (sflfile.c)

    {
    #if (defined (MSDOS_FILESYSTEM))
        int  handle;                        /*  Opened file handle               */
        char endoffile;                     /*  Last character in file           */
    
        ASSERT (filename);
    
        handle = open (filename, O_RDWR + O_BINARY, S_IREAD | S_IWRITE);
        if (handle)                         /*  If not found, ignore             */
          {
            lseek (handle, -1, SEEK_END);
            read  (handle, &endoffile, 1);
            if (endoffile == 26)
                chsize (handle, filelength (handle) - 1);
    
            close (handle);
          }
    #endif
        return (TRUE);
    }
    
     

    default_extension

    #include "sflfile.h"
    char *
    default_extension (
        char *dest,
        const char *src,
        const char *ext)
    

    Synopsis

    Copies src to dest and adds ext if necessary. Returns dest. Dest must be large enough for a fully-formatted filename; define it as char [FILE_NAME_MAX + 1]. The ext argument can start with or without If ext is null or empty, does nothing.

    Source Code - (sflfile.c)

    {
        int len, i;
        char *ptr;
    
        ASSERT (dest);
        ASSERT (src);
    
        if (dest != src)                    /*  Copy src to dest if not same     */
            strcpy (dest, src);
    
        if (ext != NULL && *ext != 0)
          {
            len = strlen (dest);
            for (i = len - 1, ptr = dest + i; i >= 0; i--, ptr--)
                if (*ptr == '\\' || *ptr == '/' || *ptr == '.')
                    break;
    
            if (i < 0 || *ptr != '.')
              {
                if (*ext != '.')
                  {
                    dest [len++] = '.';
                    dest [len] = '\0';
                  }
                strcat (dest + len, ext);
              }
          }
        return (dest);
    }
    
     

    fixed_extension

    #include "sflfile.h"
    char *
    fixed_extension (
        char *dest,
        const char *src,
        const char *ext)
    

    Synopsis

    Copies src to dest and enforces ext extension. Returns dest. Dest must be large enough for a fully-formatted filename; define it as char [FILE_NAME_MAX + 1]. The ext argument can start with or without If ext is null or empty, does nothing.

    Source Code - (sflfile.c)

    {
        ASSERT (dest);
        ASSERT (src);
    
        if (dest != src)                    /*  Copy src to dest if not same     */
            strcpy (dest, src);
    
        strip extension (dest);
        return (default extension (dest, dest, ext));
    }
    
     

    strip_extension

    #include "sflfile.h"
    char *
    strip_extension (
        char *name)
    

    Synopsis

    Removes dot and extension from the name, if any was present. If the name contained multiple extensions, removes the last one only. Returns name.

    Source Code - (sflfile.c)

    {
        char *dot, *slash;
    
        ASSERT (name);
    
        dot   = strrchr (name, '.');        /*  Find dot in name, if any         */
        slash = strrchr (name, '\\');       /*  Find last slash (DOS or Unix)    */
        if (slash == NULL)
            slash = strrchr (name, '/');
        if (dot > slash)
            *dot = 0;                       /*  If we had a dot, truncate name   */
    
        return (name);
    }
    
     

    strip_file_path

    #include "sflfile.h"
    char
    *strip_file_path (
        char *name)
    

    Synopsis

    Removes the leading path from the filename, if any path was present. Returns name. The path can be specified using the local operating system syntax; under MS-DOS, / and \ are interchangeable.

    Source Code - (sflfile.c)

    {
        char *path_end;
    
        ASSERT (name);
    
        path_end = strrchr (name, PATHEND); /*  Find end of path, if any         */
    #if (defined (MSDOS_FILESYSTEM))
        if (path_end == NULL)
            path_end = strrchr (name, '/');
    #endif
        if (path_end != NULL)
            memmove (name, path_end + 1, strlen (path_end));
        return (name);
    }
    
     

    strip_file_name

    #include "sflfile.h"
    char
    *strip_file_name (
        char *name)
    

    Synopsis

    Returns the path for a fully-qualified filename. The path is cleaned-up and resolved. The returned string is held in a static area that should be copied directly after calling this function. The returned path does not end in '/' unless that is the entire path. If the supplied name contains no path, the returned path is ".".

    Source Code - (sflfile.c)

    {
        char *path_end;
    
        ASSERT (name);
        ASSERT (strlen (name) <= LINE_MAX);
    
        strcpy (work_name, name);
        path_end = strrchr (work_name, PATHEND);
    #if (defined (MSDOS_FILESYSTEM))
        if (path_end == NULL)
            path_end = strrchr (work_name, '/');
    #endif
        if (path_end == NULL)
            return (".");
        else
          {
            path_end [1] = '\0';
            return (clean path (work_name));
          }
    }
    
     

    file_is_readable

    #include "sflfile.h"
    Bool
    file_is_readable (
        const char *filename)
    

    Synopsis

    Returns TRUE if the current process can read the specified file or directory. The filename may end in a slash (/ or \).

    Source Code - (sflfile.c)

    {
        ASSERT (filename);
        return ((file_mode (clean path (filename)) & S_IREAD) != 0);
    }
    
     

    file_is_writeable

    #include "sflfile.h"
    Bool
    file_is_writeable (
        const char *filename)
    

    Synopsis

    Returns TRUE if the current process can write the specified file or directory. The filename may end in a slash (/ or \).

    Source Code - (sflfile.c)

    {
        ASSERT (filename);
    
        return ((file_mode (clean path (filename)) & S_IWRITE) != 0);
    }
    
     

    file_is_executable

    #include "sflfile.h"
    Bool
    file_is_executable (
        const char *filename)
    

    Synopsis

    Returns TRUE if the current process can execute the specified file. Directories are _not_ considered to be executable. Under DOS, Windows, appends ".com", ".exe", and ".bat" to the filename, in that order, to build a possible executable filename. If this fails, opens the file (if it exists) and examines the first few bytes of the file: if these are "#!", or '/'*! or "MZ" then the file is assumed to be executable. #! is a standard mechanism under Unix for indicating executable files. Note that process create() uses a compatible mechanism to launch the correct interpreter for such 'executable' scripts. NOTE: '/'*! is provided for REXX. [XXX] Under OS/2 appends ".exe" and ".cmd" to the filename, in that order, to be a possible executable filename. If this fails, it opens the file (if it exists) and examines the first few bytes of the file: if these are "#!" then the file is assumed to be executable. NOTE: REXX scripts MUST be in files named script.cmd in order to be found. BAT files are not considered, nor are COM files, since at present process_create does not support launching DOS processes. Under VMS, appends .exe and .com, in that order to build a possible executable filename. Does not search the PATH symbol; the filename must be specified with a path if necessary.

    Source Code - (sflfile.c)

    {
    #if (defined (__UNIX__))
        ASSERT (filename);
    
        return ((file_mode (filename) & S_IEXEC) != 0
             && (file_mode (filename) & S_IFDIR) == 0);
    
    #elif (defined (MSDOS_FILESYSTEM))
        Bool
            executable;                     /*  Return code                      */
        FILE
            *stream;                        /*  Opened file stream               */
        char
            input_char = 0,                 /*  First and second bytes of file   */
            *extension;                     /*  File extension, if any           */
    
        ASSERT (filename);
    
        /*  Find file extension; if not found, set extension to empty string     */
        extension = strrchr (filename, '.');
        if (extension == NULL
        ||  strchr (extension, '/')         /*  If last '.' is part of the path  */
        ||  strchr (extension, '\\'))       /*  then the filename has no ext.    */
            extension = "";
    
        /*  Windows: If extension is .exe/.com/.bat, the file is an executable   */
        /*  OS/2:    If the extension is .exe/.cmd, the file is an executable    */
    #if (defined ( __OS2__))
        if (lexcmp (extension, ".exe") == 0
        ||  lexcmp (extension, ".cmd") == 0)
    #else /* DOS, WINDOWS */
        if (lexcmp (extension, ".com") == 0
        ||  lexcmp (extension, ".exe") == 0
        ||  lexcmp (extension, ".bat") == 0)
    #endif
            executable = file exists (filename);
        else
        /*  Windows: If the extension is empty, try .com, .exe, .bat             */
        /*  OS/2:    If the extension is empty, try .exe, .cmd                   */
        if (strnull (extension)
    #if (defined( __OS2__))
        && (file exists (default extension (work_name, filename, "exe"))
        ||  file exists (default extension (work_name, filename, "cmd"))))
    #else /* DOS, WINDOWS */
        && (file exists (default extension (work_name, filename, "com"))
        ||  file exists (default extension (work_name, filename, "exe"))
        ||  file exists (default extension (work_name, filename, "bat"))))
    #endif
            executable = TRUE;              /*  Executable file found            */
        else
          {
            /*  Look for magic header at start of file                           */
            stream = file open (filename, 'r');
            if (stream)
              {
                input_char = fgetc (stream);
                executable = ((input_char == '#' && fgetc (stream) == '!')
    #   if (defined (__WINDOWS__))
                           || (input_char == '/' && fgetc (stream) == '*'
                                                 && fgetc (stream) == '!')
                           || (input_char == 'M' && fgetc (stream) == 'Z')
    #   endif
                );
                file close (stream);
              }
            else
                executable = FALSE;
          }
        return (executable);
    
    #elif (defined (__VMS__))
        Bool
            executable;                     /*  Return code                      */
        char
            *extension;                     /*  File extension, if any           */
    
        ASSERT (filename);
    
        /*  Find file extension, if any                                          */
        extension = strrchr (filename, '.');
        if ((file_mode (filename) & S_IEXEC) != 0)
            executable = TRUE;
        else
        /*  If the extension is empty, try .exe and .com                         */
        if (!extension)
          {
            default extension (work_name, filename, "exe");
            if ((file_mode (work_name) & S_IEXEC) != 0)
                executable = TRUE;
            else
              {
                default extension (work_name, filename, "com");
                if ((file_mode (work_name) & S_IEXEC) != 0)
                    executable = TRUE;
                else
                    executable = FALSE;
              }
          }
        else
            executable = FALSE;
    
        return (executable);
    
    #else
        return (FALSE);                     /*  Not supported on this system     */
    #endif
    }
    
     

    file_is_program

    #include "sflfile.h"
    Bool
    file_is_program (
        const char *filename)
    

    Synopsis

    Returns TRUE if the specified filename is an executable program on the PATH. Under DOS, and Windows, appends ".com", ".exe", and ".bat" to the file, in that order, to build an executable filename, then searches the PATH definition for the executable filename. Under OS/2, appends ".exe", and ".cmd" to the file, in that order, to build an executable filename, then searches the PATH definition for the executable filename. If the filename already has a path specifier, will not use the PATH definition. Under VMS, appends "exe" and "com" to the file, in that order, to build an executable filename. Searches the PATH if necessary.

    Source Code - (sflfile.c)

    {
        Bool
            executable = FALSE;             /*  Return code                      */
    
    #if (defined (__UNIX__))
        char
            *found_file;
    
        ASSERT (filename);
    
        found_file = file where ('r', "PATH", filename, "");
        if (found_file && (file_mode (found_file) & S_IEXEC))
            executable = TRUE;              /*  Executable file found            */
    
    #elif (defined (__VMS__))
        char
            *found_file;
    
        ASSERT (filename);
    
        found_file = file where ('r', "PATH", filename, "");
        if (!found_file)
            found_file = file where ('r', "PATH", filename, ".exe");
        if (!found_file)
            found_file = file where ('r', "PATH", filename, ".com");
    
        if (found_file && (file_mode (found_file) & S_IEXEC))
            executable = TRUE;              /*  Executable file found            */
    
    #elif (defined (MSDOS_FILESYSTEM))
        char
            *path;                          /*  What path do we search?          */
    
        ASSERT (filename);
        /*  If the filename already contains a path, don't look at PATH          */
        if (strchr (filename, '/') || strchr (filename, '\\'))
            path = NULL;
        else
            path = "PATH";
    
    #   if (defined (__WINDOWS__))
        if (file where ('r', path, filename, ".com")
        ||  file where ('r', path, filename, ".exe")
        ||  file where ('r', path, filename, ".bat"))
            executable = TRUE;              /*  Executable file found            */
    #   else /* OS/2 */
        if (file where ('r', path, filename, ".exe")
        ||  file where ('r', path, filename, ".cmd"))
            executable = TRUE;
    #   endif
    #endif
    
        return (executable);
    }
    
     

    file_is_directory

    #include "sflfile.h"
    Bool
    file_is_directory (
        const char *filename)
    

    Synopsis

    Returns TRUE if the specified file is a directory. The filename may end in a slash (/ or \). Under MS-DOS/OS2/Windows, a directory name may consist solely of a disk-drive specifier. Under VMS the directory may optionally take the extension '.dir'.

    Source Code - (sflfile.c)

    {
        static char
            dir_name [LINE_MAX + 1];
    
        ASSERT (filename);
    
        strcpy (dir_name, clean path (filename));
    #if (defined (__VMS__))
        if (!file exists (dir_name))
            default extension (dir_name, dir_name, "dir");
    #endif
        return ((file_mode (dir_name) & S_IFDIR) != 0);
    }
    
     

    file_is_legal

    #include "sflfile.h"
    Bool
    file_is_legal (
        const char *arg_filename)
    

    Synopsis

    Checks whether the specified file is 'legal', which is a system-dependent definition. Under 32-bit Windows, a legal file is one who's name is not a shortened 8.3 version of a long name. This can be used to bypass filename-based security schemes. On other systems, the notion of 'illegal' is not defined. Returns TRUE if the file exists and is legal. Returns FALSE otherwise.

    Source Code - (sflfile.c)

    {
    #if (defined (WIN32))
        static WIN32_FIND_DATA
            found;
        HANDLE
            handle;
        char
            *filename,                      /*  Our copy of arg_filename         */
            *slash,                         /*  Position of '\' in filename      */
            *component;                     /*  Component to compare             */
        Bool
            feedback;                       /*  Function feedback                */
    
        /*  For each path component of the filename, check that the long form
         *  of the name is the same as the short form.  We scan backwards
         *  from the end of the filename, get the full pathname, and compare
         *  the last component each time:
         *
         *      aaa\bbb\ccc\name.ext    name.ext
         *      aaa\bbb\ccc             ccc
         *      aaa\bbb                 bbb
         *      aaa                     aaa
         */
    
        filename = mem_strdup (arg_filename);
        feedback = TRUE;                    /*  Assume we match everything       */
        strconvch (filename, '/', '\\');
        if (strlast (filename) == '\\')
            strlast (filename) = '\0';      /*  Drop any trailing slash          */
    
        do
          {
            slash     = strrchr (filename, '\\');
            component = slash? slash + 1: filename;
            handle    = FindFirstFile (filename, &found);
    
            if (handle == INVALID_HANDLE_VALUE
            ||  lexcmp (component, found.cFileName))
              {
                feedback = FALSE;
                break;
              }
            FindClose (handle);
            if (slash)
              {
                *slash = '\0';              /*  Cut filename at slash            */
                if (filename [1] == ':'
                &&  filename [2] == '\0')   /*  We're at a disk specifier        */
                    break;                  /*    which is okay by now           */
              }
          }
        while (slash && *filename);
        mem_free (filename);
        return (feedback);
    
    #else
        return (file exists (arg_filename));
    #endif
    }
    
     

    file_exec_name

    #include "sflfile.h"
    char *
    file_exec_name (
        const char *filename)
    

    Synopsis

    If the specified filename is an executable program, formats a filename including any required extension and returns a static string with that value. If the specified filename is not an executable program, returns NULL. Under DOS, and Windows, appends ".com", ".exe", and ".bat" to the filename, in that order, to build a possible executable filename. Under OS/2, appends ".exe", and ".cmd" to the filename, in that order, to build a possible executable filename. If this fails, returns NULL. Does not search the PATH symbol; the filename must be specified with a path if necessary. The returned filename (if not NULL) points to a static string.

    Source Code - (sflfile.c)

    {
    #if (defined (__UNIX__) || defined (__VMS__))
        ASSERT (filename);
    
        strcpy (exec_name, filename);
    
        if (file_mode (exec_name) & S_IEXEC)
            return (exec_name);
        else
            return (NULL);
    
    #elif (defined (MSDOS_FILESYSTEM))
        char
            *extension;                     /*  File extension, if any           */
    
        ASSERT (filename);
    
        /*  Find file extension; if not found, set extension to empty string     */
        extension = strrchr (filename, '.');
        if (extension == NULL
        ||  strchr (extension, '/')         /*  If last '.' is part of the path  */
        ||  strchr (extension, '\\'))       /*  then the filename has no ext.    */
            extension = "";
    
        /*  Windows: If extension is .exe/.com/.bat, the file is an executable   */
        /*  OS/2:    If extension is .exe/.cmd, the file is executable           */
    #   if (defined (__OS2__))
        if (lexcmp (extension, ".exe") == 0
        ||  lexcmp (extension, ".cmd") == 0
    #   else /* DOS, WINDOWS */
        if (lexcmp (extension, ".com") == 0
        ||  lexcmp (extension, ".exe") == 0
        ||  lexcmp (extension, ".bat") == 0
    #     if (defined (__WINDOWS__))
        ||  is_exe_file (filename)
    #     endif
    #   endif
        )
          {
            strcpy (exec_name, filename);
            return (exec_name);
          }
        else
        /*  Windows: If the extension is empty, try .com, .exe, .bat             */
        /*  OS/2:    If the extension is empty, try .exe, .cmd                   */
        if (strnull (extension)
    #   if (defined (__OS2__))
        && (file exists (default extension (exec_name, filename, "exe"))
        ||  file exists (default extension (exec_name, filename, "cmd"))))
    #   else /* DOS, WINDOWS */
        && (file exists (default extension (exec_name, filename, "com"))
        ||  file exists (default extension (exec_name, filename, "exe"))
        ||  file exists (default extension (exec_name, filename, "bat"))))
    #   endif
            return (exec_name);             /*  Executable file found            */
        else
            return (NULL);
    #else
        return (NULL);                      /*  Not supported on this system     */
    #endif
    }
    
     

    get_file_size

    #include "sflfile.h"
    long
    get_file_size (
        const char *filename)
    

    Synopsis

    Returns the size, in bytes, of the specified file or directory. The size of a directory is not a portable concept. If there is an error, returns -1.

    Source Code - (sflfile.c)

    {
    #if (defined (WIN32_NOT_IMPLEMENTED))
        /*  This code has been disactivated because it cannot work with a file
            that is opened by another function.
         */
        HANDLE
            handle;                         /*  We open file to get its size     */
        long
            size;
    
        ASSERT (filename);
        handle = CreateFile (filename, 0, FILE_SHARE_WRITE,
                             NULL, OPEN_EXISTING, 0, 0);
        if (handle == INVALID_HANDLE_VALUE)
            return (-1);
    
        size = GetFileSize (handle, NULL);
        CloseHandle (handle);
        return (size);
    
    #else
        struct stat
            stat_buf;
    
        ASSERT (filename);
    
        if (stat ((char *) filename, &stat_buf) == 0)
            return ((long) stat_buf.st_size);
        else
            return (-1);
    #endif
    }
    
     

    get_file_time

    #include "sflfile.h"
    time_t
    get_file_time (
        const char *filename)
    

    Synopsis

    Returns the modification time of the specified file or directory. The returned time is suitable for feeding to localtime().

    Source Code - (sflfile.c)

    {
    #if (defined (WIN32_NOT_IMPLEMENTED))
        /*  This code has been disactivated because it returns incorrect
            values depending on the seasonal clock change.
         */
        unsigned long thi,tlo;
        double  dthi,dtlo;
        double  secs_since_1601, secs_time_t;
        double  delta = 11644473600.;
        double  two_to_32 = 4294967296.;
        HANDLE  handle;
        FILETIME creation, last_access, last_write;
    
        handle = CreateFile (filename, GENERIC_READ, FILE_SHARE_WRITE,
                             NULL, OPEN_EXISTING, 0, 0);
        if (handle == INVALID_HANDLE_VALUE)
            return (0);
        GetFileTime (handle, &creation, &last_access, &last_write);
        CloseHandle (handle);
        thi = last_write.dwHighDateTime;
        tlo = last_write.dwLowDateTime;
        dthi = (double) thi;
        dtlo = (double) tlo;
        secs_since_1601 = (dthi * two_to_32 + dtlo) / 1.0e7;
        secs_time_t     = secs_since_1601 - delta;
        return ((time_t) secs_time_t);
    
    #else
        struct stat
            stat_buf;
    
        ASSERT (filename);
    
        if (stat ((char *) filename, &stat_buf) == 0)
            return (stat_buf.st_mtime > 0? stat_buf.st_mtime: 0);
        else
            return (0);
    #endif
    }
    
     

    get_file_lines

    #include "sflfile.h"
    long
    get_file_lines (
        const char *filename)
    

    Synopsis

    Reads an entire file, and returns the number of lines in the file. The file should be normal text. Returns 0 if the file cannot be opened for reading. May be a bit slow on large files.

    Source Code - (sflfile.c)

    {
        long
            file_size;
        FILE
            *file_stream;
        int
            ch;
    
        ASSERT (filename);
    
        file_stream = file open (filename, 'r');
        if (file_stream == NULL)
            return (0);
    
        file_size = 0;
        while ((ch = fgetc (file_stream)) != EOF)
            if (ch == '\n')
                file_size++;
    
        fclose (file_stream);
        return (file_size);
    }
    
     

    file_slurp

    #include "sflfile.h"
    DESCR *
    file_slurp (
        const char *filename)
    

    Synopsis

    Reads an entire file, and returns a DESCR containing the file data. The file is read as binary data. The returned DESCR should be freed using the mem_free() call. if the file is > 64K long, only the first 64K bytes are read into memory. This is to stop really silly things from happening. Returns NULL if the file cannot be found. Appends a null byte to the data in any case.

    Source Code - (sflfile.c)

    {
        return (file_load_data (filename, 65535UL));
    }
    
     

    file_slurpl

    #include "sflfile.h"
    DESCR *
    file_slurpl (
        const char *filename)
    

    Synopsis

    Reads an entire file, and returns a DESCR containing the file data. The file is read as binary data. The returned DESCR should be freed using the mem_free() call. Does not impose any limit on the size of the file (unlike file slurp() which stops at 64K bytes). Returns NULL if the file cannot be found. Appends a null byte to the data in any case.

    Source Code - (sflfile.c)

    {
        return (file_load_data (filename, 0));
    }
    
     

    file_set_eoln

    #include "sflfile.h"
    dbyte
    file_set_eoln (char *dst, const char *src, dbyte src_size, Bool add_cr)
    

    Synopsis

    Formats any end-of-line sequences in the buffer according to the value of the add_cr argument. If this is TRUE, all end-of- lines (LF or CRLF or LFCR) are represented by a CRLF sequence. If FALSE, all end-of-lines are represented by LF by itself. The target buffer must be large enough to accomodate the resulting line (twice the size of the source data). Returns the size of the resulting data in the target buffer not counting the final trailing null byte. The input data does not need to be null- terminated, but the output data is terminated with an extra null in any case.

    Source Code - (sflfile.c)

    {
        char
            *srcptr,                        /*  Current character in src         */
            *dstptr,                        /*  Current character in dst         */
            *last;                          /*  Last character in src            */
    
        ASSERT (src);
        ASSERT (dst);
    
        srcptr = (char *) src;
        dstptr = dst;
        last   = (char *) src + src_size;
    
        while (*srcptr && srcptr < last)
          {
            if (*srcptr == '\n'
            ||  *srcptr == EOF)
              {
                if (add_cr)
                    *dstptr++ = '\r';
                *dstptr++ = '\n';
              }
            else
            if (*srcptr != '\r' && *srcptr != 26)
                *dstptr++ = *srcptr;
            srcptr++;
          }
        *dstptr = '\0';
        return ((dbyte) (dstptr - dst));
    }
    
     

    get_tmp_file_name

    #include "sflfile.h"
    char *
    get_tmp_file_name (const char *path, qbyte *index, const char *ext)
    

    Synopsis

    Get a temporary file name.

    Source Code - (sflfile.c)

    {
        static char
            file_name [LINE_MAX + 1];
    
        do
          {
            if (path)
                sprintf (file_name, "%s%c%08lX.%s", path, PATHEND, *index, ext);
            else
                sprintf (file_name, "%08lX.%s", *index++, ext);
            (*index)++;
          } while (file exists (file_name));
        return (file_name);
    }
    

    Initialisation file access functions

    Filename: sflini.h
    Package: Standard Function Library (SFL)
    Written: 94/01/08 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to read an initialisation file that follows the MS-Windows style, i.e. consists of [Sections] followed by keyword = value lines.

    List of Functions

    List of Symbol Definitions

    sflini.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SLFINI_INCLUDED TRUE
     

    ini_find_section

    #include "sflini.h"
    Bool
    ini_find_section (
        FILE *inifile,
        char *section,
        Bool top)
    

    Synopsis

    Finds a specific section in the ini file. An ini file contains lines as shown below. The section name can be any mix of upper or lowercase. You should open the ini file using file_open before you call this function. If the 'top' argument is TRUE, repositions to the start of the file before reading, else reads from the current file offset. Returns TRUE if the section was found, and positions on the line that follows the section. Returns FALSE if the section was not found, and positions at the end of the file.

    Examples

        ;   comments like this, or
        #   comments like this if you prefer
        !   Text is echoed to console using trace()
        [Section]
            keyword = key_value; comments
            keyword = "key_value"; comments
            keyword = 'key_value'; comments
            ...
        [Section]
            keyword = key_value; comments
            ...
    

    Source Code - (sflini.c)

    {
        char
            *first;
    
        ASSERT (inifile != NULL);
        ASSERT (section != NULL);
    
        if (top)                            /*  Reposition at top if wanted      */
            fseek (inifile, 0, SEEK_SET);
    
        /*  Read through file until we find what we are looking for              */
        while (file read (inifile, iniline))
          {
            first = strskp (iniline);       /*  Skip leading spaces              */
            if (*first == ';' || *first == '#' || *first == 0)
                continue;                   /*  Comment line                     */
            else
            if (*first == '!')
              {
                first = strskp (first + 1);
                trace (first);
              }
            else
            if (sscanf (first, "[%[^]]", ini_section) == 1
            &&  lexcmp (ini_section, section) == 0)
                return (TRUE);
          }
        return (FALSE);
    }
    
     

    ini_scan_section

    #include "sflini.h"
    Bool
    ini_scan_section (
        FILE *inifile,
        char **keyword,
        char **value)
    

    Synopsis

    Scans the current section of the ini file, and returns a keyword and value if such was found. Returns the address of these values in the supplied arguments. The addresses point to static values that are overwritten with each call. Returns TRUE when a keyword/value pair is found. Returns FALSE if a new section name or end of file is found. In the first case, sets the keyword to the section name; in the second case sets the keyword to NULL. Ignores blank and comment lines, and lines that look like junk. Keyword and section names are returned as lower-case; values are returned exactly as specified in the ini file.

    Source Code - (sflini.c)

    {
        char
            *first;
    
        /*  Read through file until we find what we are looking for              */
        while (file read (inifile, iniline))
          {
            first = strskp (iniline);       /*  Skip leading spaces              */
            if (*first == ';' || *first == '#' || *first == 0)
                continue;                   /*  Comment line                     */
            else
            if (*first == '!')
              {
                first = strskp (first + 1);
                trace (first);
              }
            else
            if (sscanf (first, "[%[^]]", ini_section) == 1)
              {
                *keyword = strlwc (ini_section);
                *value   = NULL;
                return (FALSE);             /*  New section name                 */
              }
            else
            if (streq (first, "[]"))        /*  Allow empty section names        */
              {
                strcpy (ini_section, "");
                *keyword = ini_section;
                *value   = NULL;
                return (FALSE);             /*  New section name                 */
              }
            else
            if (sscanf (first, "%[^=] = \"%[^\"]\"", ini_keyword, ini_value) == 2
            ||  sscanf (first, "%[^=] = '%[^\']'",   ini_keyword, ini_value) == 2
            ||  sscanf (first, "%[^=] = %[^;#]",     ini_keyword, ini_value) == 2)
              {
                strcrop (strlwc (ini_keyword));
                strcrop (ini_value);
                /*  sscanf can't handle "" or '' as an empty value, so we do this
                 *  ourselves.  Note that this breaks '""' and "''".  :-(
                 */
                if (streq (ini_value, "\"\"")
                ||  streq (ini_value, "''"))
                    strclr (ini_value);
    
                *keyword = ini_keyword;
                *value   = ini_value;
                return (TRUE);              /*  Found keyword = value            */
              }
          }
        *keyword = NULL;
        return (FALSE);                     /*  End of file                      */
    }
    
     

    ini_dyn_load

    #include "sflini.h"
    SYMTAB *
    ini_dyn_load (
        SYMTAB *load_symtab,
        const char *filename)
    

    Synopsis

    Loads the contents of an .ini file into a symbol table. If no symbol table is specified, creates a new symbol table. The ini file data is loaded as a set of symbols and values, where the symbol name is built from the section name and keyword like this: "section:keyword". The symbol name is always stored in lowercase, with no trailing spaces. If the same keyword occurs several times in a section, earlier symbols are overwritten. Ignores all comments and blank lines. Returns NULL if there is not enough memory. Stores these control variables in the symbol table if the table was freshly created or the file was loaded:
    filename Name of input file
    filetime Time of input file, as 8-digit string "HHMMSSCC"
    filedate Date of input file, as 8-digit string "YYYYMMDD"
    Also creates a symbol for each section, with name equal to the section name, and value equal to a null string. Looks for the .ini file on the current PATH. The table is sorted after loading.

    Source Code - (sflini.c)

    {
        FILE
            *inifile;
        SYMTAB
            *symtab,                        /*  Symbol table to populate         */
            *envtab;                        /*  Environment, as symbol table     */
        char
            *section = NULL,                /*  Filled as we scan through        */
            *keyword = NULL,                /*    the ini file                   */
            *value   = NULL,
            *section_end;                   /*  Null byte at end of section      */
    
        ASSERT (filename);
        inifile = file locate ("PATH", filename, NULL);
    
        if (load_symtab)                    /*  Use specified symbol table       */
            symtab = load_symtab;           /*    or create a new one            */
        else
          {
            symtab = sym create table ();
            if (symtab == NULL)
                return (NULL);              /*  Quit if insufficient memory      */
          }
        /*  Store control variables in symbol table                              */
        if (inifile || load_symtab == NULL)
          {
            sym assume symbol (symtab, "filename", filename);
            sprintf (iniline, "%ld", timer to date (get file time (filename)));
            sym assume symbol (symtab, "filedate", iniline);
            sprintf (iniline, "%ld", timer to time (get file time (filename)));
            sym assume symbol (symtab, "filetime", iniline);
          }
        if (!inifile)
            return (symtab);                /*  File not found; empty table      */
    
        /*  Now load the ini file, starting from the beginning                   */
        envtab = env2symb ();
        fseek (inifile, 0, SEEK_SET);
        FOREVER
          {
            if (ini scan section (inifile, &keyword, &value))
              {
                if (section)
                  {
                    section_end = strchr (section, '\0');
                    ASSERT (section_end);
                    xstrcat (section, ":", keyword, NULL);
                    value = tok subst (value, envtab);
                    sym assume symbol (symtab, section, value);
                    mem_strfree (&value);
                    *section_end = '\0';
                  }
              }
            else
            if (keyword)                    /*  Found new section                */
              {
                section = keyword;
                sym assume symbol (symtab, section, "");
              }
            else
                break;
          }
        file close (inifile);
        sym delete table (envtab);
        sym sort table (symtab, NULL);      /*  Sort table by symbol name        */
        return (symtab);
    }
    
     

    ini_dyn_save

    #include "sflini.h"
    int
    ini_dyn_save (
        SYMTAB *symtab,
        const char *filename)
    

    Synopsis

    Saves a symbol table to the specified file. The symbol table entries must be formatted as "section:name=value" - see ini dyn load(). Scans the ini file for a line containing only "#*END", then writes the symbol data to the file from that point. Returns the number of symbols saved, or -1 if there was an error. As a side-effect, sorts the table on the symbol name.

    Source Code - (sflini.c)

    {
        FILE
            *inifile,
            *wrkfile;
        SYMBOL
            *symbol;                        /*  Next symbol in table             */
        Bool
            header_found;                   /*  Did we find a file header?       */
        int
            count;                          /*  How many symbols did we save?    */
        char
            *colon;                         /*  Points to ':' in symbol name     */
    
        ASSERT (filename);
        ASSERT (symtab);
    
        /*  Copy ini file header to temporary file                               */
        wrkfile = tmpfile ();
        header_found = FALSE;
        if ((inifile = file open (filename, 'r')) != NULL)
          {
            while (file read (inifile, iniline))
              {
                if (streq (iniline, "#*END"))
                  {
                    header_found = TRUE;
                    break;
                  }
                file write (wrkfile, iniline);
              }
            file close (inifile);
          }
        /*  Now rewrite ini file                                                 */
        if ((inifile = file open (filename, 'w')) == NULL)
          {
            fclose (wrkfile);
            return (-1);                    /*  No permission to write file      */
          }
        if (header_found)
          {
            fseek (wrkfile, 0, SEEK_SET);
            while (file read (wrkfile, iniline))
                file write (inifile, iniline);
          }
        file close (wrkfile);               /*  Finished with temporary file     */
    
        /*  Output ini file values                                               */
        file write (inifile, "#*END");
        strclr (ini_section);               /*  Current section                  */
        count = 0;
    
        sym sort table (symtab, NULL);      /*  Sort table by symbol name        */
        for (symbol = symtab-> symbols; symbol; symbol = symbol-> next)
          {
            /*  Output symbols formatted as key:name                             */
            colon = strrchr (symbol-> name, ':');
            if (colon)
              {
                memcpy (ini_value, symbol-> name, colon - symbol-> name);
                ini_value [colon - symbol-> name] = '\0';
                strcpy (ini_keyword, colon + 1);
    
                /*  If we start a new section, output the section header         */
                *ini_value   = toupper (*ini_value);
                *ini_keyword = toupper (*ini_keyword);
                if (strneq (ini_section, ini_value))
                  {
                    strcpy (ini_section, ini_value);
                    sprintf (iniline, "[%s]", ini_section);
                    file write (inifile, "");
                    file write (inifile, iniline);
                  }
                if (strnull (symbol-> value))
                    sprintf (iniline, "    %s=\"\"", ini_keyword);
                else
                if (strpbrk (symbol-> value, ";#="))
                    sprintf (iniline, "    %s=\"%s\"", ini_keyword, symbol-> value);
                else
                    sprintf (iniline, "    %s=%s", ini_keyword, symbol-> value);
                file write (inifile, iniline);
              }
          }
        file close (inifile);
        return (count);
    }
    
     

    ini_dyn_changed

    #include "sflini.h"
    Bool
    ini_dyn_changed (
        SYMTAB *symtab)
    

    Synopsis

    Returns TRUE if the ini file loaded into the specified table has in the meantime been changed. Returns FALSE if not.

    Source Code - (sflini.c)

    {
        char
            *filename;
    
        ASSERT (symtab);
    
        /*  Date, time, and name of original ini file are in the table           */
        filename = sym get value (symtab, "filename", NULL);
        if (filename
        &&  file has changed (filename,
                              sym get number (symtab, "filedate", 0),
                              sym get number (symtab, "filetime", 0)))
            return (TRUE);
        else
            return (FALSE);
    }
    
     

    ini_dyn_refresh

    #include "sflini.h"
    Bool
    ini_dyn_refresh (
        SYMTAB *symtab)
    

    Synopsis

    Refreshes a symbol table created by ini dyn load(). If the original file (as specified by the 'filename' symbol) has been modified, reloads the whole ini file. You would typically call this function at regular intervals to permit automatic reloading of an ini file in an application. Returns TRUE if the ini file was actually reloaded, or FALSE if the file had not changed or could not be accessed, or if the symbol table was incorrectly created. If the symbol table is reloaded from the ini file, all previous symbols are deleted.

    Source Code - (sflini.c)

    {
        char
            *filename;
    
        ASSERT (symtab);
        if (ini dyn changed (symtab))
          {
            filename = mem_strdup (sym get value (symtab, "filename", NULL));
            sym empty table (symtab);       /*  Delete previous table contents   */
            ini dyn load (symtab, filename);
            mem_free (filename);
            return (TRUE);
          }
        return (FALSE);
    }
    
     

    ini_dyn_value

    #include "sflini.h"
    char *
    ini_dyn_value (
        SYMTAB *symtab,
        const char *section,
        const char *keyword,
        const char *default_value)
    

    Synopsis

    Finds a section:keyword in the symbol table and returns a pointer to its value. Returns the default value if the symbol is not defined in the table. The default value may be NULL. The specified section and keyword can be in any case; they are converted internally to lowercase to match the symbol table. If the keyword is empty or NULL, no ':keyword' is appended to the section name.

    Source Code - (sflini.c)

    {
        ASSERT (section);
        if (keyword && *keyword)
            sprintf (ini_keyword, "%s:%s", section, keyword);
        else
            strcpy  (ini_keyword, section);
    
        strlwc (ini_keyword);
        return (sym get value (symtab, ini_keyword, default_value));
    }
    
     

    ini_dyn_values

    #include "sflini.h"
    char **
    ini_dyn_values (
        SYMTAB *symtab,
        const char *section,
        const char *keyword,
        const char *default_value)
    

    Synopsis

    Finds a section:keyword in the symbol table and returns a pointer to a string table containing the values, delimited by commas. When finished with the string table you should call tok free() to free the memory allocated for it. The default value may not be NULL. Returns a pointer to a table of string tokens (see tok split()), or NULL if there was insufficient memory. The specified section and keyword can be in any case; they are converted internally to lowercase to match the symbol table. If the keyword is empty or NULL, no ':keyword' is appended to the section name.

    Source Code - (sflini.c)

    {
        ASSERT (section);
        ASSERT (default_value);
    
        if (keyword && *keyword)
            sprintf (ini_keyword, "%s:%s", section, keyword);
        else
            strcpy  (ini_keyword, section);
    
        strlwc (ini_keyword);
        strcpy (iniline, sym get value (symtab, ini_keyword, default_value));
        strconvch (iniline, ',', ' ');
        return (tok split (iniline));
    }
    

    Multilanguage support

    Filename: sfllang.h
    Package: Standard Function Library (SFL)
    Written: 97/06/04 iMatix SFL project team sfl@imatix.com
    Revised: 98/05/31
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides hard-coded multilanguage dictionaries for dates and numbers, The hard-coded dictionaries work with most European languages.

    List of Functions

    List of Symbol Definitions

    sfllang.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    USERLANG_TOP USERLANG_SV + 1
    _SFLLANG_INCLUDED TRUE
     

    set_userlang

    #include "sfllang.h"
    int
    set_userlang (int language)
    

    Synopsis

    Sets language used for date and numeric translation. The valid user languages are:
    USERLANG DEFAULT Default language (use hard-coded values)
    USERLANG DA Danish
    USERLANG DE German
    USERLANG EN English
    USERLANG ES Castillian Spanish
    USERLANG FB Belgian or Swiss French
    USERLANG FR French
    USERLANG IS Icelandic
    USERLANG IT Italian
    USERLANG NL Dutch
    USERLANG NO Norwegian
    USERLANG PO Portuguese
    USERLANG SV Swedish
    Returns 0 if okay, -1 if an unsupported language was specified.

    Source Code - (sfllang.c)

    {
        /*  Order of this table is not critical                                  */
        static struct {
            int  language;
            char **units;
            char **tens;
            char **days;
            char **months;
        } languages [] =
        {
            { USERLANG_DEFAULT, EN_units, EN_tens, EN_days, EN_months },
            { USERLANG_DA,      DA_units, DA_tens, EN_days, DA_months },
            { USERLANG_DE,      DE_units, DE_tens, EN_days, DE_months },
            { USERLANG_EN,      EN_units, EN_tens, EN_days, EN_months },
            { USERLANG_ES,      ES_units, ES_tens, EN_days, ES_months },
            { USERLANG_FB,      FR_units, FB_tens, FR_days, FR_months },
            { USERLANG_FR,      FR_units, FR_tens, FR_days, FR_months },
            { USERLANG_IS,      IS_units, IS_tens, EN_days, IS_months },
            { USERLANG_IT,      IT_units, IT_tens, EN_days, IT_months },
            { USERLANG_NL,      NL_units, NL_tens, NL_days, NL_months },
            { USERLANG_NO,      NO_units, NO_tens, EN_days, NO_months },
            { USERLANG_PO,      PO_units, PO_tens, EN_days, PO_months },
            { USERLANG_SV,      SV_units, SV_tens, EN_days, SV_months },
            { -1,               NULL,     NULL,    NULL,    NULL      }
        };
    
        int
            index;
    
        for (index = 0; languages [index].language != -1; index++)
            if (languages [index].language == language)
              {
                user_language = language;
                units_table   = languages [index].units;
                tens_table    = languages [index].tens;
                day_table     = languages [index].days;
                month_table   = languages [index].months;
                return (0);
              }
        return (-1);
    }
    
     

    set_userlang_str

    #include "sfllang.h"
    int
    set_userlang_str (const char *language)
    

    Synopsis

    Sets language used for date and numeric translation, using a string representation of the language. The valid user languages are:
    "" Default language (use hard-coded values)
    "--" Alternative form for default language
    "DA" Danish
    "DE" German
    "EN" English
    "ES" Castillian Spanish
    "FB" Belgian or Swiss French
    "FR" French
    "IS" Icelandic
    "IT" Italian
    "NL" Dutch
    "NO" Norwegian
    "PO" Portuguese
    "SV" Swedish
    Returns 0 if okay, -1 if an unsupported language was specified.

    Source Code - (sfllang.c)

    {
        int
            index;
    
        if (strnull (language))
            return (set userlang (USERLANG_DEFAULT));
    
        for (index = 0; index < USERLANG_TOP; index++)
            if (streq (language, language_str [index]))
                return (set userlang (index));
    
        return (-1);
    }
    
     

    get_userlang

    #include "sfllang.h"
    int
    get_userlang (void)
    

    Synopsis

    Returns the current user language code.

    Source Code - (sfllang.c)

    {
        return (user_language);
    }
    
     

    get_userlang_str

    #include "sfllang.h"
    char *
    get_userlang_str (void)
    

    Synopsis

    Returns the current user language as a 2-character string.

    Source Code - (sfllang.c)

    {
        return (language_str [user_language]);
    }
    
     

    set_accents

    #include "sfllang.h"
    int
    set_accents (Bool accents)
    

    Synopsis

    Enables or disables native-language accents. If enabled, accented characters in translated words are produced in the current system character set, if possible. Otherwise, suitable translations are made into the 26-letter English alphabet. By default, accents are enabled.

    Source Code - (sfllang.c)

    {
        use_accents = accents;
        return (0);
    }
    
     

    get_accents

    #include "sfllang.h"
    Bool
    get_accents (void)
    

    Synopsis

    Returns TRUE if accents are enabled, FALSE if not.

    Source Code - (sfllang.c)

    {
        return (use_accents);
    }
    
     

    get_units_name

    #include "sfllang.h"
    char *
    get_units_name (int units)
    

    Synopsis

    Returns the name for the specified units, which is a value from zero to 19. Accented characters are formatted according to the current accents setting.

    Source Code - (sfllang.c)

    {
        ASSERT (units >= 0 && units <= 19);
        return (handle_accents (units_table [units]));
    }
    
     

    get_tens_name

    #include "sfllang.h"
    char *
    get_tens_name (int tens)
    

    Synopsis

    Returns the name for the specified tens, which is a value from 10 to 90; it is rounded as required. Accented characters are formatted according to the current accents setting.

    Source Code - (sfllang.c)

    {
        ASSERT (tens >= 10 && tens < 100);
        return (handle_accents (tens_table [tens / 10 - 1]));
    }
    
     

    get_day_name

    #include "sfllang.h"
    char *
    get_day_name (int day)
    

    Synopsis

    Returns the name for the specified day, which must be a value from 0 (Sunday) to 6 (Saturday). Accented characters are formatted according to the current accents setting.

    Source Code - (sfllang.c)

    {
        ASSERT (day >= 0 && day <= 6);
        return (handle_accents (day_table [day]));
    }
    
     

    get_day_abbrev

    #include "sfllang.h"
    char *
    get_day_abbrev (int day, Bool upper)
    

    Synopsis

    Returns the abbreviation for the specified day, which must be a value from 0 (Sunday) to 6 (Saturday). The abbreviation (3 letters) is converted into uppercase if the 'upper' argument is true. Accented characters are formatted according to the current accents setting.

    Source Code - (sfllang.c)

    {
        char
            abbrev [4];
    
        ASSERT (day >= 0 && day <= 6);
    
        strncpy (abbrev, day_table [day], 3);
        abbrev [3] = '\0';
        if (upper)
            strupc (abbrev);
        return (handle_accents (abbrev));
    }
    
     

    get_month_name

    #include "sfllang.h"
    char *
    get_month_name (int month)
    

    Synopsis

    Returns the name for the specified month, which must be a value from 1 to 12. Accented characters are handled as per the current accents setting.

    Source Code - (sfllang.c)

    {
        ASSERT (month >= 1 && month <= 12);
        return (handle_accents (month_table [month - 1]));
    }
    
     

    get_month_abbrev

    #include "sfllang.h"
    char *
    get_month_abbrev (int month, Bool upper)
    

    Synopsis

    Returns the abbreviation for the specified month, which must be a value from 1 to 12. The abbreviation (3 letters) is converted into uppercase if the 'upper' argument is true. Accented characters are formatted according to the current accents setting.

    Source Code - (sfllang.c)

    {
        char
            abbrev [4];
    
        ASSERT (month >= 1 && month <= 12);
    
        strncpy (abbrev, month_table [month - 1], 3);
        abbrev [3] = '\0';
        if (upper)
            strupc (abbrev);
    
        return (handle_accents (abbrev));
    }
    
     

    timestamp_string

    #include "sfllang.h"
    char *
    timestamp_string (char *buffer, const char *pattern)
    

    Synopsis

    Formats a timestamp according to a user-supplied pattern. The result is returned in a buffer supplied by the caller; if this argument is NULL, allocates a buffer and returns that (the caller must then free the buffer using mem_free()). The pattern consists of arbitrary text mixed with insertion symbols indicated by '%':
    %y day of year, 001-366
    %yy year 2 digits, 00-99
    %yyyy year 4 digits, 100-9999
    %mm month, 01-12
    %mmm month, Jan
    %mmmm month, January
    %MMM month, JAN
    %MMMM month, JANUARY
    %dd day, 01-31
    %ddd day of week, Sun
    %dddd day of week, Sunday
    %DDD day of week, SUN
    %DDDD day of week, SUNDAY
    %w day of week, 1-7 (1=Sunday)
    %ww week of year, 01-53
    %q year quarter, 1-4
    %h hour, 00- 23
    %m minute, 00- 59
    %s second, 00- 59
    %c centisecond, 00-99
    %% literal character %

    Source Code - (sfllang.c)

    {
        long
            date,                           /*  Current date                     */
            time;                           /*    and time                       */
        int
            century,                        /*  Century component of date        */
            year,                           /*  Year component of date           */
            month,                          /*  Month component of date          */
            day,                            /*  Day component of date            */
            hour,                           /*  Hour component of time           */
            minute,                         /*  Minute component of time         */
            second,                         /*  Second component of time         */
            centi,                          /*  1/100 sec component of time      */
            cursize;                        /*  Size of current component        */
        char
           *dest,                           /*  Store formatted data here        */
            ch;                             /*  Next character in picture        */
    
        date = date now ();
        time = time now ();
    
        century = GET_CENTURY (date);
        year    = GET_YEAR    (date);
        month   = GET_MONTH   (date);
        day     = GET_DAY     (date);
        hour    = GET_HOUR    (time);
        minute  = GET_MINUTE  (time);
        second  = GET_SECOND  (time);
        centi   = GET_CENTI   (time);
    
        if (buffer == NULL)
            buffer = mem_alloc (strlen (pattern) * 2);
    
        /*  Scan through picture, converting each component                      */
        dest = buffer;
        *dest = 0;                          /*  String is empty                  */
        while (*pattern)
          {
            ch = *pattern++;
            if (ch == '%' && *pattern)
              {
                ch = *pattern++;            /*  Count size of pattern after %    */
                for (cursize = 1; *pattern == ch; cursize++)
                    pattern++;
              }
            else
              {
                *dest++ = ch;               /*  Something else - store and next  */
                *dest = 0;                  /*  Terminate the string nicely      */
                continue;
              }
    
            /*  Now process pattern itself                                       */
            switch (ch)
              {
                case 'y':
                    if (cursize == 1)       /*  y     day of year, 001-366       */
                        sprintf (dest, "%03d", julian date (date));
                    else
                    if (cursize == 2)       /*  yy    year 2 digits, 00-99       */
                        sprintf (dest, "%02d", year);
                    else
                    if (cursize == 4)       /*  yyyy  year 4 digits, 0100-9999   */
                        sprintf (dest, "%02d%02d", century, year);
                    break;
    
                case 'm':
                    if (cursize == 1)       /*  m     minute, 00-59              */
                        sprintf (dest, "%02d", minute);
                    else
                    if (cursize == 2)       /*  mm    month, 01-12               */
                        sprintf (dest, "%02d", month);
                    else
                    if (cursize == 3)       /*  mmm   month, 3 letters           */
                        strcpy (dest, get month abbrev (month, FALSE));
                    else
                    if (cursize == 4)       /*  mmmm  month, full name           */
                        strcpy (dest, get month name (month));
                    break;
    
                case 'M':
                    if (cursize == 3)       /*  MMM   month, 3-letters, ucase    */
                        strcpy (dest, get month abbrev (month, TRUE));
                    else
                    if (cursize == 4)       /*  MMMM  month, full name, ucase    */
                      {
                        strcpy (dest, get month name (month));
                        strupc (dest);
                      }
                    break;
    
                case 'd':
                    if (cursize == 2)       /*  dd    day, 01-31                 */
                        sprintf (dest, "%02d", day);
                    else
                    if (cursize == 3)       /*  ddd   day of week, Sun           */
                        strcpy (dest, get day abbrev (day of week (date), FALSE));
                    else
                    if (cursize == 4)       /*  dddd  day of week, Sunday        */
                        strcpy (dest, get day name (day of week (date)));
                    break;
    
                case 'D':
                    if (cursize == 3)       /*  DDD   day of week, SUN           */
                        strcpy (dest, get day abbrev (day of week (date), TRUE));
                    else
                    if (cursize == 4)       /*  DDDD  day of week, SUNDAY        */
                      {
                        strcpy (dest, get day name (day of week (date)));
                        strupc (dest);
                      }
                    break;
    
                case 'w':
                    if (cursize == 1)       /*  w     day of week, 1-7 (1=Sun)   */
                        sprintf (dest, "%d", day of week (date) + 1);
                    else
                    if (cursize == 2)       /*  ww    week of year, 01-53        */
                        sprintf (dest, "%d", week of year (date));
                    break;
    
                case 'q':
                    if (cursize == 1)       /*  q     year quarter, 1-4          */
                        sprintf (dest, "%d", year quarter (date));
                    break;
    
                case 'h':
                    if (cursize == 1)       /*  h     hour, 00-23                */
                        sprintf (dest, "%02d", hour);
                    break;
    
                case 's':
                    if (cursize == 1)       /*  s     second, 00-59              */
                        sprintf (dest, "%02d", second);
                    break;
    
                case 'c':
                    if (cursize == 1)       /*  c     centisecond, 00-99         */
                        sprintf (dest, "%02d", centi);
                    break;
    
                case '%':
                    if (cursize == 1)       /*  %     literal '%'                */
                        strcpy (dest, "%");
                    break;
            }
            if (*dest)                      /*  If something was output,         */
                while (*dest)               /*    skip to end of string          */
                    dest++;
            else
              {
                while (cursize--)           /*  Else output ch once or more      */
                    *dest++ = ch;           /*    and bump dest pointer          */
                *dest = 0;                  /*  Terminate the string nicely      */
              }
        }
        return (buffer);
    }
    

    Line buffering functions

    Filename: sfllbuf.h
    Package: Standard Function Library (SFL)
    Written: 97/09/07 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides circular line buffering functions. A line buffer is a data structure that holds a fixed amount of data in a serial fashion; the oldest data gets discarded as new data is added.

    List of Functions

    List of Symbol Definitions

    sfllbuf.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLLBUF_INCLUDED TRUE
     

    linebuf_create

    #include "sfllbuf.h"
    LINEBUF *
    linebuf_create (size_t maxsize)
    

    Synopsis

    Creates a new line buffer with the specified size. The size must be at least LINE_MAX + 1 characters long. Returns the address of the newly-created buffer, or NULL if there was insufficient memory. The fresh line buffer is set to empty (tail == head).

    Source Code - (sfllbuf.c)

    {
        LINEBUF
            *buffer;
    
        ASSERT (maxsize > LINE_MAX);
    
        buffer = mem_alloc (sizeof (LINEBUF));
        if (!buffer)
            return (NULL);
    
        buffer-> data = mem_alloc (maxsize);
        if (!buffer-> data)
          {
            free (buffer);
            return (NULL);
          }
    
        buffer-> head = buffer-> data;
        buffer-> tail = buffer-> data;
        buffer-> top  = buffer-> data + maxsize;
        buffer-> size = maxsize;
        return (buffer);
    }
    
     

    linebuf_destroy

    #include "sfllbuf.h"
    void
    linebuf_destroy (LINEBUF *buffer)
    

    Synopsis

    Destroys a line buffer and frees its memory.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
    
        mem_free (buffer-> data);
        mem_free (buffer);
    }
    
     

    linebuf_reset

    #include "sfllbuf.h"
    void
    linebuf_reset (LINEBUF *buffer)
    

    Synopsis

    Resets a line buffer; i.e. empties it of all data. This is done simply by setting the tail and head to the start of the buffer.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
        buffer-> head = buffer-> data;
        buffer-> tail = buffer-> data;
    }
    
     

    linebuf_append

    #include "sfllbuf.h"
    void
    linebuf_append (LINEBUF *buffer, const char *line)
    

    Synopsis

    Appends a line to the line buffer. If the buffer was full, the oldest line is lost. Updates the buffer head and tail as needed.

    Source Code - (sfllbuf.c)

    {
        int
            length,                         /*  Size of line to insert           */
            room_left,                      /*  Space left between head and top  */
            tail_old,                       /*  Offset of tail into buffer       */
            head_old,                       /*  Offset of head before insert     */
            head_new;                       /*  Offset of head after insert      */
        char
            *linedata;                      /*  Address of data to store         */
    
        ASSERT (buffer);
        ASSERT (line);
    
        linedata  = (char *) line;
        length    = strlen (line) + 1;      /*  Include trailing null            */
        room_left = (int) (buffer-> top - buffer-> head);
    
        /*  We need to make space for the new line; we calculate the new head
         *  and if the tail falls between the old and new head, it must be moved
         *  up to the next line start.  We compare 'ints' not 'char *' because
         *  they can be negative.
         */
        tail_old = (int) (buffer-> tail - buffer-> data);
        head_old = (int) (buffer-> head - buffer-> data);
        if (head_old > tail_old)            /*  Shift head_old down to get it    */
            head_old -= buffer-> size;      /*    somewhere before tail_old      */
        head_new = head_old + length;       /*  And calculate head_new           */
    
        /*  If the line is too large for the remaining space, copy what we can   */
        if (length > room_left)
          {
            memcpy (buffer-> head, linedata, room_left);
            linedata += room_left;
            length   -= room_left;
            buffer-> head = buffer-> data;  /*  Bump head to start of buffer     */
          }
        /*  Copy rest of line to buffer                                          */
        memcpy (buffer-> head, linedata, length);
        buffer-> head += length;            /*  Bump head past string            */
        if (buffer-> head == buffer-> top)  /*    and maybe wrap-around          */
            buffer-> head = buffer-> data;
    
        ASSERT (buffer-> head <= buffer-> top);
    
        if (head_old <  tail_old            /*  If tail falls between head_old   */
        &&  tail_old <= head_new)           /*    and/on head_new, bump it up    */
            buffer-> tail = start_next_line (buffer, buffer-> head);
    }
    
     

    linebuf_first

    #include "sfllbuf.h"
    char *
    linebuf_first (LINEBUF *buffer, DESCR *descr)
    

    Synopsis

    Fetches the oldest line in the buffer. Returns a pointer that may be used in calls to linebuf next(). Returns NULL if the buffer is empty. The line is stored in the supplied descriptor, and is truncated if the descriptor is too small.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
        ASSERT (descr);
    
        return (linebuf next (buffer, descr, buffer-> tail));
    }
    
     

    linebuf_next

    #include "sfllbuf.h"
    char *
    linebuf_next (LINEBUF *buffer, DESCR *descr, const char *curline)
    

    Synopsis

    Fetches the next line in the buffer, using the pointer that was returned by linebuf first(). Returns NULL if there are no more lines in the buffer, or a pointer for further calls. The line is stored in the supplied descriptor, and is truncated if the descriptor is too small.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
        ASSERT (descr);
        ASSERT (curline);
    
        if (curline == buffer-> head)
            return (NULL);                  /*  We're at the end                 */
        else
            return (get_line (buffer, descr, curline));
    }
    
     

    linebuf_last

    #include "sfllbuf.h"
    char *
    linebuf_last (LINEBUF *buffer, DESCR *descr)
    

    Synopsis

    Fetches the newest line in the buffer. Returns a pointer that may be used in calls to linebuf next(). Returns NULL if the buffer is empty. The line is stored in the supplied descriptor, and is truncated if the descriptor is too small.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
        ASSERT (descr);
    
        return (linebuf prev (buffer, descr, buffer-> head));
    }
    
     

    linebuf_prev

    #include "sfllbuf.h"
    char *
    linebuf_prev (LINEBUF *buffer, DESCR *descr, const char *curline)
    

    Synopsis

    Fetches the previous line in the buffer, using the pointer that was returned by linebuf last(). Returns NULL if there are no more lines in the buffer, or a pointer for further calls. The line is stored in the supplied descriptor, and is truncated if the descriptor is too small.

    Source Code - (sfllbuf.c)

    {
        ASSERT (buffer);
        ASSERT (descr);
        ASSERT (curline);
    
        if (curline == buffer-> tail)
            return (NULL);                  /*  We're at the start               */
        else
          {
            /*  We're pointing to the byte after the line's null byte            */
            buffer_dec (curline);           /*  Bump down to null                */
            ASSERT (*curline == '\0');
    
            do
              {
                buffer_dec (curline);       /*  And now look for previous null   */
                if (*curline == '\0')
                  {
                    buffer_inc (curline);   /*  Bump up to start of string       */
                    break;
                  }
              }
            until (curline == buffer-> tail);
    
            get_line (buffer, descr, curline);
            return ((char *) curline);
          }
    }
    

    Linked-list functions

    Filename: sfllist.h
    Package: Standard Function Library (SFL)
    Written: 97/07/28 iMatix SFL project team sfl@imatix.com
    Revised: 98/07/26
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to maintain doubly-linked lists. You can use these functions to work with lists of any structure. To make this work, all structures must start with two pointers, "void *next, *prev;". When you want to attach a linked-list to another structure, declare the list head as a list. You can then refer to this variable when you attach items to the list head. The code sets the global list_unsafe to TRUE whenever it is changing a list.

    List of Functions

    List of Symbol Definitions

    sfllist.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    FORLIST(node,root) for ((node) = (root).next;
    _SFLLIST_INCLUDED TRUE
    list_create(node,size) if (((node) = mem_alloc (size)) != NULL)
    list_empty(list) ((list)-> prev == (list))
    list_pop(list,item) list_remove (list,
    list_push(list,item) list_add (list,
    list_queue(list,item) list_add (((LIST *)list)-> prev,
    list_relink_after(l,a) (list_relink (a, l, ((LIST *) a)-> next))
    list_relink_before(l,b) (list_relink (((LIST *) b)-> prev, l, b))
    list_reset(list) (list)-> prev = (list)-> next = (list)

    List of Type Definitions

    Type name: Defined as:
    NODE_COMPARE Bool (*) (LIST *t1, LIST *t2)
     

    list_unlink

    #include "sfllist.h"
    void *
    list_unlink (
        void *list)
    

    Synopsis

    Unlinks the list from any list it may be in. Returns list.

    Source Code - (sfllist.c)

    {
        list_unsafe = TRUE;
    
        /*  Join together next and previous nodes */
        ((LIST *) ((LIST *) list)-> prev)-> next = ((LIST *) list)-> next;
        ((LIST *) ((LIST *) list)-> next)-> prev = ((LIST *) list)-> prev;
    
        /*  The list is now empty */
        list_reset ((LIST *) list);
    
        list_unsafe = FALSE;
        return (list);
    }
    
     

    list_relink

    #include "sfllist.h"
    void *
    list_relink (
        void *left,
        void *list,
        void *right)
    

    Synopsis

    Links the list into a linked list. This is a general-purpose function that can be used to attach and remove lists anywhere in a list. Sets the global variable 'list_unsafe' while the list is being changed. Returns the address of list.

    Source Code - (sfllist.c)

    {
        LIST *swap;
    
        list_unsafe = TRUE;
        swap = ((LIST *) left)-> next;      /*  Exchange left pointers           */
               ((LIST *) left)-> next = list;
        ((LIST *) ((LIST *) list)-> prev)-> next = swap;
    
        swap = ((LIST *) right)-> prev;     /*  Exchange right pointers          */
               ((LIST *) right)-> prev = ((LIST *) list)-> prev;
                                         ((LIST *) list)-> prev = swap;
    
        list_unsafe = FALSE;
        return (list);
    }
    
     

    list_add

    #include "sfllist.h"
    void *
    list_add (LIST *list, void *data, size_t size)
    

    Synopsis

    Creates a node at the head of a list of a specified size and copies the specified data into it. Use with the stack & queue macros list_push and list_queue.

    Source Code - (sfllist.c)

    {
    LIST
        *node;
    
        node = mem_alloc (sizeof (LIST) + size);
        if (node)
          {
    	list_reset (node);
    	list_relink_after (node, list);
    	memcpy ((char *) node + sizeof (LIST), (char *) data, size);
          }
        return node;
    }
    
     

    list_remove

    #include "sfllist.h"
    void
    list_remove (LIST *list, void *data, size_t size)
    

    Synopsis

    Removes the node at the head of a list, copying the specified amount of data to the specified data block. This size must be exactly equal to the size of the data block. The list may not be empty. Use this with the macro list_pop.

    Source Code - (sfllist.c)

    {
    LIST
        *node;
    
        ASSERT (!list_empty (list));
    
        node = list-> next;
        ASSERT (mem_size (node) - sizeof (LIST) == size);
    
        memcpy ((char *) data, (char *) node + sizeof (LIST), size);
        list unlink (node);
        mem_free (node);
    }
    
     

    list_sort

    #include "sfllist.h"
    void list_sort (void *list, NODE_COMPARE comp)
    

    Synopsis

    Sorts a list using the "comb-sort" algorithm.

    Source Code - (sfllist.c)

    {
        int
            jump_size,
            i;
        LIST
            *base,
            *swap,
            *temp;
        Bool
            swapped;
    
        jump_size = 0;
        FORLIST (base, * (LIST *) list)
            jump_size++;
    
        swapped = TRUE;
        while ((jump_size > 1) || swapped)
          {
    	jump_size = (10 * jump_size + 3) / 13;
    	base = ((LIST *) list)-> next;
    	swap = base;
    	for (i = 0; i < jump_size; i++)
    	    swap = swap-> next;
    
    	swapped = FALSE;
    	while (swap != (LIST *) list)
    	  {
    	    if ((*comp) (base, swap))
    	      {
    		temp = base-> prev;
    		list unlink (base);
    		list_relink_after (base, swap);
    		list unlink (swap);
    		list_relink_after (swap, temp);
    		temp = base;
    		base = swap;
    		swap = temp;
    		swapped = TRUE;
    	      }
    	    base = base-> next;
    	    swap = swap-> next;
    	  }
          }
    }
    

    SMTP mailer function

    Filename: sflmail.h
    Package: standard function library (sfl)
    Written: 06/18/97 Scott Beasley (jscottb@infoave.com)
    Revised: 98/05/11
    Copyright: Copyright (C) 1991-97 Imatix

    Synopsis

    Functions to format and send SMTP messages. Messages can contain attachments, and be sent with "cc"'s "bcc"'s as well as the normal "to" receivers.

    List of Functions

    List of Symbol Definitions

    sflmail.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _sflmail_included TRUE
     

    smtp_send_mail_ex

    #include "sflmail.h"
    int smtp_send_mail_ex (
       SMTP *smtp)
    

    Synopsis

    Format and send a SMTP message. This function gives you the options of sneding to multi receivers, CC's, Bcc's and also send UUencoded attachments. Receivers and files are ";" or "," terminated. <PRE> NOTE: The sock_init function should be called before use of this function. </PRE>

    Source Code - (sflmail.c)

    {
       FILE *fpin;
       int iCnt;
       sock_t iSocket;
       char strOut[514], strFile[256], strRetBuff[513];
       char strUUEFile[256], *strRcptUserIds;
       int iOld_ip_nonblock = ip_nonblock;
    
       /* Make sure we do not block. */
       ip_nonblock = FALSE;
    
       /* Open up the SMTP port (25 most of the time). */
       iSocket = connect socket (smtp->strSmtpServer,
                                 "smtp", "tcp", NULL,
                                 smtp->connect_retry_cnt,
                                 smtp->retry_wait_time);
    
       if (getreply (iSocket, smtp) > 400 || iSocket < 1)
         {
           return -1;
         }
    
       /* Format a SMTP meassage header.  */
       /* Just say hello to the mail server. */
       xstrcpy (strOut, "HELO ", get hostname (), "\n", NULL);
       smtp_send_data (iSocket, strOut);
       if (getreply (iSocket, smtp) > 400)
           return -2;
    
       /* Tell the mail server who the message is from. */
       xstrcpy (strOut, "MAIL FROM:<", smtp->strSenderUserId, ">\n", NULL);
       smtp_send_data (iSocket, strOut);
       if (getreply (iSocket, smtp) > 400)
           return -3;
    
       strRcptUserIds = (char *) malloc (strlen (smtp->strDestUserIds) +
                                         strlen (smtp->strCcUserIds) +
                                         strlen (smtp->strBccUserIds) + 1);
       sprintf (strRcptUserIds, "%s;%s;%s", smtp->strDestUserIds,
                smtp->strCcUserIds, smtp->strBccUserIds);
       /* The following tells the mail server who to send it to. */
       iCnt = 0;
       while (1)
         {
           getstrfld (strRcptUserIds, iCnt++, 0, ",;", strRetBuff);
    
           if (*strRetBuff)
             {
               xstrcpy (strOut, "RCPT TO:<", strRetBuff, ">\r\n", NULL);
               smtp_send_data (iSocket, strOut);
               if (getreply (iSocket, smtp) > 400)
                   return -4;
             }
    
           else
               break;
         }
    
       free (strRcptUserIds);
    
       /* Now give it the Subject and the message to send. */
       smtp_send_data (iSocket, "DATA\r\n");
       if (getreply (iSocket, smtp) > 400)
           return -5;
    
       /* Set the date and time of the message. */
       xstrcpy ( strOut, "Date: ", encode mime time (date now (), time now ()),
                 " \r\n", NULL );
    
       /* The following shows all who it was sent to. */
       replacechrswith (smtp->strDestUserIds, ";", ',');
       xstrcpy (strOut, "To: ", smtp->strDestUserIds, "\r\n", NULL);
    
       /* Set up the Reply-To path. */
       if (!smtp->strRetPathUserId || !*smtp->strRetPathUserId)
           smtp->strRetPathUserId = smtp->strSenderUserId;
    
       xstrcat (strOut, "Reply-To:<", smtp->strRetPathUserId, ">\r\n", NULL);
       xstrcat (strOut, "Sender:", smtp->strSenderUserId, "\r\n", NULL);
       smtp_send_data (iSocket, strOut);
    
       *strOut = '\0';
    
       /* Post any CC's. */
       if (smtp->strCcUserIds && *smtp->strCcUserIds)
         {
           replacechrswith (smtp->strCcUserIds, ";", ',');
           xstrcat (strOut, "Cc:", smtp->strCcUserIds, "\r\n", NULL );
         }
    
       /* Post any BCC's. */
       if (smtp->strBccUserIds && *smtp->strBccUserIds)
         {
           replacechrswith (smtp->strBccUserIds, ";", ',');
           xstrcat (strOut, "Bcc:", smtp->strBccUserIds, "\r\n", NULL);
         }
       /* Post any Return-Receipt-To. */
       if (smtp->strRrcpUserId && *smtp->strRrcpUserId)
           xstrcat (strOut, "Return-Receipt-To:", smtp->strRrcpUserId, ">\r\n",
                    NULL);
    
       if (smtp->strMailerName && *smtp->strMailerName)
           xstrcat (strOut, "X-Mailer: ", smtp->strMailerName, "\r\n", NULL);
       else
           strcat (strOut, "X-Mailer: sflmail function\r\n");
    
       /* Set the mime version. */
       strcat (strOut, "MIME-Version: 1.0\r\n");
       strcat (strOut,
       "Content-Type: Multipart/Mixed; boundary=Message-Boundary-21132\r\n");
    
       smtp_send_data (iSocket, strOut);
    
       /* Write out any message comment included. */
       xstrcpy (strOut, "Comments: ", smtp->strMsgComment, "\r\n", NULL);
    
       /* Send the subject and message body. */
       xstrcat (strOut, "Subject:", smtp->strSubject, "\n\r\n", NULL);
       smtp_send_data (iSocket, strOut);
    
       /* Keep rfc822 in mind with all the sections. */
       if (smtp->strMessageBody && *smtp->strMessageBody)
         {
           strcat (strOut, "\r\n--Message-Boundary-21132\r\n");
           strcat (strOut, "Content-Type: text/plain; charset=US-ASCII\r\n");
           strcat (strOut, "Content-Transfer-Encoding: 7BIT\r\n");
           strcat (strOut, "Content-description: Body of message\r\n\r\n");
           smtp_send_data (iSocket, strOut);
           smtp_send_data (iSocket, smtp->strMessageBody);
           smtp_send_data (iSocket, "\r\n");
         }
    
       /* Include any Text type files and Attach them to the message. */
       if (smtp->strTxtFiles && *smtp->strTxtFiles)
         {
           iCnt = 0;
           while (1)
             {
               getstrfld (smtp->strTxtFiles, iCnt++, 0, ",;", strFile);
               trim (strFile);
               if (*strFile)
                 {
                   fpin = fopen (strFile, "rb");
                   if (!fpin)
                     {
                       strcpy (smtp->strlast_smtp_message, strFile);
                       return -6;
                     }
    
                   strcpy (strOut, "\r\n--Message-Boundary-21132\r\n");
                   strcat (strOut,
                           "Content-Type: text/plain; charset=US-ASCII\r\n");
                   strcat (strOut, "Content-Transfer-Encoding: 7BIT\r\n");
                   xstrcat (strOut, "Content-Disposition: attachment; filename=",
                            getfilename (strFile), "\r\n\n", NULL);
                   smtp_send_data (iSocket, strOut);
                   while (!feof (fpin))
                     {
                       memset (strRetBuff, 0, 513);
                       fread (strRetBuff, sizeof (char), 512, fpin);
                       smtp_send_data (iSocket, strRetBuff);
                     }
    
                   fclose (fpin);
                 }
               else
                   break;
             }
         }
    
       /* Attach any bin files to the message. */
       if (smtp->strBinFiles && *smtp->strBinFiles)
         {
           iCnt = 0;
           while (1)
             {
               getstrfld (smtp->strBinFiles, iCnt++, 0, ",;", strFile);
               trim (strFile);
               if (*strFile)
                 {
                   strcpy (strUUEFile, strFile);
                   if (strchr (strUUEFile, '.'))
                       *((strchr (strUUEFile, '.')))= (char)NULL;
                   strcat (strUUEFile, ".uue");
                   uuencode (strFile, strUUEFile, smtp->strlast_smtp_message);
                   fpin = fopen (strUUEFile, "rb");
                   if (!fpin)
                     {
                       return -6;
                     }
    
                   strcpy (strOut, "\r\n--Message-Boundary-21132\r\n");
                   xstrcat (strOut,
                            "Content-Type: application/octet-stream; name=",
                   getfilename (strFile), "\r\n", NULL);
                   strcat (strOut, "Content-Transfer-Encoding: x-uuencode\r\n");
                   xstrcat (strOut, "Content-Disposition: attachment; filename=",
                            getfilename (strFile), "\r\n\n", NULL);
                   smtp_send_data (iSocket, strOut);
                   while (!feof (fpin))
                     {
                       memset (strRetBuff, 0, 513);
                       fread (strRetBuff, sizeof (char), 512, fpin);
                       smtp_send_data (iSocket, strRetBuff);
                     }
    
                   fclose (fpin);
    
                   if ( !smtp->debug )
                      unlink (strUUEFile);
                 }
               else
                   break;
             }
         }
    
       /* This ends the message. */
       smtp_send_data (iSocket, ".\r\n");
       if (getreply (iSocket, smtp) > 400)
            return -7;
    
       /* Now log off the SMTP port. */
       smtp_send_data (iSocket, "QUIT\n");
       if (getreply (iSocket, smtp) > 400)
            return -8;
    
       /*
          Clean-up.
       */
       /* Close the port up. */
       close socket (iSocket);
    
       /* If a clean send, then reset and leave. */
       ip_nonblock = iOld_ip_nonblock;
    
       return 0;
    }
    
     

    smtp_send_mail

    #include "sflmail.h"
    int smtp_send_mail (
       char *strSmtpServer,
       char *strMessageBody,
       char *strSubject,
       char *strSenderUserId,
       char *strDestUserIds,
       char *strCcUserIds,
       char *strBccUserIds,
       char *strRetPathUserId,
       char *strRrcpUserId,
       char *strMsgComment,
       char *strMailerName,
       char *strBinFiles,
       char *strTxtFiles)
    

    Synopsis

    Format and send a SMTP message, by calling the smtp_send_mail_ex function. This function is kept to be compatable with previous versions of smtp_send_mail, smtp_send_mail_ex should now be used, this will be deleted soon.

    Source Code - (sflmail.c)

    {
       SMTP smtp;
    
       smtp.strSmtpServer = strSmtpServer;
       smtp.strMessageBody = strMessageBody;
       smtp.strSubject = strSubject;
       smtp.strSenderUserId = strSenderUserId;
       smtp.strDestUserIds = strDestUserIds;
       smtp.strCcUserIds = strCcUserIds;
       smtp.strBccUserIds = strBccUserIds;
       smtp.strRetPathUserId = strRetPathUserId;
       smtp.strRrcpUserId = strRrcpUserId;
       smtp.strMsgComment = strMsgComment;
       smtp.strMailerName = strMailerName;
       smtp.strBinFiles = strBinFiles;
       smtp.strTxtFiles = strTxtFiles;
       smtp.connect_retry_cnt = 3;
       smtp.retry_wait_time = 0;
       smtp.debug = 0;
    
       return smtp send mail ex (&smtp);
    }
    

    Mathematic functions

    Filename: sflmath.h
    Package: Standard Function Library (SFL)
    Written: 96/05/12 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides miscellaneous mathematical functions, including calculation of points within areas.

    List of Functions

    List of Symbol Definitions

    sflmath.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLMATH_INCLUDED TRUE
     

    point_in_rect

    #include "sflmath.h"
    int
    point_in_rect (const FPOINT *point, const FPOINT *coords)
    

    Synopsis

    Checks if the requested FPOINT is within the specified rectangle. Returns TRUE or FALSE appropriately.

    Source Code - (sflmath.c)

    {
        return ((point-> x >= coords [0].x && point-> x <= coords [1].x)
             && (point-> y >= coords [0].y && point-> y <= coords [1].y));
    }
    
     

    point_in_circle

    #include "sflmath.h"
    int
    point_in_circle (const FPOINT *point, const FPOINT *coords)
    

    Synopsis

    Checks if the requested FPOINT is within the specified circle. Returns TRUE or FALSE appropriately.

    Source Code - (sflmath.c)

    {
        double
            circle_radius,
            distance_from_centre;
    
        circle_radius        = ((coords [0].y - coords [1].y) *
                                (coords [0].y - coords [1].y)) +
                               ((coords [0].x - coords [1].x)  *
                                (coords [0].x - coords [1].x));
    
        distance_from_centre = ((coords [0].y - point-> y) *
                                (coords [0].y - point-> y)) +
                               ((coords [0].x - point-> x) *
                                (coords [0].x - point-> x));
        return (distance_from_centre <= circle_radius);
    }
    
     

    point_in_poly

    #include "sflmath.h"
    int
    point_in_poly (const FPOINT *point, const FPOINT *pgon, int nbpoints)
    

    Synopsis

    Checks if the requested FPOINT is within the specified polygon. Returns TRUE or FALSE.

    Source Code - (sflmath.c)

    {
        int
            inside_flag,
            xflag0,
            crossings;
        const double
            *stop;
        double
            *p,
            tx,
            ty,
            y;
    
        crossings = 0;
    
        tx = point-> x;
        ty = point-> y;
        y  = pgon [nbpoints - 1].y;
    
        p = (double *) pgon + 1;
        if ((y >= ty) != (*p >= ty))
          {
            if ((xflag0 = (pgon [nbpoints - 1].x >= tx)) ==
                    (*(double *) pgon >= tx))
              {
                if (xflag0)
                   crossings++;
              }
            else
                crossings += (pgon [nbpoints - 1].x - (y - ty) *
                             (*(double *) pgon - pgon [nbpoints - 1].x) /
                             (*p - y)) >= tx;
          }
         stop = &pgon [nbpoints].y;
         for (y = *p, p += 2; p <= stop; y = *p, p += 2)
           {
             if (y >= ty)
               {
                 while ((p < stop) && (*p >= ty))
                     p += 2;
                 if (p >= stop)
                    break;
                 if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx))
                   {
                     if (xflag0)
                        crossings++;
                   }
                 else
                     crossings += (*(p - 3) - (*(p - 2) - ty) *
                                  (*(p - 1) - *(p - 3)) /
                                  (*p - *(p - 2))) >= tx;
               }
             else
               {
                 while ((p < stop) && (*p < ty))
                    p += 2;
                 if (p >= stop)
                    break;
                 if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx))
                   {
                     if (xflag0)
                        crossings++;
                   }
                 else
                     crossings += (*(p - 3) - (*(p - 2) - ty) *
                                  (*(p - 1) -  *(p - 3)) /
                                  (*p - *(p - 2))) >= tx;
               }
           }
         inside_flag = crossings & 0x01;
         return (inside_flag);
    }
    

    Message-file access functions

    Filename: sflmesg.h
    Package: Standard Function Library (SFL)
    Written: 92/10/25 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to read and format messages from a message file. The intention of such a file is to provide a single location for all error messages: you can easier translate these into foreign languages, and you can control the consistency of an application's error messages.

    List of Functions

    List of Symbol Definitions

    sflmesg.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    ERROR_ANY 0000 /* Generic error message */
    _SFLMESG_INCLUDED TRUE
     

    open_message_file

    #include "sflmesg.h"
    int
    open_message_file (const char *filename)
    

    Synopsis

    Opens the specified error message file for reading. Returns 0 if the file exists and is readable, otherwise returns -1. Use this function before calling print message(). You can keep just one message file open at once; this function closes any previously-opened message file. This was done on purpose: it is common to open a message file for an entire application in the main function, then refer to it at other points in the code. It is a pain to pass file handles around the entire application, and global variables are generally a bad idea.

    Source Code - (sflmesg.c)

    {
        int feedback;
    
        close message file ();
        msgfile = file open (filename, 'r');
    
        if (msgfile)
            feedback = 0;
        else
            feedback = -1;
    
        return (feedback);
    }
    
     

    close_message_file

    #include "sflmesg.h"
    void
    close_message_file (void)
    

    Synopsis

    Closes the currently open message file, if any. Does not return anything.

    Source Code - (sflmesg.c)

    {
        if (msgfile)
          {
            file close (msgfile);
            msgfile = NULL;
          }
    }
    
     

    print_message

    #include "sflmesg.h"
    void
    print_message (int msgid, ...)
    

    Synopsis

    Scans the message file for a message with the specified id. Each line in the message file should start with a four-digit id, then a space, then the message to print. The message can include format specifiers using '%'. Values for each format are passed after the msgid. Returns nothing. The message file must be sorted by ascending message id's. Make sure you call open message file () before this function. Prints the message on stderr.

    Source Code - (sflmesg.c)

    {
        va_list argptr;                     /*  Argument list pointer            */
    
        read_msg (msgid);                   /*  Retrieve message into msgline    */
        va_start (argptr, msgid);           /*  Start variable arguments list    */
        vfprintf (stderr, msgline, argptr);
        va_end   (argptr);                  /*  End variable arguments list      */
        fprintf  (stderr, "\n");
        fflush   (stderr);
    }
    
     

    message_text

    #include "sflmesg.h"
    char *
    message_text (int msgid)
    

    Synopsis

    Works like print message(), but returns a pointer to the raw message rather than printing it. The message text is stored in a static area that is overwritten by each call. If msgid is -1, retrieves the next message sequentially, ignoring any numbering. This is only valid after previously reading a message. Places "." in the message if no more are found.

    Source Code - (sflmesg.c)

    {
        read_msg (msgid);                   /*  Retrieve message into msgline    */
        return (msgline);
    }
    

    Memory allocation functions

    Filename: sflmem.h
    Package: Standard Function Library (SFL)
    Written: 96/06/08 iMatix SFL project team sfl@imatix.com
    Revised: 98/08/31
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Encapsulated memory allocation functions. Based on an article by Jim Schimandle in DDJ August 1990. Provides 'safe' versions of malloc(), realloc(), free(), and strdup(). These functions protect the programmer from errors in calling memory allocation/free routines. When these calls are used, the allocation routines in this module add a data structure to the top of allocated memory blocks which tags them as legal memory blocks. When the free routine is called, the memory block to be freed is checked for legality. If the block is not legal, the memory list is dumped to stderr and the program is terminated. Some of these functions are called through macros that add the filename and line number of the call, for tracing. Do not call these functions directly.

    List of Functions

    List of Symbol Definitions

    sflmem.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLMEM_INCLUDED TRUE
    mem_alloc(n) (various)
    mem_assert() (various)
    mem_check(p) (various)
    mem_checkall() (various)
    mem_commit(t) (various)
    mem_descr(p,n) (various)
    mem_free(p) (various)
    mem_new_trans() (various)
    mem_realloc(p,n) (various)
    mem_rollback(t) (various)
    mem_size(p) (various)
    mem_strdup(s) (various)
    mem_strfree(ps) (various)
    memt_alloc(t,n) (various)
    memt_descr(t,p,n) (various)
    memt_strdup(t,s) (various)

    List of Type Definitions

    Type name: Defined as:
    MEMHDR struct _MEMHDR
    MEMTRN struct _MEMTRN
    scavenger Bool (*) (void *)
     

    mem_alloc_

    #include "sflmem.h"
    void *
    mem_alloc_ (
        MEMTRN     *trn,                    /*  Associated transaction           */
        size_t      size,                   /*  Desired size of memory block     */
        const char *filename,               /*  Name of source file making call  */
        word        lineno                  /*  Line number in calling source    */
    )
    

    Synopsis

    Allocates a memory block. Use the mem_alloc() macro to call this function! Use mem free () to free blocks allocated with this function. Returns a pointer to the allocated memory block, or NULL if there was not enough memory available. The supplied source file name is assumed to be in a static area. The requested block size must be greater than zero bytes.

    Source Code - (sflmem.c)

    {
        MEMHDR
           *ptr;                            /*  Allocated memory block           */
    
        /*  Allocate block with extra space for the header                       */
        ASSERT (size > 0);                  /*  Cannot allocate zero bytes!      */
    
        ptr = malloc (RESERVE_SIZE + size);
        if (ptr == NULL)                    /*  If nothing free, do a hunt       */
          {                                 /*    and try again...               */
            mem_scavenge ();
            ptr = malloc (RESERVE_SIZE + size);
            if (ptr == NULL)
                return (NULL);              /*  Really in trouble now!           */
          }
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): alloc %d bytes->%p", filename, lineno, size, ptr);
    #   endif
    
        ptr-> tag  = MEMTAG;                /*  Initialise block header          */
        ptr-> size = size;                  /*  Size of block                    */
        ptr-> file = filename;              /*  Who allocated it                 */
        ptr-> line = lineno;                /*    and where                      */
    
        if (!trn)                           /*  If no transaction then use the   */
            trn = &mem_list;                /*  main block list                  */
    
        list_reset (ptr);                   /*  Set up new block as list         */
        list_relink_before (ptr,            /*  Add to list of blocks            */
                            &trn-> memhdr);
    
        mem_total += size;                  /*  Keep count of space used         */
        mem_alloc_count += 1;               /*    and number of allocations      */
    
        return (HDR_2_CLIENT (ptr));        /*   and return client address       */
    }
    
     

    mem_realloc_

    #include "sflmem.h"
    void *
    mem_realloc_ (
        void       *client_ptr,             /*  Block of memory to reallocate    */
        size_t      size,                   /*  Desired size of memory block     */
        const char *filename,               /*  Name of source file making call  */
        word        lineno                  /*  Line number in calling source    */
    )
    

    Synopsis

    Reallocates a memory block, which remains part of the same transaction. Use the mem_realloc() macro to call this function! Accepts a pointer to a memory block and the desired size of the new memory block. Returns the address of the new memory block, or NULL if there was not enough memory available. If the specified block was not correctly allocated, dumps the memory allocation list and exits. The desired size must be greater than zero.

    Source Code - (sflmem.c)

    {
        MEMHDR
            *ptr,
            *next;
    
        ASSERT (client_ptr);
        ASSERT (size > 0);
    
        /*  Check that block is valid                                            */
        ptr = CLIENT_2_HDR (client_ptr);
        if (ptr-> tag != MEMTAG)
            mem_tag_err (ptr, filename, lineno);
    
        /*  Invalidate header                                                    */
        ptr-> tag = (word) ~MEMTAG;
    
        mem_total -= ptr-> size;
        mem_free_count += 1;
    
    
        next = ptr-> next;                  /*  Save where we were linked        */
        list unlink (ptr);                  /*     and unlink                    */
    
        /*  Reallocate memory block                                              */
        ptr = (MEMHDR *) realloc (ptr, RESERVE_SIZE + size);
        if (ptr == NULL)                    /*  If nothing free, do a hunt       */
          {                                 /*    and try again...               */
            mem_scavenge ();
            ptr = (MEMHDR *) realloc (ptr, RESERVE_SIZE + size);
            if (ptr == NULL)
                return (NULL);              /*  Really in trouble now!           */
          }
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): realloc %d bytes ->%p", filename, lineno, size, ptr);
    #   endif
    
        /*  Update header                                                        */
        ptr-> tag  = MEMTAG;
        ptr-> size = size;
        ptr-> file = filename;
        ptr-> line = lineno;
    
        list_reset (ptr);                   /*  Set up block as list             */
        list_relink_before (ptr, next);     /*  And link where old block was     */
    
        mem_total += size;                  /*  Keep count of space used         */
        mem_alloc_count += 1;               /*    and number of allocations      */
    
        return (HDR_2_CLIENT (ptr));
    }
    
     

    mem_strdup_

    #include "sflmem.h"
    char *
    mem_strdup_ (
        MEMTRN     *trn,                    /*  Associated transaction           */
        const char *string,                 /*  String to copy                   */
        const char *filename,               /*  Name of source file making call  */
        word        lineno                  /*  Line number in calling source    */
    )
    

    Synopsis

    Saves a string in dynamic memory. Use the mem_strdup() macro to call this function! The caller is responsible for freeing the space allocated when it is no longer needed. Returns a pointer to the allocated string, which holds a copy of the parameter string. Returns NULL if there was insufficient heap storage available to allocate the string, or if the original string was itself NULL.

    Source Code - (sflmem.c)

    {
        char *copy;
    
        if (string)                         /*  If string not null, copy it      */
          {
            copy = mem alloc  (trn, strlen (string) + 1, filename, lineno);
            if (copy)
                strcpy (copy, string);
          }
        else
            copy = NULL;                    /*  Just pass-through a NULL         */
    
        return (copy);
    }
    
     

    mem_strfree_

    #include "sflmem.h"
    void
    mem_strfree_ (
        char **string,                      /*  Address of string to free        */
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Releases memory occupied by a string. Use the mem_strfree() macro to call this function! Call this function to free strings allocated using mem strdup (). Accepts the address of a char pointer as argument: if the pointer is not null, the string is freed, and the pointer is set to null. Returns the address of the modified pointer.

    Examples

        char
            *string1 = NULL,
            *string2 = NULL;
        string1 = mem_strdup ("This is a string");
        mem_strfree (&string1);
        mem_strfree (&string2);
    

    Source Code - (sflmem.c)

    {
        ASSERT (string);
        if (*string)
          {
            mem free  (*string, filename, lineno);
            *string = NULL;
          }
    }
    
     

    mem_free_

    #include "sflmem.h"
    void
    mem_free_ (
        void *client_ptr,                   /*  Block of memory to free          */
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Releases memory previously allocated by mem alloc (), mem realloc (), or mem strdup (). Use the mem_free() macro to call this function! If the specified block was not correctly allocated, dumps the memory allocation list and exits. If you specify a null address, does nothing.

    Source Code - (sflmem.c)

    {
        MEMHDR
           *ptr;
    
        if (client_ptr == NULL)             /*  Do nothing if address is null    */
            return;
    
        /*  Check for valid block                                                */
        ptr = CLIENT_2_HDR (client_ptr);
        if (ptr-> tag != MEMTAG)
            mem_tag_err (ptr, filename, lineno);
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): free=%p", filename, lineno, ptr);
    #   endif
    
        /*  Invalidate header                                                    */
        ptr-> tag = (word) ~MEMTAG;
        mem_total -= ptr-> size;
        mem_free_count += 1;
        list unlink (ptr);                  /*  Remove block from list           */
    
        free (ptr);
    }
    
     

    mem_assert_

    #include "sflmem.h"
    void
    mem_assert_ (
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Checks that all allocated memory was freed. Use the mem_assert macro to call this function! If any memory is still left allocated, displays the memory list on stderr and aborts. Generally we use this function at the end of a program, after deallocating all memory. If any memory has not been allocated, we get a nice list and an abort. Our principle is that any memory allocation must be matched by a free somewhere in the code.

    Source Code - (sflmem.c)

    {
        FILE
            *trace_file;
    
        if (mem_total != 0
        ||  !list_empty (&tr_list))
          {
            fflush  (stdout);
            fprintf (stderr, "Clean-memory assertion failed - %s (%d)\n",
                              filename? filename: "<Unknown>", lineno);
            fprintf (stderr, "Details are in memtrace.lst\n");
            trace_file = fopen ("memtrace.lst", "w");
            mem display (trace_file);
            fclose (trace_file);
            abort ();
          }
    }
    
     

    mem_checkall_

    #include "sflmem.h"
    void
    mem_checkall_ (
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Checks all allocated memory blocks; if any block was corrupted, aborts with an error message, else does nothing.

    Source Code - (sflmem.c)

    {
        MEMTRN
            *trn;
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): check all memory", filename, lineno);
    #   endif
    
        mem_check_list ((MEMHDR *) &mem_list.memhdr, filename, lineno);
        trn = tr_list.next;
        while (trn != (MEMTRN *) &tr_list)
          {
            mem_check_list ((MEMHDR *) &trn-> memhdr, filename, lineno);
            trn = trn-> next;
          }
    }
    
     

    mem_check_

    #include "sflmem.h"
    void
    mem_check_ (
        const void *client_ptr,             /*  Block of memory to free          */
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Checks that a block of memory has not been corrupted. If the block is corrupted, aborts with an error message, else does nothing.

    Source Code - (sflmem.c)

    {
        MEMHDR
           *ptr;
    
        if (client_ptr == NULL)             /*  Do nothing if address is null    */
            return;
    
        /*  Check for valid block                                                */
        ptr = CLIENT_2_HDR (client_ptr);
        if (ptr-> tag != MEMTAG)
            mem_tag_err (ptr, filename, lineno);
    }
    
     

    mem_descr_

    #include "sflmem.h"
    DESCR *
    mem_descr_ (
        MEMTRN     *trn,                    /*  Associated transaction           */
        const void *data_block,             /*  Block of memory to copy          */
        size_t      data_size,              /*  Size of memory block             */
        const char *filename,               /*  Name of source file making call  */
        word        lineno                  /*  Line number in calling source    */
    )
    

    Synopsis

    Allocates a DESCR block for a specified block of data. Use the mem_descr macro to call this function! Returns a pointer to an allocated DESCR block, or NULL if there was not enough memory. The DESCR block is allocated as a single block, consisting of the DESCR block plus the data. To free the entire block you need one call to mem_free(). If the data_block argument is not null, its contents are copied into the newly allocated memory.

    Source Code - (sflmem.c)

    {
        DESCR
            *descr;
    
        descr = mem alloc  (trn, data_size + sizeof (DESCR), filename, lineno);
        if (descr == NULL)
            return (NULL);
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): allocate descr=%p", filename, lineno, descr);
    #   endif
    
        /*  Fill-in descriptor block unless it is NULL                           */
        descr-> size = data_size;
        descr-> data = (byte *) descr + sizeof (DESCR);
        if (data_block)
            memcpy (descr-> data, data_block, data_size);
    
        return (descr);
    }
    
     

    mem_new_trans_

    #include "sflmem.h"
    MEMTRN *
    mem_new_trans_(
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Allocates a transaction block. Use the mem new trans() macro to call this function. Use mem_commit or mem rollback() to delete the transaction. Returns a pointer to the allocated transaction block, or NULL if there was not enough memory available. The supplied source file name is assumed to be in a static area.

    Source Code - (sflmem.c)

    {
        MEMTRN
           *trn;                            /*  Allocated transaction block      */
    
        /*  Allocate block                                                       */
        trn = malloc (MEMTRN_SIZE);
        if (trn == NULL)
            return (NULL);
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): new transaction", filename, lineno);
    #   endif
    
        trn-> file = (char *) filename;     /*  Who allocated it                 */
        trn-> line = lineno;                /*    and where                      */
        list_reset (&trn-> memhdr);         /*  No memory blocks yet             */
    
        list_reset (trn);                   /*  Only 1 item in list              */
        list_relink_before (trn, &tr_list);  /*  Add to list of transactions      */
        return (trn);                       /*   and return address              */
    }
    
     

    mem_commit_

    #include "sflmem.h"
    void
    mem_commit_ (
        MEMTRN *trn,
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Commits all blocks allocated to a transaction.

    Source Code - (sflmem.c)

    {
        LIST
           *ptr;
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): commit transaction", filename, lineno);
    #   endif
    
        ptr = &trn-> memhdr;
        if (!list_empty (ptr))              /*  Are there any blocks to commit?  */
          {
            list_relink_before (ptr,        /*  Relink list into main list       */
                                &mem_list. memhdr);
            list unlink (ptr);
          }
    
        mem_del_trans (trn);
    }
    
     

    mem_rollback_

    #include "sflmem.h"
    void
    mem_rollback_ (
        MEMTRN *trn,
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Rolls back allocations for a particular transaction. This frees up all blocks allocated by calls to mem_alloc, mem_realloc and mem_strdup since the last call to mem_commit. Note that for blocks allocated with mem_realloc, this is not really a rollback but a free. The mem rollback() function must be used with some care... if you forget to do a mem commit(), a later mem rollback() will do damage to your memory space. The general rule is to start your processing with mem commit(), then do work, and call mem rollback() when there is an error. Finally, call mem commit() at the end, just to be sure.

    Source Code - (sflmem.c)

    {
        MEMHDR
           *ptr = NULL;
    
    #   if (defined (MEM_TRACE))
        if (filename)
            trace ("%s (%d): rollback transaction", filename, lineno);
    #   endif
    
        while (!list_empty (&trn-> memhdr))
          {
            ptr = trn-> memhdr. next;
    
            ptr-> tag = (word) ~MEMTAG;
            mem_total -= ptr-> size;
            mem_free_count += 1;
            list unlink (ptr);              /*  Remove block from list           */
    
            free (ptr);
          }
        mem_del_trans (trn);
    }
    
     

    mem_size_

    #include "sflmem.h"
    size_t
    mem_size_ (
        const void *client_ptr,             /*  Block of memory to free          */
        const char *filename,               /*  Name of source file making call  */
        word lineno                         /*  Line number in calling source    */
    )
    

    Synopsis

    Returns the size in bytes of a memory block.

    Source Code - (sflmem.c)

    {
        MEMHDR
           *ptr;
    
        if (client_ptr == NULL)             /*  Do nothing if address is null    */
            return 0;
    
        /*  Check for valid block                                                */
        ptr = CLIENT_2_HDR (client_ptr);
        if (ptr-> tag != MEMTAG)
            mem_tag_err (ptr, filename, lineno);
    
        return ptr-> size;
    }
    
     

    mem_used

    #include "sflmem.h"
    long
    mem_used (void)
    

    Synopsis

    Returns the number of bytes currently allocated using the memory management system. The value returned is simply the sum of the size requests to allocation routines. It does not reflect any overhead required by the memory management system.

    Source Code - (sflmem.c)

    {
        return (mem_total);
    }
    
     

    mem_allocs

    #include "sflmem.h"
    long
    mem_allocs (void)
    

    Synopsis

    Returns the number of blocks allocated in total. Use this to get an idea of the activity of the memory management system. When program ends cleanly, mem allocs () should be equal to mem frees().

    Source Code - (sflmem.c)

    {
        return (mem_alloc_count);
    }
    
     

    mem_frees

    #include "sflmem.h"
    long
    mem_frees (void)
    

    Synopsis

    Returns the number of blocks freed in total. Use this to get an idea of the activity of the memory management system. When program ends cleanly, mem allocs () should be equal to mem frees().

    Source Code - (sflmem.c)

    {
        return (mem_free_count);
    }
    
     

    mem_display

    #include "sflmem.h"
    void
    mem_display (
        FILE *fp                            /*  File to dump display to          */
    )
    

    Synopsis

    Displays the contents of the memory allocation list.

    Source Code - (sflmem.c)

    {
        MEMTRN
            *trn;
    
        fprintf (fp, "Index   Size  File(Line) - total size %lu\n", mem_total);
        mem_display_list ((MEMHDR *) &mem_list.memhdr, fp);
    
        trn = tr_list.next;
        while (trn != (MEMTRN *) &tr_list)
          {
            fprintf (fp, "* Transaction %s (%d)",
                     trn-> file? trn-> file: "<Unknown>", trn-> line);
            fprintf (fp, "\n");
            mem_display_list ((MEMHDR *) &trn-> memhdr, fp);
    
            trn = trn-> next;
          }
        fflush (fp);
    }
    
     

    mem_scavenger

    #include "sflmem.h"
    int
    mem_scavenger (
        scavenger scav_fct,                 /*  File to dump display to          */
        void    * scav_arg
    )
    

    Synopsis

    Registers a memory scavenger function. A memory scavenger function is an application function that is invoked by mem alloc () when memory is exhausted, so that unused application objects can be released. This allows you to allocate large amounts of memory -- for instance for caches -- and then release them when memory runs short. When you register a scavenger function you may provide a void * argument; this is passed back to the scavenger if it is ever invoked. The scavenger function returns TRUE if it could release some memory, otherwise it returns FALSE. Note that there is no way to unregister such a function. Furthermore, a scavenger function should not itself allocate any new memory, unless it can definitely free excess memory first. Scavenger functions are called in an unspecified order. Returns 0 if the scavenger function could be registered, -1 if not. There is no limit to the number of scavenger functions you can register, except available memory. The same scavenger function can be registered several times.

    Source Code - (sflmem.c)

    {
        SCAVFCT
            *scavfct;                       /*  Allocated registry function      */
    
        /*  Allocate an SCAVFCT block and attach it to the scavfcts list         */
        list_create (scavfct, sizeof (SCAVFCT));
        if (scavfct == NULL)
            return (-1);
        list_relink_before (scavfct, &scavfcts);
    
        scavfct-> scav_fct = scav_fct;
        scavfct-> scav_arg = scav_arg;
        return (0);
    }
    

    MIME support functions

    Filename: sflmime.h
    Package: Standard Function Library (SFL)
    Written: 96/03/28 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides various functions that support MIME encoding and decoding. See RFC 1521 for details.

    Notes

        Extract from RFC1521 for Base64 Content-Transfer-Encoding
        ---------------------------------------------------------
    
        The Base64 Content-Transfer-Encoding is designed to represent
        arbitrary sequences of octets in a form that need not be humanly
        readable.  The encoding and decoding algorithms are simple, but the
        encoded data are consistently only about 33 percent larger than the
        unencoded data.  This encoding is virtually identical to the one used
        in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421.
        The base64 encoding is adapted from RFC 1421, with one change: base64
        eliminates the "*" mechanism for embedded clear text.
    
        A 65-character subset of US-ASCII is used, enabling 6 bits to be
        represented per printable character. (The extra 65th character, "=",
        is used to signify a special processing function.)
    
        \NOTE: This subset has the important property that it is
        represented identically in all versions of ISO 646, including US
        ASCII, and all characters in the subset are also represented
        identically in all versions of EBCDIC.  Other popular encodings,
        such as the encoding used by the uuencode utility and the base85
        encoding specified as part of Level 2 PostScript, do not share
        these properties, and thus do not fulfill the portability
        requirements a binary transport encoding for mail must meet.
    
        The encoding process represents 24-bit groups of input bits as output
        strings of 4 encoded characters. Proceeding from left to right, a
        24-bit input group is formed by concatenating 3 8-bit input groups.
        These 24 bits are then treated as 4 concatenated 6-bit groups, each
        of which is translated into a single digit in the base64 alphabet.
        When encoding a bit stream via the base64 encoding, the bit stream
        must be presumed to be ordered with the most-significant-bit first.
    
        That is, the first bit in the stream will be the high-order bit in
        the first byte, and the eighth bit will be the low-order bit in the
        first byte, and so on.
    
        Each 6-bit group is used as an index into an array of 64 printable
        characters. The character referenced by the index is placed in the
        output string. These characters, identified in Table 1, below, are
        selected so as to be universally representable, and the set excludes
        characters with particular significance to SMTP (e.g., ".", CR, LF)
        and to the encapsulation boundaries defined in this document (e.g.,
        "-").
    
                          Table 1: The Base64 Alphabet
    
          Value Encoding  Value Encoding  Value Encoding  Value Encoding
               0 A            17 R            34 i            51 z
               1 B            18 S            35 j            52 0
               2 C            19 T            36 k            53 1
               3 D            20 U            37 l            54 2
               4 E            21 V            38 m            55 3
               5 F            22 W            39 n            56 4
               6 G            23 X            40 o            57 5
               7 H            24 Y            41 p            58 6
               8 I            25 Z            42 q            59 7
               9 J            26 a            43 r            60 8
              10 K            27 b            44 s            61 9
              11 L            28 c            45 t            62 +
              12 M            29 d            46 u            63 /
              13 N            30 e            47 v
              14 O            31 f            48 w         (pad) =
              15 P            32 g            49 x
              16 Q            33 h            50 y
    
        The output stream (encoded bytes) must be represented in lines of no
        more than 76 characters each.  All line breaks or other characters
        not found in Table 1 must be ignored by decoding software.  In base64
        data, characters other than those in Table 1, line breaks, and other
        white space probably indicate a transmission error, about which a
        warning message or even a message rejection might be appropriate
        under some circumstances.
    
        Special processing is performed if fewer than 24 bits are available
        at the end of the data being encoded.  A full encoding quantum is
        always completed at the end of a body.  When fewer than 24 input bits
        are available in an input group, zero bits are added (on the right)
        to form an integral number of 6-bit groups.  Padding at the end of
        the data is performed using the '=' character.  Since all base64
        input is an integral number of octets, only the following cases can
        \arise: (1) the final quantum of encoding input is an integral
        multiple of 24 bits; here, the final unit of encoded output will be
        an integral multiple of 4 characters with no "=" padding, (2) the
        final quantum of encoding input is exactly 8 bits; here, the final
        unit of encoded output will be two characters followed by two "="
        padding characters, or (3) the final quantum of encoding input is
        exactly 16 bits; here, the final unit of encoded output will be three
        characters followed by one "=" padding character.
    
        Because it is used only for padding at the end of the data, the
        occurrence of any '=' characters may be taken as evidence that the
        end of the data has been reached (without truncation in transit).  No
        such assurance is possible, however, when the number of octets
        transmitted was a multiple of three.
    
        Any characters outside of the base64 alphabet are to be ignored in
        base64-encoded data.  The same applies to any illegal sequence of
        characters in the base64 encoding, such as "====="
    
        Care must be taken to use the proper octets for line breaks if base64
        encoding is applied directly to text material that has not been
        converted to canonical form.  In particular, text line breaks must be
        converted into CRLF sequences prior to base64 encoding. The important
        thing to note is that this may be done directly by the encoder rather
        than in a prior canonicalization step in some implementations.
    
        \NOTE: There is no need to worry about quoting apparent
        encapsulation boundaries within base64-encoded parts of multipart
        entities because no hyphen characters are used in the base64
        encoding.
    

    List of Functions

    List of Symbol Definitions

    sflmime.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLMIME_INCLUDED TRUE
     

    encode_base64

    #include "sflmime.h"
    size_t
    encode_base64 (const char *source, byte *target, size_t source_size)
    

    Synopsis

    Encodes a source buffer in Base 64 and stores the result in the target buffer. The target buffer must be at least 1/3rd longer than the amount of data in the source buffer. The base64 data consists of portable printable characters as defined in RFC 1521. Returns the number of bytes output into the target buffer.

    Source Code - (sflmime.c)

    {
        size_t
            target_size = 0;                /*  Length of target buffer          */
        int
            nb_block;                       /*  Total number of blocks           */
        const char
            *p_source;                      /*  Pointer to source buffer         */
        byte
            *p_target,                      /*  Pointer to target buffer         */
            value;                          /*  Value of Base64 byte             */
    
        ASSERT (source);
        ASSERT (target);
    
        if (source_size == 0)
            return (0);
    
        if (!tables_initialised)
            init_conversion_tables ();
    
        /*    Bit positions
                      | byte 1 | byte 2 | byte 3 |
        source block   87654321 87654321 87654321         -> 3 bytes of 8 bits
    
                      | byte 1 | byte 2 | byte 3 | byte 4 |
        Encoded block  876543   218765   432187   654321  -> 4 bytes of 6 bits
        */
    
        nb_block = (int) (source_size / 3);
    
        /*  Check if we have a partially-filled block                            */
        if (nb_block * 3 != (int) source_size)
            nb_block++;
        target_size = (size_t) nb_block * 4;
        target [target_size] = '\0';
    
        p_source = source;                  /*  Point to start of buffers        */
        p_target = target;
    
        while (nb_block--)
          {
            /*  Byte 1                                                           */
            value       = *p_source >> 2;
            *p_target++ = base64_to_char [value];
    
            /*  Byte 2                                                           */
            value = (*p_source++ & 0x03) << 4;
            if ((size_t) (p_source - source) < source_size)
                value |= (*p_source & 0xF0) >> 4;
            *p_target++ = base64_to_char [value];
    
            /*  Byte 3 - pad the buffer with '=' if block not completed          */
            if ((size_t) (p_source - source) < source_size)
              {
                value = (*p_source++ & 0x0F) << 2;
                if ((size_t) (p_source - source) < source_size)
                    value |= (*p_source & 0xC0) >> 6;
                *p_target++ = base64_to_char [value];
              }
            else
                *p_target++ = '=';
    
            /*  Byte 4 - pad the buffer with '=' if block not completed          */
            if ((size_t) (p_source - source) < source_size)
              {
                value       = *p_source++ & 0x3F;
                *p_target++ = base64_to_char [value];
              }
            else
                *p_target++ = '=';
         }
       return (target_size);
    }
    
     

    decode_base64

    #include "sflmime.h"
    size_t
    decode_base64 (const byte *source, char *target, size_t source_size)
    

    Synopsis

    Decodes a block of Base 64 data and stores the resulting binary data in a target buffer. The target buffer must be at least 3/4 the size of the base 64 data. Returns the number of characters output into the target buffer.

    Source Code - (sflmime.c)

    {
        size_t
            target_size = 0;                /*  Length of target buffer          */
        int
            nb_block;                       /*  Total number of block            */
        const byte
            *p_source;                      /*  Pointer in source buffer         */
        byte
            value;                          /*  Value of Base64 byte             */
        char
            *p_target;                      /*  Pointer in target buffer         */
    
        ASSERT (source);
        ASSERT (target);
    
        if (source_size == 0)
            return (0);
    
        if (!tables_initialised)
            init_conversion_tables ();
    
        /*  Bit positions
                      | byte 1 | byte 2 | byte 3 | byte 4 |
        Encoded block  654321   654321   654321   654321  -> 4 bytes of 6 bits
                      | byte 1 | byte 2 | byte 3 |
        Decoded block  65432165 43216543 21654321         -> 3 bytes of 8 bits
        */
    
        nb_block    = source_size / 4;
        target_size = (size_t) nb_block * 3;
        target [target_size] = '\0';
    
        p_source = source;                  /*  Point to start of buffers        */
        p_target = target;
    
        while (nb_block--)
          {
            /*  Byte 1                                                           */
            *p_target    = char_to_base64 [(byte) *p_source++] << 2;
            value        = char_to_base64 [(byte) *p_source++];
            *p_target++ += ((value & 0x30) >> 4);
    
            /*  Byte 2                                                           */
            *p_target    = ((value & 0x0F) << 4);
            value        = char_to_base64 [(byte) *p_source++];
            *p_target++ += ((value & 0x3C) >> 2);
    
            /*  Byte 3                                                           */
            *p_target    = (value & 0x03) << 6;
            value        = char_to_base64 [(byte) *p_source++];
            *p_target++ += value;
          }
       return (target_size);
    }
    
     

    decode_mime_time

    #include "sflmime.h"
    Bool
    decode_mime_time (const char *mime_string, long *date, long *time)
    

    Synopsis

    Takes a MIME date and time string in various formats and converts to a date and time (both long values). Returns TRUE if it could convert the date and time okay, else returns FALSE. Accepts these formats:
    Mon Jan 12 12:05:01 1995 ctime format
    Monday, 12- Jan-95 12:05:01 GMT RFC 850
    Mon, 12 Jan 1995 12:05:01 GMT RFC 1123
    The returned date and time are in local time, not GMT.

    Source Code - (sflmime.c)

    {
        int
            cent  = 0,
            year  = 0,
            month = 0,
            day   = 0,
            hour  = 0,
            min   = 0,
            sec   = 0;
        static char
            month_name [20],
            buffer     [50],
            *p_char;
    
        ASSERT (mime_string);
        ASSERT (date);
        ASSERT (time);
    
        /*  Whatever format we're looking at, it will start with weekday.        */
        /*  Skip to first space.                                                 */
        if (!(p_char = strchr (mime_string, ' ')))
            return FALSE;
        else
            while (isspace (*p_char))
                ++p_char;
    
        if (isalpha (*p_char))
          {
            /*  ctime                                                            */
            sscanf (p_char, "%s %d %d:%d:%d %d",
                    month_name, &day, &hour, &min, &sec, &year);
            cent = (int) year / 100;
            year -= cent * 100;
          }
        else
        if (p_char [2] == '-')
          {
            /*  RFC 850                                                          */
            sscanf (p_char, "%s %d:%d:%d",
                    buffer, &hour, &min, &sec);
            buffer [2] = '\0';
            day        = atoi (buffer);
            buffer [6] = '\0';
            strcpy (month_name, &buffer [3]);
            year = atoi (&buffer [7]);
            /*  Prevent wraparound from ambiguity                                */
            if (year < 70)
                cent = 20;
            else
                cent = 19;
          }
        else
          {
            /*  RFC 1123                                                         */
            sscanf (p_char, "%d %s %d %d:%d:%d",
                    &day, month_name, &year, &hour, &min, &sec);
            cent = (int) year / 100;
            year -= cent * 100;
          }
        month = find_month (month_name);
        *date = MAKE_DATE (cent, year, month, day);
        *time = MAKE_TIME (hour, min,  sec,   0  );
    
        gmt to local (*date, *time, date, time);
        return (TRUE);
    }
    
     

    encode_mime_time

    #include "sflmime.h"
    char *
    encode_mime_time (long date, long time)
    

    Synopsis

    Encode date and time (in long format) in Mime RFC1123 date format, e.g. Mon, 12 Jan 1995 12:05:01 GMT. The supplied date and time are in local time. Returns the date/time string if the date was legal, else returns "?".

    Source Code - (sflmime.c)

    {
        int
            day_week,                       /*  Day of week number (0 is sunday) */
            month;                          /*  Month number                     */
        static char
            buffer [LINE_MAX];
    
        local to gmt (date, time, &date, &time);
        day_week = day of week (date);
        month    = GET_MONTH   (date);
        if (day_week >= 0 && day_week < 7 && month > 0 && month < 13)
          {
            sprintf (buffer, "%s, %02d %s %04d %02d:%02d:%02d GMT",
                             days       [day_week],
                             GET_DAY    (date),
                             months     [month - 1],
                             GET_CCYEAR (date),
                             GET_HOUR   (time),
                             GET_MINUTE (time),
                             GET_SECOND (time)
                     );
            return (buffer);
          }
        else
            return ("?");
    }
    

    Linked-list functions

    Filename: sflnode.h
    Package: Standard Function Library (SFL)
    Written: 96/06/03 iMatix SFL project team sfl@imatix.com
    Revised: 97/09/08
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    Provides functions to maintain doubly-linked lists. You can use these functions to work with lists of any structure. To make this work, all structures must start with two pointers, "void *next, *prev;". When you want to attach a linked-list to another structure, declare the list head as a NODE. You can then refer to this variable when you attach items to the list head. The code sets the global node_unsafe to TRUE whenever it is changing a list. NOTE: DEPRECATED IN FAVOUR OF SFLLIST.C.

    List of Functions

    List of Symbol Definitions

    sflnode.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    _SFLNODE_INCLUDED TRUE
    node_reset(node) (node)-> prev = (node)-> next = (node)
     

    node_create

    #include "sflnode.h"
    void *
    node_create (
        void *after,
        size_t size)
    

    Synopsis

    Creates a new node with the specified size, and attaches it to the linked list after the specified node. Initialises all fields in the node (except the main list pointers) to binary zeroes. If the 'after' argument is null, initialises but does not attach the node. Returns a pointer to the newly-created node, or NULL if there was not enough memory.

    Examples

        typedef struct {
            void *prev, *next;
            long data;
        } BLOCK;
    
        NODE head;
        BLOCK *pointer;
    
        //  Initialise head of list
        node_reset (&head);
    
        //  Attach new block to start of list
        pointer = (BLOCK *) node_create (&head, sizeof (BLOCK));
        pointer-> data = 1;
    
        //  Attach new block to end of list
        pointer = (BLOCK *) node_create (head.prev, sizeof (BLOCK));
        pointer-> data = 1000;
    

    Source Code - (sflnode.c)

    {
        NODE
            *node;                          /*  Allocated node                   */
    
        ASSERT (size > 0);
    
        if ((node = mem_alloc (size)) != NULL)
          {
            memset (node, 0, size);
            node_reset (node);              /*  Initialise node pointers         */
            if (after)                      /*  Link into list if required       */
                node relink after (node, after);
          }
        return (node);
    }
    
     

    node_destroy

    #include "sflnode.h"
    void
    node_destroy (
        void *node)
    

    Synopsis

    Unlinks the specified node from any list it may be in, and frees its memory.

    Source Code - (sflnode.c)

    {
        ASSERT (node);
    
        node unlink (node);
        mem_free (node);
    }
    
     

    node_relink_after

    #include "sflnode.h"
    void *
    node_relink_after (
        void *node,
        void *after)
    

    Synopsis

    Links a node into a doubly-linked list after a point in the list. Generally a linked list is attached to a 'head': an empty list consists of just the head node. To attach a node to the start of the list, link after the head. To attach a node to the end of the list, link before the head using node relink before(). In this way you can build doubly- ended queues, fifo queue, lists, etc. Returns the address of the node.

    Source Code - (sflnode.c)

    {
        return (node relink (after, node, ((NODE *) after)-> next));
    }
    
     

    node_relink_before

    #include "sflnode.h"
    void *
    node_relink_before (
        void *node,
        void *before)
    

    Synopsis

    Links a node into a doubly-linked list before a point in the list. To link a node to the end of a doubly-linked list, link it before the list header node.

    Source Code - (sflnode.c)

    {
        return (node relink (((NODE *) before)-> prev, node, before));
    }
    
     

    node_unlink

    #include "sflnode.h"
    void *
    node_unlink (
        void *node)
    

    Synopsis

    Unlinks the node from any list it may be in. Returns node.

    Source Code - (sflnode.c)

    {
        return (node relink (((NODE *) node)-> prev, node,
                             ((NODE *) node)-> next));
    }
    
     

    node_relink

    #include "sflnode.h"
    void *
    node_relink (
        void *left,
        void *node,
        void *right)
    

    Synopsis

    Links the node into a linked list. This is a general-purpose function that can be used to attach and remove nodes anywhere in a list. Sets the global variable 'node_unsafe' while the list is being changed. Returns the address of node.

    Source Code - (sflnode.c)

    {
        NODE *swap;
    
        node_unsafe = TRUE;
        swap = ((NODE *) left)-> next;      /*  Exchange left pointers           */
               ((NODE *) left)-> next = ((NODE *) node)-> next;
                                        ((NODE *) node)-> next = swap;
    
        swap = ((NODE *) right)-> prev;     /*  Exchange right pointers          */
               ((NODE *) right)-> prev = ((NODE *) node)-> prev;
                                         ((NODE *) node)-> prev = swap;
        node_unsafe = FALSE;
        return (node);
    }
    

    Directory access functions

    Filename: sfldir.h
    Package: Standard Function Library (SFL)
    Written: 96/04/02 iMatix SFL project team sfl@imatix.com
    Revised: 98/06/27
    Copyright: Copyright (c) 1991-98 iMatix

    Synopsis

    The directory access functions provide a portable interface to the system's file directory structure. In general these functions are modelled around the UNIX opendir and readdir functions, but they are also similar to the DOS interface. These functions can fail on SVr4 if the <dirent.h> file does not match the C library. Recompile with the switch -D _USE_BSD_DIRENT and they should work a bit better. Tested on: MS-DOS (Turbo-C), Windows (MSVC 4.0), UNIX (Linux, IBM AIX, SunOS). OS/2 port was done by Ewen McNeill ewen@naos.co.nz. DJGPP and DRDOS LFN by Rob Judd judd@alphalink.com.au. Changes for Win32 by Will Menninger willus@netcom.com.

    List of Functions

    List of Symbol Definitions

    sfldir.h defines these symbols, possibly conditionally:
    Symbol: Defined as:
    ATTR_HIDDEN 0x02 /* Hidden file */
    ATTR_MASK 0x17 /* All bits together */
    ATTR_RDONLY 0x01 /* Read only file */
    ATTR_SUBDIR 0x10 /* Subdirectory */
    ATTR_SYSTEM 0x04 /* System file */
    DEFAULT_DIR (various)
    Dirent dirent /* We'll always refer to Dirent */
    GID_CACHE_MAX 10 /* Max. different gid's we cache */
    MAXNAMLEN (various)
    NAME_MAX MAXNAMLEN
    UID_CACHE_MAX 10 /* Max. different uid's we cache */
    _SFLDIR_INCLUDED TRUE
    stat _stat

    List of Type Definitions

    Type name: Defined as:
    mode_t unsigned short
    nlink_t unsigned short
    off_t long
     

    open_dir

    #include "sfldir.h"
    Bool
    open_dir (
        DIRST *dir,
        const char *dir_name)
    

    Synopsis

    Creates a directory stream and returns the first entry in the directory. The order of entries is arbitrary, and it is undefined whether you will get entries like '.' and '..' or not. Returns TRUE if something was found, else FALSE. If TRUE, fills-in the values in the directory stream block. Use the same directory stream block for the read_dir and close dir() functions. You must supply a DIRST block when calling open dir(). If you do not supply a dir_name (i.e. it is NULL or ""), open dir() assumes you want to read from the current working directory. The strings in DIRST all point to static areas that may change after a further call to read_dir. If you need persistent data (i.e. because you want to collect a set of DIRSTs and then sort them, call fix dir() after each call to open_dir and read_dir. You should then call free dir() to release each DIRST when you are finished.

    Source Code - (sfldir.c)

    {
        static char                         /*  The name of the directory that   */
            dir_spec [LINE_MAX];            /*     we are searching through      */
        char
            *dir_spec_end;                  /*  Points to NULL in dir_spec       */
    
        ASSERT (dir != NULL);
        memset (dir, 0, sizeof (DIRST));
    
        /*  Copy and prepare the directory specification                         */
        if (dir_name == NULL || *dir_name == 0)
            strcpy (dir_spec, DEFAULT_DIR);
        else
            strcpy (dir_spec, dir_name);
    
    #if (defined (MSDOS_FILESYSTEM))
        strconvch (dir_spec, '/', '\\');
    #endif
        /*  Remove a trailing slash from the directory name                      */
        dir_spec_end = dir_spec + strlen (dir_spec);
        if (dir_spec_end [-1] == PATHEND)
          {
            dir_spec_end [-1] = '\0';
            dir_spec_end--;
          }
    
        /*  Open directory stream or find first directory entry                  */
    #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__))
        if (strnull (dir_spec))
            strcpy (dir_spec, "/");
        if ((dir-> _dir_handle = opendir (dir_spec)) == NULL)
    
    #elif (defined (WIN32))
        strcat (dir_spec, "\\*");
        if ((dir-> _dir_handle = FindFirstFile (dir_spec, &dir-> _dir_entry))
                               == INVALID_HANDLE_VALUE)
    
    #elif (defined (_MSC_VER))
        strcat (dir_spec, "\\*.*");
        if ((dir-> _dir_handle = _dos_findfirst (dir_spec, _A_NORMAL | _A_SUBDIR,
                                                 &dir-> _dir_entry)) != 0)
    
    #elif (defined (__TURBOC__) || defined (__DJGPP__))
        strcat (dir_spec, "\\*.*");
        if (findfirst (dir_spec, &dir-> _dir_entry, 255 - FA_LABEL) == -1)
    #endif
            return (FALSE);                 /*  Could not open directory         */
    
        /*  Save the directory name in directory stream structure                */
    #if (defined (__MSDOS__))
        *dir_spec_end = '\0';               /*  Kill the \*.* again              */
    #endif
        dir-> dir_name = dir_spec;
    
    #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__))
        /*  Under UNIX & VMS we still need to fetch the first file entry         */
        return (read dir (dir));
    
    #elif (defined (WIN32))
        /*  Under Win32 we have read an entry, so return those values            */
        return (populate_entry (dir));
    
    #elif (defined (_MSC_VER))
        /*  Under MSC we have read an entry, so return those values              */
        return (populate_entry (dir));
    
    #elif (defined (__TURBOC__) || defined (__DJGPP__))
        /*  Under Borland C we have read an entry, so return those values        */
        return (populate_entry (dir));
    #else
    
        return (FALSE);                     /*  Directory access not supported   */
    #endif
    }
    
     

    read_dir

    #include "sfldir.h"
    Bool
    read_dir (
        DIRST *dir)
    

    Synopsis

    Reads the next entry from the directory stream. Returns TRUE if there was more data to read; returns FALSE if there was nothing more to read. Updates the fields in the directory structure when it returns TRUE. The strings in DIRST all point to static areas that may change after a further call to read_dir. If you need persistent data (i.e. because you want to collect a set of DIRSTs and then sort them, call fix dir() after each call to open_dir and read_dir. You should then call free dir() to release each DIRST when you are finished.

    Source Code - (sfldir.c)

    {
        ASSERT (dir != NULL);
    
    #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__))
        if ((dir-> _dir_entry =
            (struct Dirent *) readdir (dir-> _dir_handle)) != NULL)
            return (populate_entry (dir));
        else
    
    #elif (defined (WIN32))
        if (FindNextFile (dir->_dir_handle, &dir->_dir_entry))
            return (populate_entry (dir));
        else
    
    #elif (defined (_MSC_VER))
        if (_dos_findnext (&dir-> _dir_entry) == 0)
            return (populate_entry (dir));
        else
    
    #elif (defined (__TURBOC__) || defined (__DJGPP__))
        if (findnext (&dir-> _dir_entry) == 0)
            return (populate_entry (dir));
        else
    #endif
    
        return (FALSE);
    }
    
     

    close_dir

    #include "sfldir.h"
    Bool
    close_dir (
        DIRST *dir)
    

    Synopsis

    Close the directory stream, and free any allocated memory. You should call this function when you are done reading a directory, or you will get memory leaks. Returns TRUE if okay, FALSE if there was an error.

    Source Code - (sfldir.c)

    {
        Bool
            rc;
    
        ASSERT (dir != NULL);
    
    #if (defined (__UNIX__) || defined (__VMS_XOPEN) || defined (__OS2__))
        rc = (closedir (dir-> _dir_handle) == 0);
    
    #elif (defined (WIN32))
        rc = FindClose (dir->_dir_handle);
    
    #elif (defined (_MSC_VER))
        rc = TRUE;                          /*  No function to close a dir       */
    
    #elif (defined (__TURBOC__) || defined (__DJGPP__))
        rc = TRUE;                          /*  No function to close a dir       */
    #else
    
        rc = FALSE;                         /*  Directory access not supported   */
    #endif
    
        return (rc);
    }
    
     

    format_dir

    #include "sfldir.h"
    char *
    format_dir (
        DIRST *dir,
        Bool full)
    

    Synopsis

    Formats the directory entry information using the same conventions as the UNIX 'ls -l' command. Returns a static buffer that contains the the formatted string. If the full argument is TRUE, adds cosmetic hints to indicate the file type; for instance '/' if the file is a directory, '*' if it is executable.

    Source Code - (sfldir.c)

    {
        static char
            buffer [LINE_MAX];              /*  Formatted directory entry        */
    
        ASSERT (dir != NULL);
        sprintf (buffer, "%s %3d %-8.8s %-8.8s %8ld %s %s",
                          format_mode (dir),
                          dir-> file_nlink,
                          dir-> owner,
                          dir-> group,
                          (long) dir-> file_size,
                          format_time (dir),
                          format_name (dir, full)
               );
        return (buffer);
    }
    
     

    fix_dir

    #include "sfldir.h"
    int
    fix_dir (DIRST *dir)
    

    Synopsis

    Converts all strings in the DIRST into permenant values, by allocating heap memory for each string. You must call this function if you intend to keep a set of DIRSTs, for searching or sorting. You do not need to call fix dir() if you handle each call to read dir() independently. If you use fix dir(), you must call free dir() for each DIRST when you terminate. Returns 0 if okay, -1 if there was insufficient memory or another fatal error.

    Source Code - (sfldir.c)

    {
        char
            *dir_name,
            *owner,
            *group,
            *file_name;
    
        /*  Allocate each string                                                 */
        dir_name  = mem_strdup (dir-> dir_name);
        owner     = mem_strdup (dir-> owner);
        group     = mem_strdup (dir-> group);
        file_name = mem_strdup (dir-> file_name);
    
        /*  If all okay, assign new strings and indicate everything okay         */
        if (dir_name && owner && group && file_name)
          {
            dir-> dir_name  = dir_name;
            dir-> owner     = owner;
            dir-> group     = group;
            dir-> file_name = file_name;
            dir-> _fixed    = TRUE;
            return (0);
          }
        else
          {
            /*  Otherwise patch things back the way they were                    */
            if (dir_name)
                mem_free (dir_name);
            if (owner)
                mem_free (owner);
            if (group)
                mem_free (group);
            if (file_name)
                mem_free (file_name);
            return (-1);
          }
    }
    
     

    free_dir

    #include "sfldir.h"
    int
    free_dir (DIRST *dir)
    

    Synopsis

    Frees all strings used in the DIRST. You should call this function to free space allocated by fix dir(). If you try to call free dir() for a DIRST that was not fixed, you will get an error feedback, and (if you compiled with DEBUG defined) an assertion fault. Returns 0 if okay, -1 if there was an error. After a call to free dir(), do not try to access the strings in DIRST; they are all set to point to an empty string.

    Source Code - (sfldir.c)

    {
        static char
            *empty = "";
    
        ASSERT (dir-> _fixed);
        if (dir-> _fixed)
          {
            /*  Free allocated strings                                           */
            mem_free (dir-> dir_name);
            mem_free (dir-> owner);
            mem_free (dir-> group);
            mem_free (dir-> file_name);
    
            /*  Now point the strings to an empty string                         */
            dir-> dir_name  = empty;
            dir-> owner     = empty;
            dir-> group     = empty;
            dir-> file_name = empty;
    
            /*  And mark the DIRST as no longer 'fixed'                          */
            dir-> _fixed    = FALSE;
            return (0);
          }
        else
            return (-1);
    }
    
     

    load_dir_list

    #include "sfldir.h"
    NODE *
    load_dir_list (
        const char *dir_name,
        const char *sort)
    

    Synopsis

    Loads and sorts the contents of a directory. Returns a NODE pointer to a linked list containing the directory entries. Each node is a FILEINFO structure (mapped onto a NODE structure for purposes of manipulating the linked list). You can ask for the directory list to be sorted in various ways; in this case subdirectory entries are always sorted first. To specify the sort order you pass a string consisting of one or more of these characters, which are then used in order:
    n Sort by ascending name.
    N Sort by descending name.
    x Sort by ascending extension.
    X Sort by descending extension.
    t Sort by ascending time and date.
    T Sort by descending time and date.
    s Sort by ascending size.
    S Sort by descending size.
    If the sort string is NULL, no sort is carried out.

    Source Code - (sfldir.c)

    {
        NODE
            *file_list;                     /*  File list head                   */
        DIRST
            dir;
        Bool
            rc;
        int
            nbr_files = 0;
    
        file_list = mem_alloc (sizeof (NODE));
        if (!file_list)
            return (NULL);
    
        node_reset (file_list);             /*  Initialise file list             */
    
        /* Load directory                                                        */
        rc = open dir (&dir, dir_name);
        while (rc)
          {
            add dir list (file_list, &dir);
            nbr_files++;
            rc = read dir (&dir);
          }
        close dir (&dir);
    
        if (sort && nbr_files > 1)
            sort dir list (file_list, sort);
        return (file_list);
    }
    
     

    free_dir_list

    #include "sfldir.h"
    Bool
    free_dir_list (NODE *file_list)
    

    Synopsis

    Frees all FILELIST blocks in the specified linked list.

    Source Code - (sfldir.c)

    {
        ASSERT (file_list);
    
        while (file_list-> next != file_list)
          {
            free dir (&((FILEINFO *) file_list-> next)-> dir);
            node destroy (file_list-> next);
          }
        mem_free (file_list);
        return (TRUE);
    }
    
     

    sort_dir_list

    #include "sfldir.h"
    void
    sort_dir_list (NODE *file_list, const char *sort)
    

    Synopsis

    Sorts the directory list as specified. Returns the number of items in the list. To specify the sort order you pass a string holding one or more of these characters, which are then used in order:
    n Sort by ascending name.
    N Sort by descending name.
    x Sort by ascending extension.
    X Sort by descending extension.
    t Sort by ascending time and date.
    T Sort by descending time and date.
    s Sort by ascending size.
    S Sort by descending size.

    Source Code - (sfldir.c)

    {
        ASSERT (file_list);
        sort_key = (char *) sort;
        list sort (file_list, compare_dir);
    }
    
     

    add_dir_list

    #include "sfldir.h"
    FILEINFO *
    add_dir_list (NODE *file_list, const DIRST *dir)
    

    Synopsis

    Adds a node to the specified directory list. Returns the address of the new node, or NULL if there was insufficient memory.

    Source Code - (sfldir.c)

    {
        FILEINFO
            *file_info;
    
        file_info = (FILEINFO *) node create (file_list-> prev, sizeof (FILEINFO));
        if (file_info)
          {
            memcpy  (&file_info-> dir, dir, sizeof (DIRST));
            fix dir (&file_info-> dir);
            file_info-> directory = (dir-> file_attrs & ATTR_SUBDIR) != 0;
          }
        return (file_info);
    }
    
     

    resolve_path

    #include "sfldir.h"
    char *
    resolve_path (
        const char *old_path)
    

    Synopsis

    Accepts a path consisting of zero or more directory names and optionally a filename plus extension. Removes '.' and '..' if they occur in the path. '..' is resolved by also removing the preceding directory name, if any. Returns the address of the resulting path, in a static area that is overwritten by each call. The returned path may be empty. Under OS/2 and MS-DOS, treats '\' and '/' both as directory separators. A '..' directory at the start of the path resolves into nothing. If the input path started with '/', the returned path also does, else it does not. For compatibility with DOS-based systems, '...' is treated as '../..', '....' as '../../..', and so on.

    Source Code - (sfldir.c)

    {
    #if (defined (__UNIX__) || defined (MSDOS_FILESYSTEM) || defined (__VMS__))
        static char
            new_path [PATH_MAX];            /*  Returned path value              */
        char
            *new_ptr,                       /*  Pointer into new_path            */
            last_char = '/';                /*  Start of path counts as delim    */
        int
            nbr_dots;                       /*  Size of '..', '...' specifier    */
    
        ASSERT (old_path);
        ASSERT (strlen (old_path) < PATH_MAX);
    
        new_ptr = new_path;
        while (*old_path)
          {
            if (path_delimiter (last_char) && *old_path == '.')
              {
                /*  Handle one or more dots followed by a path delimiter         */
                nbr_dots = 0;               /*  Count number of dots             */
                while (old_path [nbr_dots] == '.')
                    nbr_dots++;
    
                if (path_delimiter (old_path [nbr_dots]))
                  {
                    old_path += nbr_dots;   /*  Skip past dots                   */
                    if (*old_path)
                        old_path++;         /*    and past / if any              */
    
                    /*  Now backtrack in new path, dropping directories as       */
                    /*  many times as needed (0 or more times)                   */
                    while (nbr_dots > 1)
                      {
                        if (new_ptr > new_path + 1)
                          {
                            new_ptr--;      /*  Drop delimiter                   */
                            while (new_ptr > new_path)
                              {
                                if (path_delimiter (*(new_ptr - 1)))
                                    break;  /*    and end after delimiter        */
                                new_ptr--;
                              }
                          }
                        else
                            break;          /*  At start of name - finish        */
                        nbr_dots--;
                      }
                  }
                else
                    /*  Handle '.something'                                      */
                    last_char = *new_ptr++ = *old_path++;
              }
            else
                last_char = *new_ptr++ = *old_path++;
          }
    
        *new_ptr = '\0';                    /*  Terminate string nicely          */
        return (new_path);
    #else
    
        return ((char *) old_path);         /*  Path resolution not supported    */
    #endif
    }
    
     

    locate_path

    #include "sfldir.h"
    char *
    locate_path (
        const char *root,
        const char *path)
    

    Synopsis

    Accepts a root directory and a path and locates the path with respect to the root. If the path looks like an absolute directory, returns the path after cleaning it up. Otherwise appends the path to the root, and returns the result. In any case, the resulting directory does not need to exist. Cleans-up the returned path by appending a '/' if necessary, and resolving any '..' subpaths. The returned value is held in a static string that is reused by each call to this function.

    Source Code - (sfldir.c)

    {
    #if (defined (__UNIX__) || defined (MSDOS_FILESYSTEM) || defined (__VMS__))
        static char
            new_path [PATH_MAX];            /*  Returned path value              */
    
        ASSERT (root);
        ASSERT (path);
    
    #if (defined (MSDOS_FILESYSTEM))
        /*  Under MSDOS, OS/2, or Windows we have a full path if we have any of:
         *  /directory
         *  D:/directory
         *  the variations of those with backslashes.
         */
        if (path [0] == '\\'   || path [0] == '/'
        || (isalpha (path [0]) && path [1] == ':'
        && (path [2] == '\\'   || path [2] == '/')))
    
    #else
        /*  Under UNIX or VMS we have a full path if the path starts
         *  with the directory marker
         */
        if (path [0] == PATHEND)
    #endif
            strcpy (new_path, path);        /*  Use path as supplied             */
        else
          {
            strcpy (new_path, root);        /*  Build root/path                  */
            if (!path_delimiter (strlast (new_path)))
                strcat (new_path, "/");
            strcat (new_path, path);
          }
        /*  Append slash if necessary                                            */
        if (!path_delimiter (strlast (new_path)))
            strcat (new_path, "/");
        return (resolve path (new_path));
    #else
        return ((char *) path);
    #endif
    }
    
     

    clean_path

    #include "sfldir.h"
    char *
    clean_p