%% options copyright owner = Dirk Krause copyright year = 2015-xxxx license = bsd %% module /** @file Open and close dk4_app_t structure. */ #ifndef DK4APP_H_INCLUDED #include "dk4app.h" #endif #ifndef DK4STT_H_INCLUDED #include "dk4stt.h" #endif #ifndef DK4CONST_H_INCLUDED #include "dk4const.h" #endif #ifndef DK4MEM_H_INCLUDED #include "dk4mem.h" #endif #ifndef DK4STR8_H_INCLUDED #include "dk4str8.h" #endif #ifndef DK4STRW_H_INCLUDED #include "dk4strw.h" #endif #ifndef DK4STRD_H_INCLUDED #include "dk4strd.h" #endif #ifndef DK4ENC_H_INCLUDED #include "dk4enc.h" #endif #ifndef DK4LOC_H_INCLUDED #include "dk4loc.h" #endif #ifndef DK4MPL_H_INCLUDED #include "dk4mpl.h" #endif #ifndef DK4EXEPD_H_INCLUDED #include "dk4exepd.h" #endif #ifndef DK4MAO8D_H_INCLUDED #include "dk4mao8d.h" #endif #ifndef DK4INST_H_INCLUDED #include "dk4inst.h" #endif #ifndef DK4PREF_H_INCLUDED #include "dk4pref.h" #endif #ifndef DK4USER_H_INCLUDED #include "dk4user.h" #endif #ifndef DK4FS_H_INCLUDED #include "dk4fs.h" #endif #ifndef DK4STRMR_H_INCLUDED #include "dk4strmr.h" #endif #ifndef DK4TSPDK_H_INCLUDED #include "dk4tspdk.h" #endif #ifndef DK4FPUT_H_INCLUDED #include "dk4fput.h" #endif #ifndef DK4HNAMED_H_INCLUDED #include "dk4hnamed.h" #endif #ifndef DK4MAIDDDI_H_INCLUDED #include "dk4maidddi.h" #endif #ifndef DK4MAIDDDU_H_INCLUDED #include "dk4maidddu.h" #endif #ifndef DK4MAIDDSZ_H_INCLUDED #include "dk4maiddsz.h" #endif #ifndef DK4MAIDDBL_H_INCLUDED #include "dk4maiddbl.h" #endif #ifndef DK4MAODD_H_INCLUDED #include "dk4maodd.h" #endif #ifndef DK4NUML_H_INCLUDED #include "dk4numl.h" #endif #ifndef DK4STO_H_INCLUDED #include "dk4sto.h" #endif #ifndef DK4APPI_C_INCLUDED #include "dk4appi.c" #endif #ifndef DK4PATHD_H_INCLUDED #include "dk4pathd.h" #endif #ifndef DK4STATD_H_INCLUDED #include "dk4statd.h" #endif #ifndef DK4STAT_H_INCLUDED #include "dk4stat.h" #endif #ifndef DK4REC21_H_INCLUDED #include "dk4rec21.h" #endif #ifndef DK4TIME_H_INCLUDED #include "dk4time.h" #endif #ifndef DK4TIMEDK_H_INCLUDED #include "dk4timedk.h" #endif #ifndef DK4GETPID_H_INCLUDED #include "dk4getpid.h" #endif #ifndef DK4APPMKDH_H_INCLUDED #include "dk4appmkdh.h" #endif #ifndef DK4DELFILE_H_INCLUDED #include "dk4delfile.h" #endif #ifdef __cplusplus extern "C" { #endif /** Log at application startup. @param app Application structure. */ void dk4app_log_startup_debug(dk4_app_t *app); #ifdef __cplusplus } #endif $!trace-include static const dkChar * const dk4app_open_long_opt[] = { $!string-table macro=dkT # # 0 # help # # 1 # version # # 2 # license # # 3 # quiet # # 4 # manual $!end }; static const dkChar * const dk4app_open_scope_names[] = { $!string-table macro=dkT # # 0 # user # # 1 # application # # 2 # application-group # # 3 # host # # 4 # windows # # 5 # non-windows # # 6 # language # # 7 # region $!end }; /** Non localized 8 bit char keywords used by this module. */ static const char * const dk4app_open_c8_kw[] = { $!string-table ERROR: Not enough memory! ERROR: Not enough memory, failed to allocate bytes! $!end }; /** Non localized dkChar keywords used by this module. */ static const dkChar * const dk4app_open_kw[] = { $!string-table macro=dkT # # 0 Long option for running silently. # --quiet # # 1 Sub directory name for binaries # bin # # 2 Sub directory name for binaries # sbin # # 3 ... 6 Sub directory names # etc share lib var # # 7 Sub directory name # libexec # # 8 Preference config file name # dk4pref.conf # # 9 10 11 12 Parts to construct log file name. # /log / - .log # # 13 Parts to construct temporary directory name # /tmp # # 14 Compatible preferences file name # dk3pref.conf # # 15 Wildcard matching all hosts, users, programs... # * $!end }; /** Character used as separator between path name components. */ static const dkChar dk4app_filename_sep[] = #if DK4_HAVE_BACKSLASH_AS_SEP dkT("\\") #else dkT("/") #endif ; #if DK4_ON_WINDOWS /** File name suffixes for executable files on Windows systems. */ static const dkChar * const dk4app_exe_file_suffixes[] = { $!string-table macro=dkT .exe .com .dll .sys .cpl $!end }; #endif /** Initialize an application structure after allocating the memory. @param ptr Structure to initialize. */ static void dk4app_open_initialize(dk4_app_t *ptr) { $? "+ dk4app_open_initialize" ptr->s_opt_all = NULL; ptr->i_opt_all = NULL; ptr->s_opt_short = NULL; ptr->i_opt_short = NULL; ptr->s_opt_long = NULL; ptr->i_opt_long = NULL; ptr->s_pref_self = NULL; ptr->s_pref_cmd = NULL; ptr->s_pref_user = NULL; ptr->s_pref_sys = NULL; ptr->s_stt = NULL; ptr->i_pref_self = NULL; ptr->i_pref_cmd = NULL; ptr->i_pref_user = NULL; ptr->i_pref_sys = NULL; ptr->i_stt = NULL; ptr->msg_base = NULL; ptr->msg_debug = NULL; ptr->help_text = NULL; ptr->lic_text = NULL; ptr->my_argv = NULL; ptr->vers_text = NULL; ptr->help_file_name = NULL; ptr->user_name = NULL; ptr->host_name = NULL; ptr->user_home = NULL; ptr->language = NULL; ptr->region = NULL; ptr->prog_name = NULL; ptr->group_name = NULL; ptr->dir_bin = NULL; ptr->dir_etc = NULL; ptr->dir_share = NULL; ptr->dir_lib = NULL; ptr->dir_var = NULL; ptr->dir_tmp = NULL; ptr->log_file_name = NULL; ptr->source_file_name = NULL; ptr->source_file_line = (dk4_um_t)0UL; ptr->timer_stderr = (dk4_time_t)0UL; ptr->timer_file = (dk4_time_t)0UL; ptr->sz_msg_base = 0; ptr->sz_msg_debug = 0; ptr->have_stderr_time = 0; ptr->have_file_time = 0; ptr->have_file_opened = 0; ptr->have_stderr_init = 0; ptr->my_argc = 0; ptr->ll_stderr = DK4_LL_WARNING; ptr->ll_file = DK4_LL_NONE; ptr->ll_file_keep = DK4_LL_NONE; ptr->ll_found = DK4_LL_IGNORE; ptr->log_stderr_time = 1; ptr->log_file_time = 1; ptr->log_enabled = 0; ptr->encoding = ptr->enc_in_std = ptr->enc_in_file = ptr->enc_out_std = ptr->enc_out_file = DK4_FILE_ENCODING_ASCII; ptr->hvl_cmd = 0; ptr->have_self_pref = 0; ptr->fast_close = 0; ptr->tmp_dir_used = 0; $? "- dk4app_open_initialize" } /** Check command line arguments for --quiet option. @param argc Number of command line arguments. @param argv Command line arguments array. @return 1 if --quiet was found, 0 otherwise. */ static int dk4app_open_silence_check(int argc, dkChar **argv) { size_t i; /* Traverse arguments list */ int back = 0; /* Function result */ $? "+ dk4app_open_silence_check" for (i = 1; i < (size_t)argc; i++) { if (0 == dk4str_cmp(argv[i], dk4app_open_kw[0])) { back = 1; } } $? "- dk4app_open_silence_check %d", back return back; } /** Find language, region, and encoding. Set app->error_found if any error occures. @param app Application structure to set up. */ static void dk4app_open_find_language_region(dk4_app_t *app, dk4_app_error_t *ae) { dkChar lbuf[16]; dkChar rbuf[16]; int enc = DK4_ENCODING_ASCII; int res; $? "+ dk4app_open_find_language_region" lbuf[0] = dkT('\0'); rbuf[0] = dkT('\0'); res = dk4loc_get_settings( lbuf, DK4_SIZEOF(lbuf,dkChar), rbuf, DK4_SIZEOF(rbuf,dkChar), &enc, NULL ); if (0 != res) { $? ". language and region obtained" if (0 < dk4str_len(lbuf)) { app->language = dk4str_dup(lbuf, NULL); if (NULL == app->language) { ae->error_found = E_MEMORY; } } if (0 < dk4str_len(rbuf)) { app->region = dk4str_dup(rbuf, NULL); if (NULL == app->region) { ae->error_found = E_MEMORY; } } app->encoding = app->enc_in_std = app->enc_in_file = app->enc_out_std = app->enc_out_file = enc; #if DK4_CHAR_SIZE > 1 /* Multi-byte input (UTF-16, C32) typically has a BOM at start */ app->enc_in_std = DK4_FILE_ENCODING_ASCII; app->enc_in_file = DK4_FILE_ENCODING_ASCII; #endif } else { ae->error_found = E_LOCALE; } $? "- dk4app_open_find_language_region" } /** Report errors occured in initialization. @param app Application structure to report errors for. */ static void dk4app_open_report_error(dk4_app_t *app, dk4_app_error_t *ae) { /* !!!!! */ switch (ae->error_found) { case E_LOCALE: { } break; case E_MEMORY: { } break; case E_EXEFILE: { } break; case E_EXECNAME_TOO_LONG: { } break; case E_NO_DIR_IN_EXENAME: { } break; case E_EMPTY_OPTION_SPEC: { } break; case E_OPTION_LONG_SPEC_MULT: { } break; case E_OPTION_SHORT_SPEC_MULT: { } break; case E_USER_NAME_NOT_FOUND: { } break; case E_USER_HOME_NOT_FOUND: { } break; case E_LOG_FILE_NAME: { } break; } } /** Check whether a dynamically allocated string is not NULL. @param app Application to set up. @param dirname String to test. */ static void dk4app_open_check_directory_name(dk4_app_error_t *ae, dkChar *dirname) { $? "+ dk4app_open_check_directory_name" if (NULL == dirname) { if (0 == ae->error_found) { $? "! error" ae->error_found = E_MEMORY; } } $? "- dk4app_open_check_directory_name" } /** The bin directory is the one compiled into the dk4inst module, so retrieve etc, lib, share and var from the dk4inst module too. @param app Application structure to set up. */ static void dk4app_open_find_exec_from_inst(dk4_app_t *app, dk4_app_error_t *ae) { $? "+ dk4app_open_find_exec_from_inst" app->dir_etc = dk4str_dup(dk4inst_get_directory(1), NULL); app->dir_share = dk4str_dup(dk4inst_get_directory(2), NULL); app->dir_lib = dk4str_dup(dk4inst_get_directory(6), NULL); app->dir_var = dk4str_dup(dk4inst_get_directory(3), NULL); dk4app_open_check_directory_name(ae, app->dir_etc); dk4app_open_check_directory_name(ae, app->dir_share); dk4app_open_check_directory_name(ae, app->dir_lib); dk4app_open_check_directory_name(ae, app->dir_var); $? "- dk4app_open_find_exec_from_inst" } /** Create one new directory name replacing the final path component. @param app Application structure to set up. @param buf Buffer containing old directory name, changed to the new directory name. @param szbuf Size of buffer. @param p1 Pointer to first position to modify in buffer. @param nn New final component name. @return String copy of new directory name in dynamically allocated memory. */ static dkChar * dk4app_open_find_exec_replace_one( dk4_app_t *app, dk4_app_error_t *ae, dkChar *buf, size_t szbuf, dkChar *p1, dkChar const *nn ) { dkChar *back = NULL; $? "+ dk4app_open_find_exec_replace_one" *p1 = dkT('\0'); if (0 != dk4str_cat_s(buf, szbuf, nn, NULL)) { back = dk4str_dup(buf, NULL); if (NULL == back) { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { if (0 == ae->error_found) { ae->error_found = E_EXECNAME_TOO_LONG; } } $? "- dk4app_open_find_exec_replace_one" return back; } /** Create directory names by replacing the final bin or sbin component by etc, lib, share and var. @param app Application structure to set up. @param buf Buffer for directory name. @param szbuf Size of buffer. @param p1 Pointer to first character to modify. */ static void dk4app_open_find_exec_replace_final( dk4_app_t *app, dk4_app_error_t *ae, dkChar *buf, size_t szbuf, dkChar *p1 ) { $? "+ dk4app_open_find_exec_replace_final" app->dir_etc = dk4app_open_find_exec_replace_one( app, ae, buf, szbuf, p1, dk4app_open_kw[3] ); app->dir_share = dk4app_open_find_exec_replace_one( app, ae, buf, szbuf, p1, dk4app_open_kw[4] ); app->dir_lib = dk4app_open_find_exec_replace_one( app, ae, buf, szbuf, p1, dk4app_open_kw[5] ); app->dir_var = dk4app_open_find_exec_replace_one( app, ae, buf, szbuf, p1, dk4app_open_kw[6] ); $? "- dk4app_open_find_exec_replace_final" } /** Check whether the path is below the libexec directory compiled into the dk4inst module. @param buf Buffer to check. @return 1 if the path is below the libexec directory, 0 otherwise. */ static int dk4app_open_is_dkinst_libexec(dkChar *buf) { size_t sz; int back = 0; $? "+ dk4app_open_is_dkinst_libexec" sz = dk4str_len(dk4inst_get_directory(10)); if (0 < sz) { if (dk4str_len(buf) > sz) { if (buf[sz] == dk4app_filename_sep[0]) { buf[sz] = dkT('\0'); if (0 == dk4str_pathcmp(buf, dk4inst_get_directory(10))) { back = 1; } else { buf[sz] = dk4app_filename_sep[0]; } } } } $? "- dk4app_open_is_dkinst_libexec %d", back return back; } /** Check if there is any libexec component in the path. Cut path to part before libexec if found. @param buf Buffer containing the directory. @param szbuf Buffer size. @return 1 on success (libexec found), 0 otherwise. */ static int dk4app_open_is_any_libexec(dkChar *buf, size_t szbuf) { dkChar mybuf[DK4_MAX_PATH]; dkChar *p1; dkChar *p2; int back = 0; $? "+ dk4app_open_is_any_libexec" if (0 != dk4str_cpy_s(mybuf, DK4_SIZEOF(mybuf,dkChar), buf, NULL)) { p1 = dk4str_rchr(mybuf, dk4app_filename_sep[0]); while ((0 == back) && (NULL != p1)) { p2 = p1; p2++; if (0 == dk4str_pathcmp(p2, dk4app_open_kw[7])) { back = 1; *p1 = dkT('\0'); dk4str_cpy_s(buf, szbuf, mybuf, NULL); p1 = NULL; } else { *p1 = dkT('\0'); p1 = dk4str_rchr(mybuf, dk4app_filename_sep[0]); } } } $? "- dk4app_open_is_any_libexec %d", back return back; } static void dk4app_open_find_copy_bindir(dk4_app_t *app, dk4_app_error_t *ae) { $? "+ dk4app_open_find_copy_bindir" if (NULL != app->dir_bin) { app->dir_etc = dk4str_dup(app->dir_bin, NULL); app->dir_share = dk4str_dup(app->dir_bin, NULL); app->dir_lib = dk4str_dup(app->dir_bin, NULL); app->dir_var = dk4str_dup(app->dir_bin, NULL); } dk4app_open_check_directory_name(ae, app->dir_etc); dk4app_open_check_directory_name(ae, app->dir_share); dk4app_open_check_directory_name(ae, app->dir_lib); dk4app_open_check_directory_name(ae, app->dir_var); $? "- dk4app_open_find_copy_bindir" } /** Find executable file for process and derive program name and directories from it. @param app Application structure to set up. @param arg0 First command line word. */ static void dk4app_open_find_executable( dk4_app_t *app, dk4_app_error_t *ae, const dkChar *arg0 ) { dkChar buf[DK4_MAX_PATH]; dkChar *p1; size_t const szbuf = DK4_SIZEOF(buf,dkChar); $? "+ dk4app_open_find_executable" if (0 != dk4execpath(buf, szbuf, arg0, NULL)) { p1 = dk4str_rchr(buf, dk4app_filename_sep[0]); if (NULL != p1) { *(p1++) = dkT('\0'); if (0 < dk4str_len(buf)) { /* Save program name */ app->prog_name = dk4str_dup(p1, NULL); if (NULL == app->prog_name) { ae->error_found = E_MEMORY; } else { #if DK4_ON_WINDOWS /* Remove .exe suffix */ p1 = dk4path_get_suffix(app->prog_name, NULL); if (NULL != p1) { if (-1 < dk4str_array_index(dk4app_exe_file_suffixes, p1, 0)) { *p1 = dkT('\0'); } } #endif } $? ". program name: \"%!ds\"", TR_DKSTR(app->prog_name) /* Save bindir name */ app->dir_bin = dk4str_dup(buf, NULL); $? ". binary dir: \"%!ds\"", TR_DKSTR(app->dir_bin) if (NULL == app->dir_bin) { ae->error_found = E_MEMORY; } /* Find other directories */ if (0 == dk4str_pathcmp(buf, dk4inst_get_directory(4))) { $? ". binary dir as configured (bin)" /* In .../bin, use directories from dk4inst */ dk4app_open_find_exec_from_inst(app, ae); } else { if (0 == dk4str_pathcmp(buf, dk4inst_get_directory(5))) { $? ". binary dir as configured (sbin)" /* In .../sbin, use directories from dk4inst */ dk4app_open_find_exec_from_inst(app, ae); } else { if (dk4app_open_is_dkinst_libexec(buf)) { $? ". binary dir as configured (libexec)" /* In .../libexec/..., use directories from dk4inst */ dk4app_open_find_exec_from_inst(app, ae); } else { p1 = dk4str_rchr(buf, dk4app_filename_sep[0]); if (NULL != p1) { p1++; if (0 == dk4str_pathcmp(p1, dk4app_open_kw[1])) { $? ". binary dir ends in bin" /* Replace final path component by etc, lib... */ dk4app_open_find_exec_replace_final( app, ae, buf, szbuf, p1 ); } else { if (0 == dk4str_pathcmp(p1, dk4app_open_kw[2])) { $? ". binary dir ends in sbin" /* Replace final path component by etc, lib... */ dk4app_open_find_exec_replace_final( app, ae, buf, szbuf, p1 ); } else { if (dk4app_open_is_any_libexec(buf,szbuf)) { /* We can securely append the separator and the finalizer as the dk4app_open_is_any_libexec() function cutted a libexec string from the path. */ $? ". binary dir contains a libexec" p1 = buf; while(dkT('\0') != *p1) { p1++; } *(p1++) = dk4app_filename_sep[0]; *p1 = dkT('\0'); dk4app_open_find_exec_replace_final( app, ae, buf, szbuf, p1 ); } else { $? ". use binary dir" dk4app_open_find_copy_bindir(app, ae); } } } } else { $? ". dir path is one component" dk4app_open_find_copy_bindir(app, ae); } } } } $? ". etc = \"%!ds\"", TR_DKSTR(app->dir_etc) $? ". share = \"%!ds\"", TR_DKSTR(app->dir_share) $? ". lib = \"%!ds\"", TR_DKSTR(app->dir_lib) $? ". var = \"%!ds\"", TR_DKSTR(app->dir_var) } else { $? "! directory length is 0" ae->error_found = E_EXECNAME_TOO_LONG; } } else { $? "! no dir component in executable" ae->error_found = E_NO_DIR_IN_EXENAME; } } else { $? "! executable not found" ae->error_found = E_EXEFILE; } $? "- dk4app_open_find_executable" } static void dk4app_close_prefeferences(dk4_sto_t *s, dk4_sto_it_t *i) { dk4_pref_t *prefptr; if (NULL != i) { dk4sto_it_reset(i); do { prefptr = (dk4_pref_t *)dk4sto_it_next(i); if (NULL != prefptr) { dk4pref_close(prefptr); } } while(NULL != prefptr); dk4sto_it_close(i); } if (NULL != s) { dk4sto_close(s); } } int dk4app_close(dk4_app_t *app) { dk4_option_t *optptr; dk4_string_table_t *ptrstt; int back = 0; $? "+ dk4app_close PTR=%d", TR_IPTR(app) if (NULL != app) { /* Remove temporary files */ if (0 == app->fast_close) { if ((NULL != app->dir_tmp) && (0 != app->tmp_dir_used)) { (void)dk4app_del_tree(app->dir_tmp, app); } } #if 0 /* Write preferences back to file */ if ((0 != app->have_self_pref) && (0 == app->fast_close)) { /* Originally intended: Write self-set preferences back to file. 2015-04-17: Design decision not to write back. Preferences are read-only in version 4. For GUI programs there are mechanisms in wxWidgets to save configuration changes. */ } #endif /* The base and debug messages are read from string tables, so the arrays are freed when freeing the string tables. */ app->msg_base = NULL; app->msg_debug = NULL; app->source_file_name = NULL; back = 1; /* Disable logging */ app->log_enabled = 0; /* Delete log file unless there is a reason to keep it */ if (NULL != app->log_file_name) { if ((app->ll_found <= app->ll_file) && (0 != app->have_file_opened)) { if (app->ll_file_keep < app->ll_found) { (void)dk4delete_file(app->log_file_name, NULL); } } } /* Release memory for log file */ dk4mem_release(app->log_file_name); /* Release memory for directories */ app->help_text = NULL; app->lic_text = NULL; dk4mem_release(app->dir_tmp); dk4mem_release(app->dir_var); dk4mem_release(app->dir_lib); dk4mem_release(app->dir_share); dk4mem_release(app->dir_etc); dk4mem_release(app->dir_bin); dk4mem_release(app->group_name); dk4mem_release(app->prog_name); /* Release memory for language and region */ dk4mem_release(app->region); dk4mem_release(app->language); dk4mem_release(app->vers_text); dk4mem_release(app->help_file_name); dk4mem_release(app->host_name); dk4mem_release(app->user_home); dk4mem_release(app->user_name); #if TRACE_DEBUG { int i; if ((0 < app->my_argc) && (NULL != app->my_argv)) { for (i = 0; i < app->my_argc; i++) { $? ". my_argv[%d] = \"%!ds\"", i, TR_DKSTR((app->my_argv)[i]) } } } #endif dk4mem_release(app->my_argv); /* Release string tables */ if (NULL != app->s_stt) { if (NULL != app->i_stt) { dk4sto_it_reset(app->i_stt); do { ptrstt = (dk4_string_table_t *)dk4sto_it_next(app->i_stt); if (NULL != ptrstt) { dk4stt_close(ptrstt); } } while (NULL != ptrstt); dk4sto_it_close(app->i_stt); } dk4sto_close(app->s_stt); } app->s_stt = NULL; app->i_stt = NULL; /* Release options All options in s_opt_long and s_opt_short are also in s_opt_all. They are deleted later, so we can simply close the storages here. */ if (NULL != app->i_opt_long) { dk4sto_it_close(app->i_opt_long); } app->i_opt_long = NULL; if (NULL != app->i_opt_short) { dk4sto_it_close(app->i_opt_short); } app->i_opt_short = NULL; if (NULL != app->s_opt_long) { dk4sto_close(app->s_opt_long); } app->s_opt_long = NULL; if (NULL != app->s_opt_short) { dk4sto_close(app->s_opt_short); } app->s_opt_short = NULL; dk4app_close_prefeferences(app->s_pref_sys, app->i_pref_sys); dk4app_close_prefeferences(app->s_pref_user, app->i_pref_user); dk4app_close_prefeferences(app->s_pref_cmd, app->i_pref_cmd); dk4app_close_prefeferences(app->s_pref_self, app->i_pref_self); app->s_pref_self = NULL; app->s_pref_cmd = NULL; app->s_pref_user = NULL; app->s_pref_sys = NULL; app->i_pref_self = NULL; app->i_pref_cmd = NULL; app->i_pref_user = NULL; app->i_pref_sys = NULL; if (NULL != app->i_opt_all) { dk4sto_it_reset(app->i_opt_all); do { optptr = (dk4_option_t *)dk4sto_it_next(app->i_opt_all); if (NULL != optptr) { dk4opt_close(optptr); } } while(NULL != optptr) ; dk4sto_it_close(app->i_opt_all); } app->i_opt_all = NULL; if (NULL != app->s_opt_all) { dk4sto_close(app->s_opt_all); } app->s_opt_all = NULL; /* Release memory for app structure itself */ dk4mem_free(app); } $? "- dk4app_close %d", back return back; } /** Process the options specification. @param app Application to set up. @param ae Application error structure. @param optspec The option specification. @param szoptspec Size of the optspec array. */ static void dk4app_open_process_optspec( dk4_app_t *app, dk4_app_error_t *ae, const dk4_option_specification_t *optspec, size_t szoptspec ) { dk4_option_t *optptr; $? "+ dk4app_open_process_optspec" while(szoptspec--) { if ((NULL != optspec->longopt) || (dkT('\0') != optspec->c)) { optptr = dk4opt_open(optspec, NULL); if (NULL != optptr) { if (0 != dk4sto_add(app->s_opt_all, optptr, NULL)) { if (NULL != optspec->longopt) { if (NULL == dk4sto_it_find_like(app->i_opt_long,optspec->longopt,3)) { if (0 == dk4sto_add(app->s_opt_long, optptr, NULL)) { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } szoptspec = 0; } } else { $? "! already exists" if (0 == ae->error_found) { ae->error_found = E_OPTION_LONG_SPEC_MULT; ae->longopt = optspec->longopt; } szoptspec = 0; } } if (dkT('\0') != optspec->c) { if (NULL == dk4sto_it_find_like(app->i_opt_short,&(optspec->c),1)) { if (0 == dk4sto_add(app->s_opt_short, optptr, NULL)) { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } szoptspec = 0; } } else { $? "! already exists" if (0 == ae->error_found) { ae->error_found = E_OPTION_SHORT_SPEC_MULT; ae->longopt = optspec->longopt; } szoptspec = 0; } } } else { $? "! memory" if (0 == ae->error_found) { ae->error_found = E_MEMORY; } szoptspec = 0; dk4opt_close(optptr); optptr = NULL; } } else { $? "! memory" if (0 == ae->error_found) { ae->error_found = E_MEMORY; } szoptspec = 0; } } else { $? "! invalid spec" if (0 == ae->error_found) { ae->error_found = E_EMPTY_OPTION_SPEC; } szoptspec = 0; } optspec++; } $? "- dk4app_open_process_optspec" } /** Create and prepare the storage containers for command line options. @param app Application to set up. @param ae Application error structure. @param optspec The option specification. @param szoptspec Size of the optspec array. */ static void dk4app_open_create_options_storages( dk4_app_t *app, dk4_app_error_t *ae, const dk4_option_specification_t *optspec, size_t szoptspec ) { $? "+ dk4app_open_create_options_storages" if ((NULL != optspec) && (0 < szoptspec)) { app->s_opt_all = dk4sto_open(NULL); if (NULL != app->s_opt_all) { app->s_opt_long = dk4sto_open(NULL); if (NULL != app->s_opt_long) { dk4sto_set_comp(app->s_opt_long, dk4option_compare, 2); app->s_opt_short = dk4sto_open(NULL); if (NULL != app->s_opt_short) { dk4sto_set_comp(app->s_opt_short, dk4option_compare, 0); app->i_opt_all = dk4sto_it_open(app->s_opt_all, NULL); if (NULL != app->i_opt_all) { app->i_opt_long = dk4sto_it_open(app->s_opt_long, NULL); if (NULL != app->i_opt_long) { app->i_opt_short = dk4sto_it_open(app->s_opt_short, NULL); if (NULL != app->i_opt_short) { dk4app_open_process_optspec(app, ae, optspec, szoptspec); } else { $? "! memory" ae->error_found = E_MEMORY; } } else { $? "! memory" ae->error_found = E_MEMORY; } } else { $? "! memory" ae->error_found = E_MEMORY; } } else { $? "! memory" ae->error_found = E_MEMORY; } } else { $? "! memory" ae->error_found = E_MEMORY; } } else { $? "! memory" ae->error_found = E_MEMORY; } } $? "- dk4app_open_create_options_storages" } /** Create storage containers for preferences. @param app Application to set up. @param ae Error storage for setup. */ static void dk4app_open_create_pref_storages( dk4_app_t *app, dk4_app_error_t *ae ) { app->s_pref_sys = dk4sto_open(NULL); if (NULL != app->s_pref_sys) { app->s_pref_user = dk4sto_open(NULL); if (NULL != app->s_pref_user) { app->s_pref_cmd = dk4sto_open(NULL); if (NULL != app->s_pref_cmd) { app->s_pref_self = dk4sto_open(NULL); if (NULL != app->s_pref_self) { dk4sto_set_comp(app->s_pref_sys, dk4pref_compare, 0); dk4sto_set_comp(app->s_pref_user, dk4pref_compare, 0); dk4sto_set_comp(app->s_pref_cmd, dk4pref_compare, 0); dk4sto_set_comp(app->s_pref_self, dk4pref_compare, 0); app->i_pref_sys = dk4sto_it_open(app->s_pref_sys, NULL); app->i_pref_user = dk4sto_it_open(app->s_pref_user, NULL); app->i_pref_cmd = dk4sto_it_open(app->s_pref_cmd, NULL); app->i_pref_self = dk4sto_it_open(app->s_pref_self, NULL); if ((NULL == app->i_pref_sys) || (NULL == app->i_pref_user)) { ae->error_found = E_MEMORY; } if ((NULL == app->i_pref_cmd) || (NULL == app->i_pref_self)) { ae->error_found = E_MEMORY; } } else { ae->error_found = E_MEMORY; } } else { ae->error_found = E_MEMORY; } } else { ae->error_found = E_MEMORY; } } else { ae->error_found = E_MEMORY; } } /** Create storage container for string tables. @param app Application to set up. @param ae Error storage for setup. */ static void dk4app_open_create_stt_storage( dk4_app_t *app, dk4_app_error_t *ae ) { app->s_stt = dk4sto_open(NULL); if (NULL != app->s_stt) { dk4sto_set_comp(app->s_stt, dk4stt_compare, 0); app->i_stt = dk4sto_it_open(app->s_stt, NULL); if (NULL == app->i_stt) { ae->error_found = E_MEMORY; } } else { ae->error_found = E_MEMORY; } } /** Attempt to find users login name and home directory. On Linux/Unix the effective user name and home directory is searched. @param app Application to set up. @param ae Application opening error structure. */ static void dk4app_open_find_user_name_and_home(dk4_app_t *app, dk4_app_error_t *ae) { dkChar buf[DK4_MAX_PATH]; dk4_er_t er; $? "+ dk4app_open_find_user_name_and_home" dk4error_init(&er); if (0 != dk4user_get_logname(buf, DK4_SIZEOF(buf,dkChar), 1, &er)) { app->user_name = dk4str_dup(buf, &er); if (NULL != app->user_name) { if (0 != dk4user_get_homedir(buf, DK4_SIZEOF(buf,dkChar), 1, &er)) { app->user_home = dk4str_dup(buf, &er); if (NULL == app->user_home) { ae->error_found = E_MEMORY; } } else { ae->error_found = E_USER_HOME_NOT_FOUND; } } else { ae->error_found = E_MEMORY; } } else { ae->error_found = E_USER_NAME_NOT_FOUND; } $? ". user name = \"%!ds\"", TR_DKSTR(app->user_name) $? ". user home = \"%!ds\"", TR_DKSTR(app->user_home) $? "- dk4app_open_find_user_name_and_home" } /** Preference reader structure. */ typedef struct { dk4_app_t *app; /**< Application to set up. */ dk4_app_error_t *ae; /**< Application error structure. */ dk4_sto_t *st; /**< Storage for preferences. */ dk4_sto_it_t *it; /**< Storage iterator. */ int insc; /**< Flag: In current scope. */ int lasc; /**< Flag: Previous line was scope line. */ int co3; /**< Flag: DK tools 3 compatibility. */ } dk4_app_pref_rd_t; /** Initialize preference reader structure for start of file. @param ptr Preference reader structure. */ static void dk4app_open_pref_reader_init_for_one_file(dk4_app_pref_rd_t *ptr) { ptr->insc = 1; ptr->lasc = 0; ptr->co3 = 0; } /** Check one scope line entry. @param app Application. @param ae Application open error structure. @param pn Pointer to entry name. @param pv Pointer to entry value. @return 1 if condition is true, 0 otherwise. */ static int dk4app_open_check_one_scope_entry( dk4_app_t *app, dk4_app_error_t *ae, const dkChar *pn, const dkChar *pv ) { int back = 0; $? "+ dk4app_open_check_one_scope_entry \"%!ds\"=\"%!ds\"", TR_DKSTR(pn), TR_DKSTR(pv) switch (dk4str_array_index(dk4app_open_scope_names, pn, 0)) { case 0: { if ((NULL != pv) && (NULL != app->user_name)) { $? " user \"%!ds\" == \"%!ds\"", pv, app->user_name #if DK4_HAVE_CASE_INSENSITIVE_USERNAMES if (0 == dk4str_casecmp(pv, app->user_name)) #else if (0 == dk4str_cmp(pv, app->user_name)) #endif { back = 1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; case 1: { if ((NULL != pv) && (NULL != app->prog_name)) { $? ". prog \"%!ds\" == \"%!ds\"", pv, app->prog_name #if DK4_HAVE_CASE_INSENSITIVE_PATHNAMES if (0 == dk4str_casecmp(pv, app->prog_name)) #else if (0 == dk4str_cmp(pv, app->prog_name)) #endif { back = 1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; case 2: { $? ". application group" if ((NULL != pv) && (NULL != app->group_name)) { $? ". prog group \"%!ds\" == \"%!ds\"", pv, app->group_name #if DK4_HAVE_CASE_INSENSITIVE_PATHNAMES if (0 == dk4str_casecmp(pv, app->group_name)) #else if (0 == dk4str_cmp(pv, app->group_name)) #endif { back = 1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; case 3: { $? ". host name" if ((NULL != pv) && (NULL != app->host_name)) { $? ". host \"%!ds\" == \"%!ds\"", pv, app->host_name if (0 == dk4str_casecmp(pv, app->host_name)) { back = 1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; case 4: { $? ". test: on Windows" if (NULL != pv) { #if DK4_ON_WINDOWS if (0 != dk4str_is_on(pv)) { back = 1; } #else if (0 == dk4str_is_on(pv)) { back = 1; } #endif } else { #if DK4_ON_WINDOWS back = 1; #endif } } break; case 5: { $? ". test: non-Windows" if (NULL != pv) { #if DK4_ON_WINDOWS if (0 == dk4str_is_on(pv)) { back = 1; } #else if (0 != dk4str_is_on(pv)) { back = 1; } #endif } else { #if DK4_ON_WINDOWS back = 0; #else back = 1; #endif } } break; case 6 : { $? ". language" if ((NULL != pv) && (NULL != app->language)) { if (0 == dk4str_casecmp(pv, app->language)) { back =1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; case 7 : { $? ". region" if ((NULL != pv) && (NULL != app->region)) { if (0 == dk4str_casecmp(pv, app->region)) { back =1; } else { if (0 == dk4str_cmp(pv, dk4app_open_kw[15])) { back = 1; } } } } break; default: { $? ". unknown name" if (0 == dk4str_len(pn)) { $? ". empty name" back = 1; } } break; } $? "- dk4app_open_check_one_scope_entry %d", back return back; } /** Add one preference to storages. @param app Application to configure. @param ae Application open error structure. @param st Storage. @param it Iterator for storage. @param pn Preference name. @param pv Preference value. */ static void dk4app_open_add_preference( dk4_app_t *app, dk4_app_error_t *ae, dk4_sto_t *st, dk4_sto_it_t *it, const dkChar *pn, const dkChar *pv ) { dk4_pref_t *prefptr; dkChar *npv; $? "+ dk4app_open_add_preference \"%!ds\"=\"%!ds\"", TR_DKSTR(pn), TR_DKSTR(pv) prefptr = dk4sto_it_find_like(it, pn, 1); if (NULL != prefptr) { $? ". replace existing value" npv = dk4str_dup(pv, NULL); if (NULL != npv) { #if 0 dk4mem_release(prefptr->value); #endif /* 2016-12-10 Sanitize memory before releasing it. */ dk4str_release_sanitized(prefptr->value); prefptr->value = npv; } else { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { $? ". add new value" prefptr = dk4pref_open(pn, pv, NULL); if (NULL != prefptr) { if (0 == dk4sto_add(st, (void *)prefptr, NULL)) { dk4pref_close(prefptr); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } dk4app_pref_apply(app, ae, pn, pv); $? "- dk4app_open_add_preference" } /** Modify a preference value from DK tools 3 to 4 syntax. Change "/print/ps/level" to "print.ps.level". @param Preference name pointer. */ static void dk4app_open_modify_prefname_3_to_4(dkChar *pn) { if (NULL != pn) { if (dkT('/') == *pn) { dk4str_cpy_to_left(pn, &(pn[1])); while (dkT('\0') != *pn) { if (dkT('/') == *pn) { *pn = dkT('.'); } pn++; } } } } /** Process one input line. @param obj Preferences reader object. @param line Current line to process. @param lineno Line number. @param erp Error report, may be NULL. @return DK4_TSP_RES_OK if processing succeeded, DK4_TSP_RES_ERROR on errors (can continue), DK4_TSP_RES_FATAL on fatal errors (can not continue). */ static int dk4app_open_pref_line_handler( void *obj, dkChar *line, dk4_um_t lineno, dk4_er_t *erp ) { dkChar *p1; /* Name text */ dkChar *p2; /* Next text */ dkChar *pv; /* Value text */ dk4_app_pref_rd_t *ptr; /* Reader structure */ int back = DK4_TSP_RES_OK; /* Function result */ int isneg = 0; /* Flag: Exclude scope */ int ok; /* Flag: No test failed yet */ $? "+ dk4app_open_pref_line_handler \"%!ds\"", line dk4str_delnl(line); ptr = (dk4_app_pref_rd_t *)obj; p1 = dk4str_start(line, NULL); if (NULL != p1) { if (dkT('#') != *p1) { $? ". data line" if (dkT('[' == *p1)) { $? ". scope line" if (0 == ptr->lasc) { $? ". start of scope section" ptr->insc = 0; } else { $? ". continued scope section" } p2 = dk4str_chr(p1, dkT(']')); if (NULL != p2) { $? ". end of scope found" *p2 = dkT('\0'); } p1++; if (dkT('-') == *p1) { $? ". negative scope line" isneg = 1; p1++; } ok = 1; while (NULL != p1) { p2 = dk4str_chr(p1, dkT(',')); if (NULL != p2) { *(p2++) = dkT('\0'); p2 = dk4str_start(p2, NULL); } pv = dk4str_chr(p1, dkT('=')); if (NULL != pv) { *(pv++) = dkT('\0'); pv = dk4str_start(pv, NULL); } dk4str_normalize(p1, NULL); if (NULL != pv) { dk4str_rtwh(pv, NULL); } if (0 == dk4app_open_check_one_scope_entry(ptr->app,ptr->ae,p1,pv)) { ok = 0; } p1 = p2; } if (1 == ok) { $? ". no test failed" if (0 != isneg) { ptr->insc = 0; $? ". turn scope off" } else { ptr->insc = 1; $? ". turn scope on" } } else { $? ". there were failed tests" } ptr->lasc = 1; } else { $? ". attribute line" if (ptr->insc) { $? ". in scope, process line" p2 = dk4str_chr(p1, dkT('=')); if (NULL != p2) { *(p2++) = dkT('\0'); p2 = dk4str_start(p2, NULL); } if ((NULL != p1) && (NULL != p2)) { dk4str_normalize(p1, NULL); dk4str_rtwh(p2, NULL); if (0 < ptr->co3) { $? ". compatibility mode" dk4app_open_modify_prefname_3_to_4(p1); $? ". preference name now \"%!ds\"", p1 } else { $? ". no compatibility mode" } dk4app_open_add_preference(ptr->app,ptr->ae,ptr->st,ptr->it,p1,p2); } } else { $? ". out of scope, ignore line" } ptr->lasc = 0; } } else { $? ". comment line, ignore" } } else { $? ". empty line, ignore" } $? "- dk4app_open_pref_line_handler %d", back return back; } /** Read all preferences files in increasing order. Cumulative operation, redefinitions possible. @param app Application to configure. @param ae Application opening error structure. */ static void dk4app_open_read_preference_files(dk4_app_t *app, dk4_app_error_t *ae) { char ib[4096]; /* Input buffer */ dkChar il[1024]; /* Input line */ dkChar fnb[DK4_MAX_PATH]; /* File name buffer */ dk4_tspdk_t tsp; /* Text stream processor */ dk4_er_t er; /* Error report */ dk4_app_pref_rd_t rdobj; /* Reader object */ dk4_stream_t *istrm; /* Input stream */ size_t rb; /* Bytes read from stream */ int pn; /* Pass number */ int res; /* Result from path construct */ int cc; /* Flag: Can continue */ $? "+ dk4app_open_read_preference_files" rdobj.app = app; rdobj.ae = ae; for (pn = 0; ((0 == ae->error_found) && (pn <= DK4_FS_CONF_MAX)); pn++) { if (DK4_FS_CONF_SYS_MAX >= pn) { rdobj.st = app->s_pref_sys; rdobj.it = app->i_pref_sys; } else { rdobj.st = app->s_pref_user; rdobj.it = app->i_pref_user; } #if DK4_HAVE_COMPATDKTOOLS3 dk4error_init(&er); res = dk4fs_config_compat_one( fnb, DK4_SIZEOF(fnb,dkChar), dk4app_open_kw[14], app->dir_share, app->dir_etc, app->user_home, app->prog_name, app->group_name, pn, 1, 1, &er ); if (0 != res) { $? ". file = \"%!ds\"", fnb istrm = dk4stream_open_file_reader(fnb, &er); if (NULL != istrm) { $? ". stream opened" dk4app_open_pref_reader_init_for_one_file(&rdobj); rdobj.co3 = 1; res = dk4tspdk_setup_line( &tsp, &rdobj, dk4app_open_pref_line_handler, il, DK4_SIZEOF(il,dkChar), app->encoding, app->enc_in_file, &er ); if (0 != res) { $? ". line processor initialized" res = DK4_TSP_RES_OK; do { cc = 0; rb = sizeof(ib); if (0 != dk4stream_read(ib, &rb, istrm, &er)) { if (0 < rb) { cc = 1; res = dk4tspdk_add_bytes(&tsp, (const unsigned char *)ib, rb); if (DK4_TSP_RES_FATAL == res) { cc = 0; } } } } while(0 != cc); if (DK4_TSP_RES_FATAL != res) { #if VERSION_BEFORE_20150821 res = dk4tspdk_finish(&tsp); #else dk4tspdk_finish(&tsp); #endif } } else { $? "! failed to initialize line proc" } dk4stream_close(istrm, NULL); } else { $? "! failed to open stream" if (0 == ae->error_found) { ae->error_found = E_PREF_FILE_OPEN; } } } #endif dk4error_init(&er); res = dk4fs_config_one( fnb, DK4_SIZEOF(fnb,dkChar), dk4app_open_kw[8], app->dir_share, app->dir_etc, app->user_home, app->prog_name, app->group_name, pn, 1, &er ); if (0 != res) { $? ". file = \"%!ds\"", fnb istrm = dk4stream_open_file_reader(fnb, &er); if (NULL != istrm) { $? ". stream opened" dk4app_open_pref_reader_init_for_one_file(&rdobj); res = dk4tspdk_setup_line( &tsp, &rdobj, dk4app_open_pref_line_handler, il, DK4_SIZEOF(il,dkChar), app->encoding, app->enc_in_file, &er ); if (0 != res) { $? ". line processor initialized" res = DK4_TSP_RES_OK; do { cc = 0; rb = sizeof(ib); if (0 != dk4stream_read(ib, &rb, istrm, &er)) { if (0 < rb) { cc = 1; res = dk4tspdk_add_bytes(&tsp, (const unsigned char *)ib, rb); if (DK4_TSP_RES_FATAL == res) { cc = 0; } } } } while(0 != cc); if (DK4_TSP_RES_FATAL != res) { #if VERSION_BEFORE_20150821 res = dk4tspdk_finish(&tsp); #else dk4tspdk_finish(&tsp); #endif } } else { $? "! failed to initialize line proc" } dk4stream_close(istrm, NULL); } else { $? "! failed to open stream" if (0 == ae->error_found) { ae->error_found = E_PREF_FILE_OPEN; } } } } $? "- dk4app_open_read_preference_files" } /** Find host name for computer. @param app Application to set up. @param ae Application opening error structure. */ static void dk4app_open_find_host_name(dk4_app_t *app, dk4_app_error_t *ae) { dkChar buf[256]; if (0 != dk4hostname(buf, DK4_SIZEOF(buf,dkChar), NULL)) { app->host_name = dk4str_dup(buf, NULL); if (NULL == app->host_name) { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { if (0 == ae->error_found) { ae->error_found = E_HOST_NAME_NOT_FOUND; } } } /** Check whether an option specification was given and processed successfully. @param app Application to set up. @return 1 if option specification given, 0 otherwise. */ static int dk4app_open_options_check(dk4_app_t *app) { int back = 0; if ((NULL != app->s_opt_all) && (NULL != app->i_opt_all)) { if ((NULL != app->s_opt_short) && (NULL != app->i_opt_short)) { if ((NULL != app->s_opt_long) && (NULL != app->i_opt_long)) { back = 1; } } } return back; } /** Check for --help --version --license. @param app Application to set up. @param ae Application open error structure. @param arg Argument to process. @return 1 if the option needs no further processing, 0 otherwise. */ static int dk4app_open_check_long_opt(dk4_app_t *app, dk4_app_error_t *ae, dkChar *arg) { int back = 0; switch (dk4str_array_index(dk4app_open_long_opt, arg, 1)) { case 0: { /* help */ app->hvl_cmd |= DK4_APP_CMD_HELP; back = 1; } break; case 1: { /* version */ app->hvl_cmd |= DK4_APP_CMD_VERSION; back = 1; } break; case 2: { /* license */ app->hvl_cmd = DK4_APP_CMD_LICENSE; back = 1; } break; case 3: { back = 1; } break; case 4: { app->hvl_cmd |= DK4_APP_CMD_MANUAL; back = 1; } break; } return back; } /** Construct log file name, create log directory. @param app Application to set up. @param ae Application open error structure. */ static void dk4app_open_prepare_file_logging(dk4_app_t *app, dk4_app_error_t *ae) { dkChar fnb[DK4_MAX_PATH]; /* File name buffer */ dkChar dktb[32]; /* Date as dkChar text */ dkChar pidb[32]; /* Buffer for PID */ dkChar pi2b[32]; /* Buffer for padded PID */ dkChar tb[32]; /* Date as text */ dkChar *ptr; dk4_time_t timer; /* Current time */ dk4_pid_t pid; /* Process ID */ size_t sz; /* Size of fnb */ size_t szdktb; /* Size of dktb */ size_t sl; /* String length */ size_t pl; /* Padded length */ int ok = 0; $? "+ dk4app_open_prepare_file_logging" sz = DK4_SIZEOF(fnb,dkChar); dk4time_get(&timer); if (dk4str_cpy_s(fnb, sz, app->user_home, NULL)) { if (dk4str_cat_s(fnb, sz, dk4app_open_kw[9], NULL)) { dk4path_correct_sep(fnb); #if VERSION_BEFORE_20160127 ok = dk4app_mkdir_hierarchy(fnb, 1, app); #else ok = 1; #endif if (0 < ok) { ok = 0; if (dk4str_cat_s(fnb, sz, dk4app_open_kw[10], NULL)) { if (dk4str_cat_s(fnb, sz, app->prog_name, NULL)) { if (dk4time_as_text(tb, DK4_SIZEOF(tb,dkChar), &timer, NULL)) { szdktb = DK4_SIZEOF(dktb,dkChar); if (dk4str_cpy_s(dktb, szdktb, tb, NULL)) { ptr = dktb; while (dkT('\0') != *ptr) { if (dkT(':') == *ptr) { *ptr = dkT('-'); } if (dkT(' ') == *ptr) { *ptr = dkT('-'); } ptr++; } pid = dk4getpid(); ok = dk4ma_write_decimal_unsigned( pidb, DK4_SIZEOF(pidb,dkChar), (dk4_um_t)pid, 0, NULL ); if (0 != ok) { for (sl = 0; sl < DK4_SIZEOF(pi2b,dkChar); sl++) { pi2b[sl] = dkT('0'); } pi2b[DK4_SIZEOF(pi2b,dkChar)-1] = dkT('\0'); sl = dk4str_len(pidb); pl = dk4numlength(sizeof(dk4_pid_t), 0); if (sl < pl) { ok = dk4str_cpy_s( &(pi2b[pl - sl]), (DK4_SIZEOF(pi2b,dkChar) - (pl - sl)), pidb, NULL ); } else { ok = dk4str_cpy_s(pi2b,DK4_SIZEOF(pi2b,dkChar),pidb,NULL); } if (0 != ok) { ok = 0; if (dk4str_cat_s(fnb, sz, dk4app_open_kw[11], NULL)) { if (dk4str_cat_s(fnb, sz, dktb, NULL)) { if (dk4str_cat_s(fnb, sz, dk4app_open_kw[11], NULL)) { if (dk4str_cat_s(fnb, sz, pi2b, NULL)) { if (dk4str_cat_s(fnb,sz,dk4app_open_kw[12],NULL)) { app->log_file_name = dk4str_dup(fnb,NULL); if (NULL != app->log_file_name) { dk4path_correct_sep(app->log_file_name); ok = 1; $? ". log file = \"%!ds\"", fnb } } } } } } } if (0 != ok) { ok = 0; (void)dk4str_cpy_s(fnb, sz, app->user_home, NULL); (void)dk4str_cat_s(fnb, sz, dk4app_open_kw[13], NULL); (void)dk4str_cat_s(fnb, sz, dk4app_open_kw[10], NULL); (void)dk4str_cat_s(fnb, sz, app->prog_name, NULL); (void)dk4str_cat_s(fnb, sz, dk4app_open_kw[11], NULL); (void)dk4str_cat_s(fnb, sz, dktb, NULL); (void)dk4str_cat_s(fnb, sz, dk4app_open_kw[11], NULL); (void)dk4str_cat_s(fnb, sz, pi2b, NULL); dk4path_correct_sep(fnb); app->dir_tmp = dk4str_dup(fnb, NULL); if (NULL != app->dir_tmp) { #if VERSION_BEFORE_20160127 ok = dk4app_mkdir_hierarchy(app->dir_tmp, 0, app); #else ok = 1; #endif $? ". tmp dir = \"%!ds\"", fnb } } } } } } } } } } if (0 == ok) { /* ERROR: Failed to construct log file name */ ae->error_found = E_LOG_FILE_NAME; } $? "- dk4app_open_prepare_file_logging %d", ok } /** Process command line arguments. Save all non-option arguments in my_argc and my_argv if an option specification available, all arguments otherwise. @param app Application to set up. @param ae Application open error structure. @param argc Number of command line arguments. @param argv Command line arguments array. */ static void dk4app_open_process_argc_argv( dk4_app_t *app, dk4_app_error_t *ae, int argc, dkChar **argv ) { dkChar b[DK4_MAX_PATH + 128]; /* Private copy */ dkChar *argptr; /* Current option to process. */ dkChar *pv; /* Option value pointer */ dkChar *opv; /* Original option value pointer */ const dkChar *endptr; /* First char after value */ dk4_option_t *ospec; /* Option specification */ dk4_pref_t *prefptr; /* Preference */ double dval; /* Double value */ dk4_um_t uval; /* Unsigned integer value */ dk4_im_t ival; /* Integer value */ size_t szval; /* Size argument */ int i; /* Current option index to process */ int argsused; /* used elements in my_argv */ int res; /* Numeric conversion result */ int must_add; /* Flag: Must add to list */ int oi; /* Output index */ $? "+ dk4app_open_process_argc_argv" if (0 < argc) { $? ". argc ok" app->my_argv = dk4mem_new(DK4_PDKCHAR,argc,NULL); if (NULL != app->my_argv) { $? ". my_argv ok" for (i = 0; i < argc; i++) { (app->my_argv)[i] = NULL; } if (dk4app_open_options_check(app)) { $? ". have option spec" argsused = 0; for (i = 1; i < argc; i++) { argptr = argv[i]; $? ". processing %d = \"%!ds\"", i, argptr if (dkT('-') == *argptr) { $? ". option" if (dkT('-') == argptr[1]) { $? ". long option" if (0 == dk4app_open_check_long_opt(app, ae, &(argptr[2]))) { if (0 != dk4str_cpy_s(b,DK4_SIZEOF(b,dkChar),&(argptr[2]),NULL)) { $? ". copied to private buffer" opv = NULL; pv = dk4str_chr(b, dkT('=')); if (NULL != pv) { *(pv++) = dkT('\0'); pv = dk4str_start(pv, NULL); } if (NULL != pv) { opv = dk4str_chr(&(argptr[2]), dkT('=')); if (NULL != opv) { opv = dk4str_start(++opv, NULL); } } ospec = dk4sto_it_find_like( app->i_opt_long, (void *)b, 3 ); $? "b=\"%!ds\" pv=\"%!ds\" opv=\"%!ds\"",b,TR_DKSTR(pv),TR_DKSTR(opv) if (NULL != ospec) { $? ". long option" ospec->found = 1; switch ((ospec->spec).argtype) { case DK4_OPT_ARG_BOOL: case DK4_OPT_ARG_SIZE: case DK4_OPT_ARG_INT: case DK4_OPT_ARG_UNSIGNED: case DK4_OPT_ARG_DOUBLE: case DK4_OPT_ARG_STRING: { $? ". argument required" if (NULL == opv) { $? ". attempt next arg" if ((1 + i) < argc) { $? ". use next arg" opv = argv[++i]; } } if (NULL != opv) { $? ". opv argument ok" switch ((ospec->spec).argtype) { case DK4_OPT_ARG_BOOL: { (ospec->val).b = 0; if (dk4str_is_bool(opv)) { $? ". book ok" (ospec->val).b = dk4str_is_on(opv); } else { $? "! bool" /* ERROR: Not a boolean argument */ dk4app_log_base5( app, DK4_LL_ERROR, 33, 14, 15, b, opv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_SIZE: { (ospec->val).s = 0; szval = 0; endptr = NULL; res = dk4ma_input_dk_dec_size_t( &szval, opv, &endptr, 1, NULL ); if (0 != res) { $? ". size ok" (ospec->val).s = szval; } else { $? "! size" /* ERROR: Not a size specification */ dk4app_log_base7( app, DK4_LL_ERROR, 32, 17, 154, 18, b, endptr, opv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_INT: { ival = (dk4_im_t)0L; (ospec->val).i = (dk4_im_t)0L; endptr = NULL; res = dk4ma_input_dk_dec_dk4_im_t( &ival, opv, &endptr, 1, NULL ); if (0 != res) { $? ". int ok" (ospec->val).i = ival; } else { $? "! int" /* ERROR: Not an integer value */ dk4app_log_base7( app, DK4_LL_ERROR, 31, 20, 154, 21, b, endptr, opv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_UNSIGNED: { uval = (dk4_um_t)0UL; (ospec->val).u = (dk4_um_t)0UL; endptr = NULL; res = dk4ma_input_dk_dec_dk4_um_t( &uval, opv, &endptr, 1, NULL ); if (0 != res) { $? ". unsigned ok" (ospec->val).u = uval; } else { $? "! unsigned" /* ERROR: Not an unsigned integer value */ dk4app_log_base7( app, DK4_LL_ERROR, 30, 23, 154, 24, b, endptr, opv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_DOUBLE: { $? ". double" dval = 0.0; (ospec->val).d = 0.0; endptr = NULL; res = dk4ma_input_dk_double( &dval, opv, &endptr, 1, NULL ); if (0 != res) { $? ". double ok" (ospec->val).d = dval; } else { $? "! double" dk4app_log_base7( app, DK4_LL_ERROR, 29, 26, 154, 27, b, endptr, opv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_STRING: { $? ". string ok" (ospec->val).t = opv; } break; } } else { $? "! no argument found" /* ERROR: Option requires argument */ dk4app_log_base3( app, DK4_LL_ERROR, 28, 12, b ); if (0 == ae->error_found) { ae->error_found = E_OPT_REQUIRES_ARG; } } } break; } } else { $? ". preference" /* Handle preference */ if (NULL != pv) { $? ". b=\"%!ds\" pv=\"%!ds\"", b, pv prefptr = dk4sto_it_find_like(app->i_pref_cmd, b, 1); if (NULL != prefptr) { $? ". entry already exists" opv = dk4str_dup(pv, NULL); if (NULL != opv) { $? ". new value allocated" #if 0 dk4mem_release(prefptr->value); #endif /* 2016-12-10 Sanitize memory. */ dk4str_release_sanitized(prefptr->value); prefptr->value = opv; } else { $? "! new value" /* ERROR: Failed to save preference value */ dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { $? ". entry not yet found" prefptr = dk4pref_open(b, pv, NULL); if (NULL != prefptr) { $? ". new pref created" if (0 != dk4sto_add(app->s_pref_cmd, prefptr, NULL)) { $? ". new pref added successfully" } else { $? "! failed to add new pref" /* ERROR: Failed to save preference value */ dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); dk4pref_close(prefptr); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { $? "! new pref" /* ERROR: Failed to save preference value */ dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } dk4app_pref_apply(app, ae, b, pv); } else { $? "! missing pref value" /* ERROR: Missing preference value */ dk4app_log_base3(app, DK4_LL_ERROR, 37, 38, b); if (0 == ae->error_found) { ae->error_found = E_OPT_REQUIRES_ARG; } } } } else { $? "! failed to create copy" /* ERROR: Option too long! */ dk4app_log_base3(app, DK4_LL_ERROR, 39, 40, argptr); if (0 == ae->error_found) { ae->error_found = E_OPTION_TOO_LONG; } } } } else { $? ". short option" ospec = dk4sto_it_find_like( app->i_opt_short, (void *)(&(argptr[1])), 1 ); if (NULL != ospec) { $? ". option expected" ospec->found = 1; pv = NULL; switch ((ospec->spec).argtype) { case DK4_OPT_ARG_BOOL: case DK4_OPT_ARG_SIZE: case DK4_OPT_ARG_INT: case DK4_OPT_ARG_UNSIGNED: case DK4_OPT_ARG_DOUBLE: case DK4_OPT_ARG_STRING: { $? ". argument required" pv = dk4str_start(&(argptr[2]), NULL); if (NULL == pv) { $? ". attempt next argv" if ((1 + i) < argc) { $? ". use next argv" pv = argv[++i]; } } if (NULL != pv) { switch ((ospec->spec).argtype) { case DK4_OPT_ARG_BOOL: { (ospec->val).b = 0; if (dk4str_is_bool(pv)) { $? ". bool ok" (ospec->val).b = dk4str_is_on(pv); } else { $? "! bool" /* ERROR: Not a boolean argument */ dk4app_log_base5( app, DK4_LL_ERROR, 13, 14, 15, argptr, pv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_SIZE: { (ospec->val).s = 0; szval = 0; endptr = NULL; res = dk4ma_input_dk_dec_size_t( &szval, pv, &endptr, 1, NULL ); if (0 != res) { $? ". size ok" (ospec->val).s = szval; } else { $? "! size" /* ERROR: Not a size specification */ dk4app_log_base7( app, DK4_LL_ERROR, 16, 17, 154, 18, argptr, endptr, pv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_INT: { ival = (dk4_im_t)0L; (ospec->val).i = (dk4_im_t)0L; endptr = NULL; res = dk4ma_input_dk_dec_dk4_im_t( &ival, pv, &endptr, 1, NULL ); if (0 != res) { $? ". int ok" (ospec->val).i = ival; } else { $? "! int" /* ERROR: Not an integer value */ dk4app_log_base7( app, DK4_LL_ERROR, 19, 20, 154, 21, argptr, endptr, pv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_UNSIGNED: { uval = (dk4_um_t)0UL; (ospec->val).u = (dk4_um_t)0UL; endptr = NULL; res = dk4ma_input_dk_dec_dk4_um_t( &uval, pv, &endptr, 1, NULL ); if (0 != res) { $? ". unsigned ok" (ospec->val).u = uval; } else { $? "! unsigned" /* ERROR: Not an unsigned integer value */ dk4app_log_base7( app, DK4_LL_ERROR, 22, 23, 154, 24, argptr, endptr, pv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_DOUBLE: { $? ". double" dval = 0.0; (ospec->val).d = 0.0; endptr = NULL; res = dk4ma_input_dk_double( &dval, pv, &endptr, 1, NULL ); if (0 != res) { $? ". double ok" (ospec->val).d = dval; } else { $? "! double" dk4app_log_base7( app, DK4_LL_ERROR, 25, 26, 154, 27, argptr, endptr, pv ); if (0 == ae->error_found) { ae->error_found = E_INVALID_OPTION_ARG; } } } break; case DK4_OPT_ARG_STRING: { $? ". string ok" (ospec->val).t = pv; } break; } } else { $? "! required argument not found" /* ERROR: Option requires argument */ dk4app_log_base3( app, DK4_LL_ERROR, 11, 12, argptr ); if (0 == ae->error_found) { ae->error_found = E_OPT_REQUIRES_ARG; } } } break; } } else { $? "! unexpected option" /* ERROR: Unexpected option */ dk4app_log_base3(app, DK4_LL_ERROR, 9, 10, argptr); if (0 == ae->error_found) { ae->error_found = E_UNEXPECTED_OPTION; } } } } else { $? ". file name" (app->my_argv)[argsused++] = argptr; } } app->my_argc = argsused; } else { $? ". no option spec" oi = 0; for (i = 0; (i + 1) < argc; i++) { must_add = 1; if (dkT('-') == (argv[i + 1])[0]) { if (dkT('-') == (argv[i + 1])[1]) { if (0 == dk4app_open_check_long_opt(app, ae, &((argv[i+1])[2]))) { /* Treat all long options as preferences. */ must_add = dk4str_cpy_s( b, DK4_SIZEOF(b,dkChar), &((argv[i + 1])[2]), NULL ); if (0 != must_add) { opv = NULL; pv = dk4str_chr(b, dkT('=')); if (NULL != pv) { *(pv++) = dkT('\0'); pv = dk4str_start(pv, NULL); } if (NULL != pv) { opv = dk4str_chr(&((argv[i + 1])[2]), dkT('=')); if (NULL != opv) { opv = dk4str_start(++opv, NULL); } #ifdef __clang_analyzer__ /* 2015-08-21 / 2016-01-20 Static code analysis by clang complains opv is set but not used later. Probably true. */ (void)opv; #endif } if (NULL != pv) { prefptr = dk4sto_it_find_like(app->i_pref_cmd, b, 1); if (NULL != prefptr) { opv = dk4str_dup(pv, NULL); if (NULL != opv) { #if 0 dk4mem_release(prefptr->value); #endif /* 2016-12-10 Sanitize memory. */ dk4str_release_sanitized(prefptr->value); prefptr->value = opv; } else { dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { prefptr = dk4pref_open(b, pv, NULL); if (NULL != prefptr) { if (0 == dk4sto_add(app->s_pref_cmd, prefptr, NULL)) { dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); dk4pref_close(prefptr); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { dk4app_log_base5( app, DK4_LL_ERROR, 34, 35, 36, b, pv ); if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } dk4app_pref_apply(app, ae, b, pv); } else { dk4app_log_base3(app, DK4_LL_ERROR, 37, 38, b); if (0 == ae->error_found) { ae->error_found = E_OPT_REQUIRES_ARG; } } } else { /* ERROR: Option too long! */ dk4app_log_base3(app, DK4_LL_ERROR, 39, 40, argv[i + 1]); if (0 == ae->error_found) { ae->error_found = E_OPTION_TOO_LONG; } } } must_add = 0; } } if (0 != must_add) { (app->my_argv)[oi++] = argv[i + 1]; } } app->my_argc = oi; $? ". argc,argv copied" } } else { $? "! my_argv" if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } else { $? "! argc" if (0 == ae->error_found) { ae->error_found = E_ARGC; } } $? "- dk4app_open_process_argc_argv" } /** Keep private copies of version text, help file name, help text and license text. @param app @param ae @param version @param helpfile @param helptext @param lictext */ static void dk4app_open_save_names( dk4_app_t *app, dk4_app_error_t *ae, const dkChar *version, const dkChar *helpfile, const dkChar * const *helptext, const dkChar * const *lictext ) { int ok = 0; if (NULL != version) { if (NULL != helpfile) { if (NULL != helptext) { if (NULL != lictext) { ok = 1; app->help_text = helptext; app->lic_text = lictext; app->vers_text = dk4str_dup(version, NULL); app->help_file_name = dk4str_dup(helpfile, NULL); if ((NULL == app->vers_text) || (NULL == app->help_file_name)) { if (0 == ae->error_found) { ae->error_found = E_MEMORY; } } } } } } if (0 == ok) { if (0 == ae->error_found) { ae->error_found = E_BUG_ARGUMENTS; } } } /** Open an application structure for a command line program. @param argc Number of command line arguments. @param argv Command line arguments array. @param optspec Option specification, may be NULL. @param szoptspec Number of elements in optspec. @param groupname Program group name. @param version Version number string. @param helpfile Help file name. @param helptext Default help text. @param lictext License conditions text. @param enablog Flag: Enable logging. @return Pointer to dk4_app_t structure on success, NULL on error. */ static dk4_app_t * dk4app_open_internally( int argc, dkChar **argv, const dk4_option_specification_t *optspec, size_t szoptspec, const dkChar *groupname, const dkChar *version, const dkChar *helpfile, const dkChar * const *helptext, const dkChar * const *lictext, int enablog ) { dk4_app_error_t ae; /* Error during startup */ dk4_app_t *back = NULL; /* Function result */ int quiet = 0; /* Flag: Run quiet */ $? "+ dk4app_open_cmd" DK4_MEMRES(&ae, sizeof(dk4_app_error_t)); ae.error_found = 0; if (NULL != groupname) { if ((0 < argc) && (NULL != argv)) { quiet = dk4app_open_silence_check(argc, argv); back = dk4mem_new(dk4_app_t,1,NULL); if (NULL != back) { /* Set all members to reasonable default */ dk4app_open_initialize(back); back->group_name = dk4str_dup(groupname, NULL); if (NULL == back->group_name) { ae.error_found = E_MEMORY; } if (0 == ae.error_found) { dk4app_open_save_names( back, &ae, version, helpfile, helptext, lictext ); } /* Find host name */ if (0 == ae.error_found) { dk4app_open_find_host_name(back, &ae); } /* Find user name and home directory */ if (0 == ae.error_found) { dk4app_open_find_user_name_and_home(back, &ae); } /* Find language and region */ if (0 == ae.error_found) { dk4app_open_find_language_region(back, &ae); } /* Find executable path name and derive directory names from it */ if (0 == ae.error_found) { dk4app_open_find_executable(back, &ae, argv[0]); } /* Create option storage from option specification */ if (0 == ae.error_found) { dk4app_open_create_options_storages( back, &ae, optspec, szoptspec ); } /* Create preferences storage */ if (0 == ae.error_found) { dk4app_open_create_pref_storages(back, &ae); } /* Create string table storage */ if (0 == ae.error_found) { dk4app_open_create_stt_storage(back, &ae); } /* Read preference files */ if (0 == ae.error_found) { dk4app_open_read_preference_files(back, &ae); } /* Construct log file name, create log directory */ if ((0 != enablog) && (0 == quiet) && (0 == ae.error_found)) { dk4app_open_prepare_file_logging(back, &ae); } /* Enable logging if not switched to silent mode */ if ((0 != enablog) && (0 == quiet) && (0 == ae.error_found)) { back->log_enabled = 1; } /* We must be able to log, if there are errors */ if (0 == ae.error_found) { /* Process command line arguments, search for options */ dk4app_open_process_argc_argv(back, &ae, argc, argv); } /* If there were serious errors, destroy the structure */ if (ae.error_found) { dk4app_open_report_error(back, &ae); dk4app_close(back); back = NULL; } } else { $? "! allocation failed" if (0 == quiet) { char buf[64]; int res; res = dk4ma_write_c8_decimal_unsigned( buf, sizeof(buf), (dk4_um_t)sizeof(dk4_app_t), 0, NULL ); if (0 != res) { fputs(dk4app_open_c8_kw[1], stderr); fputs(buf, stderr); fputs(dk4app_open_c8_kw[2], stderr); } else { fputs(dk4app_open_c8_kw[0], stderr); } fputc('\n', stderr); fflush(stderr); } } } else { $? "! argc or argv wrong" } } else { $? "! missing group name" } if (NULL != back) { $? ". result ok" if (0 != dk4app_log_do(back, DK4_LL_DEBUG)) { $? ". log startup" (void)dk4app_log_debug_texts(back, NULL); dk4app_log_startup_debug(back); } } $? "- dk4app_open_cmd PTR=%d", TR_IPTR(back) return back; } dk4_app_t * dk4app_open_cmd( int argc, dkChar **argv, const dk4_option_specification_t *optspec, size_t szoptspec, const dkChar *groupname, const dkChar *version, const dkChar *helpfile, const dkChar * const *helptext, const dkChar * const *lictext ) { dk4_app_t *back; back = dk4app_open_internally( argc, argv, optspec, szoptspec, groupname, version, helpfile, helptext, lictext, 1 ); return back; } dk4_app_t * dk4app_open_silent( int argc, dkChar **argv, const dk4_option_specification_t *optspec, size_t szoptspec, const dkChar *groupname, const dkChar *version, const dkChar *helpfile, const dkChar * const *helptext, const dkChar * const *lictext ) { dk4_app_t *back; back = dk4app_open_internally( argc, argv, optspec, szoptspec, groupname, version, helpfile, helptext, lictext, 0 ); return back; } void dk4app_set_fast_close(dk4_app_t *app) { if (NULL != app) { app->fast_close = 1; } }