From 67b35790d6ab8581c96b3182e63eb2c0ffab5123 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Wed, 5 Jan 2022 18:47:28 +0100 Subject: [PATCH] add existing source --- .gitignore | 2 + Makefile | 38 + application/Fsb.c | 3106 ++++++++++++++++++++++++++++++++++++++++++ application/Fsb.h | 272 ++++ application/FsbP.h | 211 +++ application/Makefile | 50 + application/main.c | 71 + application/main.h | 42 + application/player.c | 112 ++ application/player.h | 43 + application/window.c | 317 +++++ application/window.h | 64 + configure | 364 +++++ make/Makefile.mk | 51 + make/clang.mk | 9 + make/configure.vm | 615 +++++++++ make/gcc.mk | 10 + make/mingw.mk | 46 + make/osx.mk | 43 + make/package_unix.sh | 2 + make/project.xml | 27 + make/suncc.mk | 10 + make/toolchain.sh | 181 +++ make/windows.mk | 42 + ucx/Makefile | 62 + ucx/README | 4 + ucx/allocator.c | 60 + ucx/array.c | 467 +++++++ ucx/avl.c | 373 +++++ ucx/buffer.c | 297 ++++ ucx/list.c | 428 ++++++ ucx/logging.c | 117 ++ ucx/map.c | 402 ++++++ ucx/mempool.c | 237 ++++ ucx/properties.c | 264 ++++ ucx/stack.c | 165 +++ ucx/string.c | 807 +++++++++++ ucx/test.c | 91 ++ ucx/ucx.c | 62 + ucx/ucx/allocator.h | 206 +++ ucx/ucx/array.h | 460 +++++++ ucx/ucx/avl.h | 353 +++++ ucx/ucx/buffer.h | 339 +++++ ucx/ucx/list.h | 512 +++++++ ucx/ucx/logging.h | 253 ++++ ucx/ucx/map.h | 549 ++++++++ ucx/ucx/mempool.h | 209 +++ ucx/ucx/properties.h | 221 +++ ucx/ucx/stack.h | 240 ++++ ucx/ucx/string.h | 1201 ++++++++++++++++ ucx/ucx/test.h | 241 ++++ ucx/ucx/ucx.h | 204 +++ ucx/ucx/utils.h | 508 +++++++ ucx/utils.c | 448 ++++++ 54 files changed, 15508 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 application/Fsb.c create mode 100644 application/Fsb.h create mode 100644 application/FsbP.h create mode 100644 application/Makefile create mode 100644 application/main.c create mode 100644 application/main.h create mode 100644 application/player.c create mode 100644 application/player.h create mode 100644 application/window.c create mode 100644 application/window.h create mode 100755 configure create mode 100644 make/Makefile.mk create mode 100644 make/clang.mk create mode 100644 make/configure.vm create mode 100644 make/gcc.mk create mode 100644 make/mingw.mk create mode 100644 make/osx.mk create mode 100755 make/package_unix.sh create mode 100644 make/project.xml create mode 100644 make/suncc.mk create mode 100644 make/toolchain.sh create mode 100644 make/windows.mk create mode 100644 ucx/Makefile create mode 100644 ucx/README create mode 100644 ucx/allocator.c create mode 100644 ucx/array.c create mode 100644 ucx/avl.c create mode 100644 ucx/buffer.c create mode 100644 ucx/list.c create mode 100644 ucx/logging.c create mode 100644 ucx/map.c create mode 100644 ucx/mempool.c create mode 100644 ucx/properties.c create mode 100644 ucx/stack.c create mode 100644 ucx/string.c create mode 100644 ucx/test.c create mode 100644 ucx/ucx.c create mode 100644 ucx/ucx/allocator.h create mode 100644 ucx/ucx/array.h create mode 100644 ucx/ucx/avl.h create mode 100644 ucx/ucx/buffer.h create mode 100644 ucx/ucx/list.h create mode 100644 ucx/ucx/logging.h create mode 100644 ucx/ucx/map.h create mode 100644 ucx/ucx/mempool.h create mode 100644 ucx/ucx/properties.h create mode 100644 ucx/ucx/stack.h create mode 100644 ucx/ucx/string.h create mode 100644 ucx/ucx/test.h create mode 100644 ucx/ucx/ucx.h create mode 100644 ucx/ucx/utils.h create mode 100644 ucx/utils.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a5d528 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +config.mk diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3cd3594 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2022 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +all: config.mk + $(MAKE) -f make/Makefile.mk + +config.mk: + ./configure + +clean: FORCE + rm -fR build/* + +FORCE: diff --git a/application/Fsb.c b/application/Fsb.c new file mode 100644 index 0000000..42cd412 --- /dev/null +++ b/application/Fsb.c @@ -0,0 +1,3106 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +//define FSB_ENABLE_DETAIL + +#include "Fsb.h" +#include "FsbP.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef FSB_ENABLE_DETAIL +#include +#endif + +#define WIDGET_SPACING 5 +#define WINDOW_SPACING 8 + +#define BUTTON_EXTRA_SPACE 4 + +#define DATE_FORMAT_SAME_YEAR "%b %d %H:%M" +#define DATE_FORMAT_OTHER_YEAR "%b %d %Y" + +#define KB_SUFFIX "KiB" +#define MB_SUFFIX "MiB" +#define GB_SUFFIX "GiB" +#define TB_SUFFIX "TiB" + +#define FSB_ERROR_TITLE "Error" +#define FSB_ERROR_CHAR "Character '/' is not allowed in file names" +#define FSB_ERROR_RENAME "Cannot rename file: %s" +#define FSB_ERROR_DELETE "Cannot delete file: %s" +#define FSB_ERROR_CREATE_FOLDER "Cannot create folder: %s" +#define FSB_ERROR_OPEN_DIR "Cannot open directory: %s" + +#define FSB_DETAIL_HEADINGS "Name|Size|Last Modified" + +static void fsb_class_init(void); +static void fsb_class_part_init (WidgetClass wc); +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args); +static void fsb_resize(Widget widget); +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes); +static void fsb_destroy(Widget widget); +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); +static Boolean fsb_acceptfocus(Widget widget, Time *time); + +static void fsb_insert_child(Widget child); + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb); + +static int FSBGlobFilter(const char *a, const char *b); + +static void FSBUpdateTitle(Widget w); + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg); + +static void FSBRename(XnFileSelectionBox fsb, const char *path); +static void FSBDelete(XnFileSelectionBox fsb, const char *path); + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item); + +static char* set_selected_path(XnFileSelectionBox data, XmString item); + +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd); + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback); + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListSelect(Widget fsb, Widget view, const char *item); +static void FileListCleanup(Widget fsb, Widget view, void *userData); +static void FileListDestroy(Widget fsb, Widget view, void *userData); + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count); + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListDetailSelect(Widget fsb, Widget view, const char *item); +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData); +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData); +static void FileListDetailAdjustColWidth(Widget grid); +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth); +#endif + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value); + +static void CreateUI(XnFileSelectionBox w); +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList); +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex); +static void SelectView(XnFileSelectionBox f, int view); + +static char* FSBDialogTitle(Widget w); + +static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData); +static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData); + +static const char* GetHomeDir(void); + +static char* ConcatPath(const char *parent, const char *name); +static char* FileName(char *path); +static char* ParentPath(const char *path); +//static int CheckFileName(const char *name); + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path); +static void filedialog_cleanup_filedata(XnFileSelectionBox data); + +static void pathbar_resize(Widget w, PathBar *p, XtPointer d); + +static XtResource resources[] = { + {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL}, + {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL}, + {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING}, + {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING}, + {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN}, + {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False}, + {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True}, + {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False}, + {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0}, + + {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL}, + {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL}, + {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL}, + + {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"}, + {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL}, + + {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"}, + {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"}, + {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"}, + {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"}, + {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"}, + {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"}, + {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"}, + {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"}, + {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"}, + {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"}, + {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"}, + {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"}, + {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"}, + {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"}, + {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"}, + {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"}, + {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"}, + {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"}, + {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"}, + {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"}, + {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"}, + {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"}, + {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS}, + {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR}, + {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR}, + {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"}, + {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX}, + {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX}, + {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX}, + {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX}, + + {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE}, + {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR}, + {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME}, + {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER}, + {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE}, + {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR} +}; + +static XtActionsRec actionslist[] = { + {"focusIn", FocusInAP}, + {"NULL", NULL} +}; + + +static char defaultTranslations[] = ": focusIn()"; + +static XtResource constraints[] = {}; + +FSBClassRec fsbWidgetClassRec = { + // Core Class + { + (WidgetClass)&xmFormClassRec, + "XnFSB", // class_name + sizeof(FSBRec), // widget_size + fsb_class_init, // class_initialize + fsb_class_part_init, // class_part_initialize + FALSE, // class_inited + fsb_init, // initialize + NULL, // initialize_hook + fsb_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + fsb_destroy, // destroy + fsb_resize, // resize + XtInheritExpose, // expose + fsb_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + fsb_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + defaultTranslations, // tm_table + XtInheritQueryGeometry, // query_geometry + XtInheritDisplayAccelerator, // display_accelerator + NULL, // extension + }, + // Composite Class + { + XtInheritGeometryManager, // geometry_manager + XtInheritChangeManaged, // change_managed + fsb_insert_child, // insert_child + XtInheritDeleteChild, // delete_child + NULL, // extension + }, + // Constraint Class + { + constraints, // resources + XtNumber(constraints), // num_resources + sizeof(XmFormConstraintRec), // constraint_size + NULL, // initialize + NULL, // destroy + NULL, // set_value + NULL, // extension + }, + // XmManager Class + { + XtInheritTranslations, // translations + NULL, // syn_resources + 0, // num_syn_resources + NULL, // syn_constraint_resources + 0, // num_syn_constraint_resources + XmInheritParentProcess, // parent_process + NULL // extension + }, + // XmBulletinBoard + { + FALSE, + NULL, + XmInheritFocusMovedProc, + NULL + }, + // XmForm Class + { + NULL + }, + // FSB Class + { + 0 + } +}; + +WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec; + + +static void fsb_class_init(void) { + +} + +static void fsb_class_part_init (WidgetClass wc) { + FSBClassRec *fsbClass = (FSBClassRec*)wc; + XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass; + + fsbClass->constraint_class.initialize = formClass->constraint_class.initialize; + fsbClass->constraint_class.set_values = formClass->constraint_class.set_values; +} + + +#define STRDUP_RES(a) if(a) a = strdup(a) +#define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a) + +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) { + XnFileSelectionBox fsb = (XnFileSelectionBox)neww; + (xmFormClassRec.core_class.initialize)(request, neww, args, num_args); + + fsb->fsb.disable_set_values = 0; + + STRDUP_RES(fsb->fsb.homePath); + STRDUP_RES(fsb->fsb.selectedPath); + STRDUP_RES(fsb->fsb.currentPath); + STRDUP_RES(fsb->fsb.filterStr); + STRDUP_RES(fsb->fsb.labelListView); + STRDUP_RES(fsb->fsb.labelDetailView); + STRDUP_RES(fsb->fsb.labelOpenFileTitle); + STRDUP_RES(fsb->fsb.labelSaveFileTitle); + XMS_STRDUP_RES(fsb->fsb.labelDirUp); + XMS_STRDUP_RES(fsb->fsb.labelHome); + XMS_STRDUP_RES(fsb->fsb.labelNewFolder); + XMS_STRDUP_RES(fsb->fsb.labelFilterButton); + XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(fsb->fsb.labelDirectories); + XMS_STRDUP_RES(fsb->fsb.labelFiles); + XMS_STRDUP_RES(fsb->fsb.labelRename); + XMS_STRDUP_RES(fsb->fsb.labelDelete); + XMS_STRDUP_RES(fsb->fsb.labelOpen); + XMS_STRDUP_RES(fsb->fsb.labelSave); + XMS_STRDUP_RES(fsb->fsb.labelCancel); + XMS_STRDUP_RES(fsb->fsb.labelHelp); + XMS_STRDUP_RES(fsb->fsb.labelFileName); + XMS_STRDUP_RES(fsb->fsb.labelDirectoryName); + XMS_STRDUP_RES(fsb->fsb.labelNewFileName); + STRDUP_RES(fsb->fsb.labelDeleteFile); + STRDUP_RES(fsb->fsb.detailHeadings); + STRDUP_RES(fsb->fsb.dateFormatSameYear); + STRDUP_RES(fsb->fsb.dateFormatOtherYear); + STRDUP_RES(fsb->fsb.suffixBytes); + STRDUP_RES(fsb->fsb.suffixKB); + STRDUP_RES(fsb->fsb.suffixMB); + STRDUP_RES(fsb->fsb.suffixGB); + STRDUP_RES(fsb->fsb.suffixTB); + STRDUP_RES(fsb->fsb.errorTitle); + STRDUP_RES(fsb->fsb.errorIllegalChar); + STRDUP_RES(fsb->fsb.errorRename); + STRDUP_RES(fsb->fsb.errorFolder); + STRDUP_RES(fsb->fsb.errorDelete); + STRDUP_RES(fsb->fsb.errorOpenDir); + + CreateUI((XnFileSelectionBox)fsb); + + XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL); +} + +#define STR_FREE(a) if(a) free(a) +#define XMSTR_FREE(a) if(a) XmStringFree(a) + +static void fsb_destroy(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + + // destroy all views + for(int i=0;ifsb.numviews;i++) { + FSBView v = w->fsb.view[i]; + v.destroy(widget, v.widget, v.userData); + } + + STR_FREE(w->fsb.homePath); + + // free filelists + filedialog_cleanup_filedata(w); + STR_FREE(w->fsb.currentPath); + STR_FREE(w->fsb.selectedPath); + STR_FREE(w->fsb.filterStr); + + PathBarDestroy(w->fsb.pathBar); + + // free strings + STR_FREE(w->fsb.labelListView); + STR_FREE(w->fsb.labelDetailView); + STR_FREE(w->fsb.labelOpenFileTitle); + STR_FREE(w->fsb.labelSaveFileTitle); + + XMSTR_FREE(w->fsb.labelDirUp); + XMSTR_FREE(w->fsb.labelHome); + XMSTR_FREE(w->fsb.labelNewFolder); + XMSTR_FREE(w->fsb.labelFilterButton); + XMSTR_FREE(w->fsb.labelShowHiddenFiles); + XMSTR_FREE(w->fsb.labelDirectories); + XMSTR_FREE(w->fsb.labelFiles); + XMSTR_FREE(w->fsb.labelRename); + XMSTR_FREE(w->fsb.labelDelete); + XMSTR_FREE(w->fsb.labelOpen); + XMSTR_FREE(w->fsb.labelSave); + XMSTR_FREE(w->fsb.labelCancel); + XMSTR_FREE(w->fsb.labelHelp); + XMSTR_FREE(w->fsb.labelFileName); + XMSTR_FREE(w->fsb.labelDirectoryName); + XMSTR_FREE(w->fsb.labelNewFileName); + STR_FREE(w->fsb.labelDeleteFile); + STR_FREE(w->fsb.detailHeadings); + + STR_FREE(w->fsb.dateFormatSameYear); + STR_FREE(w->fsb.dateFormatOtherYear); + STR_FREE(w->fsb.suffixBytes); + STR_FREE(w->fsb.suffixKB); + STR_FREE(w->fsb.suffixMB); + STR_FREE(w->fsb.suffixGB); + STR_FREE(w->fsb.suffixTB); + + STR_FREE(w->fsb.errorTitle); + STR_FREE(w->fsb.errorIllegalChar); + STR_FREE(w->fsb.errorRename); + STR_FREE(w->fsb.errorFolder); + STR_FREE(w->fsb.errorDelete); + STR_FREE(w->fsb.errorOpenDir); +} + +static void fsb_resize(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.resize)(widget); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.realize)(widget, mask, attributes); + + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void FSBUpdateTitle(Widget w) { + if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) { + char *title = FSBDialogTitle(w); + XtVaSetValues(XtParent(w), XmNtitle, title, NULL); + } +} + +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + Boolean r = False; + + XnFileSelectionBox o = (XnFileSelectionBox)old; + XnFileSelectionBox n = (XnFileSelectionBox)neww; + + int setOkBtnLabel = 0; + int ismanaged = XtIsManaged(neww); + Dimension width, height; + if(!ismanaged) { + width = n->core.width; + height = n->core.height; + if(n->fsb.pathBar) { + n->fsb.pathBar->disableResize = True; + } + } + + if(o->fsb.selectedview != n->fsb.selectedview) { + int selectedview = n->fsb.selectedview; + n->fsb.selectedview = o->fsb.selectedview; + SelectView(n, selectedview); + } + + char *updateDir = NULL; + int selectItem = 0; + if(o->fsb.selectedPath != n->fsb.selectedPath) { + STR_FREE(o->fsb.selectedPath); + STRDUP_RES(n->fsb.selectedPath); + XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath)); + // also update current directory + updateDir = ParentPath(n->fsb.selectedPath); + selectItem = 1; + } + if(o->fsb.currentPath != n->fsb.currentPath) { + STR_FREE(o->fsb.currentPath); + updateDir = strdup(n->fsb.currentPath); + n->fsb.currentPath = NULL; + } + + if(o->fsb.filterStr != n->fsb.filterStr) { + STR_FREE(o->fsb.filterStr); + STRDUP_RES(n->fsb.filterStr); + XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr); + if(!updateDir) { + filedialog_update_dir(n, NULL); + } + } + + if(updateDir) { + filedialog_update_dir(n, updateDir); + PathBarSetPath(n->fsb.pathBar, updateDir); + free(updateDir); + } + + if(o->fsb.type != n->fsb.type) { + if(n->fsb.type == FILEDIALOG_OPEN) { + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL); + XtUnmanageChild(n->fsb.name); + XtUnmanageChild(n->fsb.nameLabel); + } else { + XtManageChild(n->fsb.name); + XtManageChild(n->fsb.nameLabel); + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL); + } + FSBUpdateTitle(neww); + setOkBtnLabel = 1; + } + + // label strings + int updateTitle = 0; + if(o->fsb.labelListView != n->fsb.labelListView) { + STR_FREE(o->fsb.labelListView); + STRDUP_RES(n->fsb.labelListView); + XmString label = XmStringCreateLocalized(n->fsb.labelListView); + XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL); + XmStringFree(label); + } + if(o->fsb.labelDetailView != n->fsb.labelDetailView) { + STR_FREE(o->fsb.labelDetailView); + STRDUP_RES(n->fsb.labelDetailView); + XmString label = XmStringCreateLocalized(n->fsb.labelDetailView); + XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL); + if(n->fsb.detailToggleButton) { + XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL); + } + XmStringFree(label); + } + if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) { + STR_FREE(o->fsb.labelOpenFileTitle); + STRDUP_RES(n->fsb.labelOpenFileTitle); + updateTitle = 1; + } + if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) { + STR_FREE(o->fsb.labelSaveFileTitle); + STRDUP_RES(n->fsb.labelSaveFileTitle); + updateTitle = 1; + } + + if(o->fsb.labelDirUp != n->fsb.labelDirUp) { + XMSTR_FREE(o->fsb.labelDirUp); + XMS_STRDUP_RES(n->fsb.labelDirUp); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL); + } + if(o->fsb.labelHome != n->fsb.labelHome) { + XMSTR_FREE(o->fsb.labelHome); + XMS_STRDUP_RES(n->fsb.labelHome); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL); + } + if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) { + XMSTR_FREE(o->fsb.labelNewFolder); + XMS_STRDUP_RES(n->fsb.labelNewFolder); + XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL); + } + if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) { + XMSTR_FREE(o->fsb.labelFilterButton); + XMS_STRDUP_RES(n->fsb.labelFilterButton); + XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL); + } + if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) { + XMSTR_FREE(o->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles); + XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL); + } + if(o->fsb.labelDirectories != n->fsb.labelDirectories) { + XMSTR_FREE(o->fsb.labelDirectories); + XMS_STRDUP_RES(n->fsb.labelDirectories); + XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL); + } + if(o->fsb.labelFiles != n->fsb.labelFiles) { + XMSTR_FREE(o->fsb.labelFiles); + XMS_STRDUP_RES(n->fsb.labelFiles); + XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL); + } + int recreateContextMenu = 0; + if(o->fsb.labelRename != n->fsb.labelRename) { + XMSTR_FREE(o->fsb.labelRename); + XMS_STRDUP_RES(n->fsb.labelRename); + recreateContextMenu = 1; + } + if(o->fsb.labelDelete != n->fsb.labelDelete) { + XMSTR_FREE(o->fsb.labelDelete); + XMS_STRDUP_RES(n->fsb.labelDelete); + recreateContextMenu = 1; + } + + if(o->fsb.labelOpen != n->fsb.labelOpen) { + XMSTR_FREE(o->fsb.labelOpen); + XMS_STRDUP_RES(n->fsb.labelOpen); + setOkBtnLabel = 1; + } + if(o->fsb.labelSave != n->fsb.labelSave) { + XMSTR_FREE(o->fsb.labelSave); + XMS_STRDUP_RES(n->fsb.labelSave); + setOkBtnLabel = 1; + } + if(o->fsb.labelCancel != n->fsb.labelCancel) { + XMSTR_FREE(o->fsb.labelCancel); + XMS_STRDUP_RES(n->fsb.labelCancel); + XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL); + } + if(o->fsb.labelHelp != n->fsb.labelHelp) { + XMSTR_FREE(o->fsb.labelHelp); + XMS_STRDUP_RES(n->fsb.labelHelp); + XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL); + } + if(o->fsb.labelFileName != n->fsb.labelFileName) { + XMSTR_FREE(o->fsb.labelFileName); + XMS_STRDUP_RES(n->fsb.labelFileName); + XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL); + } + if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) { + XMSTR_FREE(o->fsb.labelDirectoryName); + XMS_STRDUP_RES(n->fsb.labelDirectoryName); + } + if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) { + XMSTR_FREE(o->fsb.labelNewFileName); + XMS_STRDUP_RES(n->fsb.labelNewFileName); + } + + if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) { + STR_FREE(o->fsb.labelDeleteFile); + STRDUP_RES(n->fsb.labelDeleteFile); + } +#ifdef FSB_ENABLE_DETAIL + if(o->fsb.detailHeadings != n->fsb.detailHeadings) { + STR_FREE(o->fsb.detailHeadings); + STRDUP_RES(n->fsb.detailHeadings); + XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL); + } +#endif + if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) { + STR_FREE(o->fsb.dateFormatSameYear); + STRDUP_RES(n->fsb.dateFormatSameYear); + } + if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) { + STR_FREE(o->fsb.dateFormatOtherYear); + STRDUP_RES(n->fsb.dateFormatOtherYear); + } + if(o->fsb.suffixBytes != n->fsb.suffixBytes) { + STR_FREE(o->fsb.suffixBytes); + STRDUP_RES(n->fsb.suffixBytes); + } + if(o->fsb.suffixMB != n->fsb.suffixMB) { + STR_FREE(o->fsb.suffixMB); + STRDUP_RES(n->fsb.suffixMB); + } + if(o->fsb.suffixGB != n->fsb.suffixGB) { + STR_FREE(o->fsb.suffixGB); + STRDUP_RES(n->fsb.suffixGB); + } + if(o->fsb.suffixTB != n->fsb.suffixTB) { + STR_FREE(o->fsb.suffixTB); + STRDUP_RES(n->fsb.suffixTB); + } + if(o->fsb.errorTitle != n->fsb.errorTitle) { + STR_FREE(o->fsb.errorTitle); + STRDUP_RES(n->fsb.errorTitle); + } + if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) { + STR_FREE(o->fsb.errorIllegalChar); + STRDUP_RES(n->fsb.errorIllegalChar); + } + if(o->fsb.errorRename != n->fsb.errorRename) { + STR_FREE(o->fsb.errorRename); + STRDUP_RES(n->fsb.errorRename); + } + if(o->fsb.errorFolder != n->fsb.errorFolder) { + STR_FREE(o->fsb.errorFolder); + STRDUP_RES(n->fsb.errorFolder); + } + if(o->fsb.errorDelete != n->fsb.errorDelete) { + STR_FREE(o->fsb.errorDelete); + STRDUP_RES(n->fsb.errorDelete); + } + if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) { + STR_FREE(o->fsb.errorOpenDir); + STRDUP_RES(n->fsb.errorOpenDir); + } + + if(updateTitle) { + FSBUpdateTitle(neww); + } + if(recreateContextMenu) { + XtDestroyWidget(n->fsb.listContextMenu); + XtDestroyWidget(n->fsb.gridContextMenu); + n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB); + n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB); + } + if(setOkBtnLabel) { + XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL); + } + + if(!ismanaged && !n->fsb.disable_set_values) { + n->fsb.disable_set_values = 1; + XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL); + n->fsb.disable_set_values = 0; + + if(n->fsb.pathBar) + n->fsb.pathBar->disableResize = False; + } + + if(selectItem) { + if(ismanaged) { + FSBSelectItem(n, FileName(n->fsb.selectedPath)); + } + } + + Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args); + return fr ? fr : r; +} + +static void fsb_insert_child(Widget child) { + XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child); + (xmFormClassRec.composite_class.insert_child)(child); + + if(!p->fsb.gui_created) { + return; + } + + // custom child widget insert + XtVaSetValues(child, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, p->fsb.bottom_widget, + XmNbottomOffset, p->fsb.widgetSpacing, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, p->fsb.windowSpacing, + XmNrightAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, p->fsb.windowSpacing, + NULL); + + + XtVaSetValues(p->fsb.listform, + XmNbottomWidget, child, + XmNbottomOffset, 0, + NULL); + + p->fsb.workarea = child; +} + +Boolean fsb_acceptfocus(Widget widget, Time *time) { + return 0; +} + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL); + + if(w->fsb.type == FILEDIALOG_OPEN) { + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + } else { + XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT); + } + + + if(w->fsb.selectedPath) { + FSBSelectItem(w, FileName(w->fsb.selectedPath)); + } +} + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { + +} + +static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) { + if(!pattern) return 0; + + FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter; + return func(pattern, string); +} + +static int FSBGlobFilter(const char *a, const char *b) { + return fnmatch(a, b, 0); +} + + +static void errCB(Widget w, XtPointer d, XtPointer cbs) { + XtDestroyWidget(w); +} + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) { + Arg args[16]; + int n = 0; + + XmString titleStr = XmStringCreateLocalized((char*)title); + XmString msg = XmStringCreateLocalized((char*)errmsg); + + XtSetArg(args[n], XmNdialogTitle, titleStr); n++; + XtSetArg(args[n], XmNselectionLabelString, msg); n++; + XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++; + + Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON); + XtUnmanageChild(cancel); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, errCB, NULL); + + XtManageChild(dialog); + + XmStringFree(titleStr); + XmStringFree(msg); +} + +static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + char *fileName = NULL; + XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + // make sure the new file name doesn't contain a path separator + if(strchr(fileName, '/')) { + ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar); + XtFree(fileName); + return; + } + + char *parentPath = ParentPath(path); + char *newPath = ConcatPath(parentPath, fileName); + + if(rename(path, newPath)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + filedialog_update_dir(fsb, parentPath); + } + + free(parentPath); + free(newPath); + XtFree(fileName); + XtDestroyWidget(XtParent(w)); +} + +static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBRename(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + + XmString filename = XmStringCreateLocalized(name); + XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++; + XtSetArg(args[n], XmNtextString, filename); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + XmStringFree(filename); + XtManageChild(dialog); +} + +static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + if(unlink(path)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + char *parentPath = ParentPath(path); + filedialog_update_dir(fsb, parentPath); + free(parentPath); + } + + XtDestroyWidget(XtParent(w)); +} + +static void FSBDelete(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + size_t len = strlen(name); + size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4; + char *msg = malloc(msglen); + snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name); + + XmString prompt = XmStringCreateLocalized(msg); + XtSetArg(args[n], XmNselectionLabelString, prompt); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + free(msg); + XmStringFree(prompt); + XtManageChild(dialog); +} + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) { + FSBView view = fsb->fsb.view[fsb->fsb.selectedview]; + if(view.select) { + view.select((Widget)fsb, view.widget, item); + } +} + +static char* set_selected_path(XnFileSelectionBox data, XmString item) +{ + char *name = NULL; + XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name); + if(!name) { + return NULL; + } + char *path = ConcatPath(data->fsb.currentPath, name); + XtFree(name); + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; + + return path; +} + +// item0: rename +// item1: delete +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) { + intptr_t i = (intptr_t)index; + Widget parent = XtParent(item); + XnFileSelectionBox fsb = NULL; + XtVaGetValues(parent, XmNuserData, &fsb, NULL); + + const char *path = fsb->fsb.selectedPath; + if(path) { + if(i == 0) { + FSBRename(fsb, path); + } else if(i == 1) { + FSBDelete(fsb, path); + } + } +} + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) { + return XmVaCreateSimplePopupMenu( + parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC, + XmNuserData, fsb, + XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL, + XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL, + NULL); +} + + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) { + XnFileSelectionBox data = userData; + FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount); +} + +static void FileListSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numItems = 0; + XmStringTable items = NULL; + XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL); + + for(int i=0;ifsb.filelist, i+1, False); + break; + } + XtFree(str); + } +} + +static void FileListCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + XmListDeleteAllItems(data->fsb.filelist); +} + +static void FileListDestroy(Widget fsb, Widget view, void *userData) { + // unused +} + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + data->fsb.selIsDir = False; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } +} + +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *name = NULL; + XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name); + XmTextFieldSetString(data->fsb.name, name); + XtFree(name); + } else { + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = False; + } + } +} + + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count) +{ + if(count > 0) { + XmStringTable items = calloc(count, sizeof(XmString)); + int i = 0; + + for(int j=0;jpath); + if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) { + continue; + } + + items[i] = XmStringCreateLocalized(name); + i++; + } + XmListAddItems(w, items, i, 0); + for(i=0;ifsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen); +} +#endif + +/* + * create file size string with kb/mb/gb/tb suffix + */ +static char* size_str(XnFileSelectionBox fsb, FileElm *f) { + char *str = malloc(16); + uint64_t size = f->size; + + if(f->isDirectory) { + str[0] = '\0'; + } else if(size < 0x400) { + snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes); + } else if(size < 0x100000) { + float s = (float)size/0x400; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x2800 && diff != 0) { + // size < 10 KiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB); + } + } else if(size < 0x40000000) { + float s = (float)size/0x100000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0xa00000 && diff != 0) { + // size < 10 MiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB); + } + } else if(size < 0x1000000000ULL) { + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 GiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB); + } + } else { + size /= 1024; + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 TiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB); + } + } + return str; +} + +static char* date_str(XnFileSelectionBox fsb, time_t tm) { + struct tm t; + struct tm n; + time_t now = time(NULL); + + localtime_r(&tm, &t); + localtime_r(&now, &n); + + char *str = malloc(24); + if(t.tm_year == n.tm_year) { + strftime(str, 24, fsb->fsb.dateFormatSameYear, &t); + } else { + strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t); + } + return str; +} + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailAdjustColWidth(Widget grid) { + XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0); + XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1); + XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2); + + Dimension col0Width = XmLGridColumnWidthInPixels(column0); + Dimension col1Width = XmLGridColumnWidthInPixels(column1); + Dimension col2Width = XmLGridColumnWidthInPixels(column2); + + Dimension totalWidth = col0Width + col1Width + col2Width; + + Dimension gridWidth = 0; + Dimension gridShadow = 0; + XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL); + + Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow; + + if(gridWidth > totalWidth) { + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2, + XmNcolumnSizePolicy, XmCONSTANT, + NULL); + } +} + +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth) +{ + XmLGridAddRows(grid, XmCONTENT, 1, count); + + int row = 0; + for(int i=0;ipath); + if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) { + continue; + } + + // name + XmString str = XmStringCreateLocalized(name); + XtVaSetValues(grid, + XmNcolumn, 0, + XmNrow, row, + XmNcellString, str, NULL); + XmStringFree(str); + // size + char *szbuf = size_str(fsb, e); + str = XmStringCreateLocalized(szbuf); + XtVaSetValues(grid, + XmNcolumn, 1, + XmNrow, row, + XmNcellString, str, NULL); + free(szbuf); + XmStringFree(str); + // date + char *datebuf = date_str(fsb, e->lastModified); + str = XmStringCreateLocalized(datebuf); + XtVaSetValues(grid, + XmNcolumn, 2, + XmNrow, row, + XmNcellString, str, NULL); + free(datebuf); + XmStringFree(str); + + XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL); + row++; + } + + // remove unused rows + if(count > row) { + XmLGridDeleteRows(grid, XmCONTENT, row, count-row); + } + + if(maxWidth < 16) { + maxWidth = 16; + } + + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, maxWidth, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 1, + XmNcolumnRangeEnd, 1, + XmNcolumnWidth, 9, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 2, + XmNcolumnRangeEnd, 2, + XmNcolumnWidth, 16, + XmNcellAlignment, XmALIGNMENT_RIGHT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + + FileListDetailAdjustColWidth(grid); +} + +static void FileListDetailSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numRows = 0; + XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL); + + XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0); + for(int i=0;ifsb.grid, XmCONTENT, i); + FileElm *elm = NULL; + XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL); + if(elm) { + if(!strcmp(item, FileName(elm->path))) { + XmLGridSelectRow(w->fsb.grid, i, False); + XmLGridFocusAndShowRow(w->fsb.grid, i+1); + break; + } + } + } +} + +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + // cleanup grid + Cardinal rows = 0; + XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL); + XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows); +} + +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) { + // unused +} +#endif + +static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) { + char *fileName = NULL; + XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName); + if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno)); + ErrDialog(data, data->fsb.errorTitle, errmsg); + } else { + char *p = strdup(data->fsb.currentPath); + filedialog_update_dir(data, p); + free(p); + } + free(newFolder); + + XtDestroyWidget(XtParent(w)); +} + +static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u) +{ + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++; + XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++; + XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data); + + XtManageChild(dialog); + +} + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) { + const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir(); + filedialog_update_dir(data, homePath); + PathBarSetPath(data->fsb.pathBar, homePath); +} + + +/* + * file_cmp_field + * 0: compare path + * 1: compare size + * 2: compare mtime + */ +static int file_cmp_field = 0; + +/* + * 1 or -1 + */ +static int file_cmp_order = 1; + +static int filecmp(const void *f1, const void *f2) +{ + const FileElm *file1 = f1; + const FileElm *file2 = f2; + if(file1->isDirectory != file2->isDirectory) { + return file1->isDirectory < file2->isDirectory; + } + + int cmp_field = file_cmp_field; + int cmp_order = file_cmp_order; + if(file1->isDirectory) { + cmp_field = 0; + cmp_order = 1; + } + + int ret = 0; + switch(cmp_field) { + case 0: { + ret = strcmp(FileName(file1->path), FileName(file2->path)); + break; + } + case 1: { + if(file1->size < file2->size) { + ret = -1; + } else if(file1->size == file2->size) { + ret = 0; + } else { + ret = 1; + } + break; + } + case 2: { + if(file1->lastModified < file2->lastModified) { + ret = -1; + } else if(file1->lastModified == file2->lastModified) { + ret = 0; + } else { + ret = 1; + } + break; + } + } + + return ret * cmp_order; +} + + +static void free_files(FileElm *ls, int count) +{ + for(int i=0;ifsb.dirs, data->fsb.dircount); + free_files(data->fsb.files, data->fsb.filecount); + data->fsb.dirs = NULL; + data->fsb.files = NULL; + data->fsb.dircount = 0; + data->fsb.filecount = 0; + data->fsb.maxnamelen = 0; +} + +#define FILE_ARRAY_SIZE 1024 + +static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) { + int c = *count; + int a = *alloc; + if(c >= a) { + a *= 2; + FileElm *newarray = realloc(*files, sizeof(FileElm) * a); + + *files = newarray; + *alloc = a; + } + + (*files)[c] = elm; + c++; + *count = c; +} + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path) +{ + DIR *dir = NULL; + if(path) { + // try to check first, if we can open the path + dir = opendir(path); + if(!dir) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno)); + + ErrDialog(data, data->fsb.errorTitle, errmsg); + return 1; + } + } + + FSBView view = data->fsb.view[data->fsb.selectedview]; + view.cleanup((Widget)data, view.widget, view.userData); + + if(view.useDirList) { + XmListDeleteAllItems(data->fsb.dirlist); + } + + /* read dir and insert items */ + if(path) { + int dircount = 0; + int filecount = 0; + size_t maxNameLen = 0; + + FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + int dirs_alloc = FILE_ARRAY_SIZE; + int files_alloc = FILE_ARRAY_SIZE; + + filedialog_cleanup_filedata(data); + + /* dir reading complete - set the path textfield */ + XmTextFieldSetString(data->fsb.path, (char*)path); + char *oldPath = data->fsb.currentPath; + data->fsb.currentPath = strdup(path); + if(oldPath) { + free(oldPath); + } + path = data->fsb.currentPath; + + struct dirent *ent; + while((ent = readdir(dir)) != NULL) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + char *entpath = ConcatPath(path, ent->d_name); + + struct stat s; + if(stat(entpath, &s)) { + free(entpath); + continue; + } + + FileElm new_entry; + new_entry.path = entpath; + new_entry.isDirectory = S_ISDIR(s.st_mode); + new_entry.size = (uint64_t)s.st_size; + new_entry.lastModified = s.st_mtime; + + size_t nameLen = strlen(ent->d_name); + if(nameLen > maxNameLen) { + maxNameLen = nameLen; + } + + if(new_entry.isDirectory) { + file_array_add(&dirs, &dirs_alloc, &dircount, new_entry); + } else { + file_array_add(&files, &files_alloc, &filecount, new_entry); + } + } + closedir(dir); + + data->fsb.dirs = dirs; + data->fsb.files = files; + data->fsb.dircount = dircount; + data->fsb.filecount = filecount; + data->fsb.maxnamelen = maxNameLen; + + // sort file arrays + qsort(dirs, dircount, sizeof(FileElm), filecmp); + qsort(files, filecount, sizeof(FileElm), filecmp); + } + + Widget filterTF = XmDropDownGetText(data->fsb.filter); + char *filter = XmTextFieldGetString(filterTF); + char *filterStr = filter; + if(!filter || strlen(filter) == 0) { + filterStr = "*"; + } + + if(view.useDirList) { + FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount); + view.update( + (Widget)data, + view.widget, + NULL, + 0, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } else { + view.update( + (Widget)data, + view.widget, + data->fsb.dirs, + data->fsb.dircount, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } + + if(filter) { + XtFree(filter); + } + + return 0; +} + + +static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + if(!filedialog_update_dir(data, path)) { + PathBarSetPath(data->fsb.pathBar, path); + data->fsb.selIsDir = TRUE; + } + } +} + +static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = TRUE; + } +} + +static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) { + SelectView(data, tb->set); // 0: list, 1: detail +} + + +static void filedialog_setshowhidden( + Widget w, + XnFileSelectionBox data, + XmToggleButtonCallbackStruct *tb) +{ + data->fsb.showHidden = tb->set; + filedialog_update_dir(data, NULL); +} + +static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); +} + +static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); + +} + +static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d) +{ + char *newPath = ParentPath(data->fsb.currentPath); + filedialog_update_dir(data, newPath); + PathBarSetPath(data->fsb.pathBar, newPath); + free(newPath); +} + +static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *newName = XmTextFieldGetString(data->fsb.name); + if(newName) { + if(strchr(newName, '/')) { + ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar); + XtFree(newName); + return; + } + + if(strlen(newName) > 0) { + char *selPath = ConcatPath(data->fsb.currentPath, newName); + if(data->fsb.selectedPath) free(data->fsb.selectedPath); + data->fsb.selectedPath = selPath; + } + XtFree(newName); + + data->fsb.selIsDir = False; + } + } + + if(data->fsb.selectedPath) { + if(!data->fsb.selIsDir) { + data->fsb.status = FILEDIALOG_OK; + data->fsb.end = True; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } + } +} + +static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d) +{ + data->fsb.end = 1; + data->fsb.status = FILEDIALOG_CANCEL; + FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath); +} + +static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d) +{ + FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath); +} + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) { + XmFileSelectionBoxCallbackStruct cbs; + memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct)); + + char *dir = fsb->fsb.currentPath; + size_t dirlen = dir ? strlen(dir) : 0; + if(dir && dirlen > 0) { + char *dir2 = NULL; + if(dir[dirlen-1] != '/') { + // add a trailing / to the dir string + dir2 = malloc(dirlen+2); + memcpy(dir2, dir, dirlen); + dir2[dirlen] = '/'; + dir2[dirlen+1] = '\0'; + dirlen++; + dir = dir2; + } + cbs.dir = XmStringCreateLocalized(dir); + cbs.dir_length = dirlen; + if(dir2) { + free(dir2); + } + } else { + cbs.dir = XmStringCreateLocalized(""); + cbs.dir_length = 0; + } + cbs.reason = reason; + + cbs.value = XmStringCreateLocalized((char*)value); + cbs.length = strlen(value); + + XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs); + + XmStringFree(cbs.dir); + XmStringFree(cbs.value); +} + +static void CreateUI(XnFileSelectionBox w) { + Arg args[32]; + int n = 0; + XmString str; + + int widget_spacing = w->fsb.widgetSpacing; + int window_spacing = w->fsb.windowSpacing; + + Widget form = (Widget)w; + int type = w->fsb.type; + + XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL); + + /* upper part of the gui */ + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNresizable, True); n++; + XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++; + w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n); + XtManageChild(w->fsb.dirUp); + XtAddCallback(w->fsb.dirUp, XmNactivateCallback, + (XtCallbackProc)filedialog_goup, w); + + // View Option Menu + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget viewframe = XmCreateForm(form, "vframe", args, n); + XtManageChild(viewframe); + + w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0); + + Widget view; + if(w->fsb.showViewMenu) { + n = 0; + XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNmarginWidth, 0); n++; + view = XmCreateOptionMenu(viewframe, "option_menu", args, n); + XtManageChild(view); + w->fsb.viewOption = view; + w->fsb.detailToggleButton = NULL; + } else { + n = 0; + str = XmStringCreateLocalized(w->fsb.labelDetailView); + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNfillOnSelect, True); n++; + XtSetArg(args[n], XmNindicatorOn, False); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + if(w->fsb.selectedview == 1) { + XtSetArg(args[n], XmNset, 1); n++; + } + w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n); + XtManageChild(w->fsb.detailToggleButton); + view = w->fsb.detailToggleButton; + XmStringFree(str); + + XtAddCallback( + w->fsb.detailToggleButton, + XmNvalueChangedCallback, + (XtCallbackProc)filedialog_enable_detailview, + w); + + w->fsb.viewOption = NULL; + } + + n = 0; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightWidget, view); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++; + w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n); + XtManageChild(w->fsb.newFolder); + XtAddCallback( + w->fsb.newFolder, + XmNactivateCallback, + (XtCallbackProc)FSBNewFolder, + w); + + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n); + XtManageChild(w->fsb.home); + XtAddCallback( + w->fsb.home, + XmNactivateCallback, + (XtCallbackProc)FSBHome, + w); + + // match visual appearance of detailToggleButton with the other buttons + if(w->fsb.detailToggleButton) { + Dimension highlight, shadow; + XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL); + XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL); + } + + // pathbar + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, viewframe); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++; + Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n); + XtManageChild(pathBarFrame); + w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0); + w->fsb.pathBar->updateDir = (updatedir_callback)filedialog_update_dir; + w->fsb.pathBar->updateDirData = w; + XtManageChild(w->fsb.pathBar->widget); + w->fsb.path = XmCreateTextField(form, "textfield", args, 0); + + XtVaSetValues(w->fsb.dirUp, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + if(!w->fsb.showViewMenu) { + XtVaSetValues(viewframe, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + } + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, pathBarFrame); n++; + XtSetArg(args[n], XmNtopOffset, 2*widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.filterForm = XmCreateForm(form, "filterform", args, n); + XtManageChild(w->fsb.filterForm); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirectories); n++; + w->fsb.lsDirLabel = XmCreateLabel(w->fsb.filterForm, "labelDirs", args, n); + XtManageChild(w->fsb.lsDirLabel); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 35); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.filterForm, "labelFiles", args, n); + XtManageChild(w->fsb.lsFileLabel); + + if(w->fsb.showHiddenButton) { + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelShowHiddenFiles); n++; + XtSetArg(args[n], XmNset, w->fsb.showHidden); n++; + w->fsb.showHiddenButtonW = XmCreateToggleButton(w->fsb.filterForm, "showHidden", args, n); + XtManageChild(w->fsb.showHiddenButtonW); + XtAddCallback(w->fsb.showHiddenButtonW, XmNvalueChangedCallback, + (XtCallbackProc)filedialog_setshowhidden, w); + } + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFilterButton); n++; + if(w->fsb.showHiddenButton) { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.showHiddenButtonW); n++; + } else { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + } + w->fsb.filterButton = XmCreatePushButton(w->fsb.filterForm, "filedialog_filter", args, n); + XtManageChild(w->fsb.filterButton); + XtAddCallback(w->fsb.filterButton, XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.lsFileLabel); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.filterButton); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshowLabel, False); n++; + XtSetArg(args[n], XmNuseTextField, True); n++; + XtSetArg(args[n], XmNverify, False); n++; + w->fsb.filter = XmCreateDropDown(w->fsb.filterForm, "filedialog_filter_textfield", args, n); + XtManageChild(w->fsb.filter); + XmTextFieldSetString(XmDropDownGetText(w->fsb.filter), w->fsb.filterStr); + XtAddCallback(XmDropDownGetText(w->fsb.filter), XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + XtAddCallback(w->fsb.filter, XmNupdateTextCallback, + (XtCallbackProc)filedialog_update_filter, w); + Widget filterList = XmDropDownGetList(w->fsb.filter); + str = XmStringCreateSimple("*"); + XmListAddItem(filterList, str, 0); + XmStringFree(str); + + /* lower part */ + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing * 2); n++; + Widget buttons = XmCreateForm(form, "buttons", args, n); + XtManageChild(buttons); + + n = 0; + str = type == FILEDIALOG_OPEN ? w->fsb.labelOpen : w->fsb.labelSave; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 14); n++; + w->fsb.okBtn = XmCreatePushButton(buttons, "filedialog_open", args, n); + XtManageChild(w->fsb.okBtn); + XmStringFree(str); + XtAddCallback(w->fsb.okBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHelp); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 86); n++; + w->fsb.helpBtn = XmCreatePushButton(buttons, "filedialog_help", args, n); + XtManageChild(w->fsb.helpBtn); + XtAddCallback(w->fsb.helpBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_help, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 43); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 57); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelCancel); n++; + w->fsb.cancelBtn = XmCreatePushButton(buttons, "filedialog_cancel", args, n); + XtManageChild(w->fsb.cancelBtn); + XtAddCallback(w->fsb.cancelBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_cancel, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, buttons); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, 1); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, 1); n++; + w->fsb.separator = XmCreateSeparator(form, "ofd_separator", args, n); + XtManageChild(w->fsb.separator); + + Widget bottomWidget = w->fsb.separator; + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.separator); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.name = XmCreateTextField(form, "textfield", args, n); + XtAddCallback(w->fsb.name, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.name); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFileName); n++; + w->fsb.nameLabel = XmCreateLabel(form, "label", args, n); + + if(type == FILEDIALOG_SAVE) { + bottomWidget = w->fsb.nameLabel; + XtManageChild(w->fsb.name); + XtManageChild(w->fsb.nameLabel); + } + w->fsb.bottom_widget = bottomWidget; + + + // middle + // form for dir/file lists + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.filterForm); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, bottomWidget); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNwidth, 580); n++; + XtSetArg(args[n], XmNheight, 400); n++; + w->fsb.listform = XmCreateForm(form, "fds_listform", args, n); + + // dir/file lists + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.lsDirLabel); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 35); n++; + w->fsb.dirlist = XmCreateScrolledList(w->fsb.listform, "dirlist", args, n); + Dimension width, height; + XtMakeResizeRequest(w->fsb.dirlist, 150, 200, &width, &height); + XtManageChild(w->fsb.dirlist); + + XtAddCallback( + w->fsb.dirlist, + XmNdefaultActionCallback, + (XtCallbackProc)dirlist_activate, + w); + XtAddCallback( + w->fsb.dirlist, + XmNbrowseSelectionCallback, + (XtCallbackProc)dirlist_select, + w); + + // FileList + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelListView, + CreateListView, + FileListUpdate, + FileListSelect, + FileListCleanup, + FileListDestroy, + True, + w); + + // Detail FileList +#ifdef FSB_ENABLE_DETAIL + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelDetailView, + CreateDetailView, + FileListDetailUpdate, + FileListDetailSelect, + FileListDetailCleanup, + FileListDetailDestroy, + True, + w); +#endif + + /* + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + //XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + //XtSetArg(args[n], XmNbottomWidget, w->fsb.filelist); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.listform, "label", args, n); + XtManageChild(w->fsb.lsFileLabel); + */ + + XtManageChild(w->fsb.listform); + + int selview = w->fsb.selectedview; + if(selview < 2) { + XtManageChild(w->fsb.view[selview].widget); + } else { + w->fsb.selectedview = 0; + } + + + if(w->fsb.selectedPath) { + char *parentPath = ParentPath(w->fsb.selectedPath); + filedialog_update_dir(w, parentPath); + PathBarSetPath(w->fsb.pathBar, parentPath); + free(parentPath); + + if(w->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(w->fsb.name, FileName(w->fsb.selectedPath)); + } + } else { + char cwd[PATH_MAX]; + const char *currentPath = w->fsb.currentPath; + if(!currentPath) { + if(getcwd(cwd, PATH_MAX)) { + currentPath = cwd; + } else { + currentPath = GetHomeDir(); + } + } + + filedialog_update_dir(w, currentPath); + PathBarSetPath(w->fsb.pathBar, w->fsb.currentPath); + } + + + w->fsb.selectedview = selview; + + XtVaSetValues((Widget)w, XmNcancelButton, w->fsb.cancelBtn, NULL); + + w->fsb.gui_created = 1; +} + +static char* FSBDialogTitle(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + if(w->fsb.type == FILEDIALOG_OPEN) { + return w->fsb.labelOpenFileTitle; + } else { + return w->fsb.labelSaveFileTitle; + } +} + +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc create, void *userData, Boolean useDirList) { + Arg args[64]; + int n = 0; + + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + if(useDirList) { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, w->fsb.widgetSpacing); n++; + } else { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, w->fsb.widgetSpacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + } + + return create(w->fsb.listform, args, n, userData); +} + + +typedef struct FSBViewSelection { + XnFileSelectionBox fsb; + int index; +} FSBViewSelection; + +static void SelectView(XnFileSelectionBox f, int view) { + FSBView current = f->fsb.view[f->fsb.selectedview]; + FSBView newview = f->fsb.view[view]; + + XtUnmanageChild(current.widget); + if(newview.useDirList != current.useDirList) { + if(current.useDirList) { + XtUnmanageChild(f->fsb.listform); + } else { + XtManageChild(f->fsb.listform); + } + } + + current.cleanup((Widget)f, current.widget, current.userData); + XtManageChild(newview.widget); + + f->fsb.selectedview = view; + + filedialog_update_dir(f, NULL); + XmProcessTraversal(newview.focus, XmTRAVERSE_CURRENT); +} + +static void SelectViewCallback(Widget w, FSBViewSelection *data, XtPointer u) { + SelectView(data->fsb, data->index); +} + +static void SelectViewItemDestroy(Widget w, FSBViewSelection *data, XtPointer u) { + free(data); +} + +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex) { + Arg args[4]; + int n = 0; + + XmString label = XmStringCreateLocalized((char*)name); + + XtSetArg(args[n], XmNlabelString, label); n++; + XtSetArg(args[1], XmNpositionIndex, w->fsb.selectedview == w->fsb.numviews ? 0 : w->fsb.numviews+1); n++; + Widget item = XmCreatePushButton(w->fsb.viewMenu, "menuitem", args, n); + + if(viewIndex == 0) { + w->fsb.viewSelectorList = item; + } else if(viewIndex == 1) { + w->fsb.viewSelectorDetail = item; + } + + XtManageChild(item); + XmStringFree(label); + + FSBViewSelection *data = malloc(sizeof(FSBViewSelection)); + data->fsb = w; + data->index = viewIndex; + + XtAddCallback( + item, + XmNactivateCallback, + (XtCallbackProc)SelectViewCallback, + data); + XtAddCallback( + item, + XmNdestroyCallback, + (XtCallbackProc)SelectViewItemDestroy, + data); +} + +static FSBViewWidgets CreateListView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox fsb = (XnFileSelectionBox)userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget frame = XmCreateFrame(parent, "filelistframe", args, n); + + fsb->fsb.filelist = XmCreateScrolledList(frame, "filelist", NULL, 0); + XtManageChild(fsb->fsb.filelist); + + XtAddCallback( + fsb->fsb.filelist, + XmNdefaultActionCallback, + (XtCallbackProc)FileListActivateCB, + userData); + XtAddCallback( + fsb->fsb.filelist, + XmNbrowseSelectionCallback, + (XtCallbackProc)FileListSelectCB, + userData); + + fsb->fsb.listContextMenu = CreateContextMenu(fsb, fsb->fsb.filelist, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = frame; + widgets.focus = fsb->fsb.filelist; + return widgets; +} + +#ifdef FSB_ENABLE_DETAIL +static void set_path_from_row(XnFileSelectionBox data, int row) { + FileElm *elm = NULL; + XmLGridRow rowPtr = XmLGridGetRow(data->fsb.grid, XmCONTENT, row); + XtVaGetValues(data->fsb.grid, XmNrowPtr, rowPtr, XmNrowUserData, &elm, NULL); + if(!elm) { + fprintf(stderr, "error: no row data\n"); + return; + } + + char *path = strdup(elm->path); + + data->fsb.selIsDir = False; + if(data->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(data->fsb.name, FileName(path)); + } + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; +} + +static void grid_select(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); +} + +static void grid_activate(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); +} + +static void grid_key_pressed(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + char chars[16]; + KeySym keysym; + int nchars; + + nchars = XLookupString(&cb->event->xkey, chars, 15, &keysym, NULL); + + if(nchars == 0) return; + + // if data->showHidden is 0, data->files contains more items than the grid + // this means SelectedRow might not be the correct index for data->files + // we have to count files manually and increase 'row', if the file + // is actually displayed in the grid + int row = 0; + int selectedRow = XmLGridGetSelectedRow(w); + + int match = -1; + + for(int i=0;ifsb.filecount;i++) { + const char *name = FileName(data->fsb.files[i].path); + if(!data->fsb.showHidden && name[0] == '.') continue; + + size_t namelen = strlen(name); + + size_t cmplen = namelen < nchars ? namelen : nchars; + if(!memcmp(name, chars, cmplen)) { + if(row <= selectedRow) { + if(match == -1) { + match = row; + } + } else { + match = row; + break; + } + } + + row++; + } + + if(match > -1) { + XmLGridSelectRow(w, match, True); + XmLGridFocusAndShowRow(w, match+1); + } else { + XBell(XtDisplay(w), 0); + } +} + +static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + int new_cmp_field = 0; + switch(cb->column) { + case 0: { + new_cmp_field = 0; + break; + } + case 1: { + new_cmp_field = 1; + break; + } + case 2: { + new_cmp_field = 2; + break; + } + } + + if(new_cmp_field == file_cmp_field) { + file_cmp_order = -file_cmp_order; // revert sort order + } else { + file_cmp_field = new_cmp_field; // change file cmp order to new field + file_cmp_order = 1; + } + + int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING; + XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type); + + qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp); + + // refresh widget + filedialog_update_dir(data, NULL); +} + +static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox w = userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n); + XtManageChild(gridcontainer); + + n = 0; + XtSetArg(args[n], XmNcolumns, 3); n++; + XtSetArg(args[n], XmNheadingColumns, 0); n++; + XtSetArg(args[n], XmNheadingRows, 1); n++; + XtSetArg(args[n], XmNallowColumnResize, 1); n++; + XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++; + XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++; + + w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n); + XmLGridSetIgnoreModifyVerify(w->fsb.grid, True); + XtManageChild(w->fsb.grid); + + XtVaSetValues( + w->fsb.grid, + XmNcellDefaults, True, + XtVaTypedArg, XmNblankBackground, XmRString, "white", 6, + XtVaTypedArg, XmNcellBackground, XmRString, "white", 6, + NULL); + + XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w); + XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w); + XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w); + XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w); + + // context menu + w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = gridcontainer; + widgets.focus = w->fsb.grid; + return widgets; +} +#endif + + +/* ------------------------------ Path Utils ------------------------------ */ + +const char* GetHomeDir(void) { + char *home = getenv("HOME"); + if(!home) { + home = getenv("USERPROFILE"); + if(!home) { + home = "/"; + } + } + return home; +} + +static char* ConcatPath(const char *parent, const char *name) +{ + size_t parentlen = strlen(parent); + size_t namelen = strlen(name); + + size_t pathlen = parentlen + namelen + 2; + char *path = malloc(pathlen); + + memcpy(path, parent, parentlen); + if(parentlen > 0 && parent[parentlen-1] != '/') { + path[parentlen] = '/'; + parentlen++; + } + if(name[0] == '/') { + name++; + namelen--; + } + memcpy(path+parentlen, name, namelen); + path[parentlen+namelen] = '\0'; + return path; +} + +static char* FileName(char *path) { + int si = 0; + int osi = 0; + int i = 0; + int p = 0; + char c; + while((c = path[i]) != 0) { + if(c == '/') { + osi = si; + si = i; + p = 1; + } + i++; + } + + char *name = path + si + p; + if(name[0] == 0) { + name = path + osi + p; + if(name[0] == 0) { + return path; + } + } + + return name; +} + +static char* ParentPath(const char *path) { + char *name = FileName((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + if(parentlen == 0) { + parentlen++; + } + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +// unused at the moment, maybe reactivate if more illegal characters +// are defined +/* +static int CheckFileName(const char *fileName) { + size_t len = strlen(fileName); + for(int i=0;idisableResize) return; + + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension xoff; + XtVaGetValues(p->down, XmNwidth, &xoff, NULL); + + Dimension *segW = calloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;inumSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + xoff + 10 > width) { + arrows = True; + //pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + xoff + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x += p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;inumSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + XtVaSetValues(p->down, XmNx, width-xoff, XmNheight, maxHeight, NULL); + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + XtVaSetValues(p->textfield, XmNwidth, rw-xoff, XmNheight, rh, NULL); +} + +static void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); + } + } +} + +static void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + p->input = False; + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); +} + +static void pathbar_pathinput(Widget w, PathBar *pb, XtPointer d) +{ + char *newpath = XmTextFieldGetString(pb->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *cp = ConcatPath(GetHomeDir(), p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char cwd[PATH_MAX]; + if(!getcwd(cwd, sizeof(cwd))) { + cwd[0] = '/'; + cwd[1] = '\0'; + } + char *cp = ConcatPath(cwd, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + if(pb->updateDir) { + if(!pb->updateDir(pb->updateDirData, newpath)) { + PathBarSetPath(pb, newpath); + } + } else { + PathBarSetPath(pb, newpath); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + } +} + +static void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) { + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +static void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) { + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathbar_list_select(Widget w, PathBar *p, XmListCallbackStruct *cb) { + char *value = NULL; + XmStringGetLtoR(cb->item, XmSTRING_DEFAULT_CHARSET, &value); + p->updateDir(p->updateDirData, value); + PathBarSetPath(p, value); + free(value); +} + +static void pathbar_popup(Widget w, PathBar *p, XtPointer d) { + Widget parent = XtParent(w); + Display *dp = XtDisplay(parent); + Window root = XDefaultRootWindow(dp); + + int x, y; + Window child; + XTranslateCoordinates(dp, XtWindow(parent), root, 0, 0, &x, &y, &child); + + XtManageChild(p->list); + XtPopupSpringLoaded(p->popup); + XtVaSetValues(p->popup, XmNx, x, XmNy, y + parent->core.height, XmNwidth, parent->core.width, XmNheight, 200, NULL); + + XmProcessTraversal(p->list, XmTRAVERSE_CURRENT); +} + +static void popupEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *bar = data; + + Window w1 = bar->hs ? XtWindow(bar->hs) : 0; + Window w2 = bar->vs ? XtWindow(bar->vs) : 0; + + if(event->type == ButtonPress) { + if(event->xbutton.window != 0 && (event->xbutton.window == w1 || event->xbutton.window == w2)) { + bar->popupScrollEvent = 1; + } else { + bar->popupScrollEvent = 0; + } + } else if(event->type == ButtonRelease) { + if(bar->popupScrollEvent) { + *dispatch = False; + } + bar->popupScrollEvent = 0; + } else if(event->type == KeyReleaseMask) { + int keycode = event->xkey.keycode; + if(keycode == 36 || keycode == 9) { + XtUnmapWidget(bar->popup); + } + } +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } + } +} + + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = malloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + bar->disableResize = False; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + n = 0; + XtSetArg(args[n], XmNownerEvents, True), n++; + XtSetArg(args[n], XmNgrabStyle, GrabModeSync), n++; + bar->popup = XmCreateGrabShell(bar->widget, "pbpopup", args, n); + bar->list = XmCreateScrolledList(bar->popup, "pblist", NULL, 0); + XtAddEventHandler(bar->popup, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, FALSE, popupEH, bar); + bar->popupScrollEvent = 0; + + XtAddCallback( + bar->list, + XmNdefaultActionCallback, + (XtCallbackProc)pathbar_list_select, + bar); + XtAddCallback( + bar->list, + XmNbrowseSelectionCallback, + (XtCallbackProc)pathbar_list_select, + bar); + + bar->vs = NULL; + bar->hs = NULL; + XtVaGetValues(XtParent(bar->list), XmNhorizontalScrollBar, &bar->hs, XmNverticalScrollBar, &bar->vs, NULL); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XmCreateTextField(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_DOWN); + bar->down = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtManageChild(bar->down); + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->down, + XmNactivateCallback, + (XtCallbackProc)pathbar_popup, + bar); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + //XtManageChild(bar->left); + //XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = calloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +static void PathBarChangeDir(Widget w, PathBar *bar, XtPointer unused) { + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + for(int i=0;inumSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + int plen = strlen(bar->path); + int countSeg = 0; + for(int i=0;i<=plen;i++) { + char c = bar->path[i]; + if(c == '/' || c == '\0') { + if(countSeg == bar->selection) { + char *dir = malloc(i+2); + memcpy(dir, bar->path, i+1); + dir[i+1] = '\0'; + if(bar->updateDir) { + bar->updateDir(bar->updateDirData, dir); + } + free(dir); + } + countSeg++; + } + } +} + +void PathBarSetPath(PathBar *bar, const char *path) { + if(bar->path) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;inumSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + //XtManageChild(bar->left); + //XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + int i=0; + if(path[0] == '/') { + str = XmStringCreateLocalized("/"); + XtSetArg(args[0], XmNlabelString, str); + XtSetArg(args[1], XmNfillOnSelect, True); + XtSetArg(args[2], XmNindicatorOn, False); + bar->pathSegments[0] = XmCreateToggleButton( + bar->widget, "pbbutton", args, 3); + XtAddCallback( + bar->pathSegments[0], + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + bar->numSegments++; + i++; + } + + int len = strlen(path); + int begin = i; + for(;i<=len;i++) { + char c = path[i]; + if((c == '/' || c == '\0') && i > begin) { + char *segStr = malloc(i - begin + 1); + memcpy(segStr, path+begin, i-begin); + segStr[i-begin] = '\0'; + begin = i+1; + + str = XmStringCreateLocalized(segStr); + free(segStr); + XtSetArg(args[0], XmNlabelString, str); + XtSetArg(args[1], XmNfillOnSelect, True); + XtSetArg(args[2], XmNindicatorOn, False); + Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + if(bar->numSegments >= bar->segmentAlloc) { + bar->segmentAlloc += 8; + bar->pathSegments = realloc(bar->pathSegments, bar->segmentAlloc * sizeof(Widget)); + } + + bar->pathSegments[bar->numSegments++] = button; + } + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XmTextFieldSetString(bar->textfield, (char*)path); + XmTextFieldSetInsertionPosition(bar->textfield, XmTextFieldGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm) { + XmStringTable items = calloc(nelm, sizeof(XmString)); + for(int i=0;ilist); + XmListAddItems(bar->list, items, nelm, 0); + XmListSelectPos(bar->list, 1, False); + for(int i=0;ipath) { + free(bar->path); + } + free(bar->pathSegments); + free(bar); +} + +/* ------------------------------ public API ------------------------------ */ + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount) +{ + Widget dialog = XmCreateDialogShell(parent, "FileDialog", NULL, 0); + Widget fsb = XnCreateFileSelectionBox(dialog, name, arglist, argcount); + char *title = FSBDialogTitle(fsb); + XtVaSetValues(dialog, XmNtitle, title, NULL); + return fsb; +} + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount) +{ + Widget fsb = XtCreateWidget(name, xnFsbWidgetClass, parent, arglist, argcount); + return fsb; +} + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData) +{ + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + if(f->fsb.numviews >= FSB_MAX_VIEWS) { + fprintf(stderr, "XnFileSelectionBox: too many views\n"); + return; + } + + FSBView view; + view.update = update; + view.select = select; + view.cleanup = cleanup; + view.destroy = destroy; + view.useDirList = useDirList; + view.userData = userData; + + FSBViewWidgets widgets = CreateView(f, create, userData, useDirList); + view.widget = widgets.view; + view.focus = widgets.focus; + + AddViewMenuItem(f, name, f->fsb.numviews); + + f->fsb.view[f->fsb.numviews++] = view; +} + +void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + PathBarSetDirList(f->fsb.pathBar, dirlist, nelm); +} + +Widget XnFileSelectionBoxWorkArea(Widget fsb) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + return f->fsb.workarea; +} + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + switch(child) { + case XnFSB_DIR_UP_BUTTON: return w->fsb.dirUp; + case XnFSB_HOME_BUTTON: return w->fsb.home; + case XnFSB_NEW_FOLDER_BUTTON: return w->fsb.newFolder; + case XnFSB_DETAIL_TOGGLE_BUTTON: return w->fsb.detailToggleButton; + case XnFSB_VIEW_OPTION_BUTTON: return w->fsb.viewOption; + case XnFSB_FILTER_DROPDOWN: return w->fsb.filter; + case XnFSB_FILTER_BUTTON: return w->fsb.filterButton; + case XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON: return w->fsb.showHiddenButtonW; + case XnFSB_DIRECTORIES_LABEL: return w->fsb.lsDirLabel; + case XnFSB_FILES_LABEL: return w->fsb.lsFileLabel; + case XnFSB_DIRLIST: return w->fsb.dirlist; + case XnFSB_FILELIST: return w->fsb.filelist; + case XnFSB_GRID: return w->fsb.grid; + case XnFSB_OK_BUTTON: return w->fsb.okBtn; + case XnFSB_CANCEL_BUTTON: return w->fsb.cancelBtn; + case XnFSB_HELP_BUTTON: return w->fsb.helpBtn; + } + return NULL; +} + +void XnFileSelectionBoxDeleteFilters(Widget fsb) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + XmListDeleteAllItems(filterList); +} + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + + XmString str = XmStringCreateSimple((char*)filter); + XmListAddItem(filterList, str, 0); + XmStringFree(str); +} diff --git a/application/Fsb.h b/application/Fsb.h new file mode 100644 index 0000000..b79891b --- /dev/null +++ b/application/Fsb.h @@ -0,0 +1,272 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSB_H +#define FSB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern WidgetClass xnFsbWidgetClass; + +#define FILEDIALOG_OPEN 1 +#define FILEDIALOG_SAVE 2 + +#define FILEDIALOG_OK 1 +#define FILEDIALOG_CANCEL 2 + +#define XnNwidgetSpacing "fsbWidgetSpacing" +#define XnNwindowSpacing "fsbWindowSpacing" + +#define XnNfsbType "fsbType" +#define XnCfsbType "fsbType" + +#define XnNshowHidden "showHidden" +#define XnCshowHidden "showHidden" +#define XnNshowHiddenButton "showHiddenButton" +#define XnCshowHiddenButton "showHiddenButton" + +#define XnNshowViewMenu "showViewMenu" +#define XnCshowViewMenu "showViewMenu" + +#define XnNselectedView "fsbSelectedView" +#define XnCselectedView "fsbSelectedView" + +#define XnNdirectory "directory" +#define XnCdirectory "directory" +#define XnNselectedPath "selectedPath" +#define XnCselectedPath "selectedPath" +#define XnNhomePath "homePath" +#define XnChomePath "homePath" + +#define XnNfilter "filter" +#define XnCfilter "filter" + +#define XnNfilterFunc "filterFunc" +#define XnCfilterFunc "filterFunc" + +#define XnNlabelListView "labelListView" +#define XnClabelListView "labelListView" +#define XnNlabelDetailView "labelDetailView" +#define XnClabelDetailView "labelDetailView" +#define XnNlabelOpenFileTitle "labelOpenFileTitle" +#define XnClabelOpenFileTitle "labelOpenFileTitle" +#define XnNlabelSaveFileTitle "labelSaveFileTitle" +#define XnClabelSaveFileTitle "labelSaveFileTitel" +#define XnNlabelDirUp "labelDirUp" +#define XnClabelDirUp "labelDirUp" +#define XnNlabelHome "labelHome" +#define XnClabelHome "labelHome" +#define XnNlabelNewFolder "labelNewFolder" +#define XnClabelNewFolder "labelNewFolder" +#define XnNlabelFilter "labelFilter" +#define XnClabelFilter "labelFilter" +#define XnNlabelFilterButton "labelFilterButton" +#define XnClabelFilterButton "labelFilterButton" +#define XnNlabelShowHiddenFiles "labelShowHiddenFiles" +#define XnClabelShowHiddenFiles "labelShowHiddenFiles" +#define XnNlabelDirectories "labelDirectories" +#define XnClabelDirectories "labelDirectories" +#define XnNlabelFiles "labelFiles" +#define XnClabelFiles "labelFiles" +#define XnNlabelRename "labelRename" +#define XnClabelRename "labelRename" +#define XnNlabelDelete "labelDelete" +#define XnClabelDelete "labelDelete" +#define XnNlabelOpen "labelOpen" +#define XnClabelOpen "labelOpen" +#define XnNlabelSave "labelSave" +#define XnClabelSave "labelSave" +#define XnNlabelOk "labelOk" +#define XnClabelOk "labelOk" +#define XnNlabelCancel "labelCancel" +#define XnClabelCancel "labelCancel" +#define XnNlabelHelp "labelHelp" +#define XnClabelHelp "labelHelp" +#define XnNlabelFileName "labelFileName" +#define XnClabelFileName "labelFileName" +#define XnNlabelDirectoryName "labelDirectoryName" +#define XnClabelDirectoryName "labelDirectoryName" +#define XnNlabelNewFileName "labelNewFileName" +#define XnClabelNewFileName "labelNewFileName" +#define XnNlabelDeleteFile "labelDeleteFile" +#define XnClabelDeleteFile "labelDeleteFile" +#define XnNdetailHeadings "detailHeadings" +#define XnCdetailHeadings "detailHeadings" +#define XnNdateFormatSameYear "dateFormatSameYear" +#define XnCdateFormatSameYear "dateFormatSameYear" +#define XnNdateFormatOtherYear "dateFormatOtherYear" +#define XnCdateFormatOtherYear "dateFormatOtherYear" +#define XnNsuffixBytes "suffixBytes" +#define XnCsuffixBytes "suffixBytes" +#define XnNsuffixKB "suffixKB" +#define XnCsuffixKB "suffixKB" +#define XnNsuffixMB "suffixMB" +#define XnCsuffixMB "suffixMB" +#define XnNsuffixGB "suffixGB" +#define XnCsuffixGB "suffixGB" +#define XnNsuffixTB "suffixTB" +#define XnCsuffixTB "suffixTB" +#define XnNerrorTitle "errorTitle" +#define XnCerrorTitle "errorTitle" +#define XnNerrorIllegalChar "errorIllegalChar" +#define XnCerrorIllegalChar "errorIllegalChar" +#define XnNerrorRename "errorRename" +#define XnCerrorRename "errorRename" +#define XnNerrorCreateFolder "errorCreateFolder" +#define XnCerrorCreateFolder "errorCreateFolder" +#define XnNerrorDelete "errorDelete" +#define XnCerrorDelete "errorDelete" +#define XnNerrorOpenDir "errorOpenDir" +#define XnCerrorOpenDir "errorOpenDir" + +/* + * int FSBFilterFunc(const char *pattern, const char *string) + * + * Checks checks whether the string matches the pattern + * + * Return + * zero if the string matches the pattern + * non-zero if there is no match + */ +typedef int(*FSBFilterFunc)(const char*, const char*); + +typedef int(*updatedir_callback)(void*,char*); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget down; + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + Widget popup; + Widget list; + + int shift; + + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + Widget hs; + Widget vs; + int popupScrollEvent; + + updatedir_callback updateDir; + void *updateDirData; + + Boolean disableResize; +} PathBar; + +typedef struct FileElm FileElm; +struct FileElm { + char *path; + int isDirectory; + unsigned long long size; + time_t lastModified; +}; + +typedef struct { + Widget view; + Widget focus; +} FSBViewWidgets; + +enum XnFSBChild { + XnFSB_DIR_UP_BUTTON = 0, + XnFSB_HOME_BUTTON, + XnFSB_NEW_FOLDER_BUTTON, + XnFSB_DETAIL_TOGGLE_BUTTON, + XnFSB_VIEW_OPTION_BUTTON, + XnFSB_FILTER_DROPDOWN, + XnFSB_FILTER_BUTTON, + XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON, + XnFSB_DIRECTORIES_LABEL, + XnFSB_FILES_LABEL, + XnFSB_DIRLIST, + XnFSB_FILELIST, + XnFSB_GRID, + XnFSB_OK_BUTTON, + XnFSB_CANCEL_BUTTON, + XnFSB_HELP_BUTTON +}; + +typedef FSBViewWidgets(*FSBViewCreateProc)(Widget parent, ArgList args, int n, void *userData); +typedef void(*FSBViewUpdateProc)(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +typedef void(*FSBViewSelectProc)(Widget fsb, Widget view, const char *item); +typedef void(*FSBViewCleanupProc)(Widget fsb, Widget view, void *userData); +typedef void(*FSBViewDestroyProc)(Widget fsb, Widget view, void *userData); + +PathBar* CreatePathBar(Widget parent, ArgList args, int n); +void PathBarSetPath(PathBar *bar, const char *path); +void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm); +void PathBarDestroy(PathBar *bar); + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData); + +void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm); + +Widget XnFileSelectionBoxWorkArea(Widget fsb); + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child); + +void XnFileSelectionBoxDeleteFilters(Widget fsb); + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter); + +#ifdef __cplusplus +} +#endif + +#endif /* FSB_H */ diff --git a/application/FsbP.h b/application/FsbP.h new file mode 100644 index 0000000..71c70b7 --- /dev/null +++ b/application/FsbP.h @@ -0,0 +1,211 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSBP_H +#define FSBP_H + +#include +#include +#include +#include +#include + +#include "Fsb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FSB_MAX_VIEWS 8 + + +typedef struct FSBView FSBView; +struct FSBView { + Widget widget; + Widget focus; + FSBViewUpdateProc update; + FSBViewSelectProc select; + FSBViewCleanupProc cleanup; + FSBViewDestroyProc destroy; + void *userData; + Boolean useDirList; +}; + + +typedef struct FSBClassPart { + int unused; +} FSBClassPart; + +typedef struct FSBClassRec { + CoreClassPart core_class; + CompositeClassPart composite_class; + ConstraintClassPart constraint_class; + XmManagerClassPart manager_class; + XmBulletinBoardClassPart bulletin_board_class; + XmFormClassPart form_class; + FSBClassPart fsb_class; +} FSBClassRec; + +typedef struct FSBPart { + XtCallbackList okCallback; + XtCallbackList cancelCallback; + + Dimension widgetSpacing; + Dimension windowSpacing; + + Boolean showHiddenButton; + + Widget path; + PathBar *pathBar; + Widget filter; + Widget filterButton; + Widget showHiddenButtonW; + + FSBFilterFunc filterFunc; + + char *filterStr; + + Widget dirUp; + Widget home; + Widget newFolder; + + Widget viewSelectorList; + Widget viewSelectorDetail; + + Widget viewMenu; + Widget viewOption; + Widget detailToggleButton; + + Widget filterForm; + Widget lsDirLabel; + Widget lsFileLabel; + + Widget listContextMenu; + Widget gridContextMenu; + + // icon view + + // dir/file list view + Widget listform; + Widget dirlist; + + FSBView view[FSB_MAX_VIEWS]; + int numviews; + int selectedview; + + Widget filelist; + Widget grid; + + Widget separator; + + Widget nameLabel; + Widget name; + + Widget bottom_widget; + + Widget workarea; + + Widget okBtn; + Widget cancelBtn; + Widget helpBtn; + + FileElm *dirs; + FileElm *files; + int dircount; + int filecount; + int maxnamelen; + + char *homePath; + + char *currentPath; + char *selectedPath; + int selIsDir; + Boolean showHidden; + Boolean showViewMenu; + + int type; + + int end; + int status; + + int disable_set_values; + int gui_created; + + char *labelListView; + char *labelDetailView; + char* labelOpenFileTitle; + char* labelSaveFileTitle; + XmString labelDirUp; + XmString labelHome; + XmString labelNewFolder; + XmString labelFilterButton; + XmString labelShowHiddenFiles; + XmString labelDirectories; + XmString labelFiles; + XmString labelRename; + XmString labelDelete; + XmString labelOpen; + XmString labelSave; + XmString labelOk; + XmString labelCancel; + XmString labelHelp; + XmString labelFileName; + XmString labelDirectoryName; + XmString labelNewFileName; + char *labelDeleteFile; + + char *detailHeadings; + + char *dateFormatSameYear; + char *dateFormatOtherYear; + char *suffixBytes; + char *suffixKB; + char *suffixMB; + char *suffixGB; + char *suffixTB; + + char *errorTitle; + char *errorIllegalChar; + char *errorRename; + char *errorFolder; + char *errorDelete; + char *errorOpenDir; +} FSBPart; + +typedef struct FSBRec { + CorePart core; + CompositePart composite; + ConstraintPart constraint; + XmManagerPart manager; + XmBulletinBoardPart bulletin_board; + XmFormPart form; + FSBPart fsb; +} FSBRec; + +typedef struct FSBRec *XnFileSelectionBox; + +#ifdef __cplusplus +} +#endif + +#endif /* FSBP_H */ + diff --git a/application/Makefile b/application/Makefile new file mode 100644 index 0000000..ea6cb48 --- /dev/null +++ b/application/Makefile @@ -0,0 +1,50 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2022 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = .. +include $(BUILD_ROOT)/config.mk + +CFLAGS += -I../ucx -I.. + +SRC = main.c +SRC += window.c +SRC += player.c +SRC += Fsb.c + +OBJ = $(SRC:%.c=$(BUILD_ROOT)/build/application/%.$(OBJ_EXT)) + +BINTARGET = mediaplayer + +all: $(BUILD_ROOT)/build/bin/$(BINTARGET) + +$(BUILD_ROOT)/build/bin/$(BINTARGET): $(OBJ) + $(LD) -o $(BUILD_ROOT)/build/bin/$(BINTARGET) $(OBJ) -L$(BUILD_ROOT)/build/lib -lucx $(LDFLAGS) $(APP_LDFLAGS) + +$(BUILD_ROOT)/build/application/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(APP_CFLAGS) -o $@ -c $< + diff --git a/application/main.c b/application/main.c new file mode 100644 index 0000000..85687c5 --- /dev/null +++ b/application/main.c @@ -0,0 +1,71 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "window.h" + +#include +#include + +#define APP_NAME "MediaPlayer" + +static XtAppContext app; +static Display *display; + +static String fallback[] = { + "*renderTable: rt", + "*rt*fontType: FONT_IS_XFT", + "*rt*fontName: Sans", + "*rt*fontSize: 9", + NULL +}; + +int main(int argc, char** argv) { + // disable stdout buffering, because the netbeans's internal terminal + // has a bug on freebsd and doesn't flush the output after a newline + setvbuf(stdout, NULL, _IONBF, 0); + + XtToolkitInitialize(); + XtSetLanguageProc(NULL, NULL, NULL); + app = XtCreateApplicationContext(); + XtAppSetFallbackResources(app, fallback); + + display = XtOpenDisplay(app, NULL, APP_NAME, APP_NAME, NULL, 0, &argc, argv); + + MainWindow *window = WindowCreate(display); + + WindowShow(window); + XtAppMainLoop(app); + + return 0; +} + +XtAppContext* GetAppContext(void) { + return &app; +} + +void ApplicationExit(void) { + XtAppSetExitFlag(app); +} diff --git a/application/main.h b/application/main.h new file mode 100644 index 0000000..3a8e3e8 --- /dev/null +++ b/application/main.h @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef MAIN_H +#define MAIN_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +XtAppContext* GetAppContext(void); + +void ApplicationExit(void); + + +#ifdef __cplusplus +} +#endif + +#endif /* MAIN_H */ + diff --git a/application/player.c b/application/player.c new file mode 100644 index 0000000..213d184 --- /dev/null +++ b/application/player.c @@ -0,0 +1,112 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "player.h" + +#include +#include +#include +#include +#include +#include + +#include + +extern char **environ; + +#define WID_ARG_BUFSIZE 24 + +static void* start_player(void *data); + +void PlayerOpenFile(MainWindow *win) { + pthread_t tid; + if(pthread_create(&tid, NULL, start_player, win)) { + perror("pthread_create"); + } +} + +static void* start_player(void *data) { + MainWindow *win = data; + + char *player_bin = "/usr/local/bin/mpv"; // TODO: get bin from settings + + // -wid parameter value for embedding the player in the player_widget + Window wid = XtWindow(win->player_widget); + char wid_arg[WID_ARG_BUFSIZE]; + if(snprintf(wid_arg, WID_ARG_BUFSIZE, "%lu", wid) >= WID_ARG_BUFSIZE) { + return NULL; + } + + // create player arg list + char *args[32]; + args[0] = player_bin; + args[1] = "-wid"; + args[2] = wid_arg; + args[3] = win->file; + args[4] = NULL; + + // redirect stdin/stdout + int pout[2]; + int pin[2]; + if(pipe(pout)) { + perror("pipe"); + return NULL; + } + if(pipe(pin)) { + perror("pipe"); + return NULL; + } + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + + posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO); + posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO); + + // start player + pid_t player_pid; + if(posix_spawn(&player_pid, player_bin, &actions, NULL, args, environ)) { + perror("posix_spawn"); + return NULL; + } + posix_spawn_file_actions_destroy(&actions); + + Player *player = malloc(sizeof(Player)); + memset(player, 0, sizeof(Player)); + + player->in = pin[1]; + player->out = pout[0]; + close(pin[0]); + close(pout[1]); + player->process = player_pid; + + if(win->player) { + PlayerDestroy(win->player); + } + win->player = player; + + return NULL; +} + +void PlayerDestroy(Player *p) { + free(p); +} diff --git a/application/player.h b/application/player.h new file mode 100644 index 0000000..ebd6ab4 --- /dev/null +++ b/application/player.h @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#ifndef PLAYER_H +#define PLAYER_H + +#include "window.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void PlayerOpenFile(MainWindow *win); + +void PlayerDestroy(Player *p); + + +#ifdef __cplusplus +} +#endif + +#endif /* PLAYER_H */ + diff --git a/application/window.c b/application/window.c new file mode 100644 index 0000000..412381f --- /dev/null +++ b/application/window.c @@ -0,0 +1,317 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "window.h" +#include "main.h" +#include "player.h" + +#include "Fsb.h" + +static MainWindow *main_window; + +static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *args, int nargs); + +static void FileOpenCB(Widget w, void *udata, void *cdata); +static void ViewFullscreenCB(Widget w, void *udata, void *cdata); + +static void window_close_handler(Widget window, void *udata, void *cdata) { + ApplicationExit(); +} + +static unsigned int keycodeF; + +static void windowKeyEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + MainWindow *win = data; + if(win->fullscreen && event->xkey.keycode == keycodeF) { + WindowFullscreen(main_window, FALSE); + *dispatch = FALSE; + } +} + +MainWindow* WindowCreate(Display *display) { + Arg args[32]; + int n; + + MainWindow *window = malloc(sizeof(MainWindow)); + memset(window, 0, sizeof(MainWindow)); + main_window = window; + + // toplevel window + n = 0; + XtSetArg(args[n], XmNtitle, "MediaPlayer"); n++; + window->window = XtAppCreateShell( + "mediaplayer", + "mediaplayer", + //applicationShellWidgetClass, + vendorShellWidgetClass, + display, + args, + n); + + + Atom wm_delete_window; + wm_delete_window = XmInternAtom( + display, + "WM_DELETE_WINDOW", + 0); + XmAddWMProtocolCallback( + window->window, + wm_delete_window, + window_close_handler, + window); + + + n = 0; + XtSetArg(args[n], XmNwidth, 360); n++; + XtSetArg(args[n], XmNheight, 220); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget container = XmCreateForm(window->window, "form", args, n); + XtManageChild(container); + XtAddEventHandler(container, KeyPressMask, FALSE, windowKeyEH, window); + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + WindowCreateMenu(window, container, args, n); + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, window->menubar); n++; + XtSetArg(args[n], XmNbackground, BlackPixelOfScreen(XtScreen(window->window))); n++; + window->player_widget = XmCreateDrawingArea(container, "player", args, n); + XtManageChild(window->player_widget); + + // get F keycode + keycodeF = XKeysymToKeycode(XtDisplay(window->window), XStringToKeysym("F")); + + return window; +} + +void WindowShow(MainWindow *win) { + XtRealizeWidget(win->window); +} + +/* + * Creates a XmPushButton menu item + */ +static Widget createMenuItem( + Widget menu, + char *name, + char *label, + char mnemonic, + const char *accelerator, + char *accelerator_text, + XtCallbackProc callback, + void *cbData) +{ + Arg args[16]; + int n = 0; + + XmString s1 = XmStringCreateSimple(label); + XtSetArg(args[n], XmNlabelString, s1); n++; + XtSetArg(args[n], XmNmnemonic, mnemonic); n++; + XmString at = NULL; + if(accelerator && accelerator_text) { + at = XmStringCreateSimple(accelerator_text); + XtSetArg(args[n], XmNaccelerator, accelerator); n++; + XtSetArg(args[n], XmNacceleratorText, at); n++; + } + + Widget menuItem = XmCreatePushButtonGadget(menu, name, args, n); + XtManageChild(menuItem); + XmStringFree(s1); + if(at) XmStringFree(at); + + if(callback) { + XtAddCallback(menuItem, XmNactivateCallback, (XtCallbackProc)callback, cbData); + } + + return menuItem; +} + +static void WindowCreateMenu(MainWindow *win, Widget parent, Arg *mbargs, int nmbargs) { + Widget menubar = XmCreateMenuBar(parent, "menubar", mbargs, nmbargs); + XtManageChild(menubar); + win->menubar = menubar; + + Arg args[16]; + int n; + + // menus + XmString s = XmStringCreateSimple("File"); + Widget fileMenuItem = XtVaCreateManagedWidget( + "menuitem", + xmCascadeButtonWidgetClass, + menubar, + XmNlabelString, s, + NULL); + XmStringFree(s); + Widget fileMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 0, NULL, NULL); + + s = XmStringCreateSimple("Playback"); + Widget playMenuItem = XtVaCreateManagedWidget( + "menuitem", + xmCascadeButtonWidgetClass, + menubar, + XmNlabelString, s, + NULL); + XmStringFree(s); + Widget playMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 1, NULL, NULL); + + s = XmStringCreateSimple("View"); + Widget viewMenuItem = XtVaCreateManagedWidget( + "menuitem", + xmCascadeButtonWidgetClass, + menubar, + XmNlabelString, s, + NULL); + XmStringFree(s); + Widget viewMenu = XmVaCreateSimplePulldownMenu(menubar, "menu", 2, NULL, NULL); + + // file menu + createMenuItem(fileMenu, "fileOpen", "Open...", 'O', "CtrlO", "Ctrl+O", FileOpenCB, NULL); + + // view menu + createMenuItem(viewMenu, "viewFullscreen", "Fullscreen", 'F', "F", "F", ViewFullscreenCB, NULL); +} + +void go_fullscreen(Display *dsp, Window win) +{ + XEvent xev; + Atom wm_state = XInternAtom(dsp, "_NET_WM_STATE", False); + Atom fullscreen = XInternAtom(dsp, "_NET_WM_STATE_FULLSCREEN", False); + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = win; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + xev.xclient.data.l[1] = fullscreen; + xev.xclient.data.l[2] = 0; + XSendEvent(dsp, DefaultRootWindow(dsp), False, + SubstructureNotifyMask, &xev); +} + +static Atom net_wm_state; +static Atom net_wm_state_fullscreen; +static int net_wm_atoms_initialized = 0; + +void WindowFullscreen(MainWindow *win, bool enableFullscreen) { + Display *dpy = XtDisplay(win->window); + + // init net_wm_state atoms + if(!net_wm_atoms_initialized) { + net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False); + net_wm_state_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + net_wm_atoms_initialized = 1; + } + + WindowMenubarSetVisible(win, !enableFullscreen); + if(enableFullscreen && !win->fullscreen) { + XtUnmanageChild(main_window->menubar); + main_window->fullscreen = TRUE; + } else if(!enableFullscreen && win->fullscreen) { + XtManageChild(main_window->menubar); + main_window->fullscreen = FALSE; + } + + XEvent ev; + memset(&ev, 0, sizeof(XEvent)); + ev.type = ClientMessage; + ev.xclient.window = XtWindow(win->window); + ev.xclient.message_type = net_wm_state; + ev.xclient.format = 32; + ev.xclient.data.l[0] = enableFullscreen ? 1 : 0; + ev.xclient.data.l[1] = net_wm_state_fullscreen; + ev.xclient.data.l[2] = 0; + XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); +} + +void WindowMenubarSetVisible(MainWindow *win, bool visible) { + if(visible) { + XtManageChild(main_window->menubar); + XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, main_window->menubar, NULL); + } else { + XtUnmanageChild(main_window->menubar); + XtVaSetValues(main_window->player_widget, XmNtopAttachment, XmATTACH_FORM, NULL); + } +} + + + +static void filedialog_end( + Widget widget, + MainWindow *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + XtUnmanageChild(widget); + XtDestroyWidget(widget); +} + +static void filedialog_select( + Widget widget, + MainWindow *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + char *value = NULL; + if(selection->value) { + XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value); + if(value) { + if(data->file) { + XtFree(data->file); + } + data->file = value; + // no need to free the value, because it is stored in MainWindow + + PlayerOpenFile(data); + } + } + filedialog_end(widget, data, NULL); +} + + + + +static void FileOpenCB(Widget w, void *udata, void *cdata) { + MainWindow *win = main_window; + Widget dialog = XnCreateFileSelectionDialog(win->window, "dialog", NULL, 0); + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, win); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_end, win); + XtManageChild(dialog); +} + +static void ViewFullscreenCB(Widget w, void *udata, void *cdata) { + if(main_window->fullscreen) { + WindowFullscreen(main_window, FALSE); + } else { + WindowFullscreen(main_window, TRUE); + } + +} diff --git a/application/window.h b/application/window.h new file mode 100644 index 0000000..0ac5cc7 --- /dev/null +++ b/application/window.h @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + +#ifndef WINDOW_H +#define WINDOW_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Player { + pid_t process; + int in; + int out; +} Player; + +typedef struct MainWindow { + Widget window; + Widget menubar; + Widget player_widget; + char *file; + Player *player; + bool fullscreen; + bool mbvisible; +} MainWindow; + +MainWindow* WindowCreate(Display *dp); + +void WindowShow(MainWindow *win); + +void WindowFullscreen(MainWindow *win, bool enableFullscreen); + +void WindowMenubarSetVisible(MainWindow *win, bool visible); + +#ifdef __cplusplus +} +#endif + +#endif /* WINDOW_H */ + diff --git a/configure b/configure new file mode 100755 index 0000000..b640363 --- /dev/null +++ b/configure @@ -0,0 +1,364 @@ +#!/bin/sh + + +PREFIX=/usr +EPREFIX=$PREFIX + +BINDIR= +SBINDIR= +LIBDIR= +LIBEXECDIR= +DATADIR= +SYSCONFDIR= +SHAREDSTATEDIR= +LOCALSTATEDIR= +INCLUDEDIR= +INFODIR= +MANDIR= + +OS=`uname -s` +OS_VERSION=`uname -r` + +TEMP_DIR=".tmp-`uname -n`" +mkdir -p $TEMP_DIR +if [ $? -ne 0 ]; then + echo "Cannot create tmp dir" + echo "Abort" +fi +touch $TEMP_DIR/options +touch $TEMP_DIR/features + +# features + +# help text +printhelp() +{ + echo "Usage: $0 [OPTIONS]..." + cat << __EOF__ +Installation directories: + --prefix=PREFIX path prefix for architecture-independent files + [/usr] + --exec-prefix=EPREFIX path prefix for architecture-dependent files + [PREFIX] + + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR system configuration files [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --mandir=DIR man documentation [DATAROOTDIR/man] + +__EOF__ +} + +# +# parse arguments +# +for ARG in $@ +do + case "$ARG" in + "--prefix="*) PREFIX=${ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${ARG#--includedir=} ;; + "--infodir="*) INFODIR=${ARG#--infodir=} ;; + "--mandir"*) MANDIR=${ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac +done + +# set dir variables +if [ -z "$BINDIR" ]; then + BINDIR=$EPREFIX/bin +fi +if [ -z "$SBINDIR" ]; then + SBINDIR=$EPREFIX/sbin +fi +if [ -z "$LIBDIR" ]; then + LIBDIR=$EPREFIX/lib +fi +if [ -z "$LIBEXEC" ]; then + LIBEXECDIR=$EPREFIX/libexec +fi +if [ -z "$DATADIR" ]; then + DATADIR=$PREFIX/share +fi +if [ -z "$SYSCONFDIR" ]; then + SYSCONFDIR=$PREFIX/etc +fi +if [ -z "$SHAREDSTATEDIR" ]; then + SHAREDSTATEDIR=$PREFIX/com +fi +if [ -z "$LOCALSTATEDIR" ]; then + LOCALSTATEDIR=$PREFIX/var +fi +if [ -z "$INCLUDEDIR" ]; then + INCLUDEDIR=$PREFIX/include +fi +if [ -z "$INFODIR" ]; then + INFODIR=$PREFIX/info +fi +if [ -z "$MANDIR" ]; then + MANDIR=$PREFIX/man +fi + +which pkg-config > /dev/null +if [ $? -eq 0 ]; then + PKG_CONFIG=pkg-config +else + PKG_CONFIG=false +fi + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +printf "detect platform... " +if [ $OS = SunOS ]; then + PLATFORM="solaris sunos unix svr4" +fi +if [ $OS = Linux ]; then + PLATFORM="linux unix" +fi +if [ $OS = FreeBSD ]; then + PLATFORM="freebsd bsd unix" +fi +if [ $OS = Darwin ]; then + PLATFORM="macos osx bsd unix" +fi +echo $OS | grep "MINGW" > /dev/null +if [ $? -eq 0 ]; then + PLATFORM="windows mingw" +fi + +if [ -z "$PLATFORM" ]; then + PLATFORM="unix" +fi + +for p in $PLATFORM +do + PLATFORM_NAME=$p + break +done +echo $PLATFORM_NAME + +isplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 0 + fi + done + return 1 +} +isnotplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 1 + fi + done + return 0 +} + +# generate config.mk and config.h +cat > $TEMP_DIR/config.mk << __EOF__ +# +# config.mk generated by configure +# + +# general vars + +PREFIX=$PREFIX +EPREFIX=$EPREFIX + +BINDIR=$BINDIR +SBINDIR=$SBINDIR +LIBDIR=$LIBDIR +LIBEXECDIR=$LIBEXECDIR +DATADIR=$DATADIR +SYSCONFDIR=$SYSCONFDIR +SHAREDSTATEDIR=$SHAREDSTATEDIR +LOCALSTATEDIR=$LOCALSTATEDIR +INCLUDEDIR=$INCLUDEDIR +INFODIR=$INFODIR +MANDIR=$MANDIR + +__EOF__ + +echo > $TEMP_DIR/make.mk + +ENV_CFLAGS=$CFLAGS +ENV_LDFLAGS=$LDFLAGS +ENV_CXXFLAGS=$CXXFLAGS + +# Toolchain detection +# this will insert make vars to config.mk +. make/toolchain.sh + +# add user specified flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${ENV_CFLAGS}" ]; then + echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_CXXFLAGS}" ]; then + echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_LDFLAGS}" ]; then + echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# DEPENDENCIES +# + +dependency_motif() +{ + printf "checking for motif... " + # dependency motif + while true + do + CFLAGS="$CFLAGS -DUI_MOTIF" + LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread" + echo yes + return 0 + done + + echo no + return 1 +} + +DEPENDENCIES_FAILED= +ERROR=0 +# general dependencies +CFLAGS= +LDFLAGS= +while true +do + if isnotplatform "unix"; then + break + fi + while true + do + + cat >> $TEMP_DIR/make.mk << __EOF__ +OBJ_EXT = o +LIB_EXT = a +PACKAGE_SCRIPT = package_unix.sh + +__EOF__ + + break + done + + break +done +while true +do + while true + do + + LDFLAGS="$LDFLAGS -lpthread" + + break + done + + break +done +while true +do + if isnotplatform "bsd"; then + break + fi + while true + do + + CFLAGS="$CFLAGS -I/usr/local/include" + LDFLAGS="$LDFLAGS -L/usr/local/lib" + + break + done + + break +done + +# add general dependency flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# OPTION VALUES +# + +# +# TARGETS +# +CFLAGS= +CXXFLAGS= +LDFLAGS= + +# Target: app +CFLAGS= +LDFLAGS= +CXXFLAGS= + +dependency_motif +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED motif " + ERROR=1 +fi + +# Features + + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "APP_CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "APP_CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "APP_LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +if [ $ERROR -ne 0 ]; then + echo + echo "Error: Unresolved dependencies" + echo $DEPENDENCIES_FAILED + rm -Rf $TEMP_DIR + exit 1 +fi + +echo "configure finished" +echo +echo "Build Config:" +echo " PREFIX: $PREFIX" +echo " TOOLCHAIN: $TOOLCHAIN_NAME" +echo +cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk +rm -Rf $TEMP_DIR + + diff --git a/make/Makefile.mk b/make/Makefile.mk new file mode 100644 index 0000000..016534e --- /dev/null +++ b/make/Makefile.mk @@ -0,0 +1,51 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2021 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +# this makefile is invoked from the build root directory + +BUILD_ROOT = ./ +include config.mk + +BUILD_DIRS = build/bin build/lib +BUILD_DIRS += build/ucx +BUILD_DIRS += build/application + +all: $(BUILD_DIRS) ucx application + make/$(PACKAGE_SCRIPT) + +$(BUILD_DIRS): + mkdir -p $@ + +ucx: $(BUILD_DIRS) FORCE + cd ucx; $(MAKE) + +application: $(BUILD_DIRS) ucx FORCE + cd application; $(MAKE) + +FORCE: + diff --git a/make/clang.mk b/make/clang.mk new file mode 100644 index 0000000..93e8096 --- /dev/null +++ b/make/clang.mk @@ -0,0 +1,9 @@ +# +# clang toolchain config +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared diff --git a/make/configure.vm b/make/configure.vm new file mode 100644 index 0000000..80068be --- /dev/null +++ b/make/configure.vm @@ -0,0 +1,615 @@ +#!/bin/sh + +#foreach( $var in $vars ) +#if( $var.exec ) +${var.name}=`${var.value}` +#else +${var.name}=${var.value} +#end +#end + +#if ( ! $project.hasVar("PREFIX") ) +PREFIX=/usr +#end +#if ( ! $project.hasVar("EPREFIX") ) +EPREFIX=$PREFIX +#end + +#if ( ! $project.hasVar("BINDIR") ) +BINDIR= +#end +#if ( ! $project.hasVar("SBINDIR") ) +SBINDIR= +#end +#if ( ! $project.hasVar("LIBDIR") ) +LIBDIR= +#end +#if ( ! $project.hasVar("LIBEXECDIR") ) +LIBEXECDIR= +#end +#if ( ! $project.hasVar("DATADIR") ) +DATADIR= +#end +#if ( ! $project.hasVar("SYSCONFDIR") ) +SYSCONFDIR= +#end +#if ( ! $project.hasVar("SHAREDSTATEDIR") ) +SHAREDSTATEDIR= +#end +#if ( ! $project.hasVar("LOCALSTATEDIR") ) +LOCALSTATEDIR= +#end +#if ( ! $project.hasVar("INCLUDEDIR") ) +INCLUDEDIR= +#end +#if ( ! $project.hasVar("INFODIR") ) +INFODIR= +#end +#if ( ! $project.hasVar("MANDIR") ) +MANDIR= +#end + +OS=`uname -s` +OS_VERSION=`uname -r` + +TEMP_DIR=".tmp-`uname -n`" +mkdir -p $TEMP_DIR +if [ $? -ne 0 ]; then + echo "Cannot create tmp dir" + echo "Abort" +fi +touch $TEMP_DIR/options +touch $TEMP_DIR/features + +# features +#foreach( $feature in $features ) +#if( ${feature.isDefault()} ) +${feature.getVarName()}=on +#end +#end + +# help text +printhelp() +{ + echo "Usage: $0 [OPTIONS]..." + cat << __EOF__ +Installation directories: + --prefix=PREFIX path prefix for architecture-independent files + [/usr] + --exec-prefix=EPREFIX path prefix for architecture-dependent files + [PREFIX] + + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR system configuration files [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --mandir=DIR man documentation [DATAROOTDIR/man] + +#if( $options.size() > 0 ) +Options: +#foreach( $opt in $options ) + --${opt.getArgument()}=${opt.getValuesString()} +#end + +#end +#if( $features.size() > 0 ) +Optional Features: +#foreach( $feature in $features ) +#if( $feature.default ) + --disable-${feature.arg} +#else + --enable-${feature.arg} +#end +#end + +#end +__EOF__ +} + +# +# parse arguments +# +#set( $D = '$' ) +for ARG in $@ +do + case "$ARG" in + "--prefix="*) PREFIX=${D}{ARG#--prefix=} ;; + "--exec-prefix="*) EPREFIX=${D}{ARG#--exec-prefix=} ;; + "--bindir="*) BINDIR=${D}{ARG#----bindir=} ;; + "--sbindir="*) SBINDIR=${D}{ARG#--sbindir=} ;; + "--libdir="*) LIBDIR=${D}{ARG#--libdir=} ;; + "--libexecdir="*) LIBEXECDIR=${D}{ARG#--libexecdir=} ;; + "--datadir="*) DATADIR=${D}{ARG#--datadir=} ;; + "--sysconfdir="*) SYSCONFDIR=${D}{ARG#--sysconfdir=} ;; + "--sharedstatedir="*) SHAREDSTATEDIR=${D}{ARG#--sharedstatedir=} ;; + "--localstatedir="*) LOCALSTATEDIR=${D}{ARG#--localstatedir=} ;; + "--includedir="*) INCLUDEDIR=${D}{ARG#--includedir=} ;; + "--infodir="*) INFODIR=${D}{ARG#--infodir=} ;; + "--mandir"*) MANDIR=${D}{ARG#--mandir} ;; + "--help"*) printhelp; exit 1 ;; + #foreach( $opt in $options ) + "--${opt.getArgument()}="*) ${opt.getVarName()}=${D}{ARG#--${opt.getArgument()}=} ;; + #end + #foreach( $feature in $features ) + "--enable-${feature.arg}") ${feature.getVarName()}=on ;; + "--disable-${feature.arg}") unset ${feature.getVarName()} ;; + #end + "-"*) echo "unknown option: $ARG"; exit 1 ;; + esac +done + +# set dir variables +if [ -z "$BINDIR" ]; then + BINDIR=$EPREFIX/bin +fi +if [ -z "$SBINDIR" ]; then + SBINDIR=$EPREFIX/sbin +fi +if [ -z "$LIBDIR" ]; then + LIBDIR=$EPREFIX/lib +fi +if [ -z "$LIBEXEC" ]; then + LIBEXECDIR=$EPREFIX/libexec +fi +if [ -z "$DATADIR" ]; then + DATADIR=$PREFIX/share +fi +if [ -z "$SYSCONFDIR" ]; then + SYSCONFDIR=$PREFIX/etc +fi +if [ -z "$SHAREDSTATEDIR" ]; then + SHAREDSTATEDIR=$PREFIX/com +fi +if [ -z "$LOCALSTATEDIR" ]; then + LOCALSTATEDIR=$PREFIX/var +fi +if [ -z "$INCLUDEDIR" ]; then + INCLUDEDIR=$PREFIX/include +fi +if [ -z "$INFODIR" ]; then + INFODIR=$PREFIX/info +fi +if [ -z "$MANDIR" ]; then + MANDIR=$PREFIX/man +fi + +which pkg-config > /dev/null +if [ $? -eq 0 ]; then + PKG_CONFIG=pkg-config +else + PKG_CONFIG=false +fi + +# Simple uname based platform detection +# $PLATFORM is used for platform dependent dependency selection +printf "detect platform... " +if [ $OS = SunOS ]; then + PLATFORM="solaris sunos unix svr4" +fi +if [ $OS = Linux ]; then + PLATFORM="linux unix" +fi +if [ $OS = FreeBSD ]; then + PLATFORM="freebsd bsd unix" +fi +if [ $OS = Darwin ]; then + PLATFORM="macos osx bsd unix" +fi +echo $OS | grep "MINGW" > /dev/null +if [ $? -eq 0 ]; then + PLATFORM="windows mingw" +fi + +if [ -z "$PLATFORM" ]; then + PLATFORM="unix" +fi + +for p in $PLATFORM +do + PLATFORM_NAME=$p + break +done +echo $PLATFORM_NAME + +isplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 0 + fi + done + return 1 +} +isnotplatform() +{ + for p in $PLATFORM + do + if [ $p = $1 ]; then + return 1 + fi + done + return 0 +} + +# generate config.mk and config.h +cat > $TEMP_DIR/config.mk << __EOF__ +# +# config.mk generated by configure +# + +# general vars +#foreach( $var in $vars ) +${var.name}=$${var.name} +#end + +#if ( ! $project.hasVar("PREFIX") ) +PREFIX=$PREFIX +#end +#if ( ! $project.hasVar("EPREFIX") ) +EPREFIX=$EPREFIX +#end + +#if ( ! $project.hasVar("BINDIR") ) +BINDIR=$BINDIR +#end +#if ( ! $project.hasVar("SBINDIR") ) +SBINDIR=$SBINDIR +#end +#if ( ! $project.hasVar("LIBDIR") ) +LIBDIR=$LIBDIR +#end +#if ( ! $project.hasVar("LIBEXECDIR") ) +LIBEXECDIR=$LIBEXECDIR +#end +#if ( ! $project.hasVar("DATADIR") ) +DATADIR=$DATADIR +#end +#if ( ! $project.hasVar("SYSCONFDIR") ) +SYSCONFDIR=$SYSCONFDIR +#end +#if ( ! $project.hasVar("SHAREDSTATEDIR") ) +SHAREDSTATEDIR=$SHAREDSTATEDIR +#end +#if ( ! $project.hasVar("LOCALSTATEDIR") ) +LOCALSTATEDIR=$LOCALSTATEDIR +#end +#if ( ! $project.hasVar("INCLUDEDIR") ) +INCLUDEDIR=$INCLUDEDIR +#end +#if ( ! $project.hasVar("INFODIR") ) +INFODIR=$INFODIR +#end +#if ( ! $project.hasVar("MANDIR") ) +MANDIR=$MANDIR +#end + +__EOF__ + +echo > $TEMP_DIR/make.mk + +ENV_CFLAGS=$CFLAGS +ENV_LDFLAGS=$LDFLAGS +ENV_CXXFLAGS=$CXXFLAGS + +# Toolchain detection +# this will insert make vars to config.mk +. make/toolchain.sh + +# add user specified flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${ENV_CFLAGS}" ]; then + echo "CFLAGS += $ENV_CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_CXXFLAGS}" ]; then + echo "CXXFLAGS += $ENV_CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${ENV_LDFLAGS}" ]; then + echo "LDFLAGS += $ENV_LDFLAGS" >> $TEMP_DIR/config.mk +fi + +# +# DEPENDENCIES +# + +#foreach( $dependency in $namedDependencies ) +dependency_${dependency.name}() +{ + printf "checking for ${dependency.name}... " + #foreach( $sub in $dependency.getSubdependencies() ) + # dependency $sub.name $sub.getPlatformString() + while true + do + #if( $sub.platform ) + if isnotplatform "${sub.platform}"; then + break + fi + #end + #foreach( $not in $sub.getNotList() ) + if isplatform "${not}"; then + break + fi + #end + #if( $sub.pkgconfig.size() > 0 ) + if [ -z "$PKG_CONFIG" ]; then + break + fi + #end + #foreach( $pkg in $sub.pkgconfig ) + $PKG_CONFIG $pkg.getPkgConfigParam() + if [ $? -ne 0 ] ; then + break + fi + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`" + #end + #foreach( $flags in $sub.flags ) + #if( $flags.exec ) + $flags.value > /dev/null + if [ $? -eq 0 ]; then + $flags.varName="$$flags.varName `$flags.value`" + else + break + fi + #else + $flags.varName="$$flags.varName $flags.value" + #end + #end + #foreach( $test in $sub.tests ) + $test > /dev/null + if [ $? -ne 0 ]; then + break + fi + #end + #if ( $sub.make.length() > 0 ) + cat >> $TEMP_DIR/make.mk << __EOF__ +# Dependency: $dependency.name +$sub.make +__EOF__ + #end + echo yes + return 0 + done + + #end + echo no + return 1 +} +#end + +DEPENDENCIES_FAILED= +ERROR=0 +#if( $dependencies.size() > 0 ) +# general dependencies +CFLAGS= +LDFLAGS= +#foreach( $dependency in $dependencies ) +while true +do + #if( $dependency.platform ) + if isnotplatform "${dependency.platform}"; then + break + fi + #end + #foreach( $not in $dependency.getNotList() ) + if isplatform "${not}"; then + break + fi + #end + while true + do + #if( $dependency.pkgconfig.size() > 0 ) + if [ -z "$PKG_CONFIG" ]; then + ERROR=1 + break + fi + #end + #foreach( $pkg in $dependency.pkgconfig ) + printf "checking for pkg-config package $pkg.getPkgConfigParam()... " + $PKG_CONFIG $pkg.getPkgConfigParam() + if [ $? -ne 0 ]; then + echo no + ERROR=1 + break + fi + echo yes + CFLAGS="$CFLAGS `$PKG_CONFIG --cflags $pkg.getPkgConfigParam()`" + LDFLAGS="$LDFLAGS `$PKG_CONFIG --libs $pkg.getPkgConfigParam()`" + #end + + #foreach( $flags in $dependency.flags ) + #if( $flags.exec ) + $flags.value > /dev/null + if [ $? -ne 0 ]; then + $flags.varName="$$flags.varName `$flags.value`" + else + ERROR=1 + break + fi + #else + $flags.varName="$$flags.varName $flags.value" + #end + #end + #if ( $dependency.make.length() > 0 ) + cat >> $TEMP_DIR/make.mk << __EOF__ +$dependency.make +__EOF__ + #end + + break + done + + break +done +#end + +# add general dependency flags to config.mk +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "CFLAGS += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "CXXFLAGS += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "LDFLAGS += $LDFLAGS" >> $TEMP_DIR/config.mk +fi +#end + +# +# OPTION VALUES +# +#foreach( $opt in $options ) +#foreach( $val in $opt.values ) +${val.func}() +{ + VERR=0 + #foreach( $dep in $val.dependencies ) + dependency_$dep + if [ $? -ne 0 ]; then + VERR=1 + fi + #end + if [ $VERR -ne 0 ]; then + return 1 + fi + #foreach( $def in $val.defines ) + CFLAGS="$CFLAGS ${def.toFlags()}" + #end + #if( $val.hasMake() ) + cat >> $TEMP_DIR/make.mk << __EOF__ +$val.make +__EOF__ + #end + return 0 +} +#end +#end + +# +# TARGETS +# +CFLAGS= +CXXFLAGS= +LDFLAGS= + +#foreach( $target in $targets ) +#if ( $target.name ) +# Target: $target.name +#else +# Target +#end +CFLAGS= +LDFLAGS= +CXXFLAGS= + +#foreach( $dependency in $target.dependencies ) +dependency_$dependency +if [ $? -ne 0 ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 +fi +#end + +# Features +#foreach( $feature in $target.features ) +if [ ! -z "$${feature.getVarName()}" ]; then +#foreach( $dependency in $feature.dependencies ) + # check dependency + dependency_$dependency + if [ $? -ne 0 ]; then + # "auto" features can fail and are just disabled in this case + if [ $${feature.getVarName()} != "auto" ]; then + DEPENDENCIES_FAILED="$DEPENDENCIES_FAILED ${dependency} " + ERROR=1 + fi + fi +#end +fi +#end + +#foreach( $opt in $target.options ) +# Option: --${opt.argument} +if [ -z ${D}${opt.getVarName()} ]; then + SAVED_ERROR=$ERROR + SAVED_DEPENDENCIES_FAILED=$DEPENDENCIES_FAILED + ERROR=0 + while true + do + #foreach( $optdef in $opt.defaults ) + #if( $optdef.platform ) + if isplatform "$optdef.platform"; then + #end + $optdef.func + if [ $? -eq 0 ]; then + echo " ${opt.argument}: ${optdef.valueName}" >> $TEMP_DIR/options + ERROR=0 + break + fi + #if( $optdef.platform ) + fi + #end + #end + break + done + if [ $ERROR -ne 0 ]; then + SAVED_ERROR=1 + fi + ERROR=$SAVED_ERROR + DEPENDENCIES_FAILED=$SAVED_DEPENDENCIES_FAILED= +else + if false; then + false + #foreach( $optval in $opt.values ) + elif [ ${D}${opt.getVarName()} = "${optval.value}" ]; then + echo " ${opt.argument}: ${D}${opt.getVarName()}" >> $TEMP_DIR/options + $optval.func + if [ $? -ne 0 ]; then + ERROR=1 + fi + #end + fi +fi +#end + +echo >> $TEMP_DIR/config.mk +if [ ! -z "${CFLAGS}" ]; then + echo "${target.getCFlags()} += $CFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${CXXFLAGS}" ]; then + echo "${target.getCXXFlags()} += $CXXFLAGS" >> $TEMP_DIR/config.mk +fi +if [ ! -z "${LDFLAGS}" ]; then + echo "${target.getLDFlags()} += $LDFLAGS" >> $TEMP_DIR/config.mk +fi + +#end +if [ $ERROR -ne 0 ]; then + echo + echo "Error: Unresolved dependencies" + echo $DEPENDENCIES_FAILED + rm -Rf $TEMP_DIR + exit 1 +fi + +echo "configure finished" +echo +echo "Build Config:" +echo " PREFIX: $PREFIX" +echo " TOOLCHAIN: $TOOLCHAIN_NAME" +#if ( $options.size() > 0 ) +echo "Options:" +cat $TEMP_DIR/options +#end +echo +cat $TEMP_DIR/config.mk $TEMP_DIR/make.mk > config.mk +rm -Rf $TEMP_DIR + + diff --git a/make/gcc.mk b/make/gcc.mk new file mode 100644 index 0000000..624bdf1 --- /dev/null +++ b/make/gcc.mk @@ -0,0 +1,10 @@ +# +# gcc toolchain config +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -fPIC +SHLIB_LDFLAGS = -shared + diff --git a/make/mingw.mk b/make/mingw.mk new file mode 100644 index 0000000..340102e --- /dev/null +++ b/make/mingw.mk @@ -0,0 +1,46 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm +MSBUILD = MSBuild.exe + +CFLAGS = -std=gnu99 -c -O2 -m64 +COFLAGS = -o +LDFLAGS = +LOFLAGS = -o +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = .exe + +PACKAGE_SCRIPT = package_windows.sh \ No newline at end of file diff --git a/make/osx.mk b/make/osx.mk new file mode 100644 index 0000000..0db5e1c --- /dev/null +++ b/make/osx.mk @@ -0,0 +1,43 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS += -std=gnu99 -g -I/usr/include/libxml2 +LDFLAGS += -lxml2 -lz -lpthread -licucore -lm +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = o +LIB_EXT = a +APP_EXT = + +PACKAGE_SCRIPT = package_osx.sh diff --git a/make/package_unix.sh b/make/package_unix.sh new file mode 100755 index 0000000..13f4793 --- /dev/null +++ b/make/package_unix.sh @@ -0,0 +1,2 @@ +#!/bin/sh + diff --git a/make/project.xml b/make/project.xml new file mode 100644 index 0000000..9ce2c49 --- /dev/null +++ b/make/project.xml @@ -0,0 +1,27 @@ + + + + -DUI_MOTIF + -lXm -lXt -lX11 -lpthread + + + + OBJ_EXT = o + LIB_EXT = a + PACKAGE_SCRIPT = package_unix.sh + + + + -lpthread + + + + -I/usr/local/include + -L/usr/local/lib + + + + motif + + + diff --git a/make/suncc.mk b/make/suncc.mk new file mode 100644 index 0000000..a97fe3c --- /dev/null +++ b/make/suncc.mk @@ -0,0 +1,10 @@ +# +# suncc toolchain +# + +CFLAGS = +LDFLAGS = + +SHLIB_CFLAGS = -Kpic +SHLIB_LDFLAGS = -G + diff --git a/make/toolchain.sh b/make/toolchain.sh new file mode 100644 index 0000000..8e9a529 --- /dev/null +++ b/make/toolchain.sh @@ -0,0 +1,181 @@ +#!/bin/sh +# +# toolchain detection +# + +C_COMPILERS="cc gcc clang suncc" +CPP_COMPILERS="CC g++ clang++ sunCC" +unset CC_ARG_CHECKED +unset TOOLCHAIN_DETECTION_ERROR +unset TOOLCHAIN_NAME + +check_c_compiler() +{ + cat > $TEMP_DIR/test.c << __EOF__ +/* test file */ +#include +int main(int argc, char **argv) { +#if defined(__clang__) + printf("clang\n"); +#elif defined(__GNUC__) + printf("gcc\n"); +#elif defined(__sun) + printf("suncc\n"); +#else + printf("unknown\n"); +#endif + return 0; +} +__EOF__ + rm -f $TEMP_DIR/checkcc + $1 -o $TEMP_DIR/checkcc $CFLAGS $LDFLAGS $TEMP_DIR/test.c 2> /dev/null + + if [ $? -ne 0 ]; then + return 1 + fi + return 0 +} + +check_cpp_compiler() +{ + cat > $TEMP_DIR/test.cpp << __EOF__ +/* test file */ +#include +int main(int argc, char **argv) { +#if defined(__clang__) + std::cout << "clang" << std::endl; +#elif defined(__GNUC__) + std::cout << "gcc" << std::endl; +#elif defined(__sun) + std::cout << "suncc" << std::endl; +#else + std::cout << "unknown" << std::endl; +#endif + return 0; +} +__EOF__ + rm -f $TEMP_DIR/checkcc + $1 -o $TEMP_DIR/checkcc $CXXFLAGS $LDFLAGS $TEMP_DIR/test.cpp 2> /dev/null + + if [ $? -ne 0 ]; then + return 1 + fi + return 0 +} + +printf "detect C compiler... " + +for COMP in $C_COMPILERS +do + check_c_compiler $COMP + if [ $? -ne 0 ]; then + if [ ! -z "$CC" ]; then + if [ $COMP = $CC ]; then + echo "$CC is not a working C Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi + fi + else + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME + if [ $COMP = "cc" ]; then + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_c_compiler "gcc" + if [ $? -eq 0 ]; then + COMP=gcc + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_c_compiler "clang" + if [ $? -eq 0 ]; then + COMP=clang + USE_TOOLCHAIN="clang" + fi + fi + fi + + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CC=$COMP + echo $COMP + break + fi +done +if [ -z $TOOLCHAIN_CC ]; then + echo "not found" +fi + +printf "detect C++ compiler... " + +for COMP in $CPP_COMPILERS +do + check_cpp_compiler $COMP + if [ $? -ne 0 ]; then + if [ ! -z "$CXX" ]; then + if [ $COMP = $CXX ]; then + echo "$CC is not a working C++ Compiler" + TOOLCHAIN_DETECTION_ERROR="error" + break + fi + fi + else + if [ $COMP = "CC" ]; then + # we have found a working compiler, but in case + # the compiler is gcc or clang, we try to use + # these commands and not 'cc' + TOOLCHAIN_NAME=`$TEMP_DIR/checkcc` + USE_TOOLCHAIN=$TOOLCHAIN_NAME + if [ $TOOLCHAIN_NAME = "gcc" ]; then + check_cpp_compiler "g++" + if [ $? -eq 0 ]; then + COMP=g++ + USE_TOOLCHAIN="gcc" + fi + fi + if [ $TOOLCHAIN_NAME = "clang" ]; then + check_cpp_compiler "clang++" + if [ $? -eq 0 ]; then + COMP=clang++ + USE_TOOLCHAIN="clang" + fi + fi + fi + + TOOLCHAIN_NAME=$USE_TOOLCHAIN + TOOLCHAIN_CXX=$COMP + echo $COMP + break + fi +done +if [ -z $TOOLCHAIN_CXX ]; then + echo "not found" +fi + +TOOLCHAIN_LD=$TOOLCHAIN_CC + +if [ -z "$TOOLCHAIN_NAME" ]; then + TOOLCHAIN_DETECTION_ERROR="error" +else + cat >> $TEMP_DIR/config.mk << __EOF__ +# toolchain +__EOF__ + echo "CC = ${TOOLCHAIN_CC}" >> $TEMP_DIR/config.mk + if [ ! -z "$TOOLCHAIN_CXX" ]; then + echo "CXX = ${TOOLCHAIN_CXX}" >> $TEMP_DIR/config.mk + fi + echo "LD = ${TOOLCHAIN_LD}" >> $TEMP_DIR/config.mk + echo >> $TEMP_DIR/config.mk + + cat "make/${TOOLCHAIN_NAME}.mk" > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "include \$(BUILD_ROOT)/make/${TOOLCHAIN_NAME}.mk" >> $TEMP_DIR/config.mk + else + echo "SHLIB_CFLAGS = -fPIC" >> $TEMP_DIR/config.mk + echo "SHLIB_LDFLAGS = -shared" >> $TEMP_DIR/config.mk + fi +fi diff --git a/make/windows.mk b/make/windows.mk new file mode 100644 index 0000000..2f3bc72 --- /dev/null +++ b/make/windows.mk @@ -0,0 +1,42 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +CC = gcc +LD = gcc +AR = ar +RM = rm + +CFLAGS = -std=gnu99 +LDFLAGS = +ARFLAGS = -r +RMFLAGS = -f + +OBJ_EXT = obj +LIB_EXT = lib +APP_EXT = .exe + diff --git a/ucx/Makefile b/ucx/Makefile new file mode 100644 index 0000000..2369197 --- /dev/null +++ b/ucx/Makefile @@ -0,0 +1,62 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2013 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = ../ +include ../config.mk + +# list of source files +SRC = utils.c +SRC += list.c +SRC += map.c +SRC += avl.c +SRC += properties.c +SRC += mempool.c +SRC += string.c +SRC += test.c +SRC += allocator.c +SRC += logging.c +SRC += buffer.c +SRC += stack.c +SRC += ucx.c +SRC += array.c + +OBJ = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT)) + +UCX_LIB = ../build/lib/libucx.$(LIB_EXT) + +all: ../build/ucx $(UCX_LIB) + +$(UCX_LIB): $(OBJ) + $(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ) + +../build/ucx: + mkdir -p ../build/ucx + +../build/ucx/%.$(OBJ_EXT): %.c + $(CC) $(CFLAGS) -o $@ -c $< + diff --git a/ucx/README b/ucx/README new file mode 100644 index 0000000..d0890d0 --- /dev/null +++ b/ucx/README @@ -0,0 +1,4 @@ +UCX is a library for common data structures, algorithms and string functions. + +More informations at: https://develop.uap-core.de/ucx/ + diff --git a/ucx/allocator.c b/ucx/allocator.c new file mode 100644 index 0000000..22a5cd5 --- /dev/null +++ b/ucx/allocator.c @@ -0,0 +1,60 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/allocator.h" + +#include + +static UcxAllocator default_allocator = { + NULL, + ucx_default_malloc, + ucx_default_calloc, + ucx_default_realloc, + ucx_default_free +}; + +UcxAllocator *ucx_default_allocator() { + UcxAllocator *allocator = &default_allocator; + return allocator; +} + +void *ucx_default_malloc(void *ignore, size_t n) { + return malloc(n); +} + +void *ucx_default_calloc(void *ignore, size_t n, size_t size) { + return calloc(n, size); +} + +void *ucx_default_realloc(void *ignore, void *data, size_t n) { + return realloc(data, n); +} + +void ucx_default_free(void *ignore, void *data) { + free(data); +} diff --git a/ucx/array.c b/ucx/array.c new file mode 100644 index 0000000..0592fc6 --- /dev/null +++ b/ucx/array.c @@ -0,0 +1,467 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _GNU_SOURCE /* we want to use qsort_r(), if available */ +#define __STDC_WANT_LIB_EXT1__ 1 /* use qsort_s, if available */ + + +#include "ucx/array.h" +#include "ucx/utils.h" + +#include +#include +#include + +#ifndef UCX_ARRAY_DISABLE_QSORT +#ifdef __GLIBC__ +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 8) +#define ucx_array_sort_impl qsort_r +#endif /* glibc version >= 2.8 */ +#elif /* not __GLIBC__ */ defined(__APPLE__) || defined(__FreeBSD__) +#define ucx_array_sort_impl ucx_qsort_r +#define USE_UCX_QSORT_R +#elif /* not (__APPLE || __FreeBSD__) */ defined(__sun) +#if __STDC_VERSION__ >= 201112L +#define ucx_array_sort_impl qsort_s +#endif +#endif /* __GLIBC__, __APLE__, __FreeBSD__, __sun */ +#endif /* UCX_ARRAY_DISABLE_QSORT */ + +#ifndef ucx_array_sort_impl +#define ucx_array_sort_impl ucx_mergesort +#endif + +static int ucx_array_ensurecap(UcxArray *array, size_t reqcap) { + size_t required_capacity = array->capacity; + while (reqcap > required_capacity) { + if (required_capacity * 2 < required_capacity) + return 1; + required_capacity <<= 1; + } + if (ucx_array_reserve(array, required_capacity)) { + return 1; + } + return 0; +} + +int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t elmsize, size_t index, void* data) { + + if(!alloc || !capacity || !array) { + errno = EINVAL; + return 1; + } + + size_t newcapacity = *capacity; + while(index >= newcapacity) { + if(ucx_szmul(newcapacity, 2, &newcapacity)) { + errno = EOVERFLOW; + return 1; + } + } + + size_t memlen, offset; + if(ucx_szmul(newcapacity, elmsize, &memlen)) { + errno = EOVERFLOW; + return 1; + } + /* we don't need to check index*elmsize - it is smaller than memlen */ + + + void* newptr = alrealloc(alloc, *array, memlen); + if(newptr == NULL) { + errno = ENOMEM; /* we cannot assume that every allocator sets this */ + return 1; + } + *array = newptr; + *capacity = newcapacity; + + + char* dest = *array; + dest += elmsize*index; + memcpy(dest, data, elmsize); + + return 0; +} + +int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t index, void* data) { + + return ucx_array_util_set_a(alloc, array, capacity, sizeof(void*), + index, &data); +} + +UcxArray* ucx_array_new(size_t capacity, size_t elemsize) { + return ucx_array_new_a(capacity, elemsize, ucx_default_allocator()); +} + +UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + UcxArray* array = almalloc(allocator, sizeof(UcxArray)); + if(array) { + ucx_array_init_a(array, capacity, elemsize, allocator); + } + return array; +} + +void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize) { + ucx_array_init_a(array, capacity, elemsize, ucx_default_allocator()); +} + +void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize, + UcxAllocator* allocator) { + + array->allocator = allocator; + array->elemsize = elemsize; + array->size = 0; + array->data = alcalloc(allocator, capacity, elemsize); + + if (array->data) { + array->capacity = capacity; + } else { + array->capacity = 0; + } +} + +int ucx_array_clone(UcxArray* dest, UcxArray const* src) { + if (ucx_array_ensurecap(dest, src->capacity)) { + return 1; + } + + dest->elemsize = src->elemsize; + dest->size = src->size; + + if (dest->data) { + memcpy(dest->data, src->data, src->size*src->elemsize); + } + + return 0; +} + +int ucx_array_equals(UcxArray const *array1, UcxArray const *array2, + cmp_func cmpfnc, void* data) { + + if (array1->size != array2->size || array1->elemsize != array2->elemsize) { + return 0; + } else { + if (array1->size == 0) + return 1; + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array1->elemsize; + data = &elemsize; + } + + for (size_t i = 0 ; i < array1->size ; i++) { + int r = cmpfnc( + ucx_array_at(array1, i), + ucx_array_at(array2, i), + data); + if (r != 0) + return 0; + } + return 1; + } +} + +void ucx_array_destroy(UcxArray *array) { + if(array->data) + alfree(array->allocator, array->data); + array->data = NULL; + array->capacity = array->size = 0; +} + +void ucx_array_free(UcxArray *array) { + ucx_array_destroy(array); + alfree(array->allocator, array); +} + +int ucx_array_append_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + void* dest = ucx_array_at(array, array->size); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_prepend_from(UcxArray *array, void *data, size_t count) { + if (ucx_array_ensurecap(array, array->size + count)) + return 1; + + if (array->size > 0) { + void *dest = ucx_array_at(array, count); + memmove(dest, array->data, array->elemsize*array->size); + } + + if (data) { + memcpy(array->data, data, array->elemsize*count); + } else { + memset(array->data, 0, array->elemsize*count); + } + array->size += count; + + return 0; +} + +int ucx_array_set_from(UcxArray *array, size_t index, + void *data, size_t count) { + if (ucx_array_ensurecap(array, index + count)) + return 1; + + if (index+count > array->size) { + array->size = index+count; + } + + void *dest = ucx_array_at(array, index); + if (data) { + memcpy(dest, data, array->elemsize*count); + } else { + memset(dest, 0, array->elemsize*count); + } + + return 0; +} + +int ucx_array_concat(UcxArray *array1, const UcxArray *array2) { + + if (array1->elemsize != array2->elemsize) + return 1; + + size_t capacity = array1->capacity+array2->capacity; + + if (array1->capacity < capacity) { + if (ucx_array_reserve(array1, capacity)) { + return 1; + } + } + + void* dest = ucx_array_at(array1, array1->size); + memcpy(dest, array2->data, array2->size*array2->elemsize); + + array1->size += array2->size; + + return 0; +} + +void *ucx_array_at(UcxArray const *array, size_t index) { + char* memory = array->data; + char* loc = memory + index*array->elemsize; + return loc; +} + +size_t ucx_array_find(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + + size_t elemsize; + if (cmpfnc == NULL) { + cmpfnc = ucx_cmp_mem; + elemsize = array->elemsize; + data = &elemsize; + } + + if (array->size > 0) { + for (size_t i = 0 ; i < array->size ; i++) { + void* ptr = ucx_array_at(array, i); + if (cmpfnc(ptr, elem, data) == 0) { + return i; + } + } + return array->size; + } else { + return 0; + } +} + +int ucx_array_contains(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data) { + return ucx_array_find(array, elem, cmpfnc, data) != array->size; +} + +static void ucx_mergesort_merge(void *arrdata,size_t elemsize, + cmp_func cmpfnc, void *data, + size_t start, size_t mid, size_t end) { + + char* array = arrdata; + + size_t rightstart = mid + 1; + + if (cmpfnc(array + mid*elemsize, + array + rightstart*elemsize, data) <= 0) { + /* already sorted */ + return; + } + + /* we need memory for one element */ + void *value = malloc(elemsize); + + while (start <= mid && rightstart <= end) { + if (cmpfnc(array + start*elemsize, + array + rightstart*elemsize, data) <= 0) { + start++; + } else { + /* save the value from the right */ + memcpy(value, array + rightstart*elemsize, elemsize); + + /* shift all left elements one element to the right */ + size_t shiftcount = rightstart-start; + void *startptr = array + start*elemsize; + void *dest = array + (start+1)*elemsize; + memmove(dest, startptr, shiftcount*elemsize); + + /* bring the first value from the right to the left */ + memcpy(startptr, value, elemsize); + + start++; + mid++; + rightstart++; + } + } + + /* free the temporary memory */ + free(value); +} + +static void ucx_mergesort_impl(void *arrdata, size_t elemsize, + cmp_func cmpfnc, void *data, size_t l, size_t r) { + if (l < r) { + size_t m = l + (r - l) / 2; + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, l, m); + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, m + 1, r); + ucx_mergesort_merge(arrdata, elemsize, cmpfnc, data, l, m, r); + } +} + +static void ucx_mergesort(void *arrdata, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + + ucx_mergesort_impl(arrdata, elemsize, cmpfnc, data, 0, count-1); +} + +#ifdef USE_UCX_QSORT_R +struct cmpfnc_swapargs_info { + cmp_func func; + void *data; +}; + +static int cmp_func_swap_args(void *data, const void *x, const void *y) { + struct cmpfnc_swapargs_info* info = data; + return info->func(x, y, info->data); +} + +static void ucx_qsort_r(void *array, size_t count, size_t elemsize, + cmp_func cmpfnc, void *data) { + struct cmpfnc_swapargs_info info; + info.func = cmpfnc; + info.data = data; + qsort_r(array, count, elemsize, &info, cmp_func_swap_args); +} +#endif /* USE_UCX_QSORT_R */ + +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data) { + ucx_array_sort_impl(array->data, array->size, array->elemsize, + cmpfnc, data); +} + +void ucx_array_remove(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, index+1); + memmove(dest, src, (array->size - index)*array->elemsize); + } +} + +void ucx_array_remove_fast(UcxArray *array, size_t index) { + array->size--; + if (index < array->size) { + void* dest = ucx_array_at(array, index); + void* src = ucx_array_at(array, array->size); + memcpy(dest, src, array->elemsize); + } +} + +int ucx_array_shrink(UcxArray* array) { + void* newptr = alrealloc(array->allocator, array->data, + array->size*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = array->size; + return 0; + } else { + return 1; + } +} + +int ucx_array_resize(UcxArray* array, size_t capacity) { + if (array->capacity >= capacity) { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + if (array->size > array->capacity) { + array->size = array->capacity; + } + return 0; + } else { + return 1; + } + } else { + return ucx_array_reserve(array, capacity); + } +} + +int ucx_array_reserve(UcxArray* array, size_t capacity) { + if (array->capacity > capacity) { + return 0; + } else { + void* newptr = alrealloc(array->allocator, array->data, + capacity*array->elemsize); + if (newptr) { + array->data = newptr; + array->capacity = capacity; + return 0; + } else { + return 1; + } + } +} + +int ucx_array_grow(UcxArray* array, size_t count) { + return ucx_array_reserve(array, array->size+count); +} diff --git a/ucx/avl.c b/ucx/avl.c new file mode 100644 index 0000000..7639b56 --- /dev/null +++ b/ucx/avl.c @@ -0,0 +1,373 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/avl.h" + +#include + +#define ptrcast(ptr) ((void*)(ptr)) +#define alloc_tree(al) (UcxAVLTree*) almalloc((al), sizeof(UcxAVLTree)) +#define alloc_node(al) (UcxAVLNode*) almalloc((al), sizeof(UcxAVLNode)) + +static void ucx_avl_connect(UcxAVLTree *tree, + UcxAVLNode *node, UcxAVLNode *child, intptr_t nullkey) { + if (child) { + child->parent = node; + } + // if child is NULL, nullkey decides if left or right pointer is cleared + if (tree->cmpfunc( + ptrcast(child ? child->key : nullkey), + ptrcast(node->key), tree->userdata) > 0) { + node->right = child; + } else { + node->left = child; + } + size_t lh = node->left ? node->left->height : 0; + size_t rh = node->right ? node->right->height : 0; + node->height = 1 + (lh > rh ? lh : rh); +} + +#define avlheight(node) ((node) ? (node)->height : 0) + +static UcxAVLNode* avl_rotright(UcxAVLTree *tree, UcxAVLNode *l0) { + UcxAVLNode *p = l0->parent; + UcxAVLNode *l1 = l0->left; + if (p) { + ucx_avl_connect(tree, p, l1, 0); + } else { + l1->parent = NULL; + } + ucx_avl_connect(tree, l0, l1->right, l1->key); + ucx_avl_connect(tree, l1, l0, 0); + return l1; +} + +static UcxAVLNode* avl_rotleft(UcxAVLTree *tree, UcxAVLNode *l0) { + UcxAVLNode *p = l0->parent; + UcxAVLNode *l1 = l0->right; + if (p) { + ucx_avl_connect(tree, p, l1, 0); + } else { + l1->parent = NULL; + } + ucx_avl_connect(tree, l0, l1->left, l1->key); + ucx_avl_connect(tree, l1, l0, 0); + return l1; +} + +static void ucx_avl_balance(UcxAVLTree *tree, UcxAVLNode *n) { + int lh = avlheight(n->left); + int rh = avlheight(n->right); + n->height = 1 + (lh > rh ? lh : rh); + + if (lh - rh == 2) { + UcxAVLNode *c = n->left; + if (avlheight(c->right) - avlheight(c->left) == 1) { + avl_rotleft(tree, c); + } + n = avl_rotright(tree, n); + } else if (rh - lh == 2) { + UcxAVLNode *c = n->right; + if (avlheight(c->left) - avlheight(c->right) == 1) { + avl_rotright(tree, c); + } + n = avl_rotleft(tree, n); + } + + if (n->parent) { + ucx_avl_balance(tree, n->parent); + } else { + tree->root = n; + } +} + +UcxAVLTree *ucx_avl_new(cmp_func cmpfunc) { + return ucx_avl_new_a(cmpfunc, ucx_default_allocator()); +} + +UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator) { + UcxAVLTree* tree = alloc_tree(allocator); + if (tree) { + tree->allocator = allocator; + tree->cmpfunc = cmpfunc; + tree->root = NULL; + tree->userdata = NULL; + } + + return tree; +} + +static void ucx_avl_free_node(UcxAllocator *al, UcxAVLNode *node) { + if (node) { + ucx_avl_free_node(al, node->left); + ucx_avl_free_node(al, node->right); + alfree(al, node); + } +} + +void ucx_avl_free(UcxAVLTree *tree) { + UcxAllocator *al = tree->allocator; + ucx_avl_free_node(al, tree->root); + alfree(al, tree); +} + +static void ucx_avl_free_content_node(UcxAllocator *al, UcxAVLNode *node, + ucx_destructor destr) { + if (node) { + ucx_avl_free_content_node(al, node->left, destr); + ucx_avl_free_content_node(al, node->right, destr); + if (destr) { + destr(node->value); + } else { + alfree(al, node->value); + } + } +} + +void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr) { + ucx_avl_free_content_node(tree->allocator, tree->root, destr); +} + +UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key) { + UcxAVLNode *n = tree->root; + int cmpresult; + while (n && (cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + n = cmpresult > 0 ? n->right : n->left; + } + return n; +} + +void *ucx_avl_get(UcxAVLTree *tree, intptr_t key) { + UcxAVLNode *n = ucx_avl_get_node(tree, key); + return n ? n->value : NULL; +} + +UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = tree->root; + UcxAVLNode *closest = NULL; + + intmax_t cmpresult; + intmax_t closest_dist; + closest_dist = mode == UCX_AVL_FIND_LOWER_BOUNDED ? INTMAX_MIN : INTMAX_MAX; + + while (n && (cmpresult = dfnc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + if (mode == UCX_AVL_FIND_CLOSEST) { + intmax_t dist = cmpresult; + if (dist < 0) dist *= -1; + if (dist < closest_dist) { + closest_dist = dist; + closest = n; + } + } else if (mode == UCX_AVL_FIND_LOWER_BOUNDED && cmpresult <= 0) { + if (cmpresult > closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } else if (mode == UCX_AVL_FIND_UPPER_BOUNDED && cmpresult >= 0) { + if (cmpresult < closest_dist) { + closest_dist = cmpresult; + closest = n; + } + } + n = cmpresult > 0 ? n->right : n->left; + } + return n ? n : closest; +} + +void *ucx_avl_find(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode) { + UcxAVLNode *n = ucx_avl_find_node(tree, key, dfnc, mode); + return n ? n->value : NULL; +} + +int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value) { + return ucx_avl_put_s(tree, key, value, NULL); +} + +int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, + void **oldvalue) { + if (tree->root) { + UcxAVLNode *n = tree->root; + int cmpresult; + while ((cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + UcxAVLNode *m = cmpresult > 0 ? n->right : n->left; + if (m) { + n = m; + } else { + break; + } + } + + if (cmpresult) { + UcxAVLNode* e = alloc_node(tree->allocator); + if (e) { + e->key = key; e->value = value; e->height = 1; + e->parent = e->left = e->right = NULL; + ucx_avl_connect(tree, n, e, 0); + ucx_avl_balance(tree, n); + return 0; + } else { + return 1; + } + } else { + if (oldvalue) { + *oldvalue = n->value; + } + n->value = value; + return 0; + } + } else { + tree->root = alloc_node(tree->allocator); + if (tree->root) { + tree->root->key = key; tree->root->value = value; + tree->root->height = 1; + tree->root->parent = tree->root->left = tree->root->right = NULL; + + if (oldvalue) { + *oldvalue = NULL; + } + + return 0; + } else { + return 1; + } + } +} + +int ucx_avl_remove(UcxAVLTree *tree, intptr_t key) { + return ucx_avl_remove_s(tree, key, NULL, NULL); +} + +int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node) { + return ucx_avl_remove_s(tree, node->key, NULL, NULL); +} + +int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key, + intptr_t *oldkey, void **oldvalue) { + + UcxAVLNode *n = tree->root; + int cmpresult; + while (n && (cmpresult = tree->cmpfunc( + ptrcast(key), ptrcast(n->key), tree->userdata))) { + n = cmpresult > 0 ? n->right : n->left; + } + if (n) { + if (oldkey) { + *oldkey = n->key; + } + if (oldvalue) { + *oldvalue = n->value; + } + + UcxAVLNode *p = n->parent; + if (n->left && n->right) { + UcxAVLNode *s = n->right; + while (s->left) { + s = s->left; + } + ucx_avl_connect(tree, s->parent, s->right, s->key); + n->key = s->key; n->value = s->value; + p = s->parent; + alfree(tree->allocator, s); + } else { + if (p) { + ucx_avl_connect(tree, p, n->right ? n->right:n->left, n->key); + } else { + tree->root = n->right ? n->right : n->left; + if (tree->root) { + tree->root->parent = NULL; + } + } + alfree(tree->allocator, n); + } + + if (p) { + ucx_avl_balance(tree, p); + } + + return 0; + } else { + return 1; + } +} + +static size_t ucx_avl_countn(UcxAVLNode *node) { + if (node) { + return 1 + ucx_avl_countn(node->left) + ucx_avl_countn(node->right); + } else { + return 0; + } +} + +size_t ucx_avl_count(UcxAVLTree *tree) { + return ucx_avl_countn(tree->root); +} + +UcxAVLNode* ucx_avl_pred(UcxAVLNode* node) { + if (node->left) { + UcxAVLNode* n = node->left; + while (n->right) { + n = n->right; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->right == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} + +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node) { + if (node->right) { + UcxAVLNode* n = node->right; + while (n->left) { + n = n->left; + } + return n; + } else { + UcxAVLNode* n = node; + while (n->parent) { + if (n->parent->left == n) { + return n->parent; + } else { + n = n->parent; + } + } + return NULL; + } +} diff --git a/ucx/buffer.c b/ucx/buffer.c new file mode 100644 index 0000000..a6a8085 --- /dev/null +++ b/ucx/buffer.c @@ -0,0 +1,297 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/buffer.h" + +#include +#include +#include + +UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags) { + UcxBuffer *buffer = (UcxBuffer*) malloc(sizeof(UcxBuffer)); + if (buffer) { + buffer->flags = flags; + if (!space) { + buffer->space = (char*)malloc(capacity); + if (!buffer->space) { + free(buffer); + return NULL; + } + memset(buffer->space, 0, capacity); + buffer->flags |= UCX_BUFFER_AUTOFREE; + } else { + buffer->space = (char*)space; + } + buffer->capacity = capacity; + buffer->size = 0; + + buffer->pos = 0; + } + + return buffer; +} + +void ucx_buffer_free(UcxBuffer *buffer) { + if ((buffer->flags & UCX_BUFFER_AUTOFREE) == UCX_BUFFER_AUTOFREE) { + free(buffer->space); + } + free(buffer); +} + +UcxBuffer* ucx_buffer_extract( + UcxBuffer *src, size_t start, size_t length, int flags) { + if (src->size == 0 || length == 0 || + ((size_t)-1) - start < length || start+length > src->capacity) + { + return NULL; + } + + UcxBuffer *dst = (UcxBuffer*) malloc(sizeof(UcxBuffer)); + if (dst) { + dst->space = (char*)malloc(length); + if (!dst->space) { + free(dst); + return NULL; + } + dst->capacity = length; + dst->size = length; + dst->flags = flags | UCX_BUFFER_AUTOFREE; + dst->pos = 0; + memcpy(dst->space, src->space+start, length); + } + return dst; +} + +int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence) { + size_t npos; + switch (whence) { + case SEEK_CUR: + npos = buffer->pos; + break; + case SEEK_END: + npos = buffer->size; + break; + case SEEK_SET: + npos = 0; + break; + default: + return -1; + } + + size_t opos = npos; + npos += offset; + + if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { + return -1; + } + + if (npos >= buffer->size) { + return -1; + } else { + buffer->pos = npos; + return 0; + } + +} + +int ucx_buffer_eof(UcxBuffer *buffer) { + return buffer->pos >= buffer->size; +} + +int ucx_buffer_extend(UcxBuffer *buffer, size_t len) { + size_t newcap = buffer->capacity; + + if (buffer->capacity + len < buffer->capacity) { + return -1; + } + + while (buffer->capacity + len > newcap) { + newcap <<= 1; + if (newcap < buffer->capacity) { + return -1; + } + } + + char *newspace = (char*)realloc(buffer->space, newcap); + if (newspace) { + memset(newspace+buffer->size, 0, newcap-buffer->size); + buffer->space = newspace; + buffer->capacity = newcap; + } else { + return -1; + } + + return 0; +} + +size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer) { + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } + size_t required = buffer->pos + len; + if (buffer->pos > required) { + return 0; + } + + if (required > buffer->capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if (ucx_buffer_extend(buffer, required - buffer->capacity)) { + return 0; + } + } else { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len%size; + } + } + } + + if (len == 0) { + return len; + } + + memcpy(buffer->space + buffer->pos, ptr, len); + buffer->pos += len; + if(buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + + return len / size; +} + +size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer) { + size_t len; + if(ucx_szmul(size, nitems, &len)) { + return 0; + } + if (buffer->pos + len > buffer->size) { + len = buffer->size - buffer->pos; + if (size > 1) len -= len%size; + } + + if (len <= 0) { + return len; + } + + memcpy(ptr, buffer->space + buffer->pos, len); + buffer->pos += len; + + return len / size; +} + +int ucx_buffer_putc(UcxBuffer *buffer, int c) { + if(buffer->pos >= buffer->capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if(ucx_buffer_extend(buffer, 1)) { + return EOF; + } + } else { + return EOF; + } + } + + c &= 0xFF; + buffer->space[buffer->pos] = (char) c; + buffer->pos++; + if(buffer->pos > buffer->size) { + buffer->size = buffer->pos; + } + return c; +} + +int ucx_buffer_getc(UcxBuffer *buffer) { + if (ucx_buffer_eof(buffer)) { + return EOF; + } else { + int c = ((unsigned char*)buffer->space)[buffer->pos]; + buffer->pos++; + return c; + } +} + +size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str) { + return ucx_buffer_write((const void*)str, 1, strlen(str), buffer); +} + +int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift) { + if (shift >= buffer->size) { + buffer->pos = buffer->size = 0; + } else { + memmove(buffer->space, buffer->space + shift, buffer->size - shift); + buffer->size -= shift; + + if (buffer->pos >= shift) { + buffer->pos -= shift; + } else { + buffer->pos = 0; + } + } + return 0; +} + +int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift) { + size_t req_capacity = buffer->size + shift; + size_t movebytes; + + // auto extend buffer, if required and enabled + if (buffer->capacity < req_capacity) { + if ((buffer->flags & UCX_BUFFER_AUTOEXTEND) == UCX_BUFFER_AUTOEXTEND) { + if (ucx_buffer_extend(buffer, req_capacity - buffer->capacity)) { + return 1; + } + movebytes = buffer->size; + } else { + movebytes = buffer->capacity - shift; + } + } else { + movebytes = buffer->size; + } + + memmove(buffer->space + shift, buffer->space, movebytes); + buffer->size = shift+movebytes; + + buffer->pos += shift; + if (buffer->pos > buffer->size) { + buffer->pos = buffer->size; + } + + return 0; +} + +int ucx_buffer_shift(UcxBuffer* buffer, off_t shift) { + if (shift < 0) { + return ucx_buffer_shift_left(buffer, (size_t) (-shift)); + } else if (shift > 0) { + return ucx_buffer_shift_right(buffer, (size_t) shift); + } else { + return 0; + } +} diff --git a/ucx/list.c b/ucx/list.c new file mode 100644 index 0000000..293592c --- /dev/null +++ b/ucx/list.c @@ -0,0 +1,428 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/list.h" + +UcxList *ucx_list_clone(const UcxList *l, copy_func fnc, void *data) { + return ucx_list_clone_a(ucx_default_allocator(), l, fnc, data); +} + +UcxList *ucx_list_clone_a(UcxAllocator *alloc, const UcxList *l, + copy_func fnc, void *data) { + UcxList *ret = NULL; + while (l) { + if (fnc) { + ret = ucx_list_append_a(alloc, ret, fnc(l->data, data)); + } else { + ret = ucx_list_append_a(alloc, ret, l->data); + } + l = l->next; + } + return ret; +} + +int ucx_list_equals(const UcxList *l1, const UcxList *l2, + cmp_func fnc, void* data) { + if (l1 == l2) return 1; + + while (l1 != NULL && l2 != NULL) { + if (fnc == NULL) { + if (l1->data != l2->data) return 0; + } else { + if (fnc(l1->data, l2->data, data) != 0) return 0; + } + l1 = l1->next; + l2 = l2->next; + } + + return (l1 == NULL && l2 == NULL); +} + +void ucx_list_free(UcxList *l) { + ucx_list_free_a(ucx_default_allocator(), l); +} + +void ucx_list_free_a(UcxAllocator *alloc, UcxList *l) { + UcxList *e = l, *f; + while (e != NULL) { + f = e; + e = e->next; + alfree(alloc, f); + } +} + +void ucx_list_free_content(UcxList* list, ucx_destructor destr) { + if (!destr) destr = free; + while (list != NULL) { + destr(list->data); + list = list->next; + } +} + +UcxList *ucx_list_append(UcxList *l, void *data) { + return ucx_list_append_a(ucx_default_allocator(), l, data); +} + +UcxList *ucx_list_append_a(UcxAllocator *alloc, UcxList *l, void *data) { + UcxList *nl = (UcxList*) almalloc(alloc, sizeof(UcxList)); + if (!nl) { + return NULL; + } + + nl->data = data; + nl->next = NULL; + if (l) { + UcxList *t = ucx_list_last(l); + t->next = nl; + nl->prev = t; + return l; + } else { + nl->prev = NULL; + return nl; + } +} + +UcxList *ucx_list_prepend(UcxList *l, void *data) { + return ucx_list_prepend_a(ucx_default_allocator(), l, data); +} + +UcxList *ucx_list_prepend_a(UcxAllocator *alloc, UcxList *l, void *data) { + UcxList *nl = ucx_list_append_a(alloc, NULL, data); + if (!nl) { + return NULL; + } + l = ucx_list_first(l); + + if (l) { + nl->next = l; + l->prev = nl; + } + return nl; +} + +UcxList *ucx_list_concat(UcxList *l1, UcxList *l2) { + if (l1) { + UcxList *last = ucx_list_last(l1); + last->next = l2; + if (l2) { + l2->prev = last; + } + return l1; + } else { + return l2; + } +} + +UcxList *ucx_list_last(const UcxList *l) { + if (l == NULL) return NULL; + + const UcxList *e = l; + while (e->next != NULL) { + e = e->next; + } + return (UcxList*)e; +} + +ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem) { + ssize_t index = 0; + while (list) { + if (list == elem) { + return index; + } + list = list->next; + index++; + } + return -1; +} + +UcxList *ucx_list_get(const UcxList *l, size_t index) { + if (l == NULL) return NULL; + + const UcxList *e = l; + while (e->next && index > 0) { + e = e->next; + index--; + } + + return (UcxList*)(index == 0 ? e : NULL); +} + +ssize_t ucx_list_find(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { + ssize_t index = 0; + UCX_FOREACH(e, l) { + if (fnc) { + if (fnc(elem, e->data, cmpdata) == 0) { + return index; + } + } else { + if (elem == e->data) { + return index; + } + } + index++; + } + return -1; +} + +int ucx_list_contains(const UcxList *l, void *elem, + cmp_func fnc, void *cmpdata) { + return ucx_list_find(l, elem, fnc, cmpdata) > -1; +} + +size_t ucx_list_size(const UcxList *l) { + if (l == NULL) return 0; + + const UcxList *e = l; + size_t s = 1; + while (e->next != NULL) { + e = e->next; + s++; + } + + return s; +} + +static UcxList *ucx_list_sort_merge(size_t length, + UcxList* ls, UcxList* le, UcxList* re, + cmp_func fnc, void* data) { + + UcxList** sorted = (UcxList**) malloc(sizeof(UcxList*)*length); + UcxList *rc, *lc; + + lc = ls; rc = le; + size_t n = 0; + while (lc && lc != le && rc != re) { + if (fnc(lc->data, rc->data, data) <= 0) { + sorted[n] = lc; + lc = lc->next; + } else { + sorted[n] = rc; + rc = rc->next; + } + n++; + } + while (lc && lc != le) { + sorted[n] = lc; + lc = lc->next; + n++; + } + while (rc && rc != re) { + sorted[n] = rc; + rc = rc->next; + n++; + } + + // Update pointer + sorted[0]->prev = NULL; + for (int i = 0 ; i < length-1 ; i++) { + sorted[i]->next = sorted[i+1]; + sorted[i+1]->prev = sorted[i]; + } + sorted[length-1]->next = NULL; + + UcxList *ret = sorted[0]; + free(sorted); + return ret; +} + +UcxList *ucx_list_sort(UcxList *l, cmp_func fnc, void *data) { + if (l == NULL) { + return NULL; + } + + UcxList *lc; + size_t ln = 1; + + UcxList *ls = l, *le, *re; + + // check how many elements are already sorted + lc = ls; + while (lc->next != NULL && fnc(lc->next->data, lc->data, data) > 0) { + lc = lc->next; + ln++; + } + le = lc->next; + + if (le == NULL) { + return l; // this list is already sorted :) + } else { + UcxList *rc; + size_t rn = 1; + rc = le; + // skip already sorted elements + while (rc->next != NULL && fnc(rc->next->data, rc->data, data) > 0) { + rc = rc->next; + rn++; + } + re = rc->next; + + // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them + UcxList *sorted = ucx_list_sort_merge(ln+rn, + ls, le, re, + fnc, data); + + // Something left? Sort it! + size_t remainder_length = ucx_list_size(re); + if (remainder_length > 0) { + UcxList *remainder = ucx_list_sort(re, fnc, data); + + // merge sorted list with (also sorted) remainder + l = ucx_list_sort_merge(ln+rn+remainder_length, + sorted, remainder, NULL, fnc, data); + } else { + // no remainder - we've got our sorted list + l = sorted; + } + + return l; + } +} + +UcxList *ucx_list_first(const UcxList *l) { + if (!l) { + return NULL; + } + + const UcxList *e = l; + while (e->prev) { + e = e->prev; + } + return (UcxList *)e; +} + +UcxList *ucx_list_remove(UcxList *l, UcxList *e) { + return ucx_list_remove_a(ucx_default_allocator(), l, e); +} + +UcxList *ucx_list_remove_a(UcxAllocator *alloc, UcxList *l, UcxList *e) { + if (l == e) { + l = e->next; + } + + if (e->next) { + e->next->prev = e->prev; + } + + if (e->prev) { + e->prev->next = e->next; + } + + alfree(alloc, e); + return l; +} + + +static UcxList* ucx_list_setoperation_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata, + int op) { + + UcxList *res = NULL; + UcxList *cur = NULL; + const UcxList *src = left; + + do { + UCX_FOREACH(node, src) { + void* elem = node->data; + if ( + (op == 0 && !ucx_list_contains(res, elem, cmpfnc, cmpdata)) || + (op == 1 && ucx_list_contains(right, elem, cmpfnc, cmpdata)) || + (op == 2 && !ucx_list_contains(right, elem, cmpfnc, cmpdata))) { + UcxList *nl = almalloc(allocator, sizeof(UcxList)); + nl->prev = cur; + nl->next = NULL; + if (cpfnc) { + nl->data = cpfnc(elem, cpdata); + } else { + nl->data = elem; + } + if (cur != NULL) + cur->next = nl; + cur = nl; + if (res == NULL) + res = cur; + } + } + if (op == 0 && src == left) + src = right; + else + src = NULL; + } while (src != NULL); + + return res; +} + +UcxList* ucx_list_union(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_union_a(ucx_default_allocator(), + left, right, cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_union_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 0); +} + +UcxList* ucx_list_intersection(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_intersection_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 1); +} + +UcxList* ucx_list_difference(UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + return ucx_list_difference_a(ucx_default_allocator(), left, right, + cmpfnc, cmpdata, cpfnc, cpdata); +} + +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + UcxList const *left, UcxList const *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata) { + + return ucx_list_setoperation_a(allocator, left, right, + cmpfnc, cmpdata, cpfnc, cpdata, 2); +} diff --git a/ucx/logging.c b/ucx/logging.c new file mode 100644 index 0000000..d6fdce0 --- /dev/null +++ b/ucx/logging.c @@ -0,0 +1,117 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/logging.h" + +#include +#include +#include +#include + +UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask) { + UcxLogger *logger = (UcxLogger*) malloc(sizeof(UcxLogger)); + if (logger != NULL) { + logger->stream = stream; + logger->writer = (write_func)fwrite; + logger->dateformat = (char*) "%F %T %z "; + logger->level = level; + logger->mask = mask; + logger->levels = ucx_map_new(8); + + unsigned int l; + l = UCX_LOGGER_ERROR; + ucx_map_int_put(logger->levels, l, (void*) "[ERROR]"); + l = UCX_LOGGER_WARN; + ucx_map_int_put(logger->levels, l, (void*) "[WARNING]"); + l = UCX_LOGGER_INFO; + ucx_map_int_put(logger->levels, l, (void*) "[INFO]"); + l = UCX_LOGGER_DEBUG; + ucx_map_int_put(logger->levels, l, (void*) "[DEBUG]"); + l = UCX_LOGGER_TRACE; + ucx_map_int_put(logger->levels, l, (void*) "[TRACE]"); + } + + return logger; +} + +void ucx_logger_free(UcxLogger *logger) { + ucx_map_free(logger->levels); + free(logger); +} + +// estimated max. message length (documented) +#define UCX_LOGGER_MSGMAX 4096 + +void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, + const unsigned int line, const char *format, ...) { + if (level <= logger->level) { + char msg[UCX_LOGGER_MSGMAX]; + const char *text; + size_t k = 0; + size_t n; + + if ((logger->mask & UCX_LOGGER_LEVEL) > 0) { + text = (const char*) ucx_map_int_get(logger->levels, level); + if (!text) { + text = "[UNKNOWN]"; + } + n = strlen(text); + n = n > 256 ? 256 : n; + memcpy(msg+k, text, n); + k += n; + msg[k++] = ' '; + } + if ((logger->mask & UCX_LOGGER_TIMESTAMP) > 0) { + time_t now = time(NULL); + k += strftime(msg+k, 128, logger->dateformat, localtime(&now)); + } + if ((logger->mask & UCX_LOGGER_SOURCE) > 0) { + char *fpart = strrchr(file, '/'); + if (fpart) file = fpart+1; + fpart = strrchr(file, '\\'); + if (fpart) file = fpart+1; + n = strlen(file); + memcpy(msg+k, file, n); + k += n; + k += sprintf(msg+k, ":%u ", line); + } + + if (k > 0) { + msg[k++] = '-'; msg[k++] = ' '; + } + + va_list args; + va_start (args, format); + k += vsnprintf(msg+k, UCX_LOGGER_MSGMAX-k-1, format, args); + va_end (args); + + msg[k++] = '\n'; + + logger->writer(msg, 1, k, logger->stream); + } +} diff --git a/ucx/map.c b/ucx/map.c new file mode 100644 index 0000000..ba7961d --- /dev/null +++ b/ucx/map.c @@ -0,0 +1,402 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/map.h" + +#include +#include + +UcxMap *ucx_map_new(size_t size) { + return ucx_map_new_a(NULL, size); +} + +UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size) { + if(size == 0) { + size = 16; + } + + if(!allocator) { + allocator = ucx_default_allocator(); + } + + UcxMap *map = (UcxMap*)almalloc(allocator, sizeof(UcxMap)); + if (!map) { + return NULL; + } + + map->allocator = allocator; + map->map = (UcxMapElement**)alcalloc( + allocator, size, sizeof(UcxMapElement*)); + if(map->map == NULL) { + alfree(allocator, map); + return NULL; + } + map->size = size; + map->count = 0; + + return map; +} + +static void ucx_map_free_elmlist_contents(UcxMap *map) { + for (size_t n = 0 ; n < map->size ; n++) { + UcxMapElement *elem = map->map[n]; + if (elem != NULL) { + do { + UcxMapElement *next = elem->next; + alfree(map->allocator, elem->key.data); + alfree(map->allocator, elem); + elem = next; + } while (elem != NULL); + } + } +} + +void ucx_map_free(UcxMap *map) { + ucx_map_free_elmlist_contents(map); + alfree(map->allocator, map->map); + alfree(map->allocator, map); +} + +void ucx_map_free_content(UcxMap *map, ucx_destructor destr) { + UcxMapIterator iter = ucx_map_iterator(map); + void *val; + UCX_MAP_FOREACH(key, val, iter) { + if (destr) { + destr(val); + } else { + alfree(map->allocator, val); + } + } +} + +void ucx_map_clear(UcxMap *map) { + if (map->count == 0) { + return; // nothing to do + } + ucx_map_free_elmlist_contents(map); + memset(map->map, 0, map->size*sizeof(UcxMapElement*)); + map->count = 0; +} + +int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data) { + UcxMapIterator i = ucx_map_iterator(from); + void *value; + UCX_MAP_FOREACH(key, value, i) { + if (ucx_map_put(to, key, fnc ? fnc(value, data) : value)) { + return 1; + } + } + return 0; +} + +UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data) { + return ucx_map_clone_a(ucx_default_allocator(), map, fnc, data); +} + +UcxMap *ucx_map_clone_a(UcxAllocator *allocator, + UcxMap const *map, copy_func fnc, void *data) { + size_t bs = (map->count * 5) >> 1; + UcxMap *newmap = ucx_map_new_a(allocator, bs > map->size ? bs : map->size); + if (!newmap) { + return NULL; + } + ucx_map_copy(map, newmap, fnc, data); + return newmap; +} + +int ucx_map_rehash(UcxMap *map) { + size_t load = (map->size * 3) >> 2; + if (map->count > load) { + UcxMap oldmap; + oldmap.map = map->map; + oldmap.size = map->size; + oldmap.count = map->count; + oldmap.allocator = map->allocator; + + map->size = (map->count * 5) >> 1; + map->map = (UcxMapElement**)alcalloc( + map->allocator, map->size, sizeof(UcxMapElement*)); + if (!map->map) { + *map = oldmap; + return 1; + } + map->count = 0; + ucx_map_copy(&oldmap, map, NULL, NULL); + + /* free the UcxMapElement list of oldmap */ + ucx_map_free_elmlist_contents(&oldmap); + alfree(map->allocator, oldmap.map); + } + return 0; +} + +int ucx_map_put(UcxMap *map, UcxKey key, void *data) { + UcxAllocator *allocator = map->allocator; + + if (key.hash == 0) { + key.hash = ucx_hash((const char*)key.data, key.len); + } + + struct UcxMapKey mapkey; + mapkey.hash = key.hash; + + size_t slot = mapkey.hash%map->size; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *prev = NULL; + + while (elm && elm->key.hash < mapkey.hash) { + prev = elm; + elm = elm->next; + } + + if (!elm || elm->key.hash != mapkey.hash) { + UcxMapElement *e = (UcxMapElement*)almalloc( + allocator, sizeof(UcxMapElement)); + if (!e) { + return -1; + } + e->key.data = NULL; + if (prev) { + prev->next = e; + } else { + map->map[slot] = e; + } + e->next = elm; + elm = e; + } + + if (!elm->key.data) { + void *kd = almalloc(allocator, key.len); + if (!kd) { + return -1; + } + memcpy(kd, key.data, key.len); + mapkey.data = kd; + mapkey.len = key.len; + elm->key = mapkey; + map->count++; + } + elm->data = data; + + return 0; +} + +static void* ucx_map_get_and_remove(UcxMap *map, UcxKey key, int remove) { + if(key.hash == 0) { + key.hash = ucx_hash((const char*)key.data, key.len); + } + + size_t slot = key.hash%map->size; + UcxMapElement *elm = map->map[slot]; + UcxMapElement *pelm = NULL; + while (elm && elm->key.hash <= key.hash) { + if(elm->key.hash == key.hash) { + int n = (key.len > elm->key.len) ? elm->key.len : key.len; + if (memcmp(elm->key.data, key.data, n) == 0) { + void *data = elm->data; + if (remove) { + if (pelm) { + pelm->next = elm->next; + } else { + map->map[slot] = elm->next; + } + alfree(map->allocator, elm->key.data); + alfree(map->allocator, elm); + map->count--; + } + + return data; + } + } + pelm = elm; + elm = pelm->next; + } + + return NULL; +} + +void *ucx_map_get(UcxMap const *map, UcxKey key) { + return ucx_map_get_and_remove((UcxMap *)map, key, 0); +} + +void *ucx_map_remove(UcxMap *map, UcxKey key) { + return ucx_map_get_and_remove(map, key, 1); +} + +UcxKey ucx_key(const void *data, size_t len) { + UcxKey key; + key.data = data; + key.len = len; + key.hash = ucx_hash((const char*)data, len); + return key; +} + + +int ucx_hash(const char *data, size_t len) { + /* murmur hash 2 */ + + int m = 0x5bd1e995; + int r = 24; + + int h = 25 ^ len; + + int i = 0; + while (len >= 4) { + int k = data[i + 0] & 0xFF; + k |= (data[i + 1] & 0xFF) << 8; + k |= (data[i + 2] & 0xFF) << 16; + k |= (data[i + 3] & 0xFF) << 24; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + i += 4; + len -= 4; + } + + switch (len) { + case 3: h ^= (data[i + 2] & 0xFF) << 16; + /* no break */ + case 2: h ^= (data[i + 1] & 0xFF) << 8; + /* no break */ + case 1: h ^= (data[i + 0] & 0xFF); h *= m; + /* no break */ + } + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} + +UcxMapIterator ucx_map_iterator(UcxMap const *map) { + UcxMapIterator i; + i.map = map; + i.cur = NULL; + i.index = 0; + return i; +} + +int ucx_map_iter_next(UcxMapIterator *i, UcxKey *key, void **elm) { + UcxMapElement *e = i->cur; + + if (e) { + e = e->next; + } else { + e = i->map->map[0]; + } + + while (i->index < i->map->size) { + if (e) { + if (e->data) { + i->cur = e; + *elm = e->data; + key->data = e->key.data; + key->hash = e->key.hash; + key->len = e->key.len; + return 1; + } + + e = e->next; + } else { + i->index++; + + if (i->index < i->map->size) { + e = i->map->map[i->index]; + } + } + } + + return 0; +} + +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_union_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap* result = ucx_map_clone_a(allocator, first, cpfnc, cpdata); + ucx_map_copy(second, result, cpfnc, cpdata); + return result; +} + +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_intersection_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + UcxMap *result = ucx_map_new_a(allocator, first->size < second->size ? + first->size : second->size); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + return result; +} + +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + return ucx_map_difference_a(ucx_default_allocator(), + first, second, cpfnc, cpdata); +} + +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata) { + + UcxMap *result = ucx_map_new_a(allocator, first->size - second->count); + + UcxMapIterator iter = ucx_map_iterator(first); + void* value; + UCX_MAP_FOREACH(key, value, iter) { + if (!ucx_map_get(second, key)) { + ucx_map_put(result, key, cpfnc ? cpfnc(value, cpdata) : value); + } + } + + ucx_map_rehash(result); + return result; +} \ No newline at end of file diff --git a/ucx/mempool.c b/ucx/mempool.c new file mode 100644 index 0000000..beedc31 --- /dev/null +++ b/ucx/mempool.c @@ -0,0 +1,237 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/mempool.h" + +#include +#include +#include +#ifdef __cplusplus +#define __STDC_FORMAT_MACROS +#endif +#include + +/** Capsule for destructible memory chunks. */ +typedef struct { + /** The destructor for the memory chunk. */ + ucx_destructor destructor; + /** + * First byte of the memory chunk. + * Note, that the address &c is also the address + * of the whole memory chunk. + */ + char c; +} ucx_memchunk; + +/** Capsule for data and its destructor. */ +typedef struct { + /** The destructor for the data. */ + ucx_destructor destructor; + /** A pointer to the data. */ + void *ptr; +} ucx_regdestr; + +#ifdef __cplusplus +extern "C" +#endif +void ucx_mempool_shared_destr(void* ptr) { + ucx_regdestr *rd = (ucx_regdestr*)ptr; + rd->destructor(rd->ptr); +} + +UcxMempool *ucx_mempool_new(size_t n) { + size_t poolsz; + if(ucx_szmul(n, sizeof(void*), &poolsz)) { + return NULL; + } + + UcxMempool *pool = (UcxMempool*)malloc(sizeof(UcxMempool)); + if (!pool) { + return NULL; + } + + pool->data = (void**) malloc(poolsz); + if (pool->data == NULL) { + free(pool); + return NULL; + } + + pool->ndata = 0; + pool->size = n; + + UcxAllocator *allocator = (UcxAllocator*)malloc(sizeof(UcxAllocator)); + if(!allocator) { + free(pool->data); + free(pool); + return NULL; + } + allocator->malloc = (ucx_allocator_malloc)ucx_mempool_malloc; + allocator->calloc = (ucx_allocator_calloc)ucx_mempool_calloc; + allocator->realloc = (ucx_allocator_realloc)ucx_mempool_realloc; + allocator->free = (ucx_allocator_free)ucx_mempool_free; + allocator->pool = pool; + pool->allocator = allocator; + + return pool; +} + +int ucx_mempool_chcap(UcxMempool *pool, size_t newcap) { + if (newcap < pool->ndata) { + return 1; + } + + size_t newcapsz; + if(ucx_szmul(newcap, sizeof(void*), &newcapsz)) { + return 1; + } + + void **data = (void**) realloc(pool->data, newcapsz); + if (data) { + pool->data = data; + pool->size = newcap; + return 0; + } else { + return 1; + } +} + +void *ucx_mempool_malloc(UcxMempool *pool, size_t n) { + if(((size_t)-1) - sizeof(ucx_destructor) < n) { + return NULL; + } + + if (pool->ndata >= pool->size) { + size_t newcap = pool->size*2; + if (newcap < pool->size || ucx_mempool_chcap(pool, newcap)) { + return NULL; + } + } + + void *p = malloc(sizeof(ucx_destructor) + n); + ucx_memchunk *mem = (ucx_memchunk*)p; + if (!mem) { + return NULL; + } + + mem->destructor = NULL; + pool->data[pool->ndata] = mem; + pool->ndata++; + + return &(mem->c); +} + +void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize) { + size_t msz; + if(ucx_szmul(nelem, elsize, &msz)) { + return NULL; + } + + void *ptr = ucx_mempool_malloc(pool, msz); + if (!ptr) { + return NULL; + } + memset(ptr, 0, nelem * elsize); + return ptr; +} + +void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n) { + if(((size_t)-1) - sizeof(ucx_destructor) < n) { + return NULL; + } + + char *mem = ((char*)ptr) - sizeof(ucx_destructor); + char *newm = (char*) realloc(mem, n + sizeof(ucx_destructor)); + if (!newm) { + return NULL; + } + if (mem != newm) { + for(size_t i=0 ; i < pool->ndata ; i++) { + if(pool->data[i] == mem) { + pool->data[i] = newm; + return newm + sizeof(ucx_destructor); + } + } + fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", + (intptr_t)ptr, (intptr_t)pool); + abort(); + } else { + return newm + sizeof(ucx_destructor); + } +} + +void ucx_mempool_free(UcxMempool *pool, void *ptr) { + ucx_memchunk *chunk = (ucx_memchunk*)((char*)ptr-sizeof(ucx_destructor)); + for(size_t i=0 ; indata ; i++) { + if(chunk == pool->data[i]) { + if(chunk->destructor != NULL) { + chunk->destructor(&(chunk->c)); + } + free(chunk); + size_t last_index = pool->ndata - 1; + if(i != last_index) { + pool->data[i] = pool->data[last_index]; + pool->data[last_index] = NULL; + } + pool->ndata--; + return; + } + } + fprintf(stderr, "FATAL: 0x%08" PRIxPTR" not in mpool 0x%08" PRIxPTR"\n", + (intptr_t)ptr, (intptr_t)pool); + abort(); +} + +void ucx_mempool_destroy(UcxMempool *pool) { + ucx_memchunk *chunk; + for(size_t i=0 ; indata ; i++) { + chunk = (ucx_memchunk*) pool->data[i]; + if(chunk) { + if(chunk->destructor) { + chunk->destructor(&(chunk->c)); + } + free(chunk); + } + } + free(pool->data); + free(pool->allocator); + free(pool); +} + +void ucx_mempool_set_destr(void *ptr, ucx_destructor func) { + *(ucx_destructor*)((char*)ptr-sizeof(ucx_destructor)) = func; +} + +void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr) { + ucx_regdestr *rd = (ucx_regdestr*)ucx_mempool_malloc( + pool, + sizeof(ucx_regdestr)); + rd->destructor = destr; + rd->ptr = ptr; + ucx_mempool_set_destr(rd, ucx_mempool_shared_destr); +} + diff --git a/ucx/properties.c b/ucx/properties.c new file mode 100644 index 0000000..1cb4de0 --- /dev/null +++ b/ucx/properties.c @@ -0,0 +1,264 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/properties.h" + +#include +#include +#include + +UcxProperties *ucx_properties_new() { + UcxProperties *parser = (UcxProperties*)malloc( + sizeof(UcxProperties)); + if(!parser) { + return NULL; + } + + parser->buffer = NULL; + parser->buflen = 0; + parser->pos = 0; + parser->tmp = NULL; + parser->tmplen = 0; + parser->tmpcap = 0; + parser->error = 0; + parser->delimiter = '='; + parser->comment1 = '#'; + parser->comment2 = 0; + parser->comment3 = 0; + + return parser; +} + +void ucx_properties_free(UcxProperties *parser) { + if(parser->tmp) { + free(parser->tmp); + } + free(parser); +} + +void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) { + parser->buffer = buf; + parser->buflen = len; + parser->pos = 0; +} + +static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) { + if(parser->tmpcap - parser->tmplen < len) { + size_t newcap = parser->tmpcap + len + 64; + parser->tmp = (char*)realloc(parser->tmp, newcap); + parser->tmpcap = newcap; + } + memcpy(parser->tmp + parser->tmplen, buf, len); + parser->tmplen += len; +} + +int ucx_properties_next(UcxProperties *parser, sstr_t *name, sstr_t *value) { + if(parser->tmplen > 0) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + sstr_t str = sstrn(buf, len); + sstr_t nl = sstrchr(str, '\n'); + if(nl.ptr) { + size_t newlen = (size_t)(nl.ptr - buf) + 1; + parser_tmp_append(parser, buf, newlen); + // the tmp buffer contains exactly one line now + + char *orig_buf = parser->buffer; + size_t orig_len = parser->buflen; + + parser->buffer = parser->tmp; + parser->buflen = parser->tmplen; + parser->pos = 0; + parser->tmp = NULL; + parser->tmpcap = 0; + parser->tmplen = 0; + // run ucx_properties_next with the tmp buffer as main buffer + int ret = ucx_properties_next(parser, name, value); + + // restore original buffer + parser->tmp = parser->buffer; + parser->buffer = orig_buf; + parser->buflen = orig_len; + parser->pos = newlen; + + /* + * if ret == 0 the tmp buffer contained just space or a comment + * we parse again with the original buffer to get a name/value + * or a new tmp buffer + */ + return ret ? ret : ucx_properties_next(parser, name, value); + } else { + parser_tmp_append(parser, buf, len); + return 0; + } + } else if(parser->tmp) { + free(parser->tmp); + parser->tmp = NULL; + } + + char comment1 = parser->comment1; + char comment2 = parser->comment2; + char comment3 = parser->comment3; + char delimiter = parser->delimiter; + + // get one line and parse it + while(parser->pos < parser->buflen) { + char *buf = parser->buffer + parser->pos; + size_t len = parser->buflen - parser->pos; + + /* + * First we check if we have at least one line. We also get indices of + * delimiter and comment chars + */ + size_t delimiter_index = 0; + size_t comment_index = 0; + int has_comment = 0; + + size_t i = 0; + char c = 0; + for(;itmpcap = len + 128; + parser->tmp = (char*)malloc(parser->tmpcap); + parser->tmplen = len; + memcpy(parser->tmp, buf, len); + return 0; + } + + sstr_t line = has_comment ? sstrn(buf, comment_index) : sstrn(buf, i); + // check line + if(delimiter_index == 0) { + line = sstrtrim(line); + if(line.length != 0) { + parser->error = 1; + } + } else { + sstr_t n = sstrn(buf, delimiter_index); + sstr_t v = sstrn( + buf + delimiter_index + 1, + line.length - delimiter_index - 1); + n = sstrtrim(n); + v = sstrtrim(v); + if(n.length != 0 || v.length != 0) { + *name = n; + *value = v; + parser->pos += i + 1; + return 1; + } else { + parser->error = 1; + } + } + + parser->pos += i + 1; + } + + return 0; +} + +int ucx_properties2map(UcxProperties *parser, UcxMap *map) { + sstr_t name; + sstr_t value; + while(ucx_properties_next(parser, &name, &value)) { + value = sstrdup_a(map->allocator, value); + if(!value.ptr) { + return 1; + } + if(ucx_map_sstr_put(map, name, value.ptr)) { + alfree(map->allocator, value.ptr); + return 1; + } + } + if (parser->error) { + return parser->error; + } else { + return 0; + } +} + +// buffer size is documented - change doc, when you change bufsize! +#define UCX_PROPLOAD_BUFSIZE 1024 +int ucx_properties_load(UcxMap *map, FILE *file) { + UcxProperties *parser = ucx_properties_new(); + if(!(parser && map && file)) { + return 1; + } + + int error = 0; + size_t r; + char buf[UCX_PROPLOAD_BUFSIZE]; + while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) { + ucx_properties_fill(parser, buf, r); + error = ucx_properties2map(parser, map); + if (error) { + break; + } + } + ucx_properties_free(parser); + return error; +} + +int ucx_properties_store(UcxMap *map, FILE *file) { + UcxMapIterator iter = ucx_map_iterator(map); + void *v; + sstr_t value; + size_t written; + + UCX_MAP_FOREACH(k, v, iter) { + value = sstr((char*)v); + + written = 0; + written += fwrite(k.data, 1, k.len, file); + written += fwrite(" = ", 1, 3, file); + written += fwrite(value.ptr, 1, value.length, file); + written += fwrite("\n", 1, 1, file); + + if (written != k.len + value.length + 4) { + return 1; + } + } + + return 0; +} + diff --git a/ucx/stack.c b/ucx/stack.c new file mode 100644 index 0000000..467233e --- /dev/null +++ b/ucx/stack.c @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/stack.h" + +#include + +static size_t ucx_stack_align(size_t n) { + int align = n % sizeof(void*); + if (align) { + n += sizeof(void*) - align; + } + return n; +} + +void ucx_stack_init(UcxStack *stack, char* space, size_t size) { + stack->size = size - size % sizeof(void*); + stack->space = space; + stack->top = NULL; + + stack->allocator.pool = stack; + stack->allocator.malloc = (ucx_allocator_malloc) ucx_stack_malloc; + stack->allocator.calloc = (ucx_allocator_calloc) ucx_stack_calloc; + stack->allocator.realloc = (ucx_allocator_realloc) ucx_stack_realloc; + stack->allocator.free = (ucx_allocator_free) ucx_stack_free; +} + +void *ucx_stack_malloc(UcxStack *stack, size_t n) { + + if (ucx_stack_avail(stack) < ucx_stack_align(n)) { + return NULL; + } else { + char *prev = stack->top; + if (stack->top) { + stack->top += ucx_stack_align(ucx_stack_topsize(stack)); + } else { + stack->top = stack->space; + } + + ((struct ucx_stack_metadata*)stack->top)->prev = prev; + ((struct ucx_stack_metadata*)stack->top)->size = n; + stack->top += sizeof(struct ucx_stack_metadata); + + return stack->top; + } +} + +void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize) { + void *mem = ucx_stack_malloc(stack, nelem*elsize); + memset(mem, 0, nelem*elsize); + return mem; +} + +void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n) { + if (ptr == stack->top) { + if (stack->size - (stack->top - stack->space) < ucx_stack_align(n)) { + return NULL; + } else { + ((struct ucx_stack_metadata*)stack->top - 1)->size = n; + return ptr; + } + } else { + if (ucx_stack_align(((struct ucx_stack_metadata*)ptr - 1)->size) < + ucx_stack_align(n)) { + void *nptr = ucx_stack_malloc(stack, n); + if (nptr) { + memcpy(nptr, ptr, n); + ucx_stack_free(stack, ptr); + + return nptr; + } else { + return NULL; + } + } else { + ((struct ucx_stack_metadata*)ptr - 1)->size = n; + return ptr; + } + } +} + +void ucx_stack_free(UcxStack *stack, void *ptr) { + if (ptr == stack->top) { + stack->top = ((struct ucx_stack_metadata*) stack->top - 1)->prev; + } else { + struct ucx_stack_metadata *next = (struct ucx_stack_metadata*)( + (char*)ptr + + ucx_stack_align(((struct ucx_stack_metadata*) ptr - 1)->size) + ); + next->prev = ((struct ucx_stack_metadata*) ptr - 1)->prev; + } +} + +void ucx_stack_popn(UcxStack *stack, void *dest, size_t n) { + if (ucx_stack_empty(stack)) { + return; + } + + if (dest) { + size_t len = ucx_stack_topsize(stack); + if (len > n) { + len = n; + } + + memcpy(dest, stack->top, len); + } + + ucx_stack_free(stack, stack->top); +} + +size_t ucx_stack_avail(UcxStack *stack) { + size_t avail = ((stack->top ? (stack->size + - (stack->top - stack->space) + - ucx_stack_align(ucx_stack_topsize(stack))) + : stack->size)); + + if (avail > sizeof(struct ucx_stack_metadata)) { + return avail - sizeof(struct ucx_stack_metadata); + } else { + return 0; + } +} + +void *ucx_stack_push(UcxStack *stack, size_t n, const void *data) { + void *space = ucx_stack_malloc(stack, n); + if (space) { + memcpy(space, data, n); + } + return space; +} + +void *ucx_stack_pusharr(UcxStack *stack, + size_t nelem, size_t elsize, const void *data) { + + // skip the memset by using malloc + void *space = ucx_stack_malloc(stack, nelem*elsize); + if (space) { + memcpy(space, data, nelem*elsize); + } + return space; +} diff --git a/ucx/string.c b/ucx/string.c new file mode 100644 index 0000000..5ea54f3 --- /dev/null +++ b/ucx/string.c @@ -0,0 +1,807 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/string.h" + +#include "ucx/allocator.h" + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include /* for strncasecmp() */ +#endif /* _WIN32 */ + +sstr_t sstr(char *cstring) { + sstr_t string; + string.ptr = cstring; + string.length = strlen(cstring); + return string; +} + +sstr_t sstrn(char *cstring, size_t length) { + sstr_t string; + string.ptr = cstring; + string.length = length; + return string; +} + +scstr_t scstr(const char *cstring) { + scstr_t string; + string.ptr = cstring; + string.length = strlen(cstring); + return string; +} + +scstr_t scstrn(const char *cstring, size_t length) { + scstr_t string; + string.ptr = cstring; + string.length = length; + return string; +} + + +size_t scstrnlen(size_t n, ...) { + if (n == 0) return 0; + + va_list ap; + va_start(ap, n); + + size_t size = 0; + + for (size_t i = 0 ; i < n ; i++) { + scstr_t str = va_arg(ap, scstr_t); + if(SIZE_MAX - str.length < size) { + size = SIZE_MAX; + break; + } + size += str.length; + } + va_end(ap); + + return size; +} + +static sstr_t sstrvcat_a( + UcxAllocator *a, + size_t count, + scstr_t s1, + va_list ap) { + sstr_t str; + str.ptr = NULL; + str.length = 0; + if(count < 2) { + return str; + } + + scstr_t s2 = va_arg (ap, scstr_t); + + if(((size_t)-1) - s1.length < s2.length) { + return str; + } + + scstr_t *strings = (scstr_t*) calloc(count, sizeof(scstr_t)); + if(!strings) { + return str; + } + + // get all args and overall length + strings[0] = s1; + strings[1] = s2; + size_t slen = s1.length + s2.length; + int error = 0; + for (size_t i=2;i str_length) { + return 0; + } + + if(length > str_length - start) { + length = str_length - start; + } + *newlen = length; + *newpos = start; + return 1; +} + +sstr_t sstrsubs(sstr_t s, size_t start) { + return sstrsubsl (s, start, s.length-start); +} + +sstr_t sstrsubsl(sstr_t s, size_t start, size_t length) { + size_t pos; + sstr_t ret = { NULL, 0 }; + if(ucx_substring(s.length, start, length, &ret.length, &pos)) { + ret.ptr = s.ptr + pos; + } + return ret; +} + +scstr_t scstrsubs(scstr_t string, size_t start) { + return scstrsubsl(string, start, string.length-start); +} + +scstr_t scstrsubsl(scstr_t s, size_t start, size_t length) { + size_t pos; + scstr_t ret = { NULL, 0 }; + if(ucx_substring(s.length, start, length, &ret.length, &pos)) { + ret.ptr = s.ptr + pos; + } + return ret; +} + + +static int ucx_strchr(const char *str, size_t length, int chr, size_t *pos) { + for(size_t i=0;i 0) { + for(size_t i=length ; i>0 ; i--) { + if(str[i-1] == chr) { + *pos = i-1; + return 1; + } + } + } + return 0; +} + +sstr_t sstrchr(sstr_t s, int c) { + size_t pos = 0; + if(ucx_strchr(s.ptr, s.length, c, &pos)) { + return sstrsubs(s, pos); + } + return sstrn(NULL, 0); +} + +sstr_t sstrrchr(sstr_t s, int c) { + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return sstrsubs(s, pos); + } + return sstrn(NULL, 0); +} + +scstr_t scstrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); +} + +scstr_t scstrrchr(scstr_t s, int c) { + size_t pos = 0; + if(ucx_strrchr(s.ptr, s.length, c, &pos)) { + return scstrsubs(s, pos); + } + return scstrn(NULL, 0); +} + +#define ptable_r(dest, useheap, ptable, index) (dest = useheap ? \ + ((size_t*)ptable)[index] : (size_t) ((uint8_t*)ptable)[index]) + +#define ptable_w(useheap, ptable, index, src) do {\ + if (!useheap) ((uint8_t*)ptable)[index] = (uint8_t) src;\ + else ((size_t*)ptable)[index] = src;\ + } while (0); + + +static const char* ucx_strstr( + const char *str, + size_t length, + const char *match, + size_t matchlen, + size_t *newlen) +{ + *newlen = length; + if (matchlen == 0) { + return str; + } + + const char *result = NULL; + size_t resultlen = 0; + + /* + * IMPORTANT: + * our prefix table contains the prefix length PLUS ONE + * this is our decision, because we want to use the full range of size_t + * the original algorithm needs a (-1) at one single place + * and we want to avoid that + */ + + /* static prefix table */ + static uint8_t s_prefix_table[256]; + + /* check pattern length and use appropriate prefix table */ + /* if the pattern exceeds static prefix table, allocate on the heap */ + register int useheap = matchlen > 255; + register void* ptable = useheap ? + calloc(matchlen+1, sizeof(size_t)): s_prefix_table; + + /* keep counter in registers */ + register size_t i, j; + + /* fill prefix table */ + i = 0; j = 0; + ptable_w(useheap, ptable, i, j); + while (i < matchlen) { + while (j >= 1 && match[j-1] != match[i]) { + ptable_r(j, useheap, ptable, j-1); + } + i++; j++; + ptable_w(useheap, ptable, i, j); + } + + /* search */ + i = 0; j = 1; + while (i < length) { + while (j >= 1 && str[i] != match[j-1]) { + ptable_r(j, useheap, ptable, j-1); + } + i++; j++; + if (j-1 == matchlen) { + size_t start = i - matchlen; + result = str + start; + resultlen = length - start; + break; + } + } + + /* if prefix table was allocated on the heap, free it */ + if (ptable != s_prefix_table) { + free(ptable); + } + + *newlen = resultlen; + return result; +} + +sstr_t scstrsstr(sstr_t string, scstr_t match) { + sstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; +} + +scstr_t scstrscstr(scstr_t string, scstr_t match) { + scstr_t result; + + size_t reslen; + const char *resstr = ucx_strstr(string.ptr, string.length, match.ptr, match.length, &reslen); + if(!resstr) { + result.ptr = NULL; + result.length = 0; + return result; + } + + size_t pos = resstr - string.ptr; + result.ptr = string.ptr + pos; + result.length = reslen; + + return result; +} + +#undef ptable_r +#undef ptable_w + +sstr_t* scstrsplit(scstr_t s, scstr_t d, ssize_t *n) { + return scstrsplit_a(ucx_default_allocator(), s, d, n); +} + +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t s, scstr_t d, ssize_t *n) { + if (s.length == 0 || d.length == 0) { + *n = -1; + return NULL; + } + + /* special cases: delimiter is at least as large as the string */ + if (d.length >= s.length) { + /* exact match */ + if (sstrcmp(s, d) == 0) { + *n = 0; + return NULL; + } else /* no match possible */ { + *n = 1; + sstr_t *result = (sstr_t*) almalloc(allocator, sizeof(sstr_t)); + if(result) { + *result = sstrdup_a(allocator, s); + } else { + *n = -2; + } + return result; + } + } + + ssize_t nmax = *n; + size_t arrlen = 16; + sstr_t* result = (sstr_t*) alcalloc(allocator, arrlen, sizeof(sstr_t)); + + if (result) { + scstr_t curpos = s; + ssize_t j = 1; + while (1) { + scstr_t match; + /* optimize for one byte delimiters */ + if (d.length == 1) { + match = curpos; + for (size_t i = 0 ; i < curpos.length ; i++) { + if (curpos.ptr[i] == *(d.ptr)) { + match.ptr = curpos.ptr + i; + break; + } + match.length--; + } + } else { + match = scstrscstr(curpos, d); + } + if (match.length > 0) { + /* is this our last try? */ + if (nmax == 0 || j < nmax) { + /* copy the current string to the array */ + scstr_t item = scstrn(curpos.ptr, match.ptr - curpos.ptr); + result[j-1] = sstrdup_a(allocator, item); + size_t processed = item.length + d.length; + curpos.ptr += processed; + curpos.length -= processed; + + /* allocate memory for the next string */ + j++; + if (j > arrlen) { + arrlen *= 2; + size_t reallocsz; + sstr_t* reallocated = NULL; + if(!ucx_szmul(arrlen, sizeof(sstr_t), &reallocsz)) { + reallocated = (sstr_t*) alrealloc( + allocator, result, reallocsz); + } + if (reallocated) { + result = reallocated; + } else { + for (ssize_t i = 0 ; i < j-1 ; i++) { + alfree(allocator, result[i].ptr); + } + alfree(allocator, result); + *n = -2; + return NULL; + } + } + } else { + /* nmax reached, copy the _full_ remaining string */ + result[j-1] = sstrdup_a(allocator, curpos); + break; + } + } else { + /* no more matches, copy last string */ + result[j-1] = sstrdup_a(allocator, curpos); + break; + } + } + *n = j; + } else { + *n = -2; + } + + return result; +} + +int scstrcmp(scstr_t s1, scstr_t s2) { + if (s1.length == s2.length) { + return memcmp(s1.ptr, s2.ptr, s1.length); + } else if (s1.length > s2.length) { + return 1; + } else { + return -1; + } +} + +int scstrcasecmp(scstr_t s1, scstr_t s2) { + if (s1.length == s2.length) { +#ifdef _WIN32 + return _strnicmp(s1.ptr, s2.ptr, s1.length); +#else + return strncasecmp(s1.ptr, s2.ptr, s1.length); +#endif + } else if (s1.length > s2.length) { + return 1; + } else { + return -1; + } +} + +sstr_t scstrdup(scstr_t s) { + return sstrdup_a(ucx_default_allocator(), s); +} + +sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t s) { + sstr_t newstring; + newstring.ptr = (char*)almalloc(allocator, s.length + 1); + if (newstring.ptr) { + newstring.length = s.length; + newstring.ptr[newstring.length] = 0; + + memcpy(newstring.ptr, s.ptr, s.length); + } else { + newstring.length = 0; + } + + return newstring; +} + + +static size_t ucx_strtrim(const char *s, size_t len, size_t *newlen) { + const char *newptr = s; + size_t length = len; + + while(length > 0 && isspace(*newptr)) { + newptr++; + length--; + } + while(length > 0 && isspace(newptr[length-1])) { + length--; + } + + *newlen = length; + return newptr - s; +} + +sstr_t sstrtrim(sstr_t string) { + sstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); + return newstr; +} + +scstr_t scstrtrim(scstr_t string) { + scstr_t newstr; + newstr.ptr = string.ptr + + ucx_strtrim(string.ptr, string.length, &newstr.length); + return newstr; +} + +int scstrprefix(scstr_t string, scstr_t prefix) { + if (string.length == 0) { + return prefix.length == 0; + } + if (prefix.length == 0) { + return 1; + } + + if (prefix.length > string.length) { + return 0; + } else { + return memcmp(string.ptr, prefix.ptr, prefix.length) == 0; + } +} + +int scstrsuffix(scstr_t string, scstr_t suffix) { + if (string.length == 0) { + return suffix.length == 0; + } + if (suffix.length == 0) { + return 1; + } + + if (suffix.length > string.length) { + return 0; + } else { + return memcmp(string.ptr+string.length-suffix.length, + suffix.ptr, suffix.length) == 0; + } +} + +int scstrcaseprefix(scstr_t string, scstr_t prefix) { + if (string.length == 0) { + return prefix.length == 0; + } + if (prefix.length == 0) { + return 1; + } + + if (prefix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubsl(string, 0, prefix.length); + return scstrcasecmp(subs, prefix) == 0; + } +} + +int scstrcasesuffix(scstr_t string, scstr_t suffix) { + if (string.length == 0) { + return suffix.length == 0; + } + if (suffix.length == 0) { + return 1; + } + + if (suffix.length > string.length) { + return 0; + } else { + scstr_t subs = scstrsubs(string, string.length-suffix.length); + return scstrcasecmp(subs, suffix) == 0; + } +} + +sstr_t scstrlower(scstr_t string) { + sstr_t ret = sstrdup(string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = tolower(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string) { + sstr_t ret = sstrdup_a(allocator, string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = tolower(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrupper(scstr_t string) { + sstr_t ret = sstrdup(string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = toupper(ret.ptr[i]); + } + return ret; +} + +sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string) { + sstr_t ret = sstrdup_a(allocator, string); + for (size_t i = 0; i < ret.length ; i++) { + ret.ptr[i] = toupper(ret.ptr[i]); + } + return ret; +} + +#define REPLACE_INDEX_BUFFER_MAX 100 + +struct scstrreplace_ibuf { + size_t* buf; + unsigned int len; /* small indices */ + struct scstrreplace_ibuf* next; +}; + +static void scstrrepl_free_ibuf(struct scstrreplace_ibuf *buf) { + while (buf) { + struct scstrreplace_ibuf *next = buf->next; + free(buf->buf); + free(buf); + buf = next; + } +} + +sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, + scstr_t pattern, scstr_t replacement, size_t replmax) { + + if (pattern.length == 0 || pattern.length > str.length || replmax == 0) + return sstrdup(str); + + /* Compute expected buffer length */ + size_t ibufmax = str.length / pattern.length; + size_t ibuflen = replmax < ibufmax ? replmax : ibufmax; + if (ibuflen > REPLACE_INDEX_BUFFER_MAX) { + ibuflen = REPLACE_INDEX_BUFFER_MAX; + } + + /* Allocate first index buffer */ + struct scstrreplace_ibuf *firstbuf, *curbuf; + firstbuf = curbuf = calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!firstbuf) return sstrn(NULL, 0); + firstbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!firstbuf->buf) { + free(firstbuf); + return sstrn(NULL, 0); + } + + /* Search occurrences */ + scstr_t searchstr = str; + size_t found = 0; + do { + scstr_t match = scstrscstr(searchstr, pattern); + if (match.length > 0) { + /* Allocate next buffer in chain, if required */ + if (curbuf->len == ibuflen) { + struct scstrreplace_ibuf *nextbuf = + calloc(1, sizeof(struct scstrreplace_ibuf)); + if (!nextbuf) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + nextbuf->buf = calloc(ibuflen, sizeof(size_t)); + if (!nextbuf->buf) { + free(nextbuf); + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + curbuf->next = nextbuf; + curbuf = nextbuf; + } + + /* Record match index */ + found++; + size_t idx = match.ptr - str.ptr; + curbuf->buf[curbuf->len++] = idx; + searchstr.ptr = match.ptr + pattern.length; + searchstr.length = str.length - idx - pattern.length; + } else { + break; + } + } while (searchstr.length > 0 && found < replmax); + + /* Allocate result string */ + sstr_t result; + { + ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length; + size_t rcount = 0; + curbuf = firstbuf; + do { + rcount += curbuf->len; + curbuf = curbuf->next; + } while (curbuf); + result.length = str.length + rcount * adjlen; + result.ptr = almalloc(allocator, result.length); + if (!result.ptr) { + scstrrepl_free_ibuf(firstbuf); + return sstrn(NULL, 0); + } + } + + /* Build result string */ + curbuf = firstbuf; + size_t srcidx = 0; + char* destptr = result.ptr; + do { + for (size_t i = 0; i < curbuf->len; i++) { + /* Copy source part up to next match*/ + size_t idx = curbuf->buf[i]; + size_t srclen = idx - srcidx; + if (srclen > 0) { + memcpy(destptr, str.ptr+srcidx, srclen); + destptr += srclen; + srcidx += srclen; + } + + /* Copy the replacement and skip the source pattern */ + srcidx += pattern.length; + memcpy(destptr, replacement.ptr, replacement.length); + destptr += replacement.length; + } + curbuf = curbuf->next; + } while (curbuf); + memcpy(destptr, str.ptr+srcidx, str.length-srcidx); + + /* Free index buffer */ + scstrrepl_free_ibuf(firstbuf); + + return result; +} + +sstr_t scstrreplacen(scstr_t str, scstr_t pattern, + scstr_t replacement, size_t replmax) { + return scstrreplacen_a(ucx_default_allocator(), + str, pattern, replacement, replmax); +} + + +// type adjustment functions +scstr_t ucx_sc2sc(scstr_t str) { + return str; +} +scstr_t ucx_ss2sc(sstr_t str) { + scstr_t cs; + cs.ptr = str.ptr; + cs.length = str.length; + return cs; +} +scstr_t ucx_ss2c_s(scstr_t c) { + return c; +} diff --git a/ucx/test.c b/ucx/test.c new file mode 100644 index 0000000..20b80b4 --- /dev/null +++ b/ucx/test.c @@ -0,0 +1,91 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/test.h" + +UcxTestSuite* ucx_test_suite_new() { + UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite)); + if (suite != NULL) { + suite->success = 0; + suite->failure = 0; + suite->tests = NULL; + } + + return suite; +} + +void ucx_test_suite_free(UcxTestSuite* suite) { + UcxTestList *l = suite->tests; + while (l != NULL) { + UcxTestList *e = l; + l = l->next; + free(e); + } + free(suite); +} + +int ucx_test_register(UcxTestSuite* suite, UcxTest test) { + if (suite->tests) { + UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (newelem) { + newelem->test = test; + newelem->next = NULL; + + UcxTestList *last = suite->tests; + while (last->next) { + last = last->next; + } + last->next = newelem; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } else { + suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (suite->tests) { + suite->tests->test = test; + suite->tests->next = NULL; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } +} + +void ucx_test_run(UcxTestSuite* suite, FILE* output) { + suite->success = 0; + suite->failure = 0; + for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) { + elem->test(suite, output); + } + fwrite("\nAll test completed.\n", 1, 21, output); + fprintf(output, " Total: %u\n Success: %u\n Failure: %u\n", + suite->success+suite->failure, suite->success, suite->failure); +} diff --git a/ucx/ucx.c b/ucx/ucx.c new file mode 100644 index 0000000..923330d --- /dev/null +++ b/ucx/ucx.c @@ -0,0 +1,62 @@ +/** + * @mainpage UAP Common Extensions + * Library with common and useful functions, macros and data structures. + *

+ * Latest available source:
+ * + * https://sourceforge.net/projects/ucx/files/ + *

+ * + *

+ * Repositories:
+ * + * https://sourceforge.net/p/ucx/code + * - or - + * + * https://develop.uap-core.de/hg/ucx + *

+ * + *

LICENCE

+ * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/ucx.h" + +int ucx_szmul_impl(size_t a, size_t b, size_t *result) { + if(a == 0 || b == 0) { + *result = 0; + return 0; + } + size_t r = a * b; + if(r / b == a) { + *result = r; + return 0; + } else { + *result = 0; + return 1; + } +} + diff --git a/ucx/ucx/allocator.h b/ucx/ucx/allocator.h new file mode 100644 index 0000000..2159272 --- /dev/null +++ b/ucx/ucx/allocator.h @@ -0,0 +1,206 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Allocator for custom memory management. + * + * A UCX allocator consists of a pointer to the memory area / pool and four + * function pointers to memory management functions operating on this memory + * area / pool. These functions shall behave equivalent to the standard libc + * functions malloc(), calloc(), realloc() and free(). + * + * The signature of the memory management functions is based on the signature + * of the respective libc function but each of them takes the pointer to the + * memory area / pool as first argument. + * + * As the pointer to the memory area / pool can be arbitrarily chosen, any data + * can be provided to the memory management functions. A UcxMempool is just + * one example. + * + * @see mempool.h + * @see UcxMap + * + * @file allocator.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_ALLOCATOR_H +#define UCX_ALLOCATOR_H + +#include "ucx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A function pointer to the allocators malloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_malloc)(void *pool, size_t n); + +/** + * A function pointer to the allocators calloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_calloc)(void *pool, size_t n, size_t size); + +/** + * A function pointer to the allocators realloc() function. + * @see UcxAllocator + */ +typedef void*(*ucx_allocator_realloc)(void *pool, void *data, size_t n); + +/** + * A function pointer to the allocators free() function. + * @see UcxAllocator + */ +typedef void(*ucx_allocator_free)(void *pool, void *data); + +/** + * UCX allocator data structure containing memory management functions. + */ +typedef struct { + /** Pointer to an area of memory or a complex memory pool. + * This pointer will be passed to any memory management function as first + * argument. + */ + void *pool; + /** + * The malloc() function for this allocator. + */ + ucx_allocator_malloc malloc; + /** + * The calloc() function for this allocator. + */ + ucx_allocator_calloc calloc; + /** + * The realloc() function for this allocator. + */ + ucx_allocator_realloc realloc; + /** + * The free() function for this allocator. + */ + ucx_allocator_free free; +} UcxAllocator; + +/** + * Returns a pointer to the default allocator. + * + * The default allocator contains wrappers to the standard libc memory + * management functions. Use this function to get a pointer to a globally + * available allocator. You may also define an own UcxAllocator by assigning + * #UCX_ALLOCATOR_DEFAULT to a variable and pass the address of this variable + * to any function that takes a UcxAllocator as argument. Note that using + * this function is the recommended way of passing a default allocator, thus + * it never runs out of scope. + * + * @return a pointer to the default allocator + * + * @see UCX_ALLOCATOR_DEFAULT + */ +UcxAllocator *ucx_default_allocator(); + +/** + * A wrapper for the standard libc malloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to malloc() + * @return return value of malloc() + */ +void *ucx_default_malloc(void *ignore, size_t n); +/** + * A wrapper for the standard libc calloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param n argument passed to calloc() + * @param size argument passed to calloc() + * @return return value of calloc() + */ +void *ucx_default_calloc(void *ignore, size_t n, size_t size); +/** + * A wrapper for the standard libc realloc() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argumend passed to realloc() + * @param n argument passed to realloc() + * @return return value of realloc() + */ +void *ucx_default_realloc(void *ignore, void *data, size_t n); +/** + * A wrapper for the standard libc free() function. + * @param ignore ignored (may be used by allocators for pooled memory) + * @param data argument passed to free() + */ +void ucx_default_free(void *ignore, void *data); + +/** + * Shorthand for calling an allocators malloc function. + * @param allocator the allocator to use + * @param n size of space to allocate + * @return a pointer to the allocated memory area + */ +#define almalloc(allocator, n) ((allocator)->malloc((allocator)->pool, n)) + +/** + * Shorthand for calling an allocators calloc function. + * @param allocator the allocator to use + * @param n the count of elements the space should be allocated for + * @param size the size of each element + * @return a pointer to the allocated memory area + */ +#define alcalloc(allocator, n, size) \ + ((allocator)->calloc((allocator)->pool, n, size)) + +/** + * Shorthand for calling an allocators realloc function. + * @param allocator the allocator to use + * @param ptr the pointer to the memory area that shall be reallocated + * @param n the new size of the allocated memory area + * @return a pointer to the reallocated memory area + */ +#define alrealloc(allocator, ptr, n) \ + ((allocator)->realloc((allocator)->pool, ptr, n)) + +/** + * Shorthand for calling an allocators free function. + * @param allocator the allocator to use + * @param ptr the pointer to the memory area that shall be freed + */ +#define alfree(allocator, ptr) ((allocator)->free((allocator)->pool, ptr)) + +/** + * Convenient macro for a default allocator struct definition. + */ +#define UCX_ALLOCATOR_DEFAULT {NULL, \ + ucx_default_malloc, ucx_default_calloc, ucx_default_realloc, \ + ucx_default_free } + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ALLOCATOR_H */ + diff --git a/ucx/ucx/array.h b/ucx/ucx/array.h new file mode 100644 index 0000000..5b02ebf --- /dev/null +++ b/ucx/ucx/array.h @@ -0,0 +1,460 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2019 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Dynamically allocated array implementation. + * + * @file array.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_ARRAY_H +#define UCX_ARRAY_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX array type. + */ +typedef struct { + /** + * The current capacity of the array. + */ + size_t capacity; + /** + * The actual number of elements in the array. + */ + size_t size; + /** + * The size of an individual element in bytes. + */ + size_t elemsize; + /** + * A pointer to the data. + */ + void* data; + /** + * The allocator used for the data. + */ + UcxAllocator* allocator; +} UcxArray; + +/** + * Sets an element in an arbitrary user defined array. + * The data is copied from the specified data location. + * + * If the capacity is insufficient, the array is automatically reallocated and + * the possibly new pointer is stored in the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param elmsize the size of each element + * @param idx the index of the element to set + * @param data a pointer to the element data + * @return zero on success or non-zero on error (errno will be set) + */ +#define ucx_array_util_set(array, capacity, elmsize, idx, data) \ + ucx_array_util_set_a(ucx_default_allocator(), (void**)(array), capacity, \ + elmsize, idx, data) + +/** + * Sets an element in an arbitrary user defined array. + * The data is copied from the specified data location. + * + * If the capacity is insufficient, the array is automatically reallocated + * using the specified allocator and the possibly new pointer is stored in + * the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param alloc the allocator that shall be used to reallocate the array + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param elmsize the size of each element + * @param idx the index of the element to set + * @param data a pointer to the element data + * @return zero on success or non-zero on error (errno will be set) + */ +int ucx_array_util_set_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t elmsize, size_t idx, void* data); + +/** + * Stores a pointer in an arbitrary user defined array. + * The element size of the array must be sizeof(void*). + * + * If the capacity is insufficient, the array is automatically reallocated and + * the possibly new pointer is stored in the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param idx the index of the element to set + * @param ptr the pointer to store + * @return zero on success or non-zero on error (errno will be set) + */ +#define ucx_array_util_setptr(array, capacity, idx, ptr) \ + ucx_array_util_setptr_a(ucx_default_allocator(), (void**)(array), \ + capacity, idx, ptr) + +/** + * Stores a pointer in an arbitrary user defined array. + * The element size of the array must be sizeof(void*). + * + * If the capacity is insufficient, the array is automatically reallocated + * using the specified allocator and the possibly new pointer is stored in + * the array argument. + * + * On reallocation the capacity of the array is doubled until it is sufficient. + * The new capacity is stored back to capacity. + * + * @param alloc the allocator that shall be used to reallocate the array + * @param array a pointer to location of the array pointer + * @param capacity a pointer to the capacity + * @param idx the index of the element to set + * @param ptr the pointer to store + * @return zero on success or non-zero on error (errno will be set) + */ +int ucx_array_util_setptr_a(UcxAllocator* alloc, void** array, size_t* capacity, + size_t idx, void* ptr); + + +/** + * Creates a new UCX array with the given capacity and element size. + * @param capacity the initial capacity + * @param elemsize the element size + * @return a pointer to a new UCX array structure + */ +UcxArray* ucx_array_new(size_t capacity, size_t elemsize); + +/** + * Creates a new UCX array using the specified allocator. + * + * @param capacity the initial capacity + * @param elemsize the element size + * @param allocator the allocator to use + * @return a pointer to new UCX array structure + */ +UcxArray* ucx_array_new_a(size_t capacity, size_t elemsize, + UcxAllocator* allocator); + +/** + * Initializes a UCX array structure with the given capacity and element size. + * The structure must be uninitialized as the data pointer will be overwritten. + * + * @param array the structure to initialize + * @param capacity the initial capacity + * @param elemsize the element size + */ +void ucx_array_init(UcxArray* array, size_t capacity, size_t elemsize); + +/** + * Initializes a UCX array structure using the specified allocator. + * The structure must be uninitialized as the data pointer will be overwritten. + * + * @param array the structure to initialize + * @param capacity the initial capacity + * @param elemsize the element size + * @param allocator the allocator to use + */ +void ucx_array_init_a(UcxArray* array, size_t capacity, size_t elemsize, + UcxAllocator* allocator); + +/** + * Creates an shallow copy of an array. + * + * This function clones the specified array by using memcpy(). + * If the destination capacity is insufficient, an automatic reallocation is + * attempted. + * + * Note: if the destination array is uninitialized, the behavior is undefined. + * + * @param dest the array to copy to + * @param src the array to copy from + * @return zero on success, non-zero on reallocation failure. + */ +int ucx_array_clone(UcxArray* dest, UcxArray const* src); + + +/** + * Compares two UCX arrays element-wise by using a compare function. + * + * Elements of the two specified arrays are compared by using the specified + * compare function and the additional data. The type and content of this + * additional data depends on the cmp_func() used. + * + * This function always returns zero, if the element sizes of the arrays do + * not match and performs no comparisons in this case. + * + * @param array1 the first array + * @param array2 the second array + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the two arrays equal element-wise, 0 otherwise + */ +int ucx_array_equals(UcxArray const *array1, UcxArray const *array2, + cmp_func cmpfnc, void* data); + +/** + * Destroys the array. + * + * The data is freed and both capacity and count are reset to zero. + * If the array structure itself has been dynamically allocated, it has to be + * freed separately. + * + * @param array the array to destroy + */ +void ucx_array_destroy(UcxArray *array); + +/** + * Destroys and frees the array. + * + * @param array the array to free + */ +void ucx_array_free(UcxArray *array); + +/** + * Inserts elements at the end of the array. + * + * This is an O(1) operation. + * The array will automatically grow, if the capacity is exceeded. + * If a pointer to data is provided, the data is copied into the array with + * memcpy(). Otherwise the new elements are completely zeroed. + * + * @param array a pointer the array where to append the data + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, zeroed elements are appended) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_set_from() + * @see ucx_array_append() + */ +int ucx_array_append_from(UcxArray *array, void *data, size_t count); + + +/** + * Inserts elements at the beginning of the array. + * + * This is an expensive operation, because the contents must be moved. + * If there is no particular reason to prepend data, you should use + * ucx_array_append_from() instead. + * + * @param array a pointer the array where to prepend the data + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, zeroed elements are inserted) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_append_from() + * @see ucx_array_set_from() + * @see ucx_array_prepend() + */ +int ucx_array_prepend_from(UcxArray *array, void *data, size_t count); + + +/** + * Sets elements starting at the specified index. + * + * If the any index is out of bounds, the array automatically grows. + * The pointer to the data may be NULL, in which case the elements are zeroed. + * + * @param array a pointer the array where to set the data + * @param index the index of the element to set + * @param data a pointer to the data to insert (may be NULL) + * @param count number of elements to copy from data (if data is + * NULL, the memory in the array is zeroed) + * @return zero on success, non-zero if a reallocation was necessary but failed + * @see ucx_array_append_from() + * @see ucx_array_set() + */ +int ucx_array_set_from(UcxArray *array, size_t index, void *data, size_t count); + +/** + * Concatenates two arrays. + * + * The contents of the second array are appended to the first array in one + * single operation. The second array is otherwise left untouched. + * + * The first array may grow automatically. If this fails, both arrays remain + * unmodified. + * + * @param array1 first array + * @param array2 second array + * @return zero on success, non-zero if reallocation was necessary but failed + * or the element size does not match + */ +int ucx_array_concat(UcxArray *array1, const UcxArray *array2); + +/** + * Returns a pointer to the array element at the specified index. + * + * @param array the array to retrieve the element from + * @param index index of the element to return + * @return a pointer to the element at the specified index or NULL, + * if the index is greater than the array size + */ +void *ucx_array_at(UcxArray const* array, size_t index); + +/** + * Returns the index of an element containing the specified data. + * + * This function uses a cmp_func() to compare the data of each list element + * with the specified data. If no cmp_func is provided, memcmp() is used. + * + * If the array contains the data more than once, the index of the first + * occurrence is returned. + * If the array does not contain the data, the size of array is returned. + * + * @param array the array where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return the index of the element containing the specified data or the size of + * the array, if the data is not found in this array + */ +size_t ucx_array_find(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Checks, if an array contains a specific element. + * + * An element is found, if ucx_array_find() returns a value less than the size. + * + * @param array the array where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the array contains the specified element data + * @see ucx_array_find() + */ +int ucx_array_contains(UcxArray const *array, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Sorts a UcxArray with the best available sort algorithm. + * + * The qsort_r() function is used, if available (glibc, FreeBSD or MacOS). + * The order of arguments is automatically adjusted for the FreeBSD and MacOS + * version of qsort_r(). + * + * If qsort_r() is not available, a merge sort algorithm is used, which is + * guaranteed to use no more additional memory than for exactly one element. + * + * @param array the array to sort + * @param cmpfnc the function that shall be used to compare the element data + * @param data additional data for the cmp_func() or NULL + */ +void ucx_array_sort(UcxArray* array, cmp_func cmpfnc, void *data); + +/** + * Removes an element from the array. + * + * This is in general an expensive operation, because several elements may + * be moved. If the order of the elements is not relevant, use + * ucx_array_remove_fast() instead. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove(UcxArray *array, size_t index); + +/** + * Removes an element from the array. + * + * This is an O(1) operation, but does not maintain the order of the elements. + * The last element in the array is moved to the location of the removed + * element. + * + * @param array pointer to the array from which the element shall be removed + * @param index the index of the element to remove + */ +void ucx_array_remove_fast(UcxArray *array, size_t index); + +/** + * Shrinks the memory to exactly fit the contents. + * + * After this operation, the capacity equals the size. + * + * @param array a pointer to the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_shrink(UcxArray* array); + +/** + * Sets the capacity of the array. + * + * If the new capacity is smaller than the size of the array, the elements + * are removed and the size is adjusted accordingly. + * + * @param array a pointer to the array + * @param capacity the new capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_resize(UcxArray* array, size_t capacity); + +/** + * Resizes the array only, if the capacity is insufficient. + * + * If the requested capacity is smaller than the current capacity, this + * function does nothing. + * + * @param array a pointer to the array + * @param capacity the guaranteed capacity + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_reserve(UcxArray* array, size_t capacity); + +/** + * Resizes the capacity, if the specified number of elements would not fit. + * + * A call to ucx_array_grow(array, count) is effectively the same as + * ucx_array_reserve(array, array->size+count). + * + * @param array a pointer to the array + * @param count the number of elements that should additionally fit + * into the array + * @return zero on success, non-zero if reallocation failed + */ +int ucx_array_grow(UcxArray* array, size_t count); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_ARRAY_H */ + diff --git a/ucx/ucx/avl.h b/ucx/ucx/avl.h new file mode 100644 index 0000000..8b251a5 --- /dev/null +++ b/ucx/ucx/avl.h @@ -0,0 +1,353 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +/** + * @file avl.h + * + * AVL tree implementation. + * + * This binary search tree implementation allows average O(1) insertion and + * removal of elements (excluding binary search time). + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_AVL_H +#define UCX_AVL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX AVL Node type. + * + * @see UcxAVLNode + */ +typedef struct UcxAVLNode UcxAVLNode; + +/** + * UCX AVL Node. + */ +struct UcxAVLNode { + /** + * The key for this node. + */ + intptr_t key; + /** + * Data contained by this node. + */ + void *value; + /** + * The height of this (sub)-tree. + */ + size_t height; + /** + * Parent node. + */ + UcxAVLNode *parent; + /** + * Root node of left subtree. + */ + UcxAVLNode *left; + /** + * Root node of right subtree. + */ + UcxAVLNode *right; +}; + +/** + * UCX AVL Tree. + */ +typedef struct { + /** + * The UcxAllocator that shall be used to manage the memory for node data. + */ + UcxAllocator *allocator; + /** + * Root node of the tree. + */ + UcxAVLNode *root; + /** + * Compare function that shall be used to compare the UcxAVLNode keys. + * @see UcxAVLNode.key + */ + cmp_func cmpfunc; + /** + * Custom user data. + * This data will also be provided to the cmpfunc. + */ + void *userdata; +} UcxAVLTree; + +/** + * Initializes a new UcxAVLTree with a default allocator. + * + * @param cmpfunc the compare function that shall be used + * @return a new UcxAVLTree object + * @see ucx_avl_new_a() + */ +UcxAVLTree *ucx_avl_new(cmp_func cmpfunc); + +/** + * Initializes a new UcxAVLTree with the specified allocator. + * + * The cmpfunc should be capable of comparing two keys within this AVL tree. + * So if you want to use null terminated strings as keys, you could use the + * ucx_cmp_str() function here. + * + * @param cmpfunc the compare function that shall be used + * @param allocator the UcxAllocator that shall be used + * @return a new UcxAVLTree object + */ +UcxAVLTree *ucx_avl_new_a(cmp_func cmpfunc, UcxAllocator *allocator); + +/** + * Destroys a UcxAVLTree. + * + * Note, that the contents are not automatically freed. + * Use may use #ucx_avl_free_content() before calling this function. + * + * @param tree the tree to destroy + * @see ucx_avl_free_content() + */ +void ucx_avl_free(UcxAVLTree *tree); + +/** + * Frees the contents of a UcxAVLTree. + * + * This is a convenience function that iterates over the tree and passes all + * values to the specified destructor function. + * + * If no destructor is specified (NULL), the free() function of + * the tree's own allocator is used. + * + * You must ensure, that it is valid to pass each value in the map to the same + * destructor function. + * + * You should free the entire tree afterwards, as the contents will be invalid. + * + * @param tree for which the contents shall be freed + * @param destr optional pointer to a destructor function + * @see ucx_avl_free() + */ +void ucx_avl_free_content(UcxAVLTree *tree, ucx_destructor destr); + +/** + * Macro for initializing a new UcxAVLTree with the default allocator and a + * ucx_cmp_ptr() compare function. + * + * @return a new default UcxAVLTree object + */ +#define ucx_avl_default_new() \ + ucx_avl_new_a(ucx_cmp_ptr, ucx_default_allocator()) + +/** + * Gets the node from the tree, that is associated with the specified key. + * @param tree the UcxAVLTree + * @param key the key + * @return the node (or NULL, if the key is not present) + */ +UcxAVLNode *ucx_avl_get_node(UcxAVLTree *tree, intptr_t key); + +/** + * Gets the value from the tree, that is associated with the specified key. + * @param tree the UcxAVLTree + * @param key the key + * @return the value (or NULL, if the key is not present) + */ +void *ucx_avl_get(UcxAVLTree *tree, intptr_t key); + +/** + * A mode for #ucx_avl_find_node() with the same behavior as + * #ucx_avl_get_node(). + */ +#define UCX_AVL_FIND_EXACT 0 +/** + * A mode for #ucx_avl_find_node() finding the node whose key is at least + * as large as the specified key. + */ +#define UCX_AVL_FIND_LOWER_BOUNDED 1 +/** + * A mode for #ucx_avl_find_node() finding the node whose key is at most + * as large as the specified key. + */ +#define UCX_AVL_FIND_UPPER_BOUNDED 2 +/** + * A mode for #ucx_avl_find_node() finding the node with a key that is as close + * to the specified key as possible. If the key is present, the behavior is + * like #ucx_avl_get_node(). This mode only returns NULL on + * empty trees. + */ +#define UCX_AVL_FIND_CLOSEST 3 + +/** + * Finds a node within the tree. The following modes are supported: + *
    + *
  • #UCX_AVL_FIND_EXACT: the same behavior as #ucx_avl_get_node()
  • + *
  • #UCX_AVL_FIND_LOWER_BOUNDED: finds the node whose key is at least + * as large as the specified key
  • + *
  • #UCX_AVL_FIND_UPPER_BOUNDED: finds the node whose key is at most + * as large as the specified key
  • + *
  • #UCX_AVL_FIND_CLOSEST: finds the node with a key that is as close to + * the specified key as possible. If the key is present, the behavior is + * like #ucx_avl_get_node(). This mode only returns NULL on + * empty trees.
  • + *
+ * + * The distance function provided MUST agree with the compare function of + * the AVL tree. + * + * @param tree the UcxAVLTree + * @param key the key + * @param dfnc the distance function + * @param mode the find mode + * @return the node (or NULL, if no node can be found) + */ +UcxAVLNode *ucx_avl_find_node(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode); + +/** + * Finds a value within the tree. + * See #ucx_avl_find_node() for details. + * + * @param tree the UcxAVLTree + * @param key the key + * @param dfnc the distance function + * @param mode the find mode + * @return the value (or NULL, if no value can be found) + */ +void *ucx_avl_find(UcxAVLTree *tree, intptr_t key, + distance_func dfnc, int mode); + +/** + * Puts a key/value pair into the tree. + * + * Attention: use this function only, if a possible old value does not need + * to be preserved. + * + * @param tree the UcxAVLTree + * @param key the key + * @param value the new value + * @return zero, if and only if the operation succeeded + */ +int ucx_avl_put(UcxAVLTree *tree, intptr_t key, void *value); + +/** + * Puts a key/value pair into the tree. + * + * This is a secure function which saves the old value to the variable pointed + * at by oldvalue. + * + * @param tree the UcxAVLTree + * @param key the key + * @param value the new value + * @param oldvalue optional: a pointer to the location where a possible old + * value shall be stored + * @return zero, if and only if the operation succeeded + */ +int ucx_avl_put_s(UcxAVLTree *tree, intptr_t key, void *value, void **oldvalue); + +/** + * Removes a node from the AVL tree. + * + * Note: the specified node is logically removed. The tree implementation + * decides which memory area is freed. In most cases the here provided node + * is freed, so its further use is generally undefined. + * + * @param tree the UcxAVLTree + * @param node the node to remove + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove_node(UcxAVLTree *tree, UcxAVLNode *node); + +/** + * Removes an element from the AVL tree. + * + * @param tree the UcxAVLTree + * @param key the key + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove(UcxAVLTree *tree, intptr_t key); + +/** + * Removes an element from the AVL tree. + * + * This is a secure function which saves the old key and value data from node + * to the variables at the location of oldkey and oldvalue (if specified), so + * they can be freed afterwards (if necessary). + * + * Note: the returned key in oldkey is possibly not the same as the provided + * key for the lookup (in terms of memory location). + * + * @param tree the UcxAVLTree + * @param key the key of the element to remove + * @param oldkey optional: a pointer to the location where the old key shall be + * stored + * @param oldvalue optional: a pointer to the location where the old value + * shall be stored + * @return zero, if and only if an element has been removed + */ +int ucx_avl_remove_s(UcxAVLTree *tree, intptr_t key, + intptr_t *oldkey, void **oldvalue); + +/** + * Counts the nodes in the specified UcxAVLTree. + * @param tree the AVL tree + * @return the node count + */ +size_t ucx_avl_count(UcxAVLTree *tree); + +/** + * Finds the in-order predecessor of the given node. + * @param node an AVL node + * @return the in-order predecessor of the given node, or NULL if + * the given node is the in-order minimum + */ +UcxAVLNode* ucx_avl_pred(UcxAVLNode* node); + +/** + * Finds the in-order successor of the given node. + * @param node an AVL node + * @return the in-order successor of the given node, or NULL if + * the given node is the in-order maximum + */ +UcxAVLNode* ucx_avl_succ(UcxAVLNode* node); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_AVL_H */ + diff --git a/ucx/ucx/buffer.h b/ucx/ucx/buffer.h new file mode 100644 index 0000000..25f659f --- /dev/null +++ b/ucx/ucx/buffer.h @@ -0,0 +1,339 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file buffer.h + * + * Advanced buffer implementation. + * + * Instances of UcxBuffer can be used to read from or to write to like one + * would do with a stream. This allows the use of ucx_stream_copy() to copy + * contents from one buffer to another. + * + * Some features for convenient use of the buffer + * can be enabled. See the documentation of the macro constants for more + * information. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_BUFFER_H +#define UCX_BUFFER_H + +#include "ucx.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * No buffer features enabled (all flags cleared). + */ +#define UCX_BUFFER_DEFAULT 0x00 + +/** + * If this flag is enabled, the buffer will automatically free its contents. + */ +#define UCX_BUFFER_AUTOFREE 0x01 + +/** + * If this flag is enabled, the buffer will automatically extends its capacity. + */ +#define UCX_BUFFER_AUTOEXTEND 0x02 + +/** UCX Buffer. */ +typedef struct { + /** A pointer to the buffer contents. */ + char *space; + /** Current position of the buffer. */ + size_t pos; + /** Current capacity (i.e. maximum size) of the buffer. */ + size_t capacity; + /** Current size of the buffer content. */ + size_t size; + /** + * Flag register for buffer features. + * @see #UCX_BUFFER_DEFAULT + * @see #UCX_BUFFER_AUTOFREE + * @see #UCX_BUFFER_AUTOEXTEND + */ + int flags; +} UcxBuffer; + +/** + * Creates a new buffer. + * + * Note: you may provide NULL as argument for + * space. Then this function will allocate the space and enforce + * the #UCX_BUFFER_AUTOFREE flag. + * + * @param space pointer to the memory area, or NULL to allocate + * new memory + * @param capacity the capacity of the buffer + * @param flags buffer features (see UcxBuffer.flags) + * @return the new buffer + */ +UcxBuffer *ucx_buffer_new(void *space, size_t capacity, int flags); + +/** + * Destroys a buffer. + * + * If the #UCX_BUFFER_AUTOFREE feature is enabled, the contents of the buffer + * are also freed. + * + * @param buffer the buffer to destroy + */ +void ucx_buffer_free(UcxBuffer* buffer); + +/** + * Creates a new buffer and fills it with extracted content from another buffer. + * + * Note: the #UCX_BUFFER_AUTOFREE feature is enforced for the new buffer. + * + * @param src the source buffer + * @param start the start position of extraction + * @param length the count of bytes to extract (must not be zero) + * @param flags feature mask for the new buffer + * @return a new buffer containing the extraction + */ +UcxBuffer* ucx_buffer_extract(UcxBuffer *src, + size_t start, size_t length, int flags); + +/** + * A shorthand macro for the full extraction of the buffer. + * + * @param src the source buffer + * @param flags feature mask for the new buffer + * @return a new buffer with the extracted content + */ +#define ucx_buffer_clone(src,flags) \ + ucx_buffer_extract(src, 0, (src)->capacity, flags) + + +/** + * Shifts the contents of the buffer by the given offset. + * + * If the offset is positive, the contents are shifted to the right. + * If auto extension is enabled, the buffer grows, if necessary. + * In case the auto extension fails, this function returns a non-zero value and + * no contents are changed. + * If auto extension is disabled, the contents that do not fit into the buffer + * are discarded. + * + * If the offset is negative, the contents are shifted to the left where the + * first shift bytes are discarded. + * The new size of the buffer is the old size minus + * the absolute shift value. + * If this value is larger than the buffer size, the buffer is emptied (but + * not cleared, see the security note below). + * + * The buffer position gets shifted alongside with the content but is kept + * within the boundaries of the buffer. + * + * Security note: the shifting operation does not erase the + * previously occupied memory cells. You can easily do that manually, e.g. by + * calling memset(buffer->space, 0, shift) for a right shift or + * memset(buffer->size, 0, buffer->capacity-buffer->size) + * for a left shift. + * + * @param buffer the buffer + * @param shift the shift offset (negative means left shift) + * @return 0 on success, non-zero if a required auto-extension fails + */ +int ucx_buffer_shift(UcxBuffer* buffer, off_t shift); + +/** + * Shifts the buffer to the right. + * See ucx_buffer_shift() for details. + * + * @param buffer the buffer + * @param shift the shift offset + * @return 0 on success, non-zero if a required auto-extension fails + * @see ucx_buffer_shift() + */ +int ucx_buffer_shift_right(UcxBuffer* buffer, size_t shift); + +/** + * Shifts the buffer to the left. + * + * See ucx_buffer_shift() for details. Note, however, that this method expects + * a positive shift offset. + * + * Since a left shift cannot fail due to memory allocation problems, this + * function always returns zero. + * + * @param buffer the buffer + * @param shift the shift offset + * @return always zero + * @see ucx_buffer_shift() + */ +int ucx_buffer_shift_left(UcxBuffer* buffer, size_t shift); + + +/** + * Moves the position of the buffer. + * + * The new position is relative to the whence argument. + * + * SEEK_SET marks the start of the buffer. + * SEEK_CUR marks the current position. + * SEEK_END marks the end of the buffer. + * + * With an offset of zero, this function sets the buffer position to zero + * (SEEK_SET), the buffer size (SEEK_END) or leaves the buffer position + * unchanged (SEEK_CUR). + * + * @param buffer + * @param offset position offset relative to whence + * @param whence one of SEEK_SET, SEEK_CUR or SEEK_END + * @return 0 on success, non-zero if the position is invalid + * + */ +int ucx_buffer_seek(UcxBuffer *buffer, off_t offset, int whence); + +/** + * Clears the buffer by resetting the position and deleting the data. + * + * The data is deleted by a zeroing it with call to memset(). + * + * @param buffer the buffer to be cleared + */ +#define ucx_buffer_clear(buffer) memset((buffer)->space, 0, (buffer)->size); \ + (buffer)->size = 0; (buffer)->pos = 0; + +/** + * Tests, if the buffer position has exceeded the buffer capacity. + * + * @param buffer the buffer to test + * @return non-zero, if the current buffer position has exceeded the last + * available byte of the buffer. + */ +int ucx_buffer_eof(UcxBuffer *buffer); + + +/** + * Extends the capacity of the buffer. + * + * Note: The buffer capacity increased by a power of two. I.e. + * the buffer capacity is doubled, as long as it would not hold the current + * content plus the additional required bytes. + * + * Attention: the argument provided is the number of additional + * bytes the buffer shall hold. It is NOT the total number of bytes the + * buffer shall hold. + * + * @param buffer the buffer to extend + * @param additional_bytes the number of additional bytes the buffer shall + * at least hold + * @return 0 on success or a non-zero value on failure + */ +int ucx_buffer_extend(UcxBuffer *buffer, size_t additional_bytes); + +/** + * Writes data to a UcxBuffer. + * + * The position of the buffer is increased by the number of bytes written. + * + * @param ptr a pointer to the memory area containing the bytes to be written + * @param size the length of one element + * @param nitems the element count + * @param buffer the UcxBuffer to write to + * @return the total count of bytes written + */ +size_t ucx_buffer_write(const void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer); + +/** + * Reads data from a UcxBuffer. + * + * The position of the buffer is increased by the number of bytes read. + * + * @param ptr a pointer to the memory area where to store the read data + * @param size the length of one element + * @param nitems the element count + * @param buffer the UcxBuffer to read from + * @return the total number of elements read + */ +size_t ucx_buffer_read(void *ptr, size_t size, size_t nitems, + UcxBuffer *buffer); + +/** + * Writes a character to a buffer. + * + * The least significant byte of the argument is written to the buffer. If the + * end of the buffer is reached and #UCX_BUFFER_AUTOEXTEND feature is enabled, + * the buffer capacity is extended by ucx_buffer_extend(). If the feature is + * disabled or buffer extension fails, EOF is returned. + * + * On successful write the position of the buffer is increased. + * + * @param buffer the buffer to write to + * @param c the character to write as int value + * @return the byte that has bean written as int value or + * EOF when the end of the stream is reached and automatic + * extension is not enabled or not possible + */ +int ucx_buffer_putc(UcxBuffer *buffer, int c); + +/** + * Gets a character from a buffer. + * + * The current position of the buffer is increased after a successful read. + * + * @param buffer the buffer to read from + * @return the character as int value or EOF, if the + * end of the buffer is reached + */ +int ucx_buffer_getc(UcxBuffer *buffer); + +/** + * Writes a string to a buffer. + * + * @param buffer the buffer + * @param str the string + * @return the number of bytes written + */ +size_t ucx_buffer_puts(UcxBuffer *buffer, const char *str); + +/** + * Returns the complete buffer content as sstr_t. + * @param buffer the buffer + * @return the result of sstrn() with the buffer space and size + * as arguments + */ +#define ucx_buffer_to_sstr(buffer) sstrn((buffer)->space, (buffer)->size) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_BUFFER_H */ + diff --git a/ucx/ucx/list.h b/ucx/ucx/list.h new file mode 100644 index 0000000..2bda6b8 --- /dev/null +++ b/ucx/ucx/list.h @@ -0,0 +1,512 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Doubly linked list implementation. + * + * @file list.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_LIST_H +#define UCX_LIST_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX lists. + * + * The first argument is the name of the iteration variable. The scope of + * this variable is limited to the UCX_FOREACH statement. + * + * The second argument is a pointer to the list. In most cases this will be the + * pointer to the first element of the list, but it may also be an arbitrary + * element of the list. The iteration will then start with that element. + * + * @param list The first element of the list + * @param elem The variable name of the element + */ +#define UCX_FOREACH(elem,list) \ + for (UcxList* elem = (UcxList*) list ; elem != NULL ; elem = elem->next) + +/** + * UCX list type. + * @see UcxList + */ +typedef struct UcxList UcxList; + +/** + * UCX list structure. + */ +struct UcxList { + /** + * List element payload. + */ + void *data; + /** + * Pointer to the next list element or NULL, if this is the + * last element. + */ + UcxList *next; + /** + * Pointer to the previous list element or NULL, if this is + * the first element. + */ + UcxList *prev; +}; + +/** + * Creates an element-wise copy of a list. + * + * This function clones the specified list by creating new list elements and + * copying the data with the specified copy_func(). If no copy_func() is + * specified, a shallow copy is created and the new list will reference the + * same data as the source list. + * + * @param list the list to copy + * @param cpyfnc a pointer to the function that shall copy an element (may be + * NULL) + * @param data additional data for the copy_func() + * @return a pointer to the copy + */ +UcxList *ucx_list_clone(const UcxList *list, copy_func cpyfnc, void* data); + +/** + * Creates an element-wise copy of a list using a UcxAllocator. + * + * See ucx_list_clone() for details. + * + * You might want to pass the allocator via the data parameter, + * to access it within the copy function for making deep copies. + * + * @param allocator the allocator to use + * @param list the list to copy + * @param cpyfnc a pointer to the function that shall copy an element (may be + * NULL) + * @param data additional data for the copy_func() + * @return a pointer to the copy + * @see ucx_list_clone() + */ +UcxList *ucx_list_clone_a(UcxAllocator *allocator, const UcxList *list, + copy_func cpyfnc, void* data); + +/** + * Compares two UCX lists element-wise by using a compare function. + * + * Each element of the two specified lists are compared by using the specified + * compare function and the additional data. The type and content of this + * additional data depends on the cmp_func() used. + * + * If the list pointers denote elements within a list, the lists are compared + * starting with the denoted elements. Thus any previous elements are not taken + * into account. This might be useful to check, if certain list tails match + * each other. + * + * @param list1 the first list + * @param list2 the second list + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the two lists equal element-wise, 0 otherwise + */ +int ucx_list_equals(const UcxList *list1, const UcxList *list2, + cmp_func cmpfnc, void* data); + +/** + * Destroys the entire list. + * + * The members of the list are not automatically freed, so ensure they are + * otherwise referenced or destroyed by ucx_list_free_contents(). + * Otherwise, a memory leak is likely to occur. + * + * Caution: the argument MUST denote an entire list (i.e. a call + * to ucx_list_first() on the argument must return the argument itself) + * + * @param list the list to free + * @see ucx_list_free_contents() + */ +void ucx_list_free(UcxList *list); + +/** + * Destroys the entire list using a UcxAllocator. + * + * See ucx_list_free() for details. + * + * @param allocator the allocator to use + * @param list the list to free + * @see ucx_list_free() + */ +void ucx_list_free_a(UcxAllocator *allocator, UcxList *list); + +/** + * Destroys the contents of the specified list by calling the specified + * destructor on each of them. + * + * Note, that the contents are not usable afterwards and the list should be + * destroyed with ucx_list_free(). + * + * If no destructor is specified (NULL), stdlib's free() is used. + * + * @param list the list for which the contents shall be freed + * @param destr optional destructor function + * @see ucx_list_free() + */ +void ucx_list_free_content(UcxList* list, ucx_destructor destr); + + +/** + * Inserts an element at the end of the list. + * + * This is generally an O(n) operation, as the end of the list is retrieved with + * ucx_list_last(). + * + * @param list the list where to append the data, or NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL or a pointer to + * the newly created list otherwise + */ +UcxList *ucx_list_append(UcxList *list, void *data); + +/** + * Inserts an element at the end of the list using a UcxAllocator. + * + * See ucx_list_append() for details. + * + * @param allocator the allocator to use + * @param list the list where to append the data, or NULL to + * create a new list + * @param data the data to insert + * @return list, if it is not NULL or a pointer to + * the newly created list otherwise + * @see ucx_list_append() + */ +UcxList *ucx_list_append_a(UcxAllocator *allocator, UcxList *list, void *data); + + +/** + * Inserts an element at the beginning of the list. + * + * You should overwrite the old list pointer by calling + * mylist = ucx_list_prepend(mylist, mydata);. However, you may + * also perform successive calls of ucx_list_prepend() on the same list pointer, + * as this function always searchs for the head of the list with + * ucx_list_first(). + * + * @param list the list where to insert the data or NULL to create + * a new list + * @param data the data to insert + * @return a pointer to the new list head + */ +UcxList *ucx_list_prepend(UcxList *list, void *data); + +/** + * Inserts an element at the beginning of the list using a UcxAllocator. + * + * See ucx_list_prepend() for details. + * + * @param allocator the allocator to use + * @param list the list where to insert the data or NULL to create + * a new list + * @param data the data to insert + * @return a pointer to the new list head + * @see ucx_list_prepend() + */ +UcxList *ucx_list_prepend_a(UcxAllocator *allocator, UcxList *list, void *data); + +/** + * Concatenates two lists. + * + * Either of the two arguments may be NULL. + * + * This function modifies the references to the next/previous element of + * the last/first element of list1/ + * list2. + * + * @param list1 first list + * @param list2 second list + * @return if list1 is NULL, list2 is + * returned, otherwise list1 is returned + */ +UcxList *ucx_list_concat(UcxList *list1, UcxList *list2); + +/** + * Returns the first element of a list. + * + * If the argument is the list pointer, it is directly returned. Otherwise + * this function traverses to the first element of the list and returns the + * list pointer. + * + * @param elem one element of the list + * @return the first element of the list, the specified element is a member of + */ +UcxList *ucx_list_first(const UcxList *elem); + +/** + * Returns the last element of a list. + * + * If the argument has no successor, it is the last element and therefore + * directly returned. Otherwise this function traverses to the last element of + * the list and returns it. + * + * @param elem one element of the list + * @return the last element of the list, the specified element is a member of + */ +UcxList *ucx_list_last(const UcxList *elem); + +/** + * Returns the list element at the specified index. + * + * @param list the list to retrieve the element from + * @param index index of the element to return + * @return the element at the specified index or NULL, if the + * index is greater than the list size + */ +UcxList *ucx_list_get(const UcxList *list, size_t index); + +/** + * Returns the index of an element. + * + * @param list the list where to search for the element + * @param elem the element to find + * @return the index of the element or -1 if the list does not contain the + * element + */ +ssize_t ucx_list_indexof(const UcxList *list, const UcxList *elem); + +/** + * Returns the element count of the list. + * + * @param list the list whose elements are counted + * @return the element count + */ +size_t ucx_list_size(const UcxList *list); + +/** + * Returns the index of an element containing the specified data. + * + * This function uses a cmp_func() to compare the data of each list element + * with the specified data. If no cmp_func is provided, the pointers are + * compared. + * + * If the list contains the data more than once, the index of the first + * occurrence is returned. + * + * @param list the list where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return the index of the element containing the specified data or -1 if the + * data is not found in this list + */ +ssize_t ucx_list_find(const UcxList *list, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Checks, if a list contains a specific element. + * + * An element is found, if ucx_list_find() returns a value greater than -1. + * + * @param list the list where to search for the data + * @param elem the element data + * @param cmpfnc the compare function + * @param data additional data for the compare function + * @return 1, if and only if the list contains the specified element data + * @see ucx_list_find() + */ +int ucx_list_contains(const UcxList *list, void *elem, + cmp_func cmpfnc, void *data); + +/** + * Sorts a UcxList with natural merge sort. + * + * This function uses O(n) additional temporary memory for merge operations + * that is automatically freed after each merge. + * + * As the head of the list might change, you MUST call this function + * as follows: mylist = ucx_list_sort(mylist, mycmpfnc, mydata);. + * + * @param list the list to sort + * @param cmpfnc the function that shall be used to compare the element data + * @param data additional data for the cmp_func() + * @return the sorted list + */ +UcxList *ucx_list_sort(UcxList *list, cmp_func cmpfnc, void *data); + +/** + * Removes an element from the list. + * + * If the first element is removed, the list pointer changes. So it is + * highly recommended to always update the pointer by calling + * mylist = ucx_list_remove(mylist, myelem);. + * + * @param list the list from which the element shall be removed + * @param element the element to remove + * @return returns the updated list pointer or NULL, if the list + * is now empty + */ +UcxList *ucx_list_remove(UcxList *list, UcxList *element); + +/** + * Removes an element from the list using a UcxAllocator. + * + * See ucx_list_remove() for details. + * + * @param allocator the allocator to use + * @param list the list from which the element shall be removed + * @param element the element to remove + * @return returns the updated list pointer or NULL, if the list + * @see ucx_list_remove() + */ +UcxList *ucx_list_remove_a(UcxAllocator *allocator, UcxList *list, + UcxList *element); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two lists. + * + * The union is a list of unique elements regarding cmpfnc obtained from + * both source lists. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the union + */ +UcxList* ucx_list_union_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two lists. + * + * The intersection contains all elements of the left list + * (including duplicates) that can be found in the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the intersection + */ +UcxList* ucx_list_intersection_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference(const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two lists. + * + * The difference contains all elements of the left list + * (including duplicates) that are not equal to any element of the right list. + * + * @param allocator allocates the new list elements + * @param left the left source list + * @param right the right source list + * @param cmpfnc a function to compare elements + * @param cmpdata additional data for the compare function + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxList* ucx_list_difference_a(UcxAllocator *allocator, + const UcxList *left, const UcxList *right, + cmp_func cmpfnc, void* cmpdata, + copy_func cpfnc, void* cpdata); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LIST_H */ + diff --git a/ucx/ucx/logging.h b/ucx/ucx/logging.h new file mode 100644 index 0000000..32bbbae --- /dev/null +++ b/ucx/ucx/logging.h @@ -0,0 +1,253 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Logging API. + * + * @file logging.h + * @author Mike Becker, Olaf Wintermann + */ +#ifndef UCX_LOGGING_H +#define UCX_LOGGING_H + +#include "ucx.h" +#include "map.h" +#include "string.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* leave enough space for custom log levels */ + +/** Log level for error messages. */ +#define UCX_LOGGER_ERROR 0x00 + +/** Log level for warning messages. */ +#define UCX_LOGGER_WARN 0x10 + +/** Log level for information messages. */ +#define UCX_LOGGER_INFO 0x20 + +/** Log level for debug messages. */ +#define UCX_LOGGER_DEBUG 0x30 + +/** Log level for trace messages. */ +#define UCX_LOGGER_TRACE 0x40 + +/** + * Output flag for the log level. + * If this flag is set, the log message will contain the log level. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_LEVEL 0x01 + +/** + * Output flag for the timestmap. + * If this flag is set, the log message will contain the timestmap. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_TIMESTAMP 0x02 + +/** + * Output flag for the source. + * If this flag is set, the log message will contain the source file and line + * number. + * @see UcxLogger.mask + */ +#define UCX_LOGGER_SOURCE 0x04 + +/** + * The UCX Logger object. + */ +typedef struct { + /** The stream this logger writes its messages to.*/ + void *stream; + + /** + * The write function that shall be used. + * For standard file or stdout loggers this might be standard fwrite + * (default). + */ + write_func writer; + + /** + * The date format for timestamp outputs including the delimiter + * (default: "%F %T %z "). + * @see UCX_LOGGER_TIMESTAMP + */ + char *dateformat; + + /** + * The level, this logger operates on. + * If a log command is issued, the message will only be logged, if the log + * level of the message is less or equal than the log level of the logger. + */ + unsigned int level; + + /** + * A configuration mask for automatic output. + * For each flag that is set, the logger automatically outputs some extra + * information like the timestamp or the source file and line number. + * See the documentation for the flags for details. + */ + unsigned int mask; + + /** + * A map of valid log levels for this logger. + * + * The keys represent all valid log levels and the values provide string + * representations, that are used, if the UCX_LOGGER_LEVEL flag is set. + * + * The exact data types are unsigned int for the key and + * const char* for the value. + * + * @see UCX_LOGGER_LEVEL + */ + UcxMap* levels; +} UcxLogger; + +/** + * Creates a new logger. + * @param stream the stream, which the logger shall write to + * @param level the level on which the logger shall operate + * @param mask configuration mask (cf. UcxLogger.mask) + * @return a new logger object + */ +UcxLogger *ucx_logger_new(void *stream, unsigned int level, unsigned int mask); + +/** + * Destroys the logger. + * + * The map containing the valid log levels is also automatically destroyed. + * + * @param logger the logger to destroy + */ +void ucx_logger_free(UcxLogger* logger); + +/** + * Internal log function - use macros instead. + * + * This function uses the format and variadic arguments for a + * printf()-style output of the log message. + * + * Dependent on the UcxLogger.mask some information is prepended. The complete + * format is: + * + * [LEVEL] [TIMESTAMP] [SOURCEFILE]:[LINENO] message + * + * The source file name is reduced to the actual file name. This is necessary to + * get consistent behavior over different definitions of the __FILE__ macro. + * + * Attention: the message (including automatically generated information) + * is limited to 4096 characters. The level description is limited to + * 256 characters and the timestamp string is limited to 128 characters. + * + * @param logger the logger to use + * @param level the level to log on + * @param file information about the source file + * @param line information about the source line number + * @param format format string + * @param ... arguments + * @see ucx_logger_log() + */ +void ucx_logger_logf(UcxLogger *logger, unsigned int level, const char* file, + const unsigned int line, const char* format, ...); + +/** + * Registers a custom log level. + * @param logger the logger + * @param level the log level as unsigned integer + * @param name a string literal describing the level + */ +#define ucx_logger_register_level(logger, level, name) {\ + unsigned int l; \ + l = level; \ + ucx_map_int_put(logger->levels, l, (void*) "[" name "]"); \ + } while (0); + +/** + * Logs a message at the specified level. + * @param logger the logger to use + * @param level the level to log the message on + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_log(logger, level, ...) \ + ucx_logger_logf(logger, level, __FILE__, __LINE__, __VA_ARGS__) + +/** + * Shortcut for logging an error message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_error(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_ERROR, __VA_ARGS__) + +/** + * Shortcut for logging an information message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_info(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_INFO, __VA_ARGS__) + +/** + * Shortcut for logging a warning message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_warn(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_WARN, __VA_ARGS__) + +/** + * Shortcut for logging a debug message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_debug(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_DEBUG, __VA_ARGS__) + +/** + * Shortcut for logging a trace message. + * @param logger the logger to use + * @param ... format string and arguments + * @see ucx_logger_logf() + */ +#define ucx_logger_trace(logger, ...) \ + ucx_logger_log(logger, UCX_LOGGER_TRACE, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_LOGGING_H */ diff --git a/ucx/ucx/map.h b/ucx/ucx/map.h new file mode 100644 index 0000000..4a9b9a2 --- /dev/null +++ b/ucx/ucx/map.h @@ -0,0 +1,549 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file map.h + * + * Hash map implementation. + * + * This implementation uses murmur hash 2 and separate chaining with linked + * lists. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MAP_H +#define UCX_MAP_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Loop statement for UCX maps. + * + * The key variable is implicitly defined, but the + * value variable must be already declared as type information + * cannot be inferred. + * + * @param key the variable name for the key + * @param value the variable name for the value + * @param iter a UcxMapIterator + * @see ucx_map_iterator() + */ +#define UCX_MAP_FOREACH(key,value,iter) \ + for(UcxKey key;ucx_map_iter_next(&iter,&key, (void**)&value);) + +/** Type for the UCX map. @see UcxMap */ +typedef struct UcxMap UcxMap; + +/** Type for a key of a UcxMap. @see UcxKey */ +typedef struct UcxKey UcxKey; + +/** Type for an element of a UcxMap. @see UcxMapElement */ +typedef struct UcxMapElement UcxMapElement; + +/** Type for an iterator over a UcxMap. @see UcxMapIterator */ +typedef struct UcxMapIterator UcxMapIterator; + +/** Structure for the UCX map. */ +struct UcxMap { + /** An allocator that is used for the map elements. */ + UcxAllocator *allocator; + /** The array of map element lists. */ + UcxMapElement **map; + /** The size of the map is the length of the element list array. */ + size_t size; + /** The count of elements currently stored in this map. */ + size_t count; +}; + +/** Structure to publicly denote a key of a UcxMap. */ +struct UcxKey { + /** The key data. */ + const void *data; + /** The length of the key data. */ + size_t len; + /** A cache for the hash value of the key data. */ + int hash; +}; + +/** Internal structure for a key of a UcxMap. */ +struct UcxMapKey { + /** The key data. */ + void *data; + /** The length of the key data. */ + size_t len; + /** The hash value of the key data. */ + int hash; +}; + +/** Structure for an element of a UcxMap. */ +struct UcxMapElement { + /** The value data. */ + void *data; + + /** A pointer to the next element in the current list. */ + UcxMapElement *next; + + /** The corresponding key. */ + struct UcxMapKey key; +}; + +/** Structure for an iterator over a UcxMap. */ +struct UcxMapIterator { + /** The map to iterate over. */ + UcxMap const *map; + + /** The current map element. */ + UcxMapElement *cur; + + /** + * The current index of the element list array. + * Attention: this is NOT the element index! Do NOT + * manually iterate over the map by increasing this index. Use + * ucx_map_iter_next(). + * @see UcxMap.map*/ + size_t index; +}; + +/** + * Creates a new hash map with the specified size. + * @param size the size of the hash map + * @return a pointer to the new hash map + */ +UcxMap *ucx_map_new(size_t size); + +/** + * Creates a new hash map with the specified size using a UcxAllocator. + * @param allocator the allocator to use + * @param size the size of the hash map + * @return a pointer to the new hash map + */ +UcxMap *ucx_map_new_a(UcxAllocator *allocator, size_t size); + +/** + * Frees a hash map. + * + * Note: the contents are not freed, use ucx_map_free_content() + * before calling this function to achieve that. + * + * @param map the map to be freed + * @see ucx_map_free_content() + */ +void ucx_map_free(UcxMap *map); + +/** + * Frees the contents of a hash map. + * + * This is a convenience function that iterates over the map and passes all + * values to the specified destructor function. + * + * If no destructor is specified (NULL), the free() function of + * the map's own allocator is used. + * + * You must ensure, that it is valid to pass each value in the map to the same + * destructor function. + * + * You should free or clear the map afterwards, as the contents will be invalid. + * + * @param map for which the contents shall be freed + * @param destr optional pointer to a destructor function + * @see ucx_map_free() + * @see ucx_map_clear() + */ +void ucx_map_free_content(UcxMap *map, ucx_destructor destr); + +/** + * Clears a hash map. + * + * Note: the contents are not freed, use ucx_map_free_content() + * before calling this function to achieve that. + * + * @param map the map to be cleared + * @see ucx_map_free_content() + */ +void ucx_map_clear(UcxMap *map); + + +/** + * Copies contents from a map to another map using a copy function. + * + * Note: The destination map does not need to be empty. However, if it + * contains data with keys that are also present in the source map, the contents + * are overwritten. + * + * @param from the source map + * @param to the destination map + * @param fnc the copy function or NULL if the pointer address + * shall be copied + * @param data additional data for the copy function + * @return 0 on success or a non-zero value on memory allocation errors + */ +int ucx_map_copy(UcxMap const *from, UcxMap *to, copy_func fnc, void *data); + +/** + * Clones the map and rehashes if necessary. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always ensures a new UcxMap.size of at least + * 2.5*UcxMap.count. + * + * @param map the map to clone + * @param fnc the copy function to use or NULL if the new and + * the old map shall share the data pointers + * @param data additional data for the copy function + * @return the cloned map + * @see ucx_map_copy() + */ +UcxMap *ucx_map_clone(UcxMap const *map, copy_func fnc, void *data); + +/** + * Clones the map and rehashes if necessary. + * + * Note: In contrast to ucx_map_rehash() the load factor is irrelevant. + * This function always ensures a new UcxMap.size of at least + * 2.5*UcxMap.count. + * + * @param allocator the allocator to use for the cloned map + * @param map the map to clone + * @param fnc the copy function to use or NULL if the new and + * the old map shall share the data pointers + * @param data additional data for the copy function + * @return the cloned map + * @see ucx_map_copy() + */ +UcxMap *ucx_map_clone_a(UcxAllocator *allocator, + UcxMap const *map, copy_func fnc, void *data); + +/** + * Increases size of the hash map, if necessary. + * + * The load value is 0.75*UcxMap.size. If the element count exceeds the load + * value, the map needs to be rehashed. Otherwise no action is performed and + * this function simply returns 0. + * + * The rehashing process ensures, that the UcxMap.size is at least + * 2.5*UcxMap.count. So there is enough room for additional elements without + * the need of another soon rehashing. + * + * You can use this function to dramatically increase access performance. + * + * @param map the map to rehash + * @return 1, if a memory allocation error occurred, 0 otherwise + */ +int ucx_map_rehash(UcxMap *map); + +/** + * Puts a key/value-pair into the map. + * + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + */ +int ucx_map_put(UcxMap *map, UcxKey key, void *value); + +/** + * Retrieves a value by using a key. + * + * @param map the map + * @param key the key + * @return the value + */ +void* ucx_map_get(UcxMap const *map, UcxKey key); + +/** + * Removes a key/value-pair from the map by using the key. + * + * @param map the map + * @param key the key + * @return the removed value + */ +void* ucx_map_remove(UcxMap *map, UcxKey key); + +/** + * Shorthand for putting data with a sstr_t key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_sstr_put(map, key, value) \ + ucx_map_put(map, ucx_key(key.ptr, key.length), (void*)value) + +/** + * Shorthand for putting data with a C string key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_cstr_put(map, key, value) \ + ucx_map_put(map, ucx_key(key, strlen(key)), (void*)value) + +/** + * Shorthand for putting data with an integer key into the map. + * @param map the map + * @param key the key + * @param value the value + * @return 0 on success, non-zero value on failure + * @see ucx_map_put() + */ +#define ucx_map_int_put(map, key, value) \ + ucx_map_put(map, ucx_key(&key, sizeof(key)), (void*)value) + +/** + * Shorthand for getting data from the map with a sstr_t key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_sstr_get(map, key) \ + ucx_map_get(map, ucx_key(key.ptr, key.length)) + +/** + * Shorthand for getting data from the map with a C string key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_cstr_get(map, key) \ + ucx_map_get(map, ucx_key(key, strlen(key))) + +/** + * Shorthand for getting data from the map with an integer key. + * @param map the map + * @param key the key + * @return the value + * @see ucx_map_get() + */ +#define ucx_map_int_get(map, key) \ + ucx_map_get(map, ucx_key(&key, sizeof(int))) + +/** + * Shorthand for removing data from the map with a sstr_t key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_sstr_remove(map, key) \ + ucx_map_remove(map, ucx_key(key.ptr, key.length)) + +/** + * Shorthand for removing data from the map with a C string key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_cstr_remove(map, key) \ + ucx_map_remove(map, ucx_key(key, strlen(key))) + +/** + * Shorthand for removing data from the map with an integer key. + * @param map the map + * @param key the key + * @return the removed value + * @see ucx_map_remove() + */ +#define ucx_map_int_remove(map, key) \ + ucx_map_remove(map, ucx_key(&key, sizeof(key))) + +/** + * Creates a UcxKey based on the given data. + * + * This function implicitly computes the hash. + * + * @param data the data for the key + * @param len the length of the data + * @return a UcxKey with implicitly computed hash + * @see ucx_hash() + */ +UcxKey ucx_key(const void *data, size_t len); + +/** + * Computes a murmur hash-2. + * + * @param data the data to hash + * @param len the length of the data + * @return the murmur hash-2 of the data + */ +int ucx_hash(const char *data, size_t len); + +/** + * Creates an iterator for a map. + * + * Note: A UcxMapIterator iterates over all elements in all element + * lists successively. Therefore the order highly depends on the key hashes and + * may vary under different map sizes. So generally you may NOT rely on + * the iteration order. + * + * Note: The iterator is NOT initialized. You need to call + * ucx_map_iter_next() at least once before accessing any information. However, + * it is not recommended to access the fields of a UcxMapIterator directly. + * + * @param map the map to create the iterator for + * @return an iterator initialized on the first element of the + * first element list + * @see ucx_map_iter_next() + */ +UcxMapIterator ucx_map_iterator(UcxMap const *map); + +/** + * Proceeds to the next element of the map (if any). + * + * Subsequent calls on the same iterator proceed to the next element and + * store the key/value-pair into the memory specified as arguments of this + * function. + * + * If no further elements are found, this function returns zero and leaves the + * last found key/value-pair in memory. + * + * @param iterator the iterator to use + * @param key a pointer to the memory where to store the key + * @param value a pointer to the memory where to store the value + * @return 1, if another element was found, 0 if all elements has been processed + * @see ucx_map_iterator() + */ +int ucx_map_iter_next(UcxMapIterator *iterator, UcxKey *key, void **value); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the union of two maps. + * + * The union is a fresh map which is filled by two successive calls of + * ucx_map_copy() on the two input maps. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the union + */ +UcxMap* ucx_map_union_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the intersection of two maps. + * + * The intersection is defined as a copy of the first map with every element + * removed that has no valid key in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new map containing the intersection + */ +UcxMap* ucx_map_intersection_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference(const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + +/** + * Returns the difference of two maps. + * + * The difference contains a copy of all elements of the first map + * for which the corresponding keys cannot be found in the second map. + * + * @param allocator the allocator that shall be used by the new map + * @param first the first source map + * @param second the second source map + * @param cpfnc a function to copy the elements + * @param cpdata additional data for the copy function + * @return a new list containing the difference + */ +UcxMap* ucx_map_difference_a(UcxAllocator *allocator, + const UcxMap *first, const UcxMap *second, + copy_func cpfnc, void* cpdata); + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MAP_H */ + diff --git a/ucx/ucx/mempool.h b/ucx/ucx/mempool.h new file mode 100644 index 0000000..e4a8ce3 --- /dev/null +++ b/ucx/ucx/mempool.h @@ -0,0 +1,209 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file mempool.h + * + * Memory pool implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_MEMPOOL_H +#define UCX_MEMPOOL_H + +#include "ucx.h" +#include "allocator.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UCX mempool structure. + */ +typedef struct { + /** UcxAllocator based on this pool */ + UcxAllocator *allocator; + + /** List of pointers to pooled memory. */ + void **data; + + /** Count of pooled memory items. */ + size_t ndata; + + /** Memory pool size. */ + size_t size; +} UcxMempool; + +/** Shorthand for a new default memory pool with a capacity of 16 elements. */ +#define ucx_mempool_new_default() ucx_mempool_new(16) + + +/** + * Creates a memory pool with the specified initial size. + * + * As the created memory pool automatically grows in size by factor two when + * trying to allocate memory on a full pool, it is recommended that you use + * a power of two for the initial size. + * + * @param n initial pool size (should be a power of two, e.g. 16) + * @return a pointer to the new memory pool + * @see ucx_mempool_new_default() + */ +UcxMempool *ucx_mempool_new(size_t n); + +/** + * Resizes a memory pool. + * + * This function will fail if the new capacity is not sufficient for the + * present data. + * + * @param pool the pool to resize + * @param newcap the new capacity + * @return zero on success or non-zero on failure + */ +int ucx_mempool_chcap(UcxMempool *pool, size_t newcap); + +/** + * Allocates pooled memory. + * + * @param pool the memory pool + * @param n amount of memory to allocate + * @return a pointer to the allocated memory + * @see ucx_allocator_malloc() + */ +void *ucx_mempool_malloc(UcxMempool *pool, size_t n); +/** + * Allocates a pooled memory array. + * + * The content of the allocated memory is set to zero. + * + * @param pool the memory pool + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @return a pointer to the allocated memory + * @see ucx_allocator_calloc() + */ +void *ucx_mempool_calloc(UcxMempool *pool, size_t nelem, size_t elsize); + +/** + * Reallocates pooled memory. + * + * If the memory to be reallocated is not contained by the specified pool, the + * behavior is undefined. + * + * @param pool the memory pool + * @param ptr a pointer to the memory that shall be reallocated + * @param n the new size of the memory + * @return a pointer to the new location of the memory + * @see ucx_allocator_realloc() + */ +void *ucx_mempool_realloc(UcxMempool *pool, void *ptr, size_t n); + +/** + * Frees pooled memory. + * + * Before freeing the memory, the specified destructor function (if any) + * is called. + * + * If you specify memory, that is not pooled by the specified memory pool, the + * program will terminate with a call to abort(). + * + * @param pool the memory pool + * @param ptr a pointer to the memory that shall be freed + * @see ucx_mempool_set_destr() + */ +void ucx_mempool_free(UcxMempool *pool, void *ptr); + +/** + * Destroys a memory pool. + * + * For each element the destructor function (if any) is called and the element + * is freed. + * + * Each of the registered destructor function that has no corresponding element + * within the pool (namely those registered by ucx_mempool_reg_destr) is + * called interleaving with the element destruction, but with guarantee to the + * order in which they were registered (FIFO order). + * + * + * @param pool the mempool to destroy + */ +void ucx_mempool_destroy(UcxMempool *pool); + +/** + * Sets a destructor function for the specified memory. + * + * The destructor is automatically called when the memory is freed or the + * pool is destroyed. + * A destructor for pooled memory MUST NOT free the memory itself, + * as this is done by the pool. Use a destructor to free any resources + * managed by the pooled object. + * + * The only requirement for the specified memory is, that it MUST be + * pooled memory by a UcxMempool or an element-compatible mempool. The pointer + * to the destructor function is saved in a reserved area before the actual + * memory. + * + * @param ptr pooled memory + * @param func a pointer to the destructor function + * @see ucx_mempool_free() + * @see ucx_mempool_destroy() + */ +void ucx_mempool_set_destr(void *ptr, ucx_destructor func); + +/** + * Registers a destructor function for the specified (non-pooled) memory. + * + * This is useful, if you have memory that has not been allocated by a mempool, + * but shall be managed by a mempool. + * + * This function creates an entry in the specified mempool and the memory will + * therefore (logically) convert to pooled memory. + * However, this does not cause the memory to be freed automatically!. + * If you want to use this function, make the memory pool free non-pooled + * memory, the specified destructor function must call free() + * by itself. But keep in mind, that you then MUST NOT use this destructor + * function with pooled memory (e.g. in ucx_mempool_set_destr()), as it + * would cause a double-free. + * + * @param pool the memory pool + * @param ptr data the destructor is registered for + * @param destr a pointer to the destructor function + */ +void ucx_mempool_reg_destr(UcxMempool *pool, void *ptr, ucx_destructor destr); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_MEMPOOL_H */ + diff --git a/ucx/ucx/properties.h b/ucx/ucx/properties.h new file mode 100644 index 0000000..819d1e5 --- /dev/null +++ b/ucx/ucx/properties.h @@ -0,0 +1,221 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * @file properties.h + * + * Load / store utilities for properties files. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_PROPERTIES_H +#define UCX_PROPERTIES_H + +#include "ucx.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * UcxProperties object for parsing properties data. + * Most of the fields are for internal use only. You may configure the + * properties parser, e.g. by changing the used delimiter or specifying + * up to three different characters that shall introduce comments. + */ +typedef struct { + /** + * Input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + char *buffer; + + /** + * Length of the input buffer (don't set manually). + * Automatically set by calls to ucx_properties_fill(). + */ + size_t buflen; + + /** + * Current buffer position (don't set manually). + * Used by ucx_properties_next(). + */ + size_t pos; + + /** + * Internal temporary buffer (don't set manually). + * Used by ucx_properties_next(). + */ + char *tmp; + + /** + * Internal temporary buffer length (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmplen; + + /** + * Internal temporary buffer capacity (don't set manually). + * Used by ucx_properties_next(). + */ + size_t tmpcap; + + /** + * Parser error code. + * This is always 0 on success and a nonzero value on syntax errors. + * The value is set by ucx_properties_next(). + */ + int error; + + /** + * The delimiter that shall be used. + * This is '=' by default. + */ + char delimiter; + + /** + * The first comment character. + * This is '#' by default. + */ + char comment1; + + /** + * The second comment character. + * This is not set by default. + */ + char comment2; + + /** + * The third comment character. + * This is not set by default. + */ + char comment3; +} UcxProperties; + + +/** + * Constructs a new UcxProperties object. + * @return a pointer to the new UcxProperties object + */ +UcxProperties *ucx_properties_new(); + +/** + * Destroys a UcxProperties object. + * @param prop the UcxProperties object to destroy + */ +void ucx_properties_free(UcxProperties *prop); + +/** + * Sets the input buffer for the properties parser. + * + * After calling this function, you may parse the data by calling + * ucx_properties_next() until it returns 0. The function ucx_properties2map() + * is a convenience function that reads as much data as possible by using this + * function. + * + * + * @param prop the UcxProperties object + * @param buf a pointer to the new buffer + * @param len the payload length of the buffer + * @see ucx_properties_next() + * @see ucx_properties2map() + */ +void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len); + +/** + * Retrieves the next key/value-pair. + * + * This function returns a nonzero value as long as there are key/value-pairs + * found. If no more key/value-pairs are found, you may refill the input buffer + * with ucx_properties_fill(). + * + * Attention: the sstr_t.ptr pointers of the output parameters point to + * memory within the input buffer of the parser and will get invalid some time. + * If you want long term copies of the key/value-pairs, use sstrdup() after + * calling this function. + * + * @param prop the UcxProperties object + * @param name a pointer to the sstr_t that shall contain the property name + * @param value a pointer to the sstr_t that shall contain the property value + * @return Nonzero, if a key/value-pair was successfully retrieved + * @see ucx_properties_fill() + */ +int ucx_properties_next(UcxProperties *prop, sstr_t *name, sstr_t *value); + +/** + * Retrieves all available key/value-pairs and puts them into a UcxMap. + * + * This is done by successive calls to ucx_properties_next() until no more + * key/value-pairs can be retrieved. + * + * The memory for the map values is allocated by the map's own allocator. + * + * @param prop the UcxProperties object + * @param map the target map + * @return The UcxProperties.error code (i.e. 0 on success). + * @see ucx_properties_fill() + * @see UcxMap.allocator + */ +int ucx_properties2map(UcxProperties *prop, UcxMap *map); + +/** + * Loads a properties file to a UcxMap. + * + * This is a convenience function that reads data from an input + * stream until the end of the stream is reached. + * + * @param map the map object to write the key/value-pairs to + * @param file the FILE* stream to read from + * @return 0 on success, or a non-zero value on error + * + * @see ucx_properties_fill() + * @see ucx_properties2map() + */ +int ucx_properties_load(UcxMap *map, FILE *file); + +/** + * Stores a UcxMap to a file. + * + * The key/value-pairs are written by using the following format: + * + * [key] = [value]\\n + * + * @param map the map to store + * @param file the FILE* stream to write to + * @return 0 on success, or a non-zero value on error + */ +int ucx_properties_store(UcxMap *map, FILE *file); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_PROPERTIES_H */ + diff --git a/ucx/ucx/stack.h b/ucx/ucx/stack.h new file mode 100644 index 0000000..6fe8a06 --- /dev/null +++ b/ucx/ucx/stack.h @@ -0,0 +1,240 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file stack.h + * + * Default stack memory allocation implementation. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STACK_H +#define UCX_STACK_H + +#include "ucx.h" +#include "allocator.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * UCX stack structure. + */ +typedef struct { + /** UcxAllocator based on this stack */ + UcxAllocator allocator; + + /** Stack size. */ + size_t size; + + /** Pointer to the bottom of the stack */ + char *space; + + /** Pointer to the top of the stack */ + char *top; +} UcxStack; + +/** + * Metadata for each UCX stack element. + */ +struct ucx_stack_metadata { + /** + * Location of the previous element (NULL if this is the first) + */ + char *prev; + + /** Size of this element */ + size_t size; +}; + +/** + * Initializes UcxStack structure with memory. + * + * @param stack a pointer to an uninitialized stack structure + * @param space the memory area that shall be managed + * @param size size of the memory area + * @return a new UcxStack structure + */ +void ucx_stack_init(UcxStack *stack, char* space, size_t size); + +/** + * Allocates stack memory. + * + * @param stack a pointer to the stack + * @param n amount of memory to allocate + * @return a pointer to the allocated memory or NULL on stack + * overflow + * @see ucx_allocator_malloc() + */ +void *ucx_stack_malloc(UcxStack *stack, size_t n); + +/** + * Allocates memory with #ucx_stack_malloc() and copies the specified data if + * the allocation was successful. + * + * @param stack a pointer to the stack + * @param n amount of memory to allocate + * @param data a pointer to the data to copy + * @return a pointer to the allocated memory + * @see ucx_stack_malloc + */ +void *ucx_stack_push(UcxStack *stack, size_t n, const void *data); + +/** + * Allocates an array of stack memory + * + * The content of the allocated memory is set to zero. + * + * @param stack a pointer to the stack + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @return a pointer to the allocated memory + * @see ucx_allocator_calloc() + */ +void *ucx_stack_calloc(UcxStack *stack, size_t nelem, size_t elsize); + +/** + * Allocates memory with #ucx_stack_calloc() and copies the specified data if + * the allocation was successful. + * + * @param stack a pointer to the stack + * @param nelem amount of elements to allocate + * @param elsize amount of memory per element + * @param data a pointer to the data + * @return a pointer to the allocated memory + * @see ucx_stack_calloc + */ +void *ucx_stack_pusharr(UcxStack *stack, + size_t nelem, size_t elsize, const void *data); + +/** + * Reallocates memory on the stack. + * + * Shrinking memory is always safe. Extending memory can be very expensive. + * + * @param stack the stack + * @param ptr a pointer to the memory that shall be reallocated + * @param n the new size of the memory + * @return a pointer to the new location of the memory + * @see ucx_allocator_realloc() + */ +void *ucx_stack_realloc(UcxStack *stack, void *ptr, size_t n); + +/** + * Frees memory on the stack. + * + * Freeing stack memory behaves in a special way. + * + * If the element, that should be freed, is the top most element of the stack, + * it is removed from the stack. Otherwise it is marked as freed. Marked + * elements are removed, when they become the top most elements of the stack. + * + * @param stack a pointer to the stack + * @param ptr a pointer to the memory that shall be freed + */ +void ucx_stack_free(UcxStack *stack, void *ptr); + + +/** + * Returns the size of the top most element. + * @param stack a pointer to the stack + * @return the size of the top most element + */ +#define ucx_stack_topsize(stack) ((stack)->top ? ((struct ucx_stack_metadata*)\ + (stack)->top - 1)->size : 0) + +/** + * Removes the top most element from the stack and copies the content to + * dest, if specified. + * + * Use #ucx_stack_topsize()# to get the amount of memory that must be available + * at the location of dest. + * + * @param stack a pointer to the stack + * @param dest the location where the contents shall be written to, or + * NULL, if the element shall only be removed. + * @see ucx_stack_free + * @see ucx_stack_popn + */ +#define ucx_stack_pop(stack, dest) ucx_stack_popn(stack, dest, (size_t)-1) + +/** + * Removes the top most element from the stack and copies the content to + * dest. + * + * This function copies at most n bytes to the destination, but + * the element is always freed as a whole. + * If the element was larger than n, the remaining data is lost. + * + * @param stack a pointer to the stack + * @param dest the location where the contents shall be written to + * @param n copies at most n bytes to dest + * @see ucx_stack_pop + */ +void ucx_stack_popn(UcxStack *stack, void *dest, size_t n); + +/** + * Returns the remaining available memory on the specified stack. + * + * @param stack a pointer to the stack + * @return the remaining available memory + */ +size_t ucx_stack_avail(UcxStack *stack); + +/** + * Checks, if the stack is empty. + * + * @param stack a pointer to the stack + * @return nonzero, if the stack is empty, zero otherwise + */ +#define ucx_stack_empty(stack) (!(stack)->top) + +/** + * Computes a recommended size for the stack memory area. Note, that + * reallocations have not been taken into account, so you might need to reserve + * twice as much memory to allow many reallocations. + * + * @param size the approximate payload + * @param elems the approximate count of element allocations + * @return a recommended size for the stack space based on the information + * provided + */ +#define ucx_stack_dim(size, elems) (size+sizeof(struct ucx_stack_metadata) * \ + (elems + 1)) + + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STACK_H */ + diff --git a/ucx/ucx/string.h b/ucx/ucx/string.h new file mode 100644 index 0000000..90b437a --- /dev/null +++ b/ucx/ucx/string.h @@ -0,0 +1,1201 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Bounded string implementation. + * + * The UCX strings (sstr_t) provide an alternative to C strings. + * The main difference to C strings is, that sstr_t does not + * need to be NULL-terminated. Instead the length is stored + * within the structure. + * + * When using sstr_t, developers must be full aware of what type + * of string (NULL-terminated) or not) they are using, when + * accessing the char* ptr directly. + * + * The UCX string module provides some common string functions, known from + * standard libc, working with sstr_t. + * + * @file string.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_STRING_H +#define UCX_STRING_H + +#include "ucx.h" +#include "allocator.h" +#include + +/* + * Use this macro to disable the shortcuts if you experience macro collision. + */ +#ifndef UCX_NO_SSTR_SHORTCUTS +/** + * Shortcut for a sstr_t struct + * or scstr_t struct literal. + */ +#define ST(s) { s, sizeof(s)-1 } + +/** Shortcut for the conversion of a C string to a sstr_t. */ +#define S(s) sstrn(s, sizeof(s)-1) + +/** Shortcut for the conversion of a C string to a scstr_t. */ +#define SC(s) scstrn(s, sizeof(s)-1) +#endif /* UCX_NO_SSTR_SHORTCUTS */ + +/* + * Use this macro to disable the format macros. + */ +#ifndef UCX_NO_SSTR_FORMAT_MACROS +/** Expands a sstr_t or scstr_t to printf arguments. */ +#define SFMT(s) (int) (s).length, (s).ptr + +/** Format specifier for a sstr_t or scstr_t. */ +#define PRIsstr ".*s" +#endif /* UCX_NO_SSTR_FORMAT_MACROS */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The UCX string structure. + */ +typedef struct { + /** A pointer to the string + * (not necessarily NULL-terminated) */ + char *ptr; + /** The length of the string */ + size_t length; +} sstr_t; + +/** + * The UCX string structure for immutable (constant) strings. + */ +typedef struct { + /** A constant pointer to the immutable string + * (not necessarily NULL-terminated) */ + const char *ptr; + /** The length of the string */ + size_t length; +} scstr_t; + +#ifdef __cplusplus +} +#endif + + +#ifdef __cplusplus +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @param str some sstr_t + * @return an immutable (scstr_t) version of the provided string. + */ +inline scstr_t s2scstr(sstr_t s) { + scstr_t c; + c.ptr = s.ptr; + c.length = s.length; + return c; +} + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * This variant is used, when the string is already immutable and no operation + * needs to be performed. + * + * Do not use this function manually. + * + * @param str some scstr_t + * @return the argument itself + */ +inline scstr_t s2scstr(scstr_t str) { + return str; +} + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(s) s2scstr(s) +#else + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * This variant is used, when the string is already immutable and no operation + * needs to be performed. + * + * Do not use this function manually. + * + * @param str some scstr_t + * @return the argument itself + */ +scstr_t ucx_sc2sc(scstr_t str); + +/** + * One of two type adjustment functions that return an scstr_t. + * + * Used internally to convert a UCX string to an immutable UCX string. + * + * Do not use this function manually. + * + * @param str some sstr_t + * @return an immutable (scstr_t) version of the provided string. + */ +scstr_t ucx_ss2sc(sstr_t str); + +#if __STDC_VERSION__ >= 201112L +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(str) _Generic(str, sstr_t: ucx_ss2sc, scstr_t: ucx_sc2sc)(str) + +#elif defined(__GNUC__) || defined(__clang__) + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return an immutable version of the provided string + */ +#define SCSTR(str) __builtin_choose_expr( \ + __builtin_types_compatible_p(typeof(str), sstr_t), \ + ucx_ss2sc, \ + ucx_sc2sc)(str) + +#elif defined(__sun) + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return the an immutable version of the provided string + */ +#define SCSTR(str) ({typeof(str) ucx_tmp_var_str = str; \ + scstr_t ucx_tmp_var_c; \ + ucx_tmp_var_c.ptr = ucx_tmp_var_str.ptr;\ + ucx_tmp_var_c.length = ucx_tmp_var_str.length;\ + ucx_tmp_var_c; }) +#else /* no generics and no builtins */ + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * + * This internal function (ab)uses the C standard an expects one single + * argument which is then implicitly converted to scstr_t without a warning. + * + * Do not use this function manually. + * + * @return the an immutable version of the provided string + */ +scstr_t ucx_ss2c_s(); + +/** + * Converts a UCX string to an immutable UCX string (scstr_t). + * @param str some UCX string + * @return the an immutable version of the provided string + */ +#define SCSTR(str) ucx_ss2c_s(str) +#endif /* C11 feature test */ + +#endif /* C++ */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Creates a new sstr_t based on a C string. + * + * The length is implicitly inferred by using a call to strlen(). + * + * Note: the sstr_t will share the specified pointer to the C string. + * If you do want a copy, use sstrdup() on the return value of this function. + * + * If you need to wrap a constant string, use scstr(). + * + * @param cstring the C string to wrap + * @return a new sstr_t containing the C string + * + * @see sstrn() + */ +sstr_t sstr(char *cstring); + +/** + * Creates a new sstr_t of the specified length based on a C string. + * + * Note: the sstr_t will share the specified pointer to the C string. + * If you do want a copy, use sstrdup() on the return value of this function. + * + * If you need to wrap a constant string, use scstrn(). + * + * @param cstring the C string to wrap + * @param length the length of the string + * @return a new sstr_t containing the C string + * + * @see sstr() + * @see S() + */ +sstr_t sstrn(char *cstring, size_t length); + +/** + * Creates a new scstr_t based on a constant C string. + * + * The length is implicitly inferred by using a call to strlen(). + * + * Note: the scstr_t will share the specified pointer to the C string. + * If you do want a copy, use scstrdup() on the return value of this function. + * + * @param cstring the C string to wrap + * @return a new scstr_t containing the C string + * + * @see scstrn() + */ +scstr_t scstr(const char *cstring); + + +/** + * Creates a new scstr_t of the specified length based on a constant C string. + * + * Note: the scstr_t will share the specified pointer to the C string. + * If you do want a copy, use scstrdup() on the return value of this function. * + * + * @param cstring the C string to wrap + * @param length the length of the string + * @return a new scstr_t containing the C string + * + * @see scstr() + */ +scstr_t scstrn(const char *cstring, size_t length); + +/** + * Returns the accumulated length of all specified strings. + * + * Attention: if the count argument is larger than the count of the + * specified strings, the behavior is undefined. + * + * @param count the total number of specified strings + * @param ... all strings + * @return the accumulated length of all strings + */ +size_t scstrnlen(size_t count, ...); + +/** + * Returns the accumulated length of all specified strings. + * + * Attention: if the count argument is larger than the count of the + * specified strings, the behavior is undefined. + * + * @param count the total number of specified strings + * @param ... all strings + * @return the cumulated length of all strings + */ +#define sstrnlen(count, ...) scstrnlen(count, __VA_ARGS__) + +/** + * Concatenates two or more strings. + * + * The resulting string will be allocated by standard malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + */ +sstr_t scstrcat(size_t count, scstr_t s1, ...); + +/** + * Concatenates two or more strings. + * + * The resulting string will be allocated by standard malloc(). + * So developers MUST pass the sstr_t.ptr to free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + */ +#define sstrcat(count, s1, ...) scstrcat(count, SCSTR(s1), __VA_ARGS__) + +/** + * Concatenates two or more strings using a UcxAllocator. + * + * The resulting string must be freed by the allocators free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param alloc the allocator to use + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + * + * @see scstrcat() + */ +sstr_t scstrcat_a(UcxAllocator *alloc, size_t count, scstr_t s1, ...); + +/** + * Concatenates two or more strings using a UcxAllocator. + * + * The resulting string must be freed by the allocators free() + * implementation. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated. + * + * @param alloc the allocator to use + * @param count the total number of strings to concatenate + * @param s1 first string + * @param ... all remaining strings + * @return the concatenated string + * + * @see sstrcat() + */ +#define sstrcat_a(alloc, count, s1, ...) \ + scstrcat_a(alloc, count, SCSTR(s1), __VA_ARGS__) + +/** + * Returns a substring starting at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use sstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @see sstrsubsl() + * @see sstrchr() + */ +sstr_t sstrsubs(sstr_t string, size_t start); + +/** + * Returns a substring with the given length starting at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use sstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the substring + * @return a substring of string starting at start + * with a maximum length of length + * + * @see sstrsubs() + * @see sstrchr() + */ +sstr_t sstrsubsl(sstr_t string, size_t start, size_t length); + +/** + * Returns a substring of an immutable string starting at the specified + * location. + * + * Attention: the new string references the same memory area as the +* input string and is NOT required to be NULL-terminated. + * Use scstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @return a substring of string starting at start + * + * @see scstrsubsl() + * @see scstrchr() + */ +scstr_t scstrsubs(scstr_t string, size_t start); + +/** + * Returns a substring of an immutable string with a maximum length starting + * at the specified location. + * + * Attention: the new string references the same memory area as the + * input string and is NOT required to be NULL-terminated. + * Use scstrdup() to get a copy. + * + * @param string input string + * @param start start location of the substring + * @param length the maximum length of the substring + * @return a substring of string starting at start + * with a maximum length of length + * + * @see scstrsubs() + * @see scstrchr() + */ +scstr_t scstrsubsl(scstr_t string, size_t start, size_t length); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of chr + * + * @see sstrsubs() + */ +sstr_t sstrchr(sstr_t string, int chr); + +/** + * Returns a substring starting at the location of the last occurrence of the + * specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of chr + * + * @see sstrsubs() + */ +sstr_t sstrrchr(sstr_t string, int chr); + +/** + * Returns an immutable substring starting at the location of the first + * occurrence of the specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the first location of chr + * + * @see scstrsubs() + */ +scstr_t scstrchr(scstr_t string, int chr); + +/** + * Returns an immutable substring starting at the location of the last + * occurrence of the specified character. + * + * If the string does not contain the character, an empty string is returned. + * + * @param string the string where to locate the character + * @param chr the character to locate + * @return a substring starting at the last location of chr + * + * @see scstrsubs() + */ +scstr_t scstrrchr(scstr_t string, int chr); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +sstr_t scstrsstr(sstr_t string, scstr_t match); + +/** + * Returns a substring starting at the location of the first occurrence of the + * specified string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +#define sstrstr(string, match) scstrsstr(string, SCSTR(match)) + +/** + * Returns an immutable substring starting at the location of the + * first occurrence of the specified immutable string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +scstr_t scstrscstr(scstr_t string, scstr_t match); + +/** + * Returns an immutable substring starting at the location of the + * first occurrence of the specified immutable string. + * + * If the string does not contain the other string, an empty string is returned. + * + * If match is an empty string, the complete string is + * returned. + * + * @param string the string to be scanned + * @param match string containing the sequence of characters to match + * @return a substring starting at the first occurrence of + * match, or an empty string, if the sequence is not + * present in string + */ +#define sstrscstr(string, match) scstrscstr(string, SCSTR(match)) + +/** + * Splits a string into parts by using a delimiter string. + * + * This function will return NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count is used as input and determines + * the maximum size of the resulting array, i.e. the maximum count of splits to + * perform + 1. + * + * The integer referenced by count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * If the string starts with the delimiter, the first item of the resulting + * array will be an empty string. + * + * If the string ends with the delimiter and the maximum list size is not + * exceeded, the last array item will be an empty string. + * In case the list size would be exceeded, the last array item will be the + * remaining string after the last split, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). Use scstrsplit_a() with + * an allocator to managed memory, to avoid this. + * + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see scstrsplit_a() + */ +sstr_t* scstrsplit(scstr_t string, scstr_t delim, ssize_t *count); + +/** + * Splits a string into parts by using a delimiter string. + * + * This function will return NULL, if one of the following happens: + *
    + *
  • the string length is zero
  • + *
  • the delimeter length is zero
  • + *
  • the string equals the delimeter
  • + *
  • memory allocation fails
  • + *
+ * + * The integer referenced by count is used as input and determines + * the maximum size of the resulting array, i.e. the maximum count of splits to + * perform + 1. + * + * The integer referenced by count is also used as output and is + * set to + *
    + *
  • -2, on memory allocation errors
  • + *
  • -1, if either the string or the delimiter is an empty string
  • + *
  • 0, if the string equals the delimiter
  • + *
  • 1, if the string does not contain the delimiter
  • + *
  • the count of array items, otherwise
  • + *
+ * + * If the string starts with the delimiter, the first item of the resulting + * array will be an empty string. + * + * If the string ends with the delimiter and the maximum list size is not + * exceeded, the last array item will be an empty string. + * In case the list size would be exceeded, the last array item will be the + * remaining string after the last split, including the terminating + * delimiter. + * + * Attention: The array pointer AND all sstr_t.ptr of the array + * items must be manually passed to free(). Use sstrsplit_a() with + * an allocator to managed memory, to avoid this. + * + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see sstrsplit_a() + */ +#define sstrsplit(string, delim, count) \ + scstrsplit(SCSTR(string), SCSTR(delim), count) + +/** + * Performing scstrsplit() using a UcxAllocator. + * + * Read the description of scstrsplit() for details. + * + * The memory for the sstr_t.ptr pointers of the array items and the memory for + * the sstr_t array itself are allocated by using the UcxAllocator.malloc() + * function. + * + * @param allocator the UcxAllocator used for allocating memory + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see scstrsplit() + */ +sstr_t* scstrsplit_a(UcxAllocator *allocator, scstr_t string, scstr_t delim, + ssize_t *count); + +/** + * Performing sstrsplit() using a UcxAllocator. + * + * Read the description of sstrsplit() for details. + * + * The memory for the sstr_t.ptr pointers of the array items and the memory for + * the sstr_t array itself are allocated by using the UcxAllocator.malloc() + * function. + * + * @param allocator the UcxAllocator used for allocating memory + * @param string the string to split + * @param delim the delimiter string + * @param count IN: the maximum size of the resulting array (0 = no limit), + * OUT: the actual size of the array + * @return a sstr_t array containing the split strings or + * NULL on error + * + * @see sstrsplit() + */ +#define sstrsplit_a(allocator, string, delim, count) \ + scstrsplit_a(allocator, SCSTR(string), SCSTR(delim), count) + +/** + * Compares two UCX strings with standard memcmp(). + * + * At first it compares the scstr_t.length attribute of the two strings. The + * memcmp() function is called, if and only if the lengths match. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of + * memcmp() otherwise (i.e. 0 if the strings match) + */ +int scstrcmp(scstr_t s1, scstr_t s2); + +/** + * Compares two UCX strings with standard memcmp(). + * + * At first it compares the sstr_t.length attribute of the two strings. The + * memcmp() function is called, if and only if the lengths match. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of + * memcmp() otherwise (i.e. 0 if the strings match) + */ +#define sstrcmp(s1, s2) scstrcmp(SCSTR(s1), SCSTR(s2)) + +/** + * Compares two UCX strings ignoring the case. + * + * At first it compares the scstr_t.length attribute of the two strings. If and + * only if the lengths match, both strings are compared char by char ignoring + * the case. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of the platform + * specific string comparison function ignoring the case. + */ +int scstrcasecmp(scstr_t s1, scstr_t s2); + +/** + * Compares two UCX strings ignoring the case. + * + * At first it compares the sstr_t.length attribute of the two strings. If and + * only if the lengths match, both strings are compared char by char ignoring + * the case. + * + * @param s1 the first string + * @param s2 the second string + * @return -1, if the length of s1 is less than the length of s2 or 1, if the + * length of s1 is greater than the length of s2 or the result of the platform + * specific string comparison function ignoring the case. + */ +#define sstrcasecmp(s1, s2) scstrcasecmp(SCSTR(s1), SCSTR(s2)) + +/** + * Creates a duplicate of the specified string. + * + * The new sstr_t will contain a copy allocated by standard + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated and mutable, regardless of the argument. + * + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup_a() + */ +sstr_t scstrdup(scstr_t string); + +/** + * Creates a duplicate of the specified string. + * + * The new sstr_t will contain a copy allocated by standard + * malloc(). So developers MUST pass the sstr_t.ptr to + * free(). + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated, regardless of the argument. + * + * @param string the string to duplicate + * @return a duplicate of the string + * @see sstrdup_a() + */ +#define sstrdup(string) scstrdup(SCSTR(string)) + +/** + * Creates a duplicate of the specified string using a UcxAllocator. + * + * The new sstr_t will contain a copy allocated by the allocators + * UcxAllocator.malloc() function. So it is implementation depended, whether the + * returned sstr_t.ptr pointer must be passed to the allocators + * UcxAllocator.free() function manually. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated and mutable, regardless of the argument. + * + * @param allocator a valid instance of a UcxAllocator + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup() + */ +sstr_t scstrdup_a(UcxAllocator *allocator, scstr_t string); + +/** + * Creates a duplicate of the specified string using a UcxAllocator. + * + * The new sstr_t will contain a copy allocated by the allocators + * UcxAllocator.malloc() function. So it is implementation depended, whether the + * returned sstr_t.ptr pointer must be passed to the allocators + * UcxAllocator.free() function manually. + * + * The sstr_t.ptr of the return value will always be NULL- + * terminated, regardless of the argument. + * + * @param allocator a valid instance of a UcxAllocator + * @param string the string to duplicate + * @return a duplicate of the string + * @see scstrdup() + */ +#define sstrdup_a(allocator, string) scstrdup_a(allocator, SCSTR(string)) + + +/** + * Omits leading and trailing spaces. + * + * This function returns a new sstr_t containing a trimmed version of the + * specified string. + * + * Note: the new sstr_t references the same memory, thus you + * MUST NOT pass the sstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = sstrtrim(mystr); as you lose the reference to the + * source string. Assignments of this type are only permitted, if the + * sstr_t.ptr of the source string does not need to be freed or if another + * reference to the source string exists. + * + * @param string the string that shall be trimmed + * @return a new sstr_t containing the trimmed string + */ +sstr_t sstrtrim(sstr_t string); + +/** + * Omits leading and trailing spaces. + * + * This function returns a new scstr_t containing a trimmed version of the + * specified string. + * + * Note: the new scstr_t references the same memory, thus you + * MUST NOT pass the scstr_t.ptr of the return value to + * free(). It is also highly recommended to avoid assignments like + * mystr = scstrtrim(mystr); as you lose the reference to the + * source string. Assignments of this type are only permitted, if the + * scstr_t.ptr of the source string does not need to be freed or if another + * reference to the source string exists. + * + * @param string the string that shall be trimmed + * @return a new scstr_t containing the trimmed string + */ +scstr_t scstrtrim(scstr_t string); + +/** + * Checks, if a string has a specific prefix. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +int scstrprefix(scstr_t string, scstr_t prefix); + +/** + * Checks, if a string has a specific prefix. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +#define sstrprefix(string, prefix) scstrprefix(SCSTR(string), SCSTR(prefix)) + +/** + * Checks, if a string has a specific suffix. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +int scstrsuffix(scstr_t string, scstr_t suffix); + +/** + * Checks, if a string has a specific suffix. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +#define sstrsuffix(string, suffix) scstrsuffix(SCSTR(string), SCSTR(suffix)) + +/** + * Checks, if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +int scstrcaseprefix(scstr_t string, scstr_t prefix); + +/** + * Checks, if a string has a specific prefix, ignoring the case. + * + * @param string the string to check + * @param prefix the prefix the string should have + * @return 1, if and only if the string has the specified prefix, 0 otherwise + */ +#define sstrcaseprefix(string, prefix) \ + scstrcaseprefix(SCSTR(string), SCSTR(prefix)) + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +int scstrcasesuffix(scstr_t string, scstr_t suffix); + +/** + * Checks, if a string has a specific suffix, ignoring the case. + * + * @param string the string to check + * @param suffix the suffix the string should have + * @return 1, if and only if the string has the specified suffix, 0 otherwise + */ +#define sstrcasesuffix(string, suffix) \ + scstrcasesuffix(SCSTR(string), SCSTR(suffix)) + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup()). + * + * @param string the input string + * @return the resulting lower case string + * @see scstrdup() + */ +sstr_t scstrlower(scstr_t string); + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup()). + * + * @param string the input string + * @return the resulting lower case string + */ +#define sstrlower(string) scstrlower(SCSTR(string)) + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting lower case string + * @see scstrdup_a() + */ +sstr_t scstrlower_a(UcxAllocator *allocator, scstr_t string); + + +/** + * Returns a lower case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting lower case string + */ +#define sstrlower_a(allocator, string) scstrlower_a(allocator, SCSTR(string)) + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup()). + * + * @param string the input string + * @return the resulting upper case string + * @see scstrdup() + */ +sstr_t scstrupper(scstr_t string); + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup()). + * + * @param string the input string + * @return the resulting upper case string + */ +#define sstrupper(string) scstrupper(SCSTR(string)) + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see scstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting upper case string + * @see scstrdup_a() + */ +sstr_t scstrupper_a(UcxAllocator *allocator, scstr_t string); + +/** + * Returns a upper case version of a string. + * + * This function creates a duplicate of the input string, first + * (see sstrdup_a()). + * + * @param allocator the allocator used for duplicating the string + * @param string the input string + * @return the resulting upper case string + */ +#define sstrupper_a(allocator, string) scstrupper_a(allocator, string) + + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +sstr_t scstrreplacen_a(UcxAllocator *allocator, scstr_t str, + scstr_t pattern, scstr_t replacement, size_t replmax); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +sstr_t scstrreplacen(scstr_t str, scstr_t pattern, + scstr_t replacement, size_t replmax); + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +#define sstrreplacen_a(allocator, str, pattern, replacement, replmax) \ + scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \ + SCSTR(replacement), replmax) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @param replmax maximum number of replacements + * @return the resulting string after applying the replacements + */ +#define sstrreplacen(str, pattern, replacement, replmax) \ + scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), replmax) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The resulting string is allocated by the specified allocator. I.e. it + * depends on the used allocator, whether the sstr_t.ptr must be freed + * manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param allocator the allocator to use + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @return the resulting string after applying the replacements + */ +#define sstrreplace_a(allocator, str, pattern, replacement) \ + scstrreplacen_a(allocator, SCSTR(str), SCSTR(pattern), \ + SCSTR(replacement), SIZE_MAX) + +/** + * Replaces a pattern in a string with another string. + * + * The pattern is taken literally and is no regular expression. + * Replaces at most replmax occurrences. + * + * The sstr_t.ptr of the resulting string must be freed manually. + * + * If allocation fails, the sstr_t.ptr of the return value is NULL. + * + * @param str the string where replacements should be applied + * @param pattern the pattern to search for + * @param replacement the replacement string + * @return the resulting string after applying the replacements + */ +#define sstrreplace(str, pattern, replacement) \ + scstrreplacen(SCSTR(str), SCSTR(pattern), SCSTR(replacement), SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_STRING_H */ diff --git a/ucx/ucx/test.h b/ucx/ucx/test.h new file mode 100644 index 0000000..b45b1d1 --- /dev/null +++ b/ucx/ucx/test.h @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + *
+ * UCX_TEST(function_name);
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * 
+ * + * **** IN SOURCE FILE: **** + *
+ * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ *   // tests with UCX_TEST_ASSERT()
+ * }
+ * 
+ * UCX_TEST(function_name) {
+ *   // memory allocation and other stuff here
+ *   #UCX_TEST_BEGIN
+ *   // tests with UCX_TEST_ASSERT() and/or
+ *   // calls with UCX_TEST_CALL_SUBROUTINE() here
+ *   #UCX_TEST_END
+ *   // cleanup of memory here
+ * }
+ * 
+ * + * Note: if a test fails, a longjump is performed + * back to the #UCX_TEST_BEGIN macro! + * + * Attention: Do not call own functions within a test, that use + * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE(). + * + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_TEST_H +#define UCX_TEST_H + +#include "ucx.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ + +/** + * Alias for the __func__ preprocessor macro. + * Some compilers use __func__ and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * __func__. + */ +#define __FUNCTION__ __func__ +#endif + +/** Type for the UcxTestSuite. */ +typedef struct UcxTestSuite UcxTestSuite; + +/** Pointer to a test function. */ +typedef void(*UcxTest)(UcxTestSuite*,FILE*); + +/** Type for the internal list of test cases. */ +typedef struct UcxTestList UcxTestList; + +/** Structure for the internal list of test cases. */ +struct UcxTestList { + + /** Test case. */ + UcxTest test; + + /** Pointer to the next list element. */ + UcxTestList *next; +}; + +/** + * A test suite containing multiple test cases. + */ +struct UcxTestSuite { + + /** The number of successful tests after the suite has been run. */ + unsigned int success; + + /** The number of failed tests after the suite has been run. */ + unsigned int failure; + + /** + * Internal list of test cases. + * Use ucx_test_register() to add tests to this list. + */ + UcxTestList *tests; +}; + +/** + * Creates a new test suite. + * @return a new test suite + */ +UcxTestSuite* ucx_test_suite_new(); + +/** + * Destroys a test suite. + * @param suite the test suite to destroy + */ +void ucx_test_suite_free(UcxTestSuite* suite); + +/** + * Registers a test function with the specified test suite. + * + * @param suite the suite, the test function shall be added to + * @param test the test function to register + * @return EXIT_SUCCESS on success or + * EXIT_FAILURE on failure + */ +int ucx_test_register(UcxTestSuite* suite, UcxTest test); + +/** + * Runs a test suite and writes the test log to the specified stream. + * @param suite the test suite to run + * @param outstream the stream the log shall be written to + */ +void ucx_test_run(UcxTestSuite* suite, FILE* outstream); + +/** + * Macro for a #UcxTest function header. + * + * Use this macro to declare and/or define a #UcxTest function. + * + * @param name the name of the test function + */ +#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_) + +/** + * Marks the begin of a test. + * Note: Any UCX_TEST_ASSERT() calls must be performed after + * #UCX_TEST_BEGIN. + * + * @see #UCX_TEST_END + */ +#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\ + fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ + fwrite("... ", 1, 4, _output_);\ + jmp_buf _env_; \ + if (!setjmp(_env_)) { + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition the condition to check + * @param message the message that shall be printed out on failure + */ +#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \ + fwrite(message".\n", 1, 2+strlen(message), _output_); \ + _suite_->failure++; \ + longjmp(_env_, 1);\ + } + +/** + * Macro for a test subroutine function header. + * + * Use this to declare and/or define a subroutine that can be called by using + * UCX_TEST_CALL_SUBROUTINE(). + * + * @param name the name of the subroutine + * @param ... the parameter list + * + * @see UCX_TEST_CALL_SUBROUTINE() + */ +#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\ + FILE *_output_, jmp_buf _env_, __VA_ARGS__) + +/** + * Macro for calling a test subroutine. + * + * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this + * macro. + * + * Note: You may only call subroutines within a #UCX_TEST_BEGIN- + * #UCX_TEST_END-block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see UCX_TEST_SUBROUTINE() + */ +#define UCX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_env_,__VA_ARGS__); + +/** + * Marks the end of a test. + * Note: Any UCX_TEST_ASSERT() calls must be performed before + * #UCX_TEST_END. + * + * @see #UCX_TEST_BEGIN + */ +#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;} + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_TEST_H */ + diff --git a/ucx/ucx/ucx.h b/ucx/ucx/ucx.h new file mode 100644 index 0000000..0944def --- /dev/null +++ b/ucx/ucx/ucx.h @@ -0,0 +1,204 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Main UCX Header providing most common definitions. + * + * @file ucx.h + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_H +#define UCX_H + +/** Major UCX version as integer constant. */ +#define UCX_VERSION_MAJOR 2 + +/** Minor UCX version as integer constant. */ +#define UCX_VERSION_MINOR 1 + +/** Version constant which ensures to increase monotonically. */ +#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) + +#include +#include + +#ifdef _WIN32 +#if !(defined __ssize_t_defined || defined _SSIZE_T_) +#include +typedef SSIZE_T ssize_t; +#define __ssize_t_defined +#define _SSIZE_T_ +#endif /* __ssize_t_defined and _SSIZE_T */ +#else /* !_WIN32 */ +#include +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * A function pointer to a destructor function. + * @see ucx_mempool_setdestr() + * @see ucx_mempool_regdestr() + */ +typedef void(*ucx_destructor)(void*); + +/** + * Function pointer to a compare function. + * + * The compare function shall take three arguments: the two values that shall be + * compared and optional additional data. + * The function shall then return -1 if the first argument is less than the + * second argument, 1 if the first argument is greater than the second argument + * and 0 if both arguments are equal. If the third argument is + * NULL, it shall be ignored. + */ +typedef int(*cmp_func)(const void*,const void*,void*); + +/** + * Function pointer to a distance function. + * + * The distance function shall take three arguments: the two values for which + * the distance shall be computed and optional additional data. + * The function shall then return the signed distance as integer value. + */ +typedef intmax_t(*distance_func)(const void*,const void*,void*); + +/** + * Function pointer to a copy function. + * + * The copy function shall create a copy of the first argument and may use + * additional data provided by the second argument. If the second argument is + * NULL, it shall be ignored. + + * Attention: if pointers returned by functions of this type may be + * passed to free() depends on the implementation of the + * respective copy_func. + */ +typedef void*(*copy_func)(const void*,void*); + +/** + * Function pointer to a write function. + * + * The signature of the write function shall be compatible to the signature + * of standard fwrite, though it may use arbitrary data types for + * source and destination. + * + * The arguments shall contain (in ascending order): a pointer to the source, + * the length of one element, the element count and a pointer to the + * destination. + */ +typedef size_t(*write_func)(const void*, size_t, size_t, void*); + +/** + * Function pointer to a read function. + * + * The signature of the read function shall be compatible to the signature + * of standard fread, though it may use arbitrary data types for + * source and destination. + * + * The arguments shall contain (in ascending order): a pointer to the + * destination, the length of one element, the element count and a pointer to + * the source. + */ +typedef size_t(*read_func)(void*, size_t, size_t, void*); + + + +#if __GNUC__ >= 5 || defined(__clang__) +#define UCX_MUL_BUILTIN + +#if __WORDSIZE == 32 +/** + * Alias for __builtin_umul_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umul_overflow(a, b, result) +#else /* __WORDSIZE != 32 */ +/** + * Alias for __builtin_umull_overflow. + * + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) __builtin_umull_overflow(a, b, result) +#endif /* __WORDSIZE */ + +#else /* no GNUC or clang bultin */ + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t, where the result should + * be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +#define ucx_szmul(a, b, result) ucx_szmul_impl(a, b, result) + +/** + * Performs a multiplication of size_t values and checks for overflow. + * + * This is a custom implementation in case there is no compiler builtin + * available. + * + * @param a first operand + * @param b second operand + * @param result a pointer to a size_t where the result should be stored + * @return zero, if no overflow occurred and the result is correct, non-zero + * otherwise + */ +int ucx_szmul_impl(size_t a, size_t b, size_t *result); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_H */ + diff --git a/ucx/ucx/utils.h b/ucx/ucx/utils.h new file mode 100644 index 0000000..642397a --- /dev/null +++ b/ucx/ucx/utils.h @@ -0,0 +1,508 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file utils.h + * + * Compare, copy and printf functions. + * + * @author Mike Becker + * @author Olaf Wintermann + */ + +#ifndef UCX_UTILS_H +#define UCX_UTILS_H + +#include "ucx.h" +#include "string.h" +#include "allocator.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Default buffer size for ucx_stream_copy() and ucx_stream_ncopy(). + */ +#define UCX_STREAM_COPY_BUFSIZE 4096 + +/** + * Copies a string. + * @param s the string to copy + * @param data omitted + * @return a pointer to a copy of s1 that can be passed to free(void*) + */ +void *ucx_strcpy(const void *s, void *data); + +/** + * Copies a memory area. + * @param m a pointer to the memory area + * @param n a pointer to the size_t containing the size of the memory area + * @return a pointer to a copy of the specified memory area that can + * be passed to free(void*) + */ +void *ucx_memcpy(const void *m, void *n); + + +/** + * Reads data from a stream and writes it to another stream. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, this is the size of the buffer that shall be + * implicitly created + * @param n the maximum number of bytes that shall be copied + * @return the total number of bytes copied + */ +size_t ucx_stream_bncopy(void *src, void *dest, read_func rfnc, write_func wfnc, + char* buf, size_t bufsize, size_t n); + +/** + * Shorthand for an unbounded ucx_stream_bncopy call using a default buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @return total number of bytes copied + * + * @see #UCX_STREAM_COPY_BUFSIZE + */ +#define ucx_stream_copy(src,dest,rfnc,wfnc) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + NULL, UCX_STREAM_COPY_BUFSIZE, (size_t)-1) + +/** + * Shorthand for ucx_stream_bncopy using a default copy buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param n maximum number of bytes that shall be copied + * @return total number of bytes copied + */ +#define ucx_stream_ncopy(src,dest,rfnc,wfnc, n) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + NULL, UCX_STREAM_COPY_BUFSIZE, n) + +/** + * Shorthand for an unbounded ucx_stream_bncopy call using the specified buffer. + * + * @param src the source stream + * @param dest the destination stream + * @param rfnc the read function + * @param wfnc the write function + * @param buf a pointer to the copy buffer or NULL if a buffer + * shall be implicitly created on the heap + * @param bufsize the size of the copy buffer - if NULL was + * provided for buf, this is the size of the buffer that shall be + * implicitly created + * @return total number of bytes copied + */ +#define ucx_stream_bcopy(src,dest,rfnc,wfnc, buf, bufsize) ucx_stream_bncopy(\ + src, dest, (read_func)rfnc, (write_func)wfnc, \ + buf, bufsize, (size_t)-1) + +/** + * Wraps the strcmp function. + * @param s1 string one + * @param s2 string two + * @param data omitted + * @return the result of strcmp(s1, s2) + */ +int ucx_cmp_str(const void *s1, const void *s2, void *data); + +/** + * Wraps the strncmp function. + * @param s1 string one + * @param s2 string two + * @param n a pointer to the size_t containing the third strncmp parameter + * @return the result of strncmp(s1, s2, *n) + */ +int ucx_cmp_strn(const void *s1, const void *s2, void *n); + +/** + * Wraps the sstrcmp function. + * @param s1 sstr one + * @param s2 sstr two + * @param data ignored + * @return the result of sstrcmp(s1, s2) + */ +int ucx_cmp_sstr(const void *s1, const void *s2, void *data); + +/** + * Compares two integers of type int. + * @param i1 pointer to integer one + * @param i2 pointer to integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type long int. + * @param i1 pointer to long integer one + * @param i2 pointer to long integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_longint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type long long. + * @param i1 pointer to long long one + * @param i2 pointer to long long two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_longlong(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int16_t. + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int16(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int32_t. + * @param i1 pointer to int32_t one + * @param i2 pointer to int32_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int32(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type int64_t. + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_int64(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned int. + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned long int. + * @param i1 pointer to unsigned long integer one + * @param i2 pointer to unsigned long integer two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_ulongint(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type unsigned long long. + * @param i1 pointer to unsigned long long one + * @param i2 pointer to unsigned long long two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint16_t. + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint16(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint32_t. + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint32(const void *i1, const void *i2, void *data); + +/** + * Compares two integers of type uint64_t. + * @param i1 pointer to uint64_t one + * @param i2 pointer to uint64_t two + * @param data omitted + * @return -1, if *i1 is less than *i2, 0 if both are equal, + * 1 if *i1 is greater than *i2 + */ +int ucx_cmp_uint64(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int. + * @param i1 pointer to integer one + * @param i2 pointer to integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type long int. + * @param i1 pointer to long integer one + * @param i2 pointer to long integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type long long. + * @param i1 pointer to long long one + * @param i2 pointer to long long two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int16_t. + * @param i1 pointer to int16_t one + * @param i2 pointer to int16_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int32_t. + * @param i1 pointer to int32_t one + * @param i2 pointer to int32_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type int64_t. + * @param i1 pointer to int64_t one + * @param i2 pointer to int64_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned int. + * @param i1 pointer to unsigned integer one + * @param i2 pointer to unsigned integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned long int. + * @param i1 pointer to unsigned long integer one + * @param i2 pointer to unsigned long integer two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type unsigned long long. + * @param i1 pointer to unsigned long long one + * @param i2 pointer to unsigned long long two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint16_t. + * @param i1 pointer to uint16_t one + * @param i2 pointer to uint16_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint32_t. + * @param i1 pointer to uint32_t one + * @param i2 pointer to uint32_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data); + +/** + * Distance function for integers of type uint64_t. + * @param i1 pointer to uint64_t one + * @param i2 pointer to uint64_t two + * @param data omitted + * @return i1 minus i2 + */ +intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data); + +/** + * Compares two real numbers of type float. + * @param f1 pointer to float one + * @param f2 pointer to float two + * @param data if provided: a pointer to precision (default: 1e-6f) + * @return -1, if *f1 is less than *f2, 0 if both are equal, + * 1 if *f1 is greater than *f2 + */ + +int ucx_cmp_float(const void *f1, const void *f2, void *data); + +/** + * Compares two real numbers of type double. + * @param d1 pointer to double one + * @param d2 pointer to double two + * @param data if provided: a pointer to precision (default: 1e-14) + * @return -1, if *d1 is less than *d2, 0 if both are equal, + * 1 if *d1 is greater than *d2 + */ +int ucx_cmp_double(const void *d1, const void *d2, void *data); + +/** + * Compares two pointers. + * @param ptr1 pointer one + * @param ptr2 pointer two + * @param data omitted + * @return -1 if ptr1 is less than ptr2, 0 if both are equal, + * 1 if ptr1 is greater than ptr2 + */ +int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data); + +/** + * Compares two memory areas. + * @param ptr1 pointer one + * @param ptr2 pointer two + * @param n a pointer to the size_t containing the third parameter for memcmp + * @return the result of memcmp(ptr1, ptr2, *n) + */ +int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n); + +/** + * A printf() like function which writes the output to a stream by + * using a write_func(). + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ... additional arguments + * @return the total number of bytes written + */ +int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...); + +/** + * va_list version of ucx_fprintf(). + * @param stream the stream the data is written to + * @param wfc the write function + * @param fmt format string + * @param ap argument list + * @return the total number of bytes written + * @see ucx_fprintf() + */ +int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap); + +/** + * A printf() like function which allocates space for a sstr_t + * the result is written to. + * + * Attention: The sstr_t data is allocated with the allocators + * ucx_allocator_malloc() function. So it is implementation dependent, if + * the returned sstr_t.ptr pointer must be passed to the allocators + * ucx_allocator_free() function manually. + * + * Note: The sstr_t.ptr of the return value will always be + * NULL-terminated. + * + * @param allocator the UcxAllocator used for allocating the result sstr_t + * @param fmt format string + * @param ... additional arguments + * @return a sstr_t containing the formatted string + */ +sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...); + +/** + * va_list version of ucx_asprintf(). + * + * @param allocator the UcxAllocator used for allocating the result sstr_t + * @param fmt format string + * @param ap argument list + * @return a sstr_t containing the formatted string + * @see ucx_asprintf() + */ +sstr_t ucx_vasprintf(UcxAllocator *allocator, const char *fmt, va_list ap); + +/** Shortcut for ucx_asprintf() with default allocator. */ +#define ucx_sprintf(...) \ + ucx_asprintf(ucx_default_allocator(), __VA_ARGS__) + +/** + * A printf() like function which writes the output to a + * UcxBuffer. + * + * @param buffer the buffer the data is written to + * @param ... format string and additional arguments + * @return the total number of bytes written + * @see ucx_fprintf() + */ +#define ucx_bprintf(buffer, ...) ucx_fprintf((UcxBuffer*)buffer, \ + (write_func)ucx_buffer_write, __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_UTILS_H */ + diff --git a/ucx/utils.c b/ucx/utils.c new file mode 100644 index 0000000..101c33f --- /dev/null +++ b/ucx/utils.c @@ -0,0 +1,448 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "ucx/utils.h" + +#include +#include +#include +#include + +/* COPY FUCNTIONS */ +void* ucx_strcpy(const void* s, void* data) { + const char *str = (const char*) s; + size_t n = 1+strlen(str); + char *cpy = (char*) malloc(n); + memcpy(cpy, str, n); + return cpy; +} + +void* ucx_memcpy(const void* m, void* n) { + size_t k = *((size_t*)n); + void *cpy = malloc(k); + memcpy(cpy, m, k); + return cpy; +} + +size_t ucx_stream_bncopy(void *src, void *dest, read_func readfnc, + write_func writefnc, char* buf, size_t bufsize, size_t n) { + if(n == 0 || bufsize == 0) { + return 0; + } + + char *lbuf; + size_t ncp = 0; + + if(buf) { + lbuf = buf; + } else { + lbuf = (char*)malloc(bufsize); + if(lbuf == NULL) { + return 0; + } + } + + size_t r; + size_t rn = bufsize > n ? n : bufsize; + while((r = readfnc(lbuf, 1, rn, src)) != 0) { + r = writefnc(lbuf, 1, r, dest); + ncp += r; + n -= r; + rn = bufsize > n ? n : bufsize; + if(r == 0 || n == 0) { + break; + } + } + + if (lbuf != buf) { + free(lbuf); + } + + return ncp; +} + +/* COMPARE FUNCTIONS */ + +int ucx_cmp_str(const void *s1, const void *s2, void *data) { + return strcmp((const char*)s1, (const char*)s2); +} + +int ucx_cmp_strn(const void *s1, const void *s2, void *n) { + return strncmp((const char*)s1, (const char*)s2, *((size_t*) n)); +} + +int ucx_cmp_sstr(const void *s1, const void *s2, void *data) { + sstr_t a = *(const sstr_t*) s1; + sstr_t b = *(const sstr_t*) s2; + return sstrcmp(a, b); +} + +int ucx_cmp_int(const void *i1, const void *i2, void *data) { + int a = *((const int*) i1); + int b = *((const int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longint(const void *i1, const void *i2, void *data) { + long int a = *((const long int*) i1); + long int b = *((const long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_longlong(const void *i1, const void *i2, void *data) { + long long a = *((const long long*) i1); + long long b = *((const long long*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int16(const void *i1, const void *i2, void *data) { + int16_t a = *((const int16_t*) i1); + int16_t b = *((const int16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int32(const void *i1, const void *i2, void *data) { + int32_t a = *((const int32_t*) i1); + int32_t b = *((const int32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_int64(const void *i1, const void *i2, void *data) { + int64_t a = *((const int64_t*) i1); + int64_t b = *((const int64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint(const void *i1, const void *i2, void *data) { + unsigned int a = *((const unsigned int*) i1); + unsigned int b = *((const unsigned int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulongint(const void *i1, const void *i2, void *data) { + unsigned long int a = *((const unsigned long int*) i1); + unsigned long int b = *((const unsigned long int*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ulonglong(const void *i1, const void *i2, void *data) { + unsigned long long a = *((const unsigned long long*) i1); + unsigned long long b = *((const unsigned long long*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint16(const void *i1, const void *i2, void *data) { + uint16_t a = *((const uint16_t*) i1); + uint16_t b = *((const uint16_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint32(const void *i1, const void *i2, void *data) { + uint32_t a = *((const uint32_t*) i1); + uint32_t b = *((const uint32_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_uint64(const void *i1, const void *i2, void *data) { + uint64_t a = *((const uint64_t*) i1); + uint64_t b = *((const uint64_t*) i2); + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +intmax_t ucx_dist_int(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int*) i1); + intmax_t b = *((const int*) i2); + return a - b; +} + +intmax_t ucx_dist_longint(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long int*) i1); + intmax_t b = *((const long int*) i2); + return a - b; +} + +intmax_t ucx_dist_longlong(const void *i1, const void *i2, void *data) { + intmax_t a = *((const long long*) i1); + intmax_t b = *((const long long*) i2); + return a - b; +} + +intmax_t ucx_dist_int16(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int16_t*) i1); + intmax_t b = *((const int16_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int32(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int32_t*) i1); + intmax_t b = *((const int32_t*) i2); + return a - b; +} + +intmax_t ucx_dist_int64(const void *i1, const void *i2, void *data) { + intmax_t a = *((const int64_t*) i1); + intmax_t b = *((const int64_t*) i2); + return a - b; +} + +intmax_t ucx_dist_uint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned int*) i1); + uintmax_t b = *((const unsigned int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulongint(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long int*) i1); + uintmax_t b = *((const unsigned long int*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_ulonglong(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const unsigned long long*) i1); + uintmax_t b = *((const unsigned long long*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint16(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint16_t*) i1); + uintmax_t b = *((const uint16_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint32(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint32_t*) i1); + uintmax_t b = *((const uint32_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +intmax_t ucx_dist_uint64(const void *i1, const void *i2, void *data) { + uintmax_t a = *((const uint64_t*) i1); + uintmax_t b = *((const uint64_t*) i2); + return a > b ? (intmax_t)(a - b) : -(intmax_t)(b - a); +} + +int ucx_cmp_float(const void *f1, const void *f2, void *epsilon) { + float a = *((const float*) f1); + float b = *((const float*) f2); + float e = !epsilon ? 1e-6f : *((float*)epsilon); + if (fabsf(a - b) < e) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_double(const void *d1, const void *d2, void *epsilon) { + double a = *((const double*) d1); + double b = *((const double*) d2); + double e = !epsilon ? 1e-14 : *((double*)epsilon); + if (fabs(a - b) < e) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int ucx_cmp_ptr(const void *ptr1, const void *ptr2, void *data) { + const intptr_t p1 = (const intptr_t) ptr1; + const intptr_t p2 = (const intptr_t) ptr2; + if (p1 == p2) { + return 0; + } else { + return p1 < p2 ? -1 : 1; + } +} + +int ucx_cmp_mem(const void *ptr1, const void *ptr2, void *n) { + return memcmp(ptr1, ptr2, *((size_t*)n)); +} + +/* PRINTF FUNCTIONS */ + +#ifdef va_copy +#define UCX_PRINTF_BUFSIZE 256 +#else +#pragma message("WARNING: C99 va_copy macro not supported by this platform" \ + " - limiting ucx_*printf to 2 KiB") +#define UCX_PRINTF_BUFSIZE 0x800 +#endif + +int ucx_fprintf(void *stream, write_func wfc, const char *fmt, ...) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = ucx_vfprintf(stream, wfc, fmt, ap); + va_end(ap); + return ret; +} + +int ucx_vfprintf(void *stream, write_func wfc, const char *fmt, va_list ap) { + char buf[UCX_PRINTF_BUFSIZE]; +#ifdef va_copy + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret < 0) { + return ret; + } else if (ret < UCX_PRINTF_BUFSIZE) { + return (int)wfc(buf, 1, ret, stream); + } else { + if (ret == INT_MAX) { + errno = ENOMEM; + return -1; + } + + int len = ret + 1; + char *newbuf = (char*)malloc(len); + if (!newbuf) { + return -1; + } + + ret = vsnprintf(newbuf, len, fmt, ap2); + if (ret > 0) { + ret = (int)wfc(newbuf, 1, ret, stream); + } + free(newbuf); + } + return ret; +#else + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret < 0) { + return ret; + } else if (ret < UCX_PRINTF_BUFSIZE) { + return (int)wfc(buf, 1, ret, stream); + } else { + errno = ENOMEM; + return -1; + } +#endif +} + +sstr_t ucx_asprintf(UcxAllocator *allocator, const char *fmt, ...) { + va_list ap; + sstr_t ret; + va_start(ap, fmt); + ret = ucx_vasprintf(allocator, fmt, ap); + va_end(ap); + return ret; +} + +sstr_t ucx_vasprintf(UcxAllocator *a, const char *fmt, va_list ap) { + sstr_t s; + s.ptr = NULL; + s.length = 0; + char buf[UCX_PRINTF_BUFSIZE]; +#ifdef va_copy + va_list ap2; + va_copy(ap2, ap); + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) { + s.ptr = (char*)almalloc(a, ret + 1); + if (s.ptr) { + s.length = (size_t)ret; + memcpy(s.ptr, buf, ret); + s.ptr[s.length] = '\0'; + } + } else if (ret == INT_MAX) { + errno = ENOMEM; + } else { + int len = ret + 1; + s.ptr = (char*)almalloc(a, len); + if (s.ptr) { + ret = vsnprintf(s.ptr, len, fmt, ap2); + if (ret < 0) { + free(s.ptr); + s.ptr = NULL; + } else { + s.length = (size_t)ret; + } + } + } +#else + int ret = vsnprintf(buf, UCX_PRINTF_BUFSIZE, fmt, ap); + if (ret > 0 && ret < UCX_PRINTF_BUFSIZE) { + s.ptr = (char*)almalloc(a, ret + 1); + if (s.ptr) { + s.length = (size_t)ret; + memcpy(s.ptr, buf, ret); + s.ptr[s.length] = '\0'; + } + } else { + errno = ENOMEM; + } +#endif + return s; +} -- 2.43.5