pax_global_header00006660000000000000000000000064143571746000014521gustar00rootroot0000000000000052 comment=be3ff20f260585242f2f5587bed3f2e6f9dc63cc mmlib-1.4.2/000077500000000000000000000000001435717460000126255ustar00rootroot00000000000000mmlib-1.4.2/.gitignore000066400000000000000000000011661435717460000146210ustar00rootroot00000000000000# autotools generated files aclocal.m4 Makefile.in configure config.h.in config.h.in~ config.log config.status config.h config.guess config.sub configure.scan install-sh missing stamp-h1 libtool compile Makefile Makefile.in .deps INSTALL ABOUT-NLS .dirstamp *.tar.xz autom4te.cache/* build-aux/* .libs/ # ignore build-directory if any /build* # C generated objects *.o *.la *.lo a.out # check generated objects *.log *.trs ipc-test-tmp-file-* # external tool files /cscope.* tags *.unc-backup~ *.unc-backup.md5~ *.uncrustify *.gcno coverage-results/ # usual tmp files *.swp # No core files **/core # nfs tmp files *.nfs* mmlib-1.4.2/CONTRIBUTING.md000066400000000000000000000030151435717460000150550ustar00rootroot00000000000000# How to contribute Contributors are essential. Here is some advice to help you help the project. ## Project objectives We try to keep mmlib as lean and fast as possible and to conform as close as possible to **POSIX**. ## Submitting pull requests We use gerrit for code review. If you are unfamiliar with it, please look at this [gerrit walkthrough](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html) Debian provides a package `git-review` that helps submitting git branches to gerrit for review. ### Coding style The core mmlib library code follows a coding style enforced by [uncrustify](https://github.com/uncrustify/uncrustify) with a configuration file written in tools/uncrustify.cfg You can also use the build targets `checkstyle` and `fixstyle` to do the check for you. ### Tests Please consider adding tests for your new features or that trigger the bug you are fixing. This will prevent a regression from being unnoticed. ### Code review Maintainers tend to be picky, and you might feel frustrated that your code (which is perfectly working in your use case) is not merged faster. Please don't be offended, and keep in mind that maintainers are concerned about code maintainability and readability, commit history (we use the history a lot, for example to find regressions or understand why certain decisions have been made), performances, integration, API consistency (so that someone who knows how to use mmlib will know how to use your code), etc. **Thanks for reading, happy hacking!** mmlib-1.4.2/LICENSE000066400000000000000000000261351435717460000136410ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mmlib-1.4.2/Makefile.am000066400000000000000000000006131435717460000146610ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 --install SUBDIRS = po src doc tests EXTRA_DIST = \ autogen.sh \ build-aux/config.rpath \ LICENSE \ TODO.md \ tools/coverage.sh \ $(NULL) test-coverage: $(srcdir)/tools/coverage.sh run clean-local: $(srcdir)/tools/coverage.sh clean checkstyle: $(MAKE) -C src $@ fixstyle: $(MAKE) -C src $@ spelling: $(MAKE) -C src $@ api-compat-test: $(MAKE) -C src $@ mmlib-1.4.2/README.md000066400000000000000000000023521435717460000141060ustar00rootroot00000000000000# Overview mmlib is an [Operating System abstraction layer][1]. It provides an API for application developer, so that they can write portable code using relatively advanced OS features without having to care about system specifics. The reference design in mind in its conception is **POSIX**, meaning that if some posix functionality is not available for given platform, mmlib will implement that same functionality itself. [1]: https://en.wikipedia.org/wiki/Operating_system_abstraction_layer ## Dependencies ### Tests Running the tests require the `check` framework. ### Documentation Generating the documentation requires `sphinx` and [linuxdoc](https://github.com/mmlabs-mindmaze/linuxdoc) ## Install mmlib supports meson and autotools build systems. ``` # autotools mkdir build && cd build ../autogen.sh ../configure --prefix= make make check # optional make install # meson meson build --prefix= cd build ninja ninja test # optional ninja install ``` ## License mmlib is free software under the terms of the Apache license 2.0. See LICENSE file. ## Authors and Contributors mmlib has been open-sourced by [MindMaze](https://www.mindmaze.com) and is maintained by Nicolas Bourdaud mmlib-1.4.2/TODO.md000066400000000000000000000005401435717460000137130ustar00rootroot00000000000000Cleaning before opening ======================= * change source URL once the new server is in place API breaking changes ==================== * remove deprecated MTX macros ABI non-breaking changes ======================== * Get volume metadata (size, remaining size, prefered block size...) * document how to generate def and import lib from dll mmlib-1.4.2/VERSION000066400000000000000000000000061435717460000136710ustar00rootroot000000000000001.4.2 mmlib-1.4.2/autogen.sh000077500000000000000000000002251435717460000146250ustar00rootroot00000000000000#!/bin/sh -e # Go the the package root folder (the one where this script is located) cd $(dirname $0) # Generate the build scripts autoreconf -fi mmlib-1.4.2/autoheader.yml000066400000000000000000000013541435717460000154740ustar00rootroot00000000000000headers: "@mindmaze_header@": | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. owner_remap: - pattern: .*@mindmaze.(ch|com) name: MindMaze Holding SA - pattern: .*@alice-bob.com name: Alice & Bob SAS mmlib-1.4.2/config/000077500000000000000000000000001435717460000140725ustar00rootroot00000000000000mmlib-1.4.2/config/.dummy000066400000000000000000000000001435717460000152140ustar00rootroot00000000000000mmlib-1.4.2/config/api-exports/000077500000000000000000000000001435717460000163455ustar00rootroot00000000000000mmlib-1.4.2/config/api-exports/meson.build000066400000000000000000000012401435717460000205040ustar00rootroot00000000000000if host_machine.system() == 'windows' config.set('API_EXPORTED', '__declspec(dllexport)') config.set('LOCAL_SYMBOL', '') config.set('API_EXPORTED_RELOCATABLE', '__declspec(dllexport)') config.set('DEPRECATED', '__attribute__ ((deprecated))') config.set('HOTSPOT', '__attribute__ ((hot))') else config.set('API_EXPORTED', '__attribute__ ((visibility ("protected")))') config.set('LOCAL_SYMBOL', '__attribute__ ((visibility ("hidden")))') config.set('API_EXPORTED_RELOCATABLE', '__attribute__ ((visibility ("default")))') config.set('DEPRECATED', '__attribute__ ((deprecated))') config.set('HOTSPOT', '__attribute__ ((hot))') endif mmlib-1.4.2/config/autools-compat/000077500000000000000000000000001435717460000170415ustar00rootroot00000000000000mmlib-1.4.2/config/autools-compat/meson.build000066400000000000000000000012721435717460000212050ustar00rootroot00000000000000if host_machine.system() == 'windows' add_project_arguments('-DEXEEXT=".exe"', language : 'c') config.set('LT_MODULE_EXT', '".dll"') else add_project_arguments('-DEXEEXT=""', language : 'c') config.set('LT_MODULE_EXT', '".so"') endif prefix = get_option('prefix') config.set_quoted('LOCALEDIR', prefix / get_option('localedir')) config.set_quoted('SYSCONFDIR', prefix / get_option('sysconfdir')) config.set_quoted('LIBEXECDIR', prefix / get_option('libexecdir') / 'mmlib') config.set_quoted('PACKAGE_NAME', meson.project_name()) config.set_quoted('PACKAGE_VERSION', meson.project_version()) config.set_quoted('PACKAGE_STRING', meson.project_name() + ' ' + meson.project_version()) mmlib-1.4.2/config/windows/000077500000000000000000000000001435717460000155645ustar00rootroot00000000000000mmlib-1.4.2/config/windows/meson.build000066400000000000000000000003371435717460000177310ustar00rootroot00000000000000# windows-specific defines add_project_arguments('-DWIN32_LEAN_AND_MEAN', language : 'c') add_project_arguments('-D_WIN32_WINNT=_WIN32_WINNT_WIN8', language : 'c') add_project_arguments('-Wl,-no-undefined', language : 'c') mmlib-1.4.2/configure.ac000066400000000000000000000116301435717460000151140ustar00rootroot00000000000000# @mindmaze_header@ # - If the library source code has changed at all since the last update, # then increment revision. # - If any interfaces have been added, removed, or changed since the last # update, increment current, and set revision to 0. # - If any interfaces have been added since the last public release, then # increment age. # - If any interfaces have been removed since the last public release, then # set age to 0. m4_define([lib_current],3) m4_define([lib_revision],2) m4_define([lib_age],2) # Setup autoconf AC_INIT(mmlib,m4_normalize(m4_include([VERSION])),[nicolas.bourdaud@gmail.com]) AC_CONFIG_SRCDIR([src/mmlog.h]) #AC_CONFIG_LIBOBJ_DIR([lib]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_HEADERS([config/config.h]) AC_REQUIRE_AUX_FILE([tap-driver.sh]) # Setup automake AM_INIT_AUTOMAKE([no-dist-gzip dist-xz color-tests subdir-objects parallel-tests foreign]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AC_CANONICAL_HOST AC_CANONICAL_BUILD # check for and set c11 standard AX_CHECK_COMPILE_FLAG([-std=c11], [AX_APPEND_FLAG([-std=c11])]) # use POSIX definitions up to POSIX.1-2008 standard AC_DEFINE([_POSIX_C_SOURCE], [200809L], [Strive to comply to POSIX-2008]) # ensure that the "default" definitions are provided # Note: when defined with _POSIX_C_SOURCE, POSIX sources are always preferred # in case of conflict AC_DEFINE([_DEFAULT_SOURCE], [1], [ensure that the default definitions are provided]) # for compatibility with old OS AC_DEFINE([_BSD_SOURCE], [1], [ensure that the default definitions are provided]) # Check for programs AM_PROG_CC_C_O LT_INIT([win32-dll disable-static]) LT_SYS_MODULE_EXT AC_SUBST(LIBTOOL_DEPS) AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION(0.19) # Optional build of 'check' based tests PKG_CHECK_MODULES_EXT(CHECK, [check >= 0.9.12], [has_check=yes; tap_in_check=yes], [PKG_CHECK_MODULES_EXT(CHECK, [check], [has_check=yes], [has_check=no])]) AC_ARG_WITH([check], AC_HELP_STRING([--with-check], [Build and run check based tests. @<:@default=detect@:>@]), [], [with_check=detect]) AS_CASE([$with_check], [yes], [AS_IF([test "$has_check" != no], [build_check_tests=yes], [AC_MSG_FAILURE([check required but not found])]) ], [no], [build_check_tests=no], [detect], [build_check_tests=$has_check], [AC_MSG_FAILURE([invalid argument for '--with-check' option])]) AM_CONDITIONAL(BUILD_CHECK_TESTS, [test "$build_check_tests" = yes]) AM_CONDITIONAL(TAP_SUPPORT_IN_CHECK, [test "x$tap_in_check" = xyes]) # Option to build win32 lockserver in mmlib dll (not as separated process) AC_ARG_ENABLE([lockserverprocess], AC_HELP_STRING([--disable-lockserverprocess], [Do not run win32 lockserver in separated process. @<:@default=enabled@:>@]), [], [enable_lockserverprocess=yes]) AM_CONDITIONAL([LOCKSERVER_IN_MMLIB_DLL], [test "x$enable_lockserverprocess" = xno]) AS_IF([test "x$enable_lockserverprocess" = xno], AC_DEFINE([LOCKSERVER_IN_MMLIB_DLL], [1], [Define to 1 if lockserver must be built in mmlib dll])) # Check for libraries AC_CHECK_FUNCS([posix_memalign aligned_alloc _aligned_malloc], [break]) AC_CHECK_FUNCS([copy_file_range]) MM_CHECK_LIB([pthread_create], [pthread], PTHREAD) MM_CHECK_FUNCS([pthread_mutex_consistent], [], [], [$PTHREAD_LIB]) MM_CHECK_LIB([clock_gettime], [rt], CLOCK) MM_CHECK_LIB([localtime64_s], [], LOCALTIME_S, [AC_DEFINE([HAS_LOCALTIME_S], [1], [Define if localtime_s is present])]) MM_CHECK_LIB([shm_open], [rt], SHM) MM_CHECK_LIB([dlopen], [dl], DL, [AC_DEFINE([HAVE_DLOPEN], [1], [define if dlopen() is available])]) AC_CHECK_HEADERS([alloca.h linux/fs.h]) AC_DEF_API_EXPORT_ATTRS AC_SET_HOSTSYSTEM MM_CC_WARNFLAGS # Test for sphinx doc with linuxdoc AC_ARG_ENABLE([sphinxdoc], AC_HELP_STRING([--enable-sphinxdoc], [Build sphinx documention. @<:@default=detect@:>@]), [], [enable_sphinxdoc=detect]) AM_PATH_PYTHON([3]) MM_PYTHON_MODULE([sphinx]) MM_PYTHON_MODULE([sphinx_rtd_theme]) MM_PYTHON_MODULE([linuxdoc]) AC_CHECK_PROG(HAVE_SPHINXBUILD, [sphinx-build], [$HAVE_PYMOD_LINUXDOC], [no]) AS_CASE([$enable_sphinxdoc], [yes], [AS_IF([test "x$HAVE_SPHINXBUILD" != xyes], [AC_MSG_ERROR(Cannot find sphinx or its plugin linuxdoc)])], [detect], [AS_IF([test "x$HAVE_SPHINXBUILD" = xyes], [enable_sphinxdoc=yes], [enable_sphinxdoc=no])]) AM_CONDITIONAL(BUILD_SPHINXDOC, [test "x$enable_sphinxdoc" = xyes]) AC_SUBST([optional_examples]) AC_SUBST([CURRENT],[lib_current]) AC_SUBST([REVISION],[lib_revision]) AC_SUBST([AGE],[lib_age]) AS_VAR_ARITH([lib_abi], [lib_current - lib_age]) AC_SUBST([ABI_VER], [$lib_abi]) # Add ifndef in config.h to fix pthread-win32 header mess AH_TOP([#ifndef CONFIG_H #define CONFIG_H]) AH_BOTTOM([#endif /*CONFIG_H*/]) AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile tests/Makefile tests/handmaid/Makefile po/Makefile.in]) AC_OUTPUT mmlib-1.4.2/debian/000077500000000000000000000000001435717460000140475ustar00rootroot00000000000000mmlib-1.4.2/debian/.gitignore000066400000000000000000000002251435717460000160360ustar00rootroot00000000000000*.debhelper.log autoreconf.after autoreconf.before debhelper-build-stamp *.substvars files libmmlib-dev/ libmmlib0-dbg/ libmmlib0/ .debhelper/ tmp/ mmlib-1.4.2/debian/changelog000066400000000000000000000163741435717460000157340ustar00rootroot00000000000000mmlib (1.4.2) unstable; urgency=medium * No display in help of value type of argparse option when it does accept value. -- Nicolas Bourdaud Tue, 10 Jan 2023 06:36:55 +0100 mmlib (1.4.1) unstable; urgency=medium * Fix meson script for gettext. * Fix library version when compiled with autotools -- Nicolas Bourdaud Sat, 28 May 2022 00:01:20 +0200 mmlib (1.4.0) unstable; urgency=medium * Repurpose blksize field of mm_stat to expose atime. * Add mm_futimens() and mm_utimens() -- Nicolas Bourdaud Tue, 17 May 2022 08:23:53 +0200 mmlib (1.3.0) unstable; urgency=medium * Add mm_copy(). * Fix cyg_pty detection in recent MSYS2. * update debian packaging: - compat 13 - add bindnow hardening - fix section and priority - add build-depends in symbols * Install html doc and examples in mmlib-doc: they were previously in libmmlib-dev. -- Nicolas Bourdaud Mon, 13 Sep 2021 12:18:39 +0200 mmlib (1.2.2) unstable; urgency=medium * fix missing file of mmlib-l10n -- Nicolas Bourdaud Wed, 11 Mar 2020 15:31:54 +0100 mmlib (1.2.1) unstable; urgency=medium * Remove skeleton type * Remove calibration related types * Remove geometry APIs * Add protocol parameter to mm_socket() * Replace struct timespec with own struct mm_timespec * mm_poll() - ignore negative file descriptors * move locales in its own package (mmlib-l10n) -- Nicolas Bourdaud Wed, 11 Mar 2020 12:34:34 +0100 mmlib (1.1.0) unstable; urgency=medium * Add mm_error_set_flags() API * Add mm_get_environ() API * change win32 symlink behavior * change win32 ENOMSG error code value * Add completion support in argparse * define NONNULL macro mmpredefs.h * Define MM_ENONAME error code * Add mm_getpeername() * Add mm_basename() and mm_dirname() * Add mm_rename() -- Nicolas Bourdaud Mon, 06 Jan 2020 12:07:46 +0100 mmlib (1.0.0) unstable; urgency=medium [ Nicolas Bourdaud ] * Add win32 support * remove licence from packaging * debug package is now generated automatically. Consequently, -dbg is now replaced by -dbgsym package [ Gabriel Ganne ] * debian compat changed from 9 to 10 [ Nicolas Bourdaud ] * New module version 1.0.0: - Add OS abstraction (file, socket, thread, process, memory map...) - Add global error reporting system - Add command line argument parsing - Add cross platform stack memory allocation * Update libmmlib0 symbols of the new module version -- Nicolas Bourdaud Tue, 30 Oct 2018 09:50:35 +0100 mmlib (0.3.4) unstable; urgency=medium * New module version 0.3.4: - Add sternoclavicular joints ids - Add MM_ECAMERROR errno -- Nicolas Bourdaud Tue, 16 May 2017 12:06:18 +0200 mmlib (0.3.3) unstable; urgency=medium * New module version 0.3.3: - Fix buffer overfow in mmlog -- Nicolas Bourdaud Sun, 06 Sep 2015 19:34:48 +0200 mmlib (0.3.2) unstable; urgency=medium * New module version 0.3.2: - Updates translation (It + Ge) -- Sebastien Lasserre Thu, 03 Sep 2015 09:17:01 +0200 mmlib (0.3.1) unstable; urgency=medium * New module version 0.3.1: - Updated translation -- Nicolas Bourdaud Wed, 05 Aug 2015 17:58:11 +0200 mmlib (0.3.0) unstable; urgency=medium * New module version 0.3.0: - Added labelled toc for profiling - Add skeleton joint name definition of new mindmaze standard * Update libmmlib0 symbols of the new module version -- Nicolas Bourdaud Fri, 12 Dec 2014 16:34:46 +0100 mmlib (0.2.2+1) unstable; urgency=medium * Really new module version 0.2.2: - Update error message for MM_ENOINERTIAL - Removes mmlicence manpage -- Nicolas Bourdaud Tue, 15 Jul 2014 03:28:34 +0200 mmlib (0.2.2) unstable; urgency=medium * Update error message for MM_ENOINERTIAL * Removes mmlicence manpage -- Sébastien Lasserre Mon, 14 Jul 2014 22:36:22 +0200 mmlib (0.2.1) unstable; urgency=medium * New module version 0.2.1: - Remove licence check and associated utils * drop mmlib-bin package -- Nicolas Bourdaud Wed, 09 Jul 2014 12:23:50 +0200 mmlib (0.2.0+2) unstable; urgency=medium * Add MM_ENOINERTIAL error code * Remove validity time check for authorization token check -- Nicolas Bourdaud Mon, 30 Jun 2014 14:12:00 +0200 mmlib (0.2.0+1) unstable; urgency=medium * Added C++11 compatibility patch for mmlog.h -- Nicolas Bourdaud Fri, 13 Jun 2014 10:10:09 +0200 mmlib (0.2.0) unstable; urgency=medium * New module version 0.2.0: - added new image descriptor - struct camera_calibration * Update libmmlib0 symbols of the new module version -- Nicolas Bourdaud Tue, 03 Jun 2014 18:27:39 +0200 mmlib (0.1.0) unstable; urgency=low * New module version 0.1.0: - Add profiling functions - Improve readibility of log * Update libmmlib0 symbols of the new module version * Turn compilation warnings into error * Update homepage -- Nicolas Bourdaud Mon, 03 Feb 2014 15:24:34 +0100 mmlib (0.0.3) testing; urgency=low * New module version: - Fix memory leak in read signature -- Nicolas Bourdaud Fri, 06 Sep 2013 12:46:39 +0200 mmlib (0.0.2) testing; urgency=low * New module version: - robustify build * Declare breakage of old libmmlib0 by libmmlib-dev * Add missing build-deps of gnutls-bin * Re-enable unit tests during package building -- Nicolas Bourdaud Mon, 02 Sep 2013 08:39:18 +0200 mmlib (0.0.1) testing; urgency=low * New module version: - Add license check * Add libgnutls-dev and ca-certificates-mindmaze as build-depends * Install predefined tokens with libmmlib0 * Install mmlicense in mmlib-bin package * Install development manpages in libmmlib-dev * Add dependency on pciutils * Disable unit test during package building -- Nicolas Bourdaud Fri, 30 Aug 2013 11:32:55 +0200 mmlib (0.0.0) testing; urgency=low * Added skeleton save/load * New error codes defined -- Nicolas Bourdaud Thu, 22 Aug 2013 08:26:35 +0200 mmlib (0.0.0~~experimental5) prealpha; urgency=low * Added skeleton function -- Nicolas Bourdaud Tue, 11 Jun 2013 15:28:07 +0200 mmlib (0.0.0~~experimental3) prealpha; urgency=low * Added angle axis - quaternion conversion -- Nicolas Bourdaud Fri, 15 Mar 2013 18:40:52 +0100 mmlib (0.0.0~~experimental2) prealpha; urgency=low * new error codes (for database connection) -- Nicolas Bourdaud Fri, 01 Mar 2013 10:38:50 +0100 mmlib (0.0.0~~experimental) prealpha; urgency=low * Initial debian package. -- Nicolas Bourdaud Thu, 24 Jan 2013 10:22:18 +0100 mmlib-1.4.2/debian/control000066400000000000000000000046061435717460000154600ustar00rootroot00000000000000Source: mmlib Section: libs Priority: optional Maintainer: Nicolas Bourdaud Build-Depends: debhelper-compat (= 13), meson, gettext, check, python3-sphinx, python3-sphinx-rtd-theme, python3-sphinx-linuxdoc Rules-Requires-Root: no Standards-Version: 4.5.1 Vcs-Git: https://review.gerrithub.io/mmlabs-mindmaze/mmlib Vcs-Browser: https://github.com/mmlabs-mindmaze/mmlib Homepage: https://github.com/mmlabs-mindmaze/mmlib Package: libmmlib1 Architecture: any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} Recommends: mmlib-l10n Description: mmlib library (shared lib) mmlib is the general purpose library providing helper functions to other module. It provides a cross-platform OS abstaction layer as well as facilities like logging or error reporting. . This package contains the shared library Package: libmmlib-dev Section: libdevel Architecture: any Multi-Arch: same Breaks: libmmlib0 (<< 0.0.1) Depends: libmmlib1 (= ${binary:Version}), ${misc:Depends} Recommends: mmlib-doc Description: mmlib library (Development files) mmlib is the general purpose library providing helper functions to other module. It provides a cross-platform OS abstaction layer as well as facilities like logging or error reporting. . This package contains the files needed to compile and link programs which use mmlib. The manpages are shipped in this package. Package: mmlib-doc Section: doc Architecture: all Multi-Arch: foreign Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends}, ${sphinxdoc:Depends} Built-Using: ${sphinxdoc:Built-Using} Breaks: libmmlib-dev (<< 1.3.0) Replaces: libmmlib-dev (<< 1.3.0) Description: mmlib documentation mmlib is the general purpose library providing helper functions to other module. It provides a cross-platform OS abstaction layer as well as facilities like logging or error reporting. . This package contains the html doc and examples of mmlib. Package: mmlib-l10n Section: localization Architecture: all Multi-Arch: foreign Pre-Depends: ${misc:Pre-Depends} Depends: ${misc:Depends} Replaces: libmmlib0 Breaks: libmmlib0 Description: mmlib library (translations) mmlib is the general purpose library providing helper functions to other module. It provides a cross-platform OS abstaction layer as well as facilities like logging or error reporting. . This package contains the localization components of mmlib. mmlib-1.4.2/debian/copyright000066400000000000000000000017411435717460000160050ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: mmlib Upstream-Contact: Nicolas Bourdaud Source: https://github.com/mmlabs-mindmaze/mmlib Files: * License: Apache-2.0 Copyright: Copyright (C) 2012-2019 MindMaze SA License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the full text of the Apache Software License version 2 can be found in the file `/usr/share/common-licenses/Apache-2.0'. mmlib-1.4.2/debian/gbp.conf000066400000000000000000000000371435717460000154660ustar00rootroot00000000000000[DEFAULT] debian-branch=debian mmlib-1.4.2/debian/libmmlib-dev.install000066400000000000000000000001171435717460000200010ustar00rootroot00000000000000/usr/include /usr/lib/*/libmmlib.so /usr/lib/*/pkgconfig /usr/share/man/man3/* mmlib-1.4.2/debian/libmmlib1.install000066400000000000000000000000311435717460000173010ustar00rootroot00000000000000/usr/lib/*/libmmlib.so.* mmlib-1.4.2/debian/libmmlib1.symbols000066400000000000000000000102331435717460000173300ustar00rootroot00000000000000libmmlib.so.1 libmmlib1 #MINVER# * Build-Depends-Package: libmmlib-dev MMLIB_1.0@MMLIB_1.0 1.2.0 _mm_freea_on_heap@MMLIB_1.0 1.2.0 _mm_malloca_on_heap@MMLIB_1.0 1.2.0 mm_accept@MMLIB_1.0 1.2.0 mm_aligned_alloc@MMLIB_1.0 1.2.0 mm_aligned_free@MMLIB_1.0 1.2.0 mm_anon_shm@MMLIB_1.0 1.2.0 mm_arg_complete_path@MMLIB_1.0 1.2.0 mm_arg_is_completing@MMLIB_1.0 1.2.0 mm_arg_optv_parse@MMLIB_1.0 1.2.0 mm_arg_parse@MMLIB_1.0 1.2.0 mm_arg_parse_complete@MMLIB_1.0 1.2.0 mm_basename@MMLIB_1.0 1.2.0 mm_bind@MMLIB_1.0 1.2.0 mm_chdir@MMLIB_1.0 1.2.0 mm_check_access@MMLIB_1.0 1.2.0 mm_close@MMLIB_1.0 1.2.0 mm_closedir@MMLIB_1.0 1.2.0 mm_connect@MMLIB_1.0 1.2.0 mm_copy@MMLIB_1.0 1.3.0 mm_create_sockclient@MMLIB_1.0 1.2.0 mm_dirname@MMLIB_1.0 1.2.0 mm_dl_fileext@MMLIB_1.0 1.2.0 mm_dlclose@MMLIB_1.0 1.2.0 mm_dlopen@MMLIB_1.0 1.2.0 mm_dlsym@MMLIB_1.0 1.2.0 mm_dup2@MMLIB_1.0 1.2.0 mm_dup@MMLIB_1.0 1.2.0 mm_error_set_flags@MMLIB_1.0 1.2.0 mm_execv@MMLIB_1.0 1.2.0 mm_freeaddrinfo@MMLIB_1.0 1.2.0 mm_fstat@MMLIB_1.0 1.2.0 mm_fsync@MMLIB_1.0 1.2.0 mm_ftruncate@MMLIB_1.0 1.2.0 mm_futimens@MMLIB_1.0 1.4.0 mm_get_basedir@MMLIB_1.0 1.2.0 mm_get_environ@MMLIB_1.0 1.2.0 mm_get_lasterror_desc@MMLIB_1.0 1.2.0 mm_get_lasterror_extid@MMLIB_1.0 1.2.0 mm_get_lasterror_location@MMLIB_1.0 1.2.0 mm_get_lasterror_module@MMLIB_1.0 1.2.0 mm_get_lasterror_number@MMLIB_1.0 1.2.0 mm_getaddrinfo@MMLIB_1.0 1.2.0 mm_getcwd@MMLIB_1.0 1.2.0 mm_getenv@MMLIB_1.0 1.2.0 mm_getnameinfo@MMLIB_1.0 1.2.0 mm_getpeername@MMLIB_1.0 1.2.0 mm_getres@MMLIB_1.0 1.2.0 mm_getsockname@MMLIB_1.0 1.2.0 mm_getsockopt@MMLIB_1.0 1.2.0 mm_gettime@MMLIB_1.0 1.2.0 mm_ipc_connect@MMLIB_1.0 1.2.0 mm_ipc_connected_pair@MMLIB_1.0 1.2.0 mm_ipc_recvmsg@MMLIB_1.0 1.2.0 mm_ipc_sendmsg@MMLIB_1.0 1.2.0 mm_ipc_srv_accept@MMLIB_1.0 1.2.0 mm_ipc_srv_create@MMLIB_1.0 1.2.0 mm_ipc_srv_destroy@MMLIB_1.0 1.2.0 mm_isatty@MMLIB_1.0 1.2.0 mm_link@MMLIB_1.0 1.2.0 mm_listen@MMLIB_1.0 1.2.0 mm_log@MMLIB_1.0 1.2.0 mm_log_set_maxlvl@MMLIB_1.0 1.2.0 mm_mapfile@MMLIB_1.0 1.2.0 mm_mkdir@MMLIB_1.0 1.2.0 mm_nanosleep@MMLIB_1.0 1.2.0 mm_open@MMLIB_1.0 1.2.0 mm_opendir@MMLIB_1.0 1.2.0 mm_path_from_basedir@MMLIB_1.0 1.2.0 mm_pipe@MMLIB_1.0 1.2.0 mm_poll@MMLIB_1.0 1.2.0 mm_print_lasterror@MMLIB_1.0 1.2.0 mm_profile_get_data@MMLIB_1.0 1.2.0 mm_profile_print@MMLIB_1.0 1.2.0 mm_profile_reset@MMLIB_1.0 1.2.0 mm_raise_error_full@MMLIB_1.0 1.2.0 mm_raise_error_vfull@MMLIB_1.0 1.2.0 mm_raise_from_errno_full@MMLIB_1.0 1.2.0 mm_read@MMLIB_1.0 1.2.0 mm_readdir@MMLIB_1.0 1.2.0 mm_readlink@MMLIB_1.0 1.2.0 mm_recv@MMLIB_1.0 1.2.0 mm_recv_multimsg@MMLIB_1.0 1.2.0 mm_recvmsg@MMLIB_1.0 1.2.0 mm_relative_sleep_ms@MMLIB_1.0 1.2.0 mm_relative_sleep_ns@MMLIB_1.0 1.2.0 mm_relative_sleep_us@MMLIB_1.0 1.2.0 mm_remove@MMLIB_1.0 1.2.0 mm_rename@MMLIB_1.0 1.2.0 mm_rewinddir@MMLIB_1.0 1.2.0 mm_rmdir@MMLIB_1.0 1.2.0 mm_save_errorstate@MMLIB_1.0 1.2.0 mm_seek@MMLIB_1.0 1.2.0 mm_send@MMLIB_1.0 1.2.0 mm_send_multimsg@MMLIB_1.0 1.2.0 mm_sendmsg@MMLIB_1.0 1.2.0 mm_set_errorstate@MMLIB_1.0 1.2.0 mm_setenv@MMLIB_1.0 1.2.0 mm_setsockopt@MMLIB_1.0 1.2.0 mm_shm_open@MMLIB_1.0 1.2.0 mm_shm_unlink@MMLIB_1.0 1.2.0 mm_shutdown@MMLIB_1.0 1.2.0 mm_socket@MMLIB_1.0 1.2.0 mm_spawn@MMLIB_1.0 1.2.0 mm_stat@MMLIB_1.0 1.4.0 mm_strerror@MMLIB_1.0 1.2.0 mm_strerror_r@MMLIB_1.0 1.2.0 mm_symlink@MMLIB_1.0 1.2.0 mm_thr_cond_broadcast@MMLIB_1.0 1.2.0 mm_thr_cond_deinit@MMLIB_1.0 1.2.0 mm_thr_cond_init@MMLIB_1.0 1.2.0 mm_thr_cond_signal@MMLIB_1.0 1.2.0 mm_thr_cond_timedwait@MMLIB_1.0 1.2.0 mm_thr_cond_wait@MMLIB_1.0 1.2.0 mm_thr_create@MMLIB_1.0 1.2.0 mm_thr_detach@MMLIB_1.0 1.2.0 mm_thr_join@MMLIB_1.0 1.2.0 mm_thr_mutex_consistent@MMLIB_1.0 1.2.0 mm_thr_mutex_deinit@MMLIB_1.0 1.2.0 mm_thr_mutex_init@MMLIB_1.0 1.2.0 mm_thr_mutex_lock@MMLIB_1.0 1.2.0 mm_thr_mutex_trylock@MMLIB_1.0 1.2.0 mm_thr_mutex_unlock@MMLIB_1.0 1.2.0 mm_thr_once@MMLIB_1.0 1.2.0 mm_thr_self@MMLIB_1.0 1.2.0 mm_tic@MMLIB_1.0 1.2.0 mm_toc@MMLIB_1.0 1.2.0 mm_toc_label@MMLIB_1.0 1.2.0 mm_unlink@MMLIB_1.0 1.2.0 mm_unmap@MMLIB_1.0 1.2.0 mm_unsetenv@MMLIB_1.0 1.2.0 mm_utimens@MMLIB_1.0 1.4.0 mm_wait_process@MMLIB_1.0 1.2.0 mm_write@MMLIB_1.0 1.2.0 mmlib-1.4.2/debian/mmlib-doc.doc-base000066400000000000000000000004031435717460000173060ustar00rootroot00000000000000Document: mmlib Title: MMLib Manual Author: Nicolas Bourdaud Abstract: This manual describes what mmlib is, and how it can be used. Section: Programming Format: HTML Index: /usr/share/doc/mmlib-doc/html/index.html Files: /usr/share/doc/mmlib-doc/html/*.html mmlib-1.4.2/debian/mmlib-doc.docs000066400000000000000000000000721435717460000165630ustar00rootroot00000000000000/usr/share/doc/mmlib/html/ /usr/share/doc/mmlib/examples/ mmlib-1.4.2/debian/mmlib-l10n.install000066400000000000000000000000221435717460000173010ustar00rootroot00000000000000/usr/share/locale mmlib-1.4.2/debian/rules000077500000000000000000000006561435717460000151360ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 export DEB_BUILD_MAINT_OPTIONS=hardening=+bindnow ifeq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) export DEB_CFLAGS_MAINT_APPEND=-O3 endif export DEB_LDFLAGS_MAINT_APPEND=-Wl,-Bsymbolic-functions %: dh $@ --with sphinxdoc --buildsystem=meson override_dh_autoreconf: override_dh_compress: dh_compress --exclude=examples mmlib-1.4.2/debian/source/000077500000000000000000000000001435717460000153475ustar00rootroot00000000000000mmlib-1.4.2/debian/source/format000066400000000000000000000000151435717460000165560ustar00rootroot000000000000003.0 (native) mmlib-1.4.2/doc/000077500000000000000000000000001435717460000133725ustar00rootroot00000000000000mmlib-1.4.2/doc/.gitignore000066400000000000000000000002351435717460000153620ustar00rootroot00000000000000# ignore example binaries if compiled in-place multithread pshared-child pshared-parent parse_args html/ man/ .doctrees/ manpages-buildstamp html-buildstamp mmlib-1.4.2/doc/Makefile.am000066400000000000000000000076561435717460000154440ustar00rootroot00000000000000eol= AUTOMAKE_OPTIONS = -Wno-portability LDADD = $(top_builddir)/src/libmmlib.la AM_CPPFLAGS = \ -I$(top_srcdir)/src \ $(eol) AM_CFLAGS = $(CHECK_CFLAGS) $(MM_WARNFLAGS) check_PROGRAMS = \ multithread \ pshared-parent \ pshared-child \ parse_args \ $(eol) multithread_SOURCES = \ examples/multithread.c \ $(eol) pshared_parent_SOURCES = \ examples/pshared-common.h \ examples/pshared-parent.c \ $(eol) pshared_child_SOURCES = \ examples/pshared-child.c \ $(eol) parse_args_SOURCES = \ examples/parse_args.c \ $(eol) nobase_dist_doc_DATA = \ $(multithread_SOURCES) \ $(pshared_parent_SOURCES) \ $(pshared_child_SOURCES) \ $(parse_args_SOURCES) \ examples/completion_parse_args \ $(eol) OVERRIDING_MANPAGES = \ mm_log_debug.3 \ mm_log_fatal.3 \ mm_log_warn.3 \ mm_toc.3 \ mm_log_error.3 \ mm_log_info.3 \ mm_tic.3 \ mm_toc_label.3 \ $(eol) DOC_SRCS = \ alloc.rst \ argparse.rst \ design.rst \ dlfcn.rst \ env.rst \ error.rst \ filesystem.rst \ index.rst \ ipc.rst \ log.rst \ process.rst \ profiling.rst \ socket.rst \ thread.rst \ time.rst \ $(eol) EXTRA_DIST = \ conf.py \ $(OVERRIDING_MANPAGES) \ $(DOC_SRCS) \ $(eol) export srctree=$(SRCTREE) # # Sphinx documentation build targets # Skipped if --enable-sphinxdoc=no # if BUILD_SPHINXDOC SPHINXBUILD = sphinx-build SRCTREE = $(top_srcdir) sphinx_verbose = $(sphinx_verbose_@AM_V@) sphinx_verbose_ = $(sphinx_verbose_@AM_DEFAULT_V@) sphinx_verbose_0 = -q ALLSPHINXOPTS = -d $(builddir)/.doctrees $(sphinx_verbose) $(srcdir) # Due to a dependency bug in sphinx-build, we use the -M option instead of # the usual -b one. This hidden option sums up to the same as -b with makefile # dependency support ... except that it does not support -W option which turns # warnings into errors ! # => do this with a simple grep line on the output # Since html target depends on man target, this is only needed once sphinx-build.log: $(wildcard $(SRCTREE)/src/*.[ch]) $(DOC_SRCS) @$(RM) -f manpages-buildstamp $(AM_V_GEN) $(SPHINXBUILD) -M kernel-doc-man $(ALLSPHINXOPTS) $(builddir)/man &> sphinx-build.log # sphinx-build.log target does the doc generation # This target checks that it was done right manpages-buildstamp: sphinx-build.log @grep -q WARNING: sphinx-build.log && \ echo "warnings issued during documentation build. See sphinx-build.log." || true @echo @echo "Build finished. The man pages are in $(builddir)/man." @touch $@ .PHONY: man-pages man-pages: manpages-buildstamp html-local: html-buildstamp # always generate html after man: parallel sphinx invocation is not upported html-buildstamp: $(wildcard $(SRCTREE)/src/*.[ch]) $(DOC_SRCS) manpages-buildstamp @$(RM) -f $@ $(AM_V_GEN) $(SPHINXBUILD) -M html $(ALLSPHINXOPTS) $(builddir)/html &> sphinx-build-html.log @echo @echo "Build finished. The HTML pages are in $(builddir)/html." @touch $@ install-html: html-buildstamp @mkdir -p $(DESTDIR)$(docdir)/html/ cd $(builddir)/html && \ find . -type f -exec $(install_sh_DATA) '{}' $(DESTDIR)$(docdir)/html/'{}' \; uninstall-html: $(RM) -r $(DESTDIR)$(docdir)/html/ install-man3: manpages-buildstamp @mkdir -p $(DESTDIR)$(mandir)/man3 $(foreach manpage, $(notdir $(wildcard $(builddir)/man/*.3)), \ $(install_sh_DATA) $(builddir)/man/$(manpage) $(DESTDIR)$(mandir)/man3/$(manpage);) $(foreach manpage, $(OVERRIDING_MANPAGES), \ $(install_sh_DATA) $(srcdir)/$(manpage) $(DESTDIR)$(mandir)/man3/$(manpage);) uninstall-man3: manpages-buildstamp $(foreach manpage, $(notdir $(wildcard $(builddir)/man/*.3)), \ $(RM) $(DESTDIR)$(mandir)/man3/$(manpage);) $(foreach manpage, $(OVERRIDING_MANPAGES), \ $(RM) $(DESTDIR)$(mandir)/man3/$(manpage);) @-rmdir $(mandir)/man3 all-local: manpages-buildstamp html-buildstamp install-data-local: install-man3 install-html uninstall-local: uninstall-man3 uninstall-html endif # BUILD_SPHINXDOC clean-local: $(RM) -rf html .doctrees man manpages-buildstamp html-buildstamp sphinx-build.log sphinx-build-html.log mmlib-1.4.2/doc/alloc.rst000066400000000000000000000004141435717460000152150ustar00rootroot00000000000000Allocation ========== .. kernel-doc:: src/alloc.c :module: alloc :headers: mmlib.h :functions: mm_aligned_alloc, mm_aligned_free .. kernel-doc:: src/mmlib.h :module: alloc :headers: mmlib.h :functions: mm_aligned_alloca, mm_malloca, mm_freea mmlib-1.4.2/doc/argparse.rst000066400000000000000000000003241435717460000157270ustar00rootroot00000000000000Argument parsing ================ .. kernel-doc:: src/mmargparse.h :module: argparse :headers: mmargparse.h .. kernel-doc:: src/argparse.c :module: argparse :headers: mmargparse.h :export: mmlib-1.4.2/doc/conf.py000066400000000000000000000264611435717460000147020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # mmlib documentation build configuration file, created by # sphinx-quickstart on Tue Nov 14 11:46:19 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # #import os #import sys #sys.path.insert(0, os.path.abspath('../scripts')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. import linuxdoc extensions = [ 'sphinx.ext.todo', 'linuxdoc.rstFlatTable', # Implementation of the 'flat-table' reST-directive. 'linuxdoc.rstKernelDoc', # Implementation of the 'kernel-doc' reST-directive. 'linuxdoc.kernel_include', # Implementation of the 'kernel-include' reST-directive. 'linuxdoc.manKernelDoc', # Implementation of the 'kernel-doc-man' builder 'linuxdoc.cdomain', # Replacement for the sphinx c-domain. ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'mmlib' copyright = u'2019, Apache 2.0' author = u'nicolas.bourdaud@mindmaze.ch' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = open('../VERSION').read() # The full version, including alpha/beta/rc tags. release = open('../VERSION').read() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False primary_domain = 'c' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'mmlib v0.0.3' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'mmlibdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'mmlib.tex', u'mmlib Documentation', u'Nicolas Bourdaud', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'mmlib', u'mmlib Documentation', author, 'mmlib', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False kernel_doc_exp_method = 'attribute' kernel_doc_exp_ids = 'API_EXPORTED API_EXPORTED_RELOCATABLE' kernel_doc_known_attrs = 'LOCAL_SYMBOL MMLIB_API noreturn' kernel_doc_mansect = 3 # In nickpick mode, it will complain about lots of missing references that # # 1) are just typedefs like: bool, __u32, etc; # 2) It will complain for things like: enum, NULL; # 3) It will complain for symbols that should be on different # books (but currently aren't ported to ReST) # # The list below has a list of such symbols to be ignored in nitpick mode # nitpick_ignore = [ ("c:type", "bool"), ("c:type", "enum"), # stdint declarations ("c:type", "int8_t"), ("c:type", "uint8_t"), ("c:type", "int16_t"), ("c:type", "uint16_t"), ("c:type", "int32_t"), ("c:type", "uint32_t"), ("c:type", "int64_t"), ("c:type", "uint64_t"), # some complex types and structures ("c:type", "socklen_t"), ("c:type", "sockaddr"), ("c:type", "addrinfo"), ("c:type", "iovec"), ("c:type", "timespec"), # ignore warnings about variadic arguments ("c:type", "ellipsis"), # ... ("c:type", "va_list"), # ignore warnings about some unknown functions ("c:func", "poll"), ("c:func", "getaddrinfo"), ("c:func", "getnamedinfo"), ("c:func", "getsockname"), ] mmlib-1.4.2/doc/design.rst000066400000000000000000000007471435717460000154050ustar00rootroot00000000000000About POSIX =========== The 2018 edition of **POSIX** is hosted by the IEEE can be found on `the IEEE website`__ .. __: https://ieeexplore.ieee.org/document/8277153/ Since **POSIX** has been merged into Open Goup, it can also be browsed for free `from the Open Group website``__ .. __: http://pubs.opengroup.org/onlinepubs/9699919799/ Switch to `General Concepts`__ for an overview of **POSIX** .. __: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04 mmlib-1.4.2/doc/dlfcn.rst000066400000000000000000000002431435717460000152110ustar00rootroot00000000000000Dynamic shared object handling ============================== .. kernel-doc:: src/dlfcn.c :no-header: :module: dlfcn :headers: mmdlfcn.h :export: mmlib-1.4.2/doc/env.rst000066400000000000000000000003161435717460000147140ustar00rootroot00000000000000Environment manipulation ======================== .. kernel-doc:: src/mmlib.h :module: env :functions: mm_known_dir .. kernel-doc:: src/utils.c :module: env :headers: mmlib.h :export: mmlib-1.4.2/doc/error.rst000066400000000000000000000003171435717460000152560ustar00rootroot00000000000000Error reporting =============== .. kernel-doc:: src/mmerrno.h :module: error :headers: mmerrno.h .. kernel-doc:: src/error.c :module: error :no-header: :headers: mmerrno.h :export: mmlib-1.4.2/doc/examples.rst000066400000000000000000000010351435717460000157410ustar00rootroot00000000000000multithread =========== .. literalinclude:: examples/multithread.c :language: c :linenos: parse_args =========== .. literalinclude:: examples/parse_args.c :language: c :linenos: pshared =========== pshared-common.h ---------------- .. literalinclude:: examples/pshared-common.h :language: c :linenos: pshared-child.h --------------- .. literalinclude:: examples/pshared-child.c :language: c :linenos: pshared-parent.h ---------------- .. literalinclude:: examples/pshared-parent.c :language: c :linenos: mmlib-1.4.2/doc/examples/000077500000000000000000000000001435717460000152105ustar00rootroot00000000000000mmlib-1.4.2/doc/examples/completion_parse_args000066400000000000000000000022171435717460000215140ustar00rootroot00000000000000# @mindmaze_header@ # # vi:syntax=sh # # Example of bash completion script for mmlib based argument parsing (to be # sourced) # _parse_args_completion() { local cur=${COMP_WORDS[COMP_CWORD]} local prev=${COMP_WORDS[COMP_CWORD-1]} # If completion is done on empty word AND we are not completing long option # value, then we invoked the command with an explicit "" final arg # Without, the completion would have been done on previous arg if [[ "$cur" == "" && "$prev" != "=" ]]; then COMPREPLY=($(MMLIB_CMD_COMPLETION=yes $COMP_LINE "" 2>/dev/null)) else COMPREPLY=($(MMLIB_CMD_COMPLETION=yes $COMP_LINE 2>/dev/null)) fi # if there is only one possibility, do not add space if string finishes # by '='. In such a case, we are completing the value of a long # option... Same if it finishes with '/': we are completing a path # Hence we test that number of elements in COMPREPLY is 1 and that the # first element of COMPREPLY ends with '=' or '/' if [[ ${#COMPREPLY[@]} -eq 1 && ${COMPREPLY[0]} == *[=/] ]]; then compopt -o nospace fi } complete -F _parse_args_completion parse_args mmlib-1.4.2/doc/examples/multithread.c000066400000000000000000000070361435717460000177040ustar00rootroot00000000000000/* @mindmaze_header@ */ /* multithreaded data write * * example of child process, to be used by pshared-parent.c example. * * This program writes to a shared text (in shared memory) concurrently with * other threads in the same process. When notified, a worker thread tries * to write its identification string ("|-thread-X+|") onto a text field of * the shared memory. The text after update of several worker threads looks * something like: * * ...|+thread-Z+||+thread-W+||+thread-X+||+thread-Y+|... * * This file demonstrates how to: * - map file into memory * - use process shared mutex */ #include #include #include #include #define NUM_THREAD 6 #define MAX_ID_LEN 16 struct shared_data { mm_thr_mutex_t mutex; int len; char text[1024]; mm_thr_mutex_t notif_mtx; mm_thr_cond_t notif_cond; int start; }; struct thread_data { struct shared_data* shdata; char id_str[MAX_ID_LEN]; }; /* * This function do the update of the shared text. It happens the * string |+@id_str+| to the text field in @psh_data. */ static void write_shared_data(struct shared_data* shdata, const char* id_str) { int id_str_len = strlen(id_str); // Get the shared lock. Since we are using a normal mutex, we do not // have to check the return value mm_thr_mutex_lock(&shdata->mutex); // Add "|+" in the text shdata->text[shdata->len++] = '|'; shdata->text[shdata->len++] = '+'; // Append process identifier on text memcpy(shdata->text + shdata->len, id_str, id_str_len); shdata->len += id_str_len; // Add "+|" in the text shdata->text[shdata->len++] = '+'; shdata->text[shdata->len++] = '|'; mm_thr_mutex_unlock(&shdata->mutex); } static void wait_start_notification(struct shared_data* shdata) { mm_thr_mutex_lock(&shdata->notif_mtx); // A while loop is necessary, because a spurious wakeup is always // possible while (!shdata->start) mm_thr_cond_wait(&shdata->notif_cond, &shdata->notif_mtx); mm_thr_mutex_unlock(&shdata->notif_mtx); } static void broadcast_start_notification(struct shared_data* shdata) { // We want a worker thread to be be scheduled in a predictable way, // so we must own shdata->notif_mtx when calling // mm_thr_cond_broadcast() mm_thr_mutex_lock(&shdata->notif_mtx); shdata->start = 1; mm_thr_cond_broadcast(&shdata->notif_cond); mm_thr_mutex_unlock(&shdata->notif_mtx); } static void* thread_func(void* data) { struct thread_data* thdata = data; struct shared_data* shdata = thdata->shdata; const char* id_str = thdata->id_str; // Put a wait here to force a litle bit of more contention. This is // here only for demonstration purpose... Without it, since the // update of text is short and simple, the text would be likely // filed in the order of thread creation wait_start_notification(shdata); write_shared_data(shdata, id_str); return NULL; } int main(void) { int i; mm_thread_t thid[NUM_THREAD]; struct thread_data thdata[NUM_THREAD]; struct shared_data shared = { .mutex = MM_THR_MUTEX_INITIALIZER, .notif_mtx = MM_THR_MUTEX_INITIALIZER, .notif_cond = MM_THR_COND_INITIALIZER, .start = 0, }; // Create threads and assign each an ID string for (i = 0; i < NUM_THREAD; i++) { thdata[i].shdata = &shared; sprintf(thdata[i].id_str, "thread-%i", i); mm_thr_create(&thid[i], thread_func, &thdata[i]); } // Now that all thread are created, we can signal them to start broadcast_start_notification(&shared); for (i = 0; i < NUM_THREAD; i++) mm_thr_join(thid[i], NULL); printf("result string:%s\n", shared.text); return EXIT_SUCCESS; } mmlib-1.4.2/doc/examples/parse_args.c000066400000000000000000000060561435717460000175110ustar00rootroot00000000000000/* * @mindmaze_header@ */ #include #include #include #include #include #include struct config { const char* detach_flag; unsigned int num_instance; const char* ip; const char* use_local_storage; }; static struct config cfg = { .num_instance = 10, .ip = "127.0.0.1", }; #define LOREM_IPSUM "Lorem ipsum dolor sit amet, consectetur adipiscing" \ "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." \ "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi " \ "ut aliquip ex ea commodo consequat..." #define DEFAULT_PATH "/default/path" static struct mm_arg_opt cmdline_optv[] = { {"detach", MM_OPT_NOVAL, "set", {.sptr = &cfg.detach_flag}, "detach server process."}, {"n|num-instance", MM_OPT_NEEDUINT, NULL, {.uiptr = &cfg.num_instance}, "Server can accommodate up to @NUM client simultaneously. Here is " "more explanation to test text wrapping. " LOREM_IPSUM}, {"l|use-local-storage", MM_OPT_OPTSTR, DEFAULT_PATH, {NULL}, "Use local storage located at @PATH which must exist. " "If unspecified @PATH is assumed "DEFAULT_PATH "."}, {.name = "i", .flags = MM_OPT_NEEDSTR, .defval = NULL, {.sptr = &cfg.ip}, .desc = "IP address of remote server. @ADDR must have dotted form."}, }; /** * parse_option_cb() - validate some option value and parse other * @opt: parser configuration of option recognized * @value: value about to be set for option * @data: callback data * @state: flags indicating the state of option parsing. * * Return: 0 is parsing must continue, -1 if error has been detect and * parsing must stop. */ static int parse_option_cb(const struct mm_arg_opt* opt, union mm_arg_val value, void* data, int state) { struct config* conf = data; (void)state; switch (mm_arg_opt_get_key(opt)) { case 'n': if (value.ui < 1) { fprintf(stderr, "Server must support at least 1 instance\n"); return -1; } // We don't set value here, since variable to set is already // configured in option setup ({.strptr = &cfg.ip}) return 0; case 'l': if (mm_check_access(value.str, F_OK) != 0) { fprintf(stderr, "storage file %s does not exist\n", value.str); return -1; } conf->use_local_storage = value.str; return 0; default: return 0; } } int main(int argc, char* argv[]) { int i, arg_index; struct mm_arg_parser parser = { .doc = LOREM_IPSUM, .args_doc = "[options] cmd argument\n[options] hello", .optv = cmdline_optv, .num_opt = MM_NELEM(cmdline_optv), .cb = parse_option_cb, .cb_data = &cfg, .execname = argv[0], }; arg_index = mm_arg_parse(&parser, argc, argv); fprintf(stdout, "options used:\n\tdetach_flag: %s\n\tinstance: %u\n" "\tserver address: %s\n\tuse local path: %s\n", cfg.detach_flag, cfg.num_instance, cfg.ip, cfg.use_local_storage); fprintf(stdout, "Execute "); for (i = arg_index; i < argc; i++) fprintf(stdout, "%s ", argv[i]); fputc('\n', stdout); return EXIT_SUCCESS; } mmlib-1.4.2/doc/examples/pshared-child.c000066400000000000000000000150431435717460000200660ustar00rootroot00000000000000/* * @mindmaze_header@ */ /* process shared data: child program * * example of child process, to be used by pshared-parent.c example. * * Similar to the multithreaded data write example, this program writes to a * shared text (in shared memory) concurrently to other children of the * parent process. Each child maps into memory a file descriptor * (SHM_CHILD_FD) inherited from parent and initialized there. The child * tries to write its identification string ("|-child-X+|") onto a text * field of the shared memory. The text after update of several child looks * something like: * * ...|+child-Z+||+child-W+||+child-X+||+child-Y+|... * * Because of the concurrent access, the children use a process shared mutex * mapped in the shared memory. They can recover from a child dying while * owning the mutex. Put simulate this, the SEGFAULT_IN_CHILD environment * variable can be set. If a child process see its identification string * ("child-X" for Xth child created), it will provoke a segfault while * updating the text. * * This file demonstrates how to: * - map file into memory * - use process shared mutex */ #define MM_LOG_MODULE_NAME "pshared-child" #include #include #include #include #include #include #include #include #include "pshared-common.h" #define BAD_ADDR (void*)0xDEADBEEF static void handle_notif_lock_retval(int lockret, struct pshared_data* psh_data) { // By far the most usual case. We simply got the lock, nothing fancy // has happened. if (lockret == 0) return; // Contrary to psh_data->mutex, there is no shared state to recover // with psh_data->notif_mtx. Simply mark it consistent if (lockret == EOWNERDEAD) mm_thr_mutex_consistent(&psh_data->notif_mtx); if (lockret == ENOTRECOVERABLE) exit(EXIT_FAILURE); } /* * Wait that parent notifies to start, ie, mark psh_data->start = 1 and * broadcast psh_data->notif_cond. */ static void wait_start_notification(struct pshared_data* psh_data) { int lockret; lockret = mm_thr_mutex_lock(&psh_data->notif_mtx); handle_notif_lock_retval(lockret, psh_data); while (!psh_data->start) { lockret = mm_thr_cond_wait(&psh_data->notif_cond, &psh_data->notif_mtx); handle_notif_lock_retval(lockret, psh_data); } mm_thr_mutex_unlock(&psh_data->notif_mtx); } /* * This function do the update of the shared text. It must be called while * holding the lock, ie, called from write_shared_data(). It happens the * string |+@id_str+| to the text field in @psh_data. * * If requested, this function will provoke a segfault in the middle of * string appending. */ static void write_shared_text_locked(struct pshared_data* psh_data, const char* id_str, bool provoke_segfault) { int id_str_len = strlen(id_str); // Add "|+" in the text psh_data->text[psh_data->len++] = '|'; psh_data->text[psh_data->len++] = '+'; // Append process identifier on text (psh_data->len not updated yet) memcpy(psh_data->text + psh_data->len, id_str, id_str_len); // Segfaulting here is a good place for demonstration purpose: // psh_data->len will be not consistent with the null-terminated // string in psh_data->text if (provoke_segfault) strcpy(psh_data->text + psh_data->len, BAD_ADDR); // Now update psh_data->len psh_data->len += id_str_len; // Add "+|" in the text psh_data->text[psh_data->len++] = '+'; psh_data->text[psh_data->len++] = '|'; } /* * Function to recover shared state from the situation where the previous * owner died while holding the lock. */ static void recover_shared_text_from_owner_dead(struct pshared_data* psh_data) { int len = psh_data->len; char* text = psh_data->text; // Find index of text immediately after the last occurrence of "+|" while (len > 0) { if ((len > 2) && (text[len-2] == '+') && (text[len-1] == '|')) break; len--; } // Crop string and set to the proper found length text[len] = '\0'; psh_data->len = len; } static void write_shared_data(struct pshared_data* psh_data, const char* id_str, bool provoke_segfault) { int r; // Get the shared lock. Since we are using a process shared mutex, // we must check return value of the lock operation: If the previous // owner has died while owning it, it will be only occasion to know // about it and recover from this if we want to continue using it. r = mm_thr_mutex_lock(&psh_data->mutex); if (r == EOWNERDEAD) { // We have the lock, but it is time to recover state since // previous owner died while holding the lock recover_shared_text_from_owner_dead(psh_data); // We have recovered the shared state, so we can mark lock as // consistent. After this, we will be back to normal operation mm_thr_mutex_consistent(&psh_data->mutex); } else if (r == ENOTRECOVERABLE) { // There has been an lock owner that has died and the next // owner failed (or refused) to mark lock as consistent, // thus rendering the lock unusable. This provokes all // waiters for the lock to be waken up and ENOTRECOVERABLE // is returned. Any new attempt to lock the mutex will return // ENOTRECOVERABLE (until it is deinit and init again). // So now, we don't have the lock and we can only stop return; } write_shared_text_locked(psh_data, id_str, provoke_segfault); mm_thr_mutex_unlock(&psh_data->mutex); } int main(int argc, char* argv[]) { int mflags; bool must_segfault; struct pshared_data* psh_data = NULL; const char* proc_string; // identifier of process is passed in the first argument if (argc < 2) { fprintf(stderr, "%s is missing argument\n", argv[0]); return EXIT_FAILURE; } proc_string = argv[1]; // Map shared memory object onto memory. We know that child is // created with shared memrory file descriptor inherited at // SHM_CHILD_FD mflags = MM_MAP_SHARED|MM_MAP_READ|MM_MAP_WRITE; psh_data = mm_mapfile(SHM_CHILD_FD, 0, sizeof(*psh_data), mflags); if (!psh_data) { mm_print_lasterror("mm_mapfile(%i, ...) failed", SHM_CHILD_FD); return EXIT_FAILURE; } // Close SHM_CHILD_FD because now that it is mapped, we don't need // it any longer mm_close(SHM_CHILD_FD); // Get from environment if this particular instance must simulate a // segfault while holding the lock. must_segfault = false; if (!strcmp(mm_getenv("SEGFAULT_IN_CHILD", ""), proc_string)) must_segfault = true; // Wait until parent notify to start wait_start_notification(psh_data); // Try to update shared text. write_shared_data(psh_data, proc_string, must_segfault); mm_unmap(psh_data); return EXIT_SUCCESS; } mmlib-1.4.2/doc/examples/pshared-common.h000066400000000000000000000004621435717460000202770ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef PSHARED_COMMON_H #define PSHARED_COMMON_H #include #define SHM_CHILD_FD 3 struct pshared_data { mm_thr_mutex_t mutex; int len; char text[1024]; mm_thr_mutex_t notif_mtx; mm_thr_cond_t notif_cond; int start; }; #endif /* ifndef PSHARED_COMMON_H */ mmlib-1.4.2/doc/examples/pshared-parent.c000066400000000000000000000130671435717460000203000ustar00rootroot00000000000000/* * @mindmaze_header@ */ /* process shared data: parent program * * example of parent process, child is implemented in pshared-child.c. * * This program writes to a shared text (in shared memory) concurrently to * other children of the parent process. Each child maps into memory a file * descriptor (SHM_CHILD_FD) inherited from parent and initialized there. * The child tries to write its identification string ("|-child-X+|") onto a * text field of the shared memory. The text after update of several child * looks something like: * * ...|+child-Z+||+child-W+||+child-X+||+child-Y+|... * * Because of the concurrent access, the children use a process shared mutex * mapped in the shared memory. They can recover from a child dying while * owning the mutex. Put simulate this, the SEGFAULT_IN_CHILD environment * variable can be set. If a child process see its identification string * ("child-X" for Xth child created), it will provoke a segfault while * updating the text. * * Note on how to execute the program: * If left untouched, the program assumes that child executable is available * in the _current_ directory. Also it assumes that mmlib shared library is * accessible at runtime. * * This file demonstrates how to: * - create an anonymous shared memory object * - map file into memory * - initialize process shared mutex * - create child process with passing file descriptor to them */ #define MM_LOG_MODULE_NAME "pshared-parent" #include #include #include #include #include #include #include #include "pshared-common.h" #ifdef _WIN32 # define BINEXT ".exe" #else # define BINEXT #endif #define NUM_CHILD 6 #define PSHARED_CHILD_BIN "./pshared-child" BINEXT /* * Create, map into memory and initialize the data that will shared with the * children. The process shared mutex is initialized here. */ static struct pshared_data* init_shared_mem_data(int* shm_fd) { int fd, mflags; struct pshared_data* psh_data = NULL; // Create an new anonymous shared memory object. We could have use a // normal file (with mm_open()) without changing of the rest of the // following if we wanted to keep the result of memory access on the // shared memory. fd = mm_anon_shm(); if (fd < 0) return NULL; // Size it to accommodate the data that will be shared between // parent and children. if (mm_ftruncate(fd, sizeof(*psh_data))) goto failure; // Map shared memory object onto memory mflags = MM_MAP_SHARED|MM_MAP_READ|MM_MAP_WRITE; psh_data = mm_mapfile(fd, 0, sizeof(*psh_data), mflags); if (!psh_data) goto failure; // Reset the while content of structure to 0/NULL fields *psh_data = (struct pshared_data) {.start = 0}; // Initialize synchronization primitives of shared data if (mm_thr_mutex_init(&psh_data->mutex, MM_THR_PSHARED) || mm_thr_mutex_init(&psh_data->notif_mtx, MM_THR_PSHARED) || mm_thr_cond_init(&psh_data->notif_cond, MM_THR_PSHARED)) goto failure; *shm_fd = fd; return psh_data; failure: mm_close(fd); return NULL; } /* * Starts all children process ensuring that they inherit of the shared * memory file descriptor. Pass the string identify a particular process * instance as the first argument. */ static int spawn_children(int shm_fd, int num_child, mm_pid_t* children) { int i; char process_identifier[32]; char* argv[] = {PSHARED_CHILD_BIN, process_identifier, NULL}; struct mm_remap_fd fd_map = { .child_fd = SHM_CHILD_FD, .parent_fd = shm_fd, }; for (i = 0; i < num_child; i++) { // Set the process identifier (it is just a string to // identify which child process is running). This string is // already set as second element in argv, ie, the first // argument sprintf(process_identifier, "child-%i", i); // Spawn the process if (mm_spawn(&children[i], argv[0], 1, &fd_map, 0, argv, NULL)) return -1; } return 0; } static int wait_children_termination(int num_child, const mm_pid_t* children) { int i; for (i = 0; i < num_child; i++) { if (mm_wait_process(children[i], NULL)) return -1; } return 0; } static void broadcast_start_notification(struct pshared_data* psh_data) { int lockret; // We want a worker thread to be be scheduled in a predictable way, // so we must own shdata->notif_mtx when calling // mm_thr_cond_broadcast() lockret = mm_thr_mutex_lock(&psh_data->notif_mtx); if (lockret == ENOTRECOVERABLE) return; if (lockret == EOWNERDEAD) mm_thr_mutex_consistent(&psh_data->notif_mtx); psh_data->start = 1; mm_thr_cond_broadcast(&psh_data->notif_cond); mm_thr_mutex_unlock(&psh_data->notif_mtx); } int main(void) { mm_pid_t children[NUM_CHILD]; int shm_fd = -1; struct pshared_data* psh_data = NULL; int exitcode = EXIT_FAILURE; fprintf(stderr, "SEGFAULT_IN_CHILD=%s\n", mm_getenv("SEGFAULT_IN_CHILD", "")); // Create a shared memory object with the right size and map into // memory psh_data = init_shared_mem_data(&shm_fd); if (!psh_data) goto exit; // Create the children inheriting the shared memory object if (spawn_children(shm_fd, MM_NELEM(children), children)) goto exit; // Close shm_fd because now that it is mapped, and transmitted to // children, we don't need its file descriptor. mm_close(shm_fd); shm_fd = -1; broadcast_start_notification(psh_data); wait_children_termination(MM_NELEM(children), children); exitcode = EXIT_SUCCESS; exit: if (exitcode == EXIT_FAILURE) mm_print_lasterror("pshared-parent failed"); else printf("result string:%s\n", psh_data->text); mm_close(shm_fd); mm_unmap(psh_data); return exitcode; } mmlib-1.4.2/doc/filesystem.rst000066400000000000000000000003561435717460000163140ustar00rootroot00000000000000Filesystem ========== .. kernel-doc:: src/file.c :no-header: :module: filesystem :headers: mmsysio.h :export: .. kernel-doc:: src/file-posix.c :no-header: :module: filesystem :headers: mmsysio.h :export: mmlib-1.4.2/doc/index.rst000066400000000000000000000034311435717460000152340ustar00rootroot00000000000000Welcome to mmlib's documentation! ================================= mmlib is an `Operating System abstraction layer`__. .. __: https://en.wikipedia.org/wiki/Operating_system_abstraction_layer It provides an API for application developer, so that they can write portable code using relatively advanced OS features without having to care about system specifics. It also provides facilities like error reporting or logging that helps different modules to behaves consistently with each other. The reference design in mind in its conception is **POSIX**, meaning that if some posix functionality is not available for given platform, mmlib will implement that same functionality itself. **POSIX** is designed by the IEEE since before 1997. Its main goal is maintaining compatibility between operating systems through standardization. Therefore, it is a good starting point to design an OS-neutral API. There are many similarities with **UNIX** systems: historically this system was chosen because if was manufacturer-neutral. More infos and link about **POSIX**: `Wikipedia page about posix`__, .. __: https://en.wikipedia.org/wiki/POSIX Supported platforms: * **POSIX** * **Win32** .. toctree:: :caption: About implementations choices and design :maxdepth: 2 design.rst .. toctree:: :caption: API module list :titlesonly: :maxdepth: 2 alloc.rst argparse.rst dlfcn.rst env.rst error.rst filesystem.rst ipc.rst log.rst process.rst profiling.rst socket.rst thread.rst time.rst .. toctree:: :caption: Examples :maxdepth: 2 examples.rst Useful links ============ * **Sources**: https://intranet.mindmaze.ch/bitbucket/projects/MMLAB/repos/mmlib Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` mmlib-1.4.2/doc/ipc.rst000066400000000000000000000001651435717460000147010ustar00rootroot00000000000000IPC === .. kernel-doc:: src/local-ipc-posix.c :no-header: :module: ipc :headers: mmsysio.h :export: mmlib-1.4.2/doc/lock_server_init.dia000066400000000000000000000160701435717460000174160ustar00rootroot00000000000000]nH}W`P,=s4Ս`=@?L/4`ms[=].~fudN; J)t|Iy>_(u>f/׿w:ΒؿE2 +fs]Yӧh4Oʼ&C4O?_2$E. I&eYdWe̒i*fu$//şn.>-niN{~ܦWEcy7OOyf/)_]>ߕkWEo=SO,&m6{=t&GA#M)0Ċ+>UMvl~yed!|&zԲxHg~LVYdex7d~_V۩+;2~?kЯ1w_O@wͳIg?sM Z\}X"lB80kvnq٧Cu ?q6$Oyю{%2qKH *ġ8ɋMKIȍrfv>s\ y)Om0jTkdix?,i@; ?Yy*hona,8)0!z▉g`ó7\Z>Rq84WW.ϯyIQ$OFxi$(}wIBRr3$uԋ3Dc h_HCBBW4r۾ 9[kV'H k&w_w[I o9.xFL )!>q+` I$@BH P,`: it<@m`: y<Ƚڂ2rxi[jX OqD"p"`- 0kt BG 0gyϲAML$H :Pq$1-D ejSo@!%:"<`SE Rp&c; P=½׽6ҥbY b |_` 6(IKL@qOBdr0$SHQ xL Q6,* wbHcЂ6ă,/CaHuĐ{?AX0(lڀ_d7i ftoonuֿIO͟?E_:1!"9#\P%oFHhft.(iF+w@kT1)8DY;`QsUb@G"\"_U6|" v+I?ކ%,%pD;@i%x&%  @@J!%H <( lHxJ v?~RDJZER2uTE=>uvc,9!01 #$6@t%@@;P |6`aNv4xy*Kü O.-Li^<[tC !I$~UY HBf` Wb Icт$T%If<qtYzp(C:n<%Xw*y2 [^WE٬X$0M{K3o5֞Jw(U33R*$S.ˏ2ޗrh.PY#zܶ]cO dcd0 ~&Z2JA'J G21觉+O p ~dD2 ]\:L-:A} ^A~t+RxL 0)G6KdJkq&TUIŒ3<͹#24̳k\//fˤ(=Lrn8M8k b#nPa:mʗ;4LhT 0(8l@]yyD+'wXWe~ټLgׁ{8j~~~sO6N"I4@:CkLJOFX4hH b*R%ZjԤԊS;$CBa 7{?wPfX, +5ʗʚ?BUe5#/ŵqbs1'q 2RA+^TPPX{X|36oC= ="B=$*Ch(X\hG/(,$4 Y$~D\JD폑OVc%PE!TDmvAHxq] h## 3v׼d\[^HiWdIʹ˥^$Xs2G|aaF3Ct#쭓)a I\ 5ZeqT!yKy%X9X-ځвg% !y>dc BVpn,fj~&:Q.Ƥ\ 'T8uM&-Ї|`S|Hړ 8M[TD1QU-WKtk5]}8 ZřJIp"iDSt"q"B}FHAеU@¡{2I|{)fLۿ\UWh 9NW_ub 7ֶ:) ֍wg;1h*s<{xs\vK| pe7^-?IgZC{ ? §  '0l,t- 0^`XY$`{ZK f  B0" =C]xSô0\`Vb2&o@&RHlGX_) ^Cxa//N +^H…| ʼnx{s.U y@Q$:΢% Hrg_60obiny>DGiک{y]Yj?j`6W:4P/{:TT);iYw,a)bR1PүTQq;Gl٭T‡8zF3R_tЪw*بIcRɁϗb턎l/]I;|)"5@WxX #zԡAT\5tJrY\ED!Fʮ+r+8o+P^c$ 0TAcFZ¶Ң:Q7DݘJP(i0cF{A"gݓ`]ITcѥkAL!MU@LUh?J(KLTۿY1ځxb1N  um!@mNKe? PKէCXgG#0mOP30 >3q+̀ư̠XTjIi$ I$~>AqPG\X~r{=uVBJ&д(=Fᐰ0FB>{.pȯ<}.)`jo5_3qO,6 OЬh:T#L>e,>ɫm742 V^XGXG3c,cqTn}64ŘAXjN]+p#;8` 䠧*hծ*smD)LWno6=]M+`[ϐa?v%Bh* aP>FD4EAȲ!-DͩS38?wJ'KObh/_$_ч892='r5nʏ(fl-ev4eJSWg 4eQu T%LII$IA>R)_?wt%?a.>d~_u%SWӎ NT0kg1" n"K E?Y-XrL9l>eq, R%+3 _\V8aZ[.P@=`-^w~xŚ/,֗tv/ws9ā# d+=my1[X{´Ѓ4p#)#E)]^u.9 \ljKu.9'z|Ȣ.p|hj;HQ+Һ+ژՖ 4Rp%8xWpX캅lX¬Ņ]ڮ?tĔ*FC VvAD\$aWשHl#f"J>tGaIJ_h,f;TČVvHA 0vCء#iС::A:˫]:D*jąET13 =6Db/,bGځر;XA:iPα1-,+8% qqc nqC7x$Ẇ zACtС#Iُ&ԁA@x4>#N!zlwr6r!vaܔE/l:&ۉt'2HXXZ6NBjjZ&"^hJ2_]&(ҠC=v?d`3Īj`JwwVvԴ޽y9}n?o\ Q%&etdlvƇ3637_澣t7+QkSi];s9v`9eWpֹ 77W_ۍU ժL3N#8¦셝[*^>G]w6vo8/uot31"y(s*-B;,t@!F\=^,p=< m5! ! uB0~ʺX i τhD#ɴB,b1F2.1Fq|3XpێЎ\N3Z~.)4g u_`Xi[}`k#^2"<&AvSgi1v~=$8[䘈ག`LBI(3 T!*8%RBƱd ;'/]]toxrHd@#ۜ׮KؾK+ừ_a\d7lgwWu=/eeOxl2ɾ\}?:[6몸}hMnU4_W-+'eMF_C~[7OWwy^x:/~}kW-EMXk =H=ͪbvLڷƚRj4Q\{rr~]\U]eEv۲٬]Ye#bm..l_j۩PwwG\Ǜ_>/@wX|߫/fۿ4g%Chssb/5˱}o͸X'[ؽY] Y6Bw5g;ܕُҨ͒W; YY^-_kn-fJ6~z!'8uc՚&^d[v+[fK1v[GpǼxx\ys%7{q*[Seiqx%t|,/'/4 '|U^+@g|e%_$V wч_ rofQLӴ;U)Vm2)U+Ux54VJj]6P12f0 `&ƗRLK,R$wpCkH13khabGDLA!۞P^Kt/+K6AHS`zp>QRζ2@! VdHcJQpS }Dd8Đn^,vdEyWd63Ȫ*{£xCHy3@jXcHC h[HCBBS4r@:t!ߘ\!i l!U8Ā l jDr' 8H\ppF @@m@HH,"  ,0<@"7@($A "}CtHD!FtNtH!3I - ,AI}v᐀/~ .z- m ߋپLj93&ѰƐŒi:)2[h[L{2^v|xјsOf!֗h0I6UO:L%>wşUwV$sFհz5bi4.Oe_eh].(x??(ɾ6/)BFwc7*6=oڟMOy[YUCY77qeg³!x6~;8!q cmok)%%tpN8sнb@<џ+``&=IS‚]V`:?`"]Lx;gӜz9i>CiӬwc>a>rXkj`EQخmBղPjY,زcjX?- vж Scmi<.K]|koU}|c*{֥ۜdpȰ`:!r'$ ҁtH;Q ׁ BH;NV`w&Ð:"Hu;H͔lوْs}A7GMyBNtZ֋uc?aY}޿*YHbs1m9 kF&-$Hr%&HQ`Wp2]1U+[Ä:F&Ļl"ŷ{> )v=03MJ {[ی47{c4|K 3|9!=#ylƅh6mB.gݠ.yTeUhR,jFB{#tJF䢗j9h#y$6itb@Ebp"1g l@m.3lbqz ltG 4XgȆ.ğ?!=?E(DlR,3!BB[PA0y1ϗ๨*v!#1&ƭx6JE?RF%:AFmT<~!W086%Xh,]7@i%XJT36+ BLnx)A.5%>2LJH J@A#h,rHҥTŶgiw}ΐ a)zLe6@tA@=8(:. p7lA ӖΫ TӢ,w.Ӳz|$ I0"$!\IBkW*N>H!&l4)B)6lG\:3כLSؿ=W!%]mf̞ZKn, sfs.;rN?RV켫#'2DZٕTCԓC[s N-3!&}1K V#!cm7gs'†~8uQCO^i}Y(viSߺMo97Mݻ2V=5eAya$Pt^G_\FL ػOC! 2 d"Ȁ&.%IfY hɚaɊɀ/2 Kt?m 5uC<k22 $d RD0djIh` 3PUKTG7#)E 4ItǯYQ"hՎ9&%Hq"y1I]_oOdS:Tۚe*s" 0=KE_7YFTvP! !v yO.Bb.TTs4. vK!n2Oab­&1yI<py#=scCI%ofI&"pLҞLdFW2[UëՀH}b9^CY5ĊWMkhV|U/铁ɥQmZL&{32^yd97I;& ,Q6ɩJG[Ldzfכ埰=h螹sļw/gSâG;Mu6ɪ|~Ǟo}3gEϮAZ/q$}|Ws.gtSaS!fbCO"aR>mC6 m,gŝ!CBdfeExPeX8vImmlib-1.4.2/doc/log.rst000066400000000000000000000003031435717460000147010ustar00rootroot00000000000000Log utilities ============= .. kernel-doc:: src/mmlog.h :module: error :doc: error codes .. kernel-doc:: src/log.c :module: error :no-header: :headers: mmlog.h :export: mmlib-1.4.2/doc/meson.build000066400000000000000000000063651435717460000155460ustar00rootroot00000000000000install_subdir('examples', install_dir : get_option('datadir') / 'doc' / 'mmlib') install_man(files('mm_log_debug.3', 'mm_log_error.3', 'mm_log_fatal.3', 'mm_log_info.3', 'mm_log_warn.3', 'mm_tic.3', 'mm_toc.3', 'mm_toc_label.3', )) pshared_parent_doc_example_sources = files( 'examples/pshared-common.h', 'examples/pshared-parent.c' ) executable('pshared-parent-doc-example', pshared_parent_doc_example_sources, include_directories : include_directories('../src'), link_with : mmlib, ) pshared_child_doc_example_sources = files('examples/pshared-child.c') executable('pshared-child-doc-example', pshared_child_doc_example_sources, include_directories : include_directories('../src'), link_with : mmlib, ) parse_args_doc_example = files('examples/parse_args.c') executable('parse-args-doc-example', parse_args_doc_example, include_directories : include_directories('../src'), link_with : mmlib, ) sphinxbuild = find_program('sphinx-build', required : get_option('docs')) if sphinxbuild.found() # TODO # meson does not yet know how to check for a python module presence # (introduced in 0.51.0) # change this when possible python3 = import('python').find_installation('python3', required : true) check_linuxdoc = run_command(python3, '-c', '"import linuxdoc"') if check_linuxdoc.returncode() != 0 error('python3 module "linuxdoc" is required too build documentation') endif doc_sources = files( 'alloc.rst', 'argparse.rst', 'design.rst', 'dlfcn.rst', 'env.rst', 'error.rst', 'examples.rst', 'filesystem.rst', 'index.rst', 'ipc.rst', 'log.rst', 'process.rst', 'profiling.rst', 'socket.rst', 'thread.rst', 'time.rst', ) # sphinx warning: do NOT reorder the flags ! # sphinx-build NEEDS the flags -M and -d both first and in this order ! sphinxbuild_wrapper = files('sphinx-build-wrapper.sh') gen_man_pages = custom_target('man3', output : 'man3', command : [ 'sh', sphinxbuild_wrapper, sphinxbuild, meson.project_source_root(), 'kernel-doc-man', meson.project_source_root() / 'doc', 'doc/man3', ], build_by_default : true, depend_files : [mmlib_sources, doc_sources], install : true, install_dir : get_option('mandir'), ) custom_target('html', output : 'html', command : [ 'sh', sphinxbuild_wrapper, sphinxbuild, meson.project_source_root(), 'html', meson.project_source_root() / 'doc', 'doc/html', ], build_by_default : true, depend_files : [mmlib_sources, doc_sources], depends : gen_man_pages, # re-use .doctree from man install : true, install_dir : get_option('datadir') / 'doc/mmlib', ) endif # sphinxbuild mmlib-1.4.2/doc/mm_log_debug.3000066400000000000000000000000301435717460000160670ustar00rootroot00000000000000.so man3/mm_log_fatal.3 mmlib-1.4.2/doc/mm_log_error.3000066400000000000000000000000301435717460000161320ustar00rootroot00000000000000.so man3/mm_log_fatal.3 mmlib-1.4.2/doc/mm_log_fatal.3000066400000000000000000000021451435717460000161010ustar00rootroot00000000000000.\"@mindmaze_header@ .TH MM_LOG_FATAL 3 2012 "" "mmlib library manual" .SH NAME mm_log_fatal, mm_log_error, mm_log_warn, mm_log_info, mm_log_debug - Add a formatted message to the log file .SH SYNOPSIS .LP .B #include .sp .BI "void mm_log_fatal(msg, ...);" .br .BI "void mm_log_error(msg, ...);" .br .BI "void mm_log_warn(msg, ...);" .br .BI "void mm_log_info(msg, ...);" .br .BI "void mm_log_debug(msg, ...);" .br .sp Link with .I -lmmlib .SH DESCRIPTION .LP Those macros writes a log entry (using \fBmm_log\fP(3)) using the level specified by the macro name. If the preprocessor definition \fIMM_LOG_MAXLEVEL\fP is smaller than the corresponding level of the macro, this macros is simply not exanded in the code. .LP The module name in the location string is defined by \fIMM_LOG_MODULE_NAME\fP. If unset when including \fBmmlog.h\fP, this definition is initialized to the value of \fIPACKAGE_NAME\fP. If \fIMM_LOG_VERBOSE_LOCATION\fP is defined, the filename and line number are prepended to the location string. .SH "RETURN VALUE" .LP None. .SH THREAD SAFETY .LP \fBmm_log\fP() is thread-safe. .SH EXAMPLE .LP mmlib-1.4.2/doc/mm_log_info.3000066400000000000000000000000301435717460000157340ustar00rootroot00000000000000.so man3/mm_log_fatal.3 mmlib-1.4.2/doc/mm_log_warn.3000066400000000000000000000000301435717460000157500ustar00rootroot00000000000000.so man3/mm_log_fatal.3 mmlib-1.4.2/doc/mm_tic.3000066400000000000000000000033621435717460000147320ustar00rootroot00000000000000.\"@mindmaze_header@ .TH MMTIC 3 2014 "" "mmlib library manual" .SH NAME mm_tic - Start an iteration of profiling .br mm_toc - Add a point of measure to an iteration .SH SYNOPSIS .LP .B #include .sp .BI "void mm_tic();" .br .BI "void mm_toc();" .br .BI "void mm_toc_label(const char* " label "); .sp Link with .I -lmmlib .SH DESCRIPTION .LP .BR mm_tic () starts an iteration of profiling. It updates the timing statistics with the previous data if applicable and reset the metadata for a new timing iteration. Finally it measures the timestamp of the iteration start. .LP .BR mm_toc () Add simply a new point of measure to the current iteration of profiling. If the maximum number of point of measure per iteration has been reached, then the new point will silently be ignored. .LP .BR mm_toc_label () is the same as .BR mm_toc () excepting it provides a way to label the meansure point. Beware than only the first occurrence of a label associated with a measure point will be retained. Any subsequent call to .BR mm_toc_label () at the same measure point will be the same as calling .BR mm_toc (). .LP Care must be taken if a precision of few microseconds or less is desired with .BR mm_toc_label (). The label copy has indeed overhead. However this overhead has influence only on the iteration that will actually copy the label string, thus will affect only the current and max timing measures. The influence on the mean will be negligible if the number of iterations is large enough. Therefore results must be interpreted in the light of this short coming. .LP The way the time is measured depends on the type of timer set by the .BR mm_profile_reset (3) function. .SH "RETURN VALUE" .LP None. .SH "SEE ALSO" .BR mm_profile_reset (3), .BR mm_profile_print (3) mmlib-1.4.2/doc/mm_toc.3000066400000000000000000000000211435717460000147250ustar00rootroot00000000000000.so man3/mmtic.3 mmlib-1.4.2/doc/mm_toc_label.3000066400000000000000000000000211435717460000160640ustar00rootroot00000000000000.so man3/mmtic.3 mmlib-1.4.2/doc/process.rst000066400000000000000000000004201435717460000155760ustar00rootroot00000000000000Process and shared objects ========================== .. kernel-doc:: src/process-posix.c :no-header: :module: process :export: :headers: mmsysio.h .. kernel-doc:: src/shm-posix.c :no-header: :module: process :headers: mmsysio.h :export: mmlib-1.4.2/doc/profiling.rst000066400000000000000000000001611435717460000161130ustar00rootroot00000000000000Profiling ========= .. kernel-doc:: src/profile.c :module: profiling :export: :headers: mmprofile.h mmlib-1.4.2/doc/socket.rst000066400000000000000000000003101435717460000154060ustar00rootroot00000000000000Socket ====== .. kernel-doc:: src/socket-posix.c :no-header: :module: socket :headers: mmsysio.h .. kernel-doc:: src/socket.c :no-header: :module: socket :headers: mmsysio.h mmlib-1.4.2/doc/sphinx-build-wrapper.sh000066400000000000000000000002611435717460000200110ustar00rootroot00000000000000#!/bin/sh sphinxbuild=$1 source_root=$2 builder=$3 doc_srcdir=$4 out_dir=$5 export srctree=$source_root $sphinxbuild -M $builder -d doc/.doctrees -j auto $doc_srcdir $out_dir mmlib-1.4.2/doc/thread.rst000066400000000000000000000001631435717460000153730ustar00rootroot00000000000000Threading ========= .. kernel-doc:: src/thread-posix.c :module: thread :headers: mmthread.h :export: mmlib-1.4.2/doc/time.rst000066400000000000000000000004571435717460000150700ustar00rootroot00000000000000Time ==== functions --------- .. kernel-doc:: src/mmtime.h :module: time :headers: mmtime.h .. kernel-doc:: src/time.c :module: time :export: :headers: mmtime.h :no-header: .. kernel-doc:: src/time-posix.c :module: time :export: :headers: mmtime.h :no-header: mmlib-1.4.2/m4/000077500000000000000000000000001435717460000131455ustar00rootroot00000000000000mmlib-1.4.2/m4/.gitignore000066400000000000000000000010311435717460000151300ustar00rootroot00000000000000# autotools generated files libtool.m4 ltdl.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 lt~obsolete.m4 pkg.m4 codeset.m4 extern-inline.m4 fcntl-o.m4 gettext.m4 glibc2.m4 glibc21.m4 iconv.m4 intdiv0.m4 intl.m4 intldir.m4 intlmacosx.m4 intmax.m4 inttypes-pri.m4 inttypes_h.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 lock.m4 longlong.m4 nls.m4 po.m4 printf-posix.m4 progtest.m4 size_max.m4 stdint_h.m4 threadlib.m4 uintmax_t.m4 visibility.m4 wchar_t.m4 wint_t.m4 xsize.m4 ax_python_module.m4 ax_append_flag.m4 ax_check_compile_flag.m4 mmlib-1.4.2/m4/api-exports.m4000066400000000000000000000023221435717460000156610ustar00rootroot00000000000000# NOTE: If in future we need to support other compiler that don't understand # visibility attribute (might be the case on non POSIX embedded platform), # we will need to add the test for visibility attribute. In such a case, see # visibilility.m4 from gnulib to see how this could be implemented. AC_DEFUN([AC_DEF_API_EXPORT_ATTRS], [AC_REQUIRE([AC_CANONICAL_HOST]) case $host in *win32* | *mingw* | *cygwin* | *windows*) os_support=win32 ;; *) os_support=other ;; esac if test $os_support != "win32"; then AC_DEFINE(LOCAL_SYMBOL, [__attribute__ ((visibility ("hidden")))], [attribute of the non-exported symbols]) AC_DEFINE(API_EXPORTED, [__attribute__ ((visibility ("protected")))], [attribute of the symbols exported in the API]) AC_DEFINE(API_EXPORTED_RELOCATABLE, [__attribute__ ((visibility ("default")))], [attribute of the relocatable symbols exported in the API]) else AC_DEFINE(LOCAL_SYMBOL, [], [attribute of the non-exported symbols]) AC_DEFINE(API_EXPORTED, [__declspec(dllexport)], [attribute of the symbols exported in the API]) AC_DEFINE(API_EXPORTED_RELOCATABLE, [__declspec(dllexport)], [attribute of the relocatable symbols exported in the API]) fi ]) mmlib-1.4.2/m4/host-system.m4000066400000000000000000000004421435717460000157060ustar00rootroot00000000000000 AC_DEFUN([AC_SET_HOSTSYSTEM], [AC_REQUIRE([AC_CANONICAL_HOST]) case $host in *win32* | *mingw* | *windows*) os_system=win32 ;; *) os_system=posix ;; esac AM_CONDITIONAL([OS_TYPE_POSIX], [test "$os_system" = posix]) AM_CONDITIONAL([OS_TYPE_WIN32], [test "$os_system" = win32]) ]) mmlib-1.4.2/m4/mm-check.m4000066400000000000000000000015341435717460000150760ustar00rootroot00000000000000 # MM_CHECK_LIB(FUNC, LIBRARIES, VARIABLE-PREFIX, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # -------------------------------------------------------------- AC_DEFUN([MM_CHECK_LIB], [save_LIBS="$LIBS" LIBS="" AC_SEARCH_LIBS([$1], [$2], [AC_SUBST($3[]_LIB,"$LIBS") $4], [$5]) LIBS="$save_LIBS"]) # MM_CHECK_FUNC(FUNC, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND], [OTHER_LIBS]) # -------------------------------------------------------------- AC_DEFUN([MM_CHECK_FUNC], [save_LIBS="$LIBS" LIBS="$LIBS $4" AC_CHECK_FUNC([$1], [$2], [$3]) LIBS="$save_LIBS"]) # MM_CHECK_FUNCS(FUNCS, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND], [OTHER_LIBS]) # -------------------------------------------------------------- AC_DEFUN([MM_CHECK_FUNCS], [save_LIBS="$LIBS" LIBS="$LIBS $4" AC_CHECK_FUNCS([$1], [$2], [$3]) LIBS="$save_LIBS"]) mmlib-1.4.2/m4/mm-warnings.m4000066400000000000000000000011561435717460000156510ustar00rootroot00000000000000 # MM_CC_WARNFLAGS() #------------------------------------------- AC_DEFUN([MM_CC_WARNFLAGS], [AC_ARG_ENABLE([warn-all], [AS_HELP_STRING([--enable-warn-all], [turn on all warnings (default: yes)])], [case $enableval in yes|no|error) ;; *) AC_MSG_ERROR([bad value $enableval for enable-warn-all option]) ;; esac mm_warnings=$enableval], [mm_warnings=yes]) case $mm_warnings in yes) MM_WARNFLAGS="-Wall -Wextra" ;; error) MM_WARNFLAGS="-Wall -Wextra -Werror" ;; no) MM_WARNFLAGS="" ;; esac AC_SUBST([MM_WARNFLAGS]) ]) #MM_CC_WARNINGS mmlib-1.4.2/m4/mm_python_module.m4000066400000000000000000000023201435717460000167630ustar00rootroot00000000000000# # SYNOPSIS # # MM_PYTHON_MODULE(modname[, fatal, python]) # # DESCRIPTION # # Checks for Python module. # # If fatal is non-empty then absence of a module will trigger an error. # The third parameter can either be "python" for Python 2 or "python3" for # Python 3; defaults to Python 3. # # LICENSE # # Copyright (c) 2008 Andrew Collier # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 8 AC_DEFUN([MM_PYTHON_MODULE],[ if test -z $PYTHON; then if test -z "$3"; then PYTHON="python3" else PYTHON="$3" fi fi PYTHON_NAME=`basename $PYTHON` AC_MSG_CHECKING($PYTHON_NAME module: $1) $PYTHON -c "import $1" 2>/dev/null if test $? -eq 0; then AC_MSG_RESULT(yes) eval AS_TR_CPP(HAVE_PYMOD_$1)=yes else AC_MSG_RESULT(no) eval AS_TR_CPP(HAVE_PYMOD_$1)=no # if test -n "$2" then AC_MSG_ERROR(failed to find required module $1) exit 1 fi fi ]) mmlib-1.4.2/m4/pkg-ext.m4000066400000000000000000000052531435717460000147730ustar00rootroot00000000000000# pkg-fine.m4 - Macros to locate and utilise pkg-config in a fine grain way # PKG_CHECK_MODULES_EXT(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES_EXT], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_CPPFLAGS], [C preprocessor flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LDFLAGS], [linker flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [libraries for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags-only-other], [$2]) _PKG_CONFIG([$1][_CPPFLAGS], [cflags-only-I], [$2]) _PKG_CONFIG([$1][_LDFLAGS], [libs-only-L], [$2]) _PKG_CONFIG([$1][_LIBS], [libs-only-l], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CPPFLAGS, $1[]_CFLAGS, $1[]_LDFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs-only-L --libs-only-l "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs-only-L --libs-only-l "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_CPPFLAGS=$pkg_cv_[]$1[]_CPPFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS $1[]_LDFLAGS=$pkg_cv_[]$1[]_LDFLAGS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES_EXT # PKG_CHECK_VAR(VARIABLENAME, MODULE, VAR) # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [Variable $3 for $2, overriding pkg-config])dnl AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1], [variable=$3], [$2]) $1=$pkg_cv_$1 AC_MSG_RESULT([$$1]) ])# PKG_CHECK_VAR mmlib-1.4.2/meson.build000066400000000000000000000075261435717460000150010ustar00rootroot00000000000000project('mmlib', 'c', version: run_command(find_program('read_version.py')).stdout().strip(), license: 'apache2', meson_version: '>= 0.56', default_options: [ 'c_std=c11', 'warning_level=3', ], ) cc = meson.get_compiler('c') config = configuration_data() # additional (optional) warnings flags = [ '-Wshadow', '-Wstrict-prototypes', '-Wmissing-prototypes', # (clang) we know how C works '-Wno-string-plus-int' ] add_project_arguments(cc.get_supported_arguments(flags), language : 'c') # use POSIX definitions up to POSIX.1-2008 standard config.set('_POSIX_C_SOURCE', '200809L') # ensure that the "default" definitions are provided # Note: when defined with _POSIX_C_SOURCE, POSIX sources are always preferred # in case of conflict config.set('_DEFAULT_SOURCE', '1') config.set('_BSD_SOURCE', '1') # for compatibility with old OS subdir('config/api-exports') subdir('config/autools-compat') if host_machine.system() == 'windows' # windows-specifig config subdir('config/windows') endif # header checks check_headers = [ 'dlfcn.h', 'locale.h', ] foreach h : check_headers cc.check_header(h) endforeach # non-mandatory function checks check_functions = [ ['sys/mman.h', 'mmap'], ['stdlib.h', 'posix_memalign'], ['stdlib.h', 'aligned_alloc'], ['malloc.h', '_aligned_malloc'], ['malloc.h', '_aligned_free'], ['dlfcn.h', 'dlopen'], ['pthread.h', 'pthread_mutex_consistent'], ] # Note: do not use cc.has_function() here: it uses the compiler builtins to # answer. BUT sometimes, the compiler lies ... for example gcc lies on # mingw64 and has a builtin advertising for posix_memalign(). foreach f : check_functions if cc.has_header_symbol(f[0], f[1], args:'-D_POSIX_C_SOURCE=200809L') config.set('HAVE_' + f[1].underscorify().to_upper(), 1) endif endforeach if cc.has_header_symbol('unistd.h', 'copy_file_range', args:'-D_GNU_SOURCE') config.set('HAVE_COPY_FILE_RANGE', 1) endif if cc.check_header('linux/fs.h') config.set('HAVE_LINUX_FS_H', 1) endif configuration_inc = include_directories('.', 'src') # write config file build_cfg = 'config.h' # named as such to match autotools build system configure_file(output : build_cfg, configuration : config) # define HAVE_CONFIG_H with compiler command line to include the generated # config.h file (same as autotools) add_project_arguments('-DHAVE_CONFIG_H', language : 'c') nls_state = 'disabled' gettext = find_program('gettext', required: get_option('nls')) if gettext.found() and not get_option('nls').disabled() nls_state = 'enabled' subdir('po') endif subdir('src') tests_state = 'disabled' libcheck = dependency('check', required : get_option('tests')) if libcheck.found() and not get_option('tests').disabled() tests_state = 'enabled' cc.check_header('check.h') subdir('tests') endif docs_state = 'disabled' sphinxbuild = find_program('sphinx-build', required : get_option('docs')) if sphinxbuild.found() and not get_option('docs').disabled() # TODO # meson does not yet know how to check for a python module presence # (introduced in 0.51.0) # change this when possible python3 = import('python').find_installation('python3', required : true) check_linuxdoc = run_command(python3, '-c', '"import linuxdoc"') if check_linuxdoc.returncode() != 0 and get_option('docs') == 'enabled' error('python3 module "linuxdoc" is required to build documentation') elif check_linuxdoc.returncode() == 0 docs_state = 'enabled' endif endif if docs_state == 'enabled' subdir('doc') endif subdir('tools') # print the status of the auto-detect features message('Configuration summary') message('=====================') message('* Tests : @0@'.format(tests_state)) message('* Doc building : @0@'.format(docs_state)) message('* NLS usage : @0@'.format(nls_state)) mmlib-1.4.2/meson_options.txt000066400000000000000000000010461435717460000162630ustar00rootroot00000000000000option('tests', type: 'feature', value: 'auto', description: 'build unit tests') option('docs', type: 'feature', value: 'auto', description: 'build documentation') option('nls', type: 'feature', value: 'auto', description: 'use Native Language Support (Translations)') option('lock-server-process', type: 'boolean', value: true, description: '''windows-only: configure whether lockserver must be built in the mmlib dll (false) of as an external programm (true)''') mmlib-1.4.2/mmpack/000077500000000000000000000000001435717460000140755ustar00rootroot00000000000000mmlib-1.4.2/mmpack/create_srcdir_hook000066400000000000000000000001221435717460000176440ustar00rootroot00000000000000[ $1 != "git" ] && exit 0 GIT_WORK_TREE=. GIT_DIR=$VCSDIR tools/expand_headers mmlib-1.4.2/mmpack/libmmlib1.provides000066400000000000000000000060601435717460000175240ustar00rootroot00000000000000# vi: syntax=yaml sharedlib: _mm_freea_on_heap: 1.2.0 _mm_malloca_on_heap: 1.2.0 mm_accept: 1.2.0 mm_aligned_alloc: 1.2.0 mm_aligned_free: 1.2.0 mm_anon_shm: 1.2.0 mm_arg_complete_path: 1.2.0 mm_arg_is_completing: 1.2.0 mm_arg_optv_parse: 1.2.0 mm_arg_parse: 1.2.0 mm_arg_parse_complete: 1.2.0 mm_basename: 1.2.0 mm_bind: 1.2.0 mm_chdir: 1.2.0 mm_check_access: 1.2.0 mm_close: 1.2.0 mm_closedir: 1.2.0 mm_connect: 1.2.0 mm_copy: 1.3.0 mm_create_sockclient: 1.2.0 mm_dirname: 1.2.0 mm_dl_fileext: 1.2.0 mm_dlclose: 1.2.0 mm_dlopen: 1.2.0 mm_dlsym: 1.2.0 mm_dup2: 1.2.0 mm_dup: 1.2.0 mm_error_set_flags: 1.2.0 mm_execv: 1.2.0 mm_freeaddrinfo: 1.2.0 mm_fstat: 1.2.0 mm_fsync: 1.2.0 mm_ftruncate: 1.2.0 mm_futimens: 1.4.0 mm_get_basedir: 1.2.0 mm_get_environ: 1.2.0 mm_get_lasterror_desc: 1.2.0 mm_get_lasterror_extid: 1.2.0 mm_get_lasterror_location: 1.2.0 mm_get_lasterror_module: 1.2.0 mm_get_lasterror_number: 1.2.0 mm_getaddrinfo: 1.2.0 mm_getcwd: 1.2.0 mm_getenv: 1.2.0 mm_getnameinfo: 1.2.0 mm_getpeername: 1.2.0 mm_getres: 1.2.0 mm_getsockname: 1.2.0 mm_getsockopt: 1.2.0 mm_gettime: 1.2.0 mm_ipc_connect: 1.2.0 mm_ipc_connected_pair: 1.2.0 mm_ipc_recvmsg: 1.2.0 mm_ipc_sendmsg: 1.2.0 mm_ipc_srv_accept: 1.2.0 mm_ipc_srv_create: 1.2.0 mm_ipc_srv_destroy: 1.2.0 mm_isatty: 1.2.0 mm_link: 1.2.0 mm_listen: 1.2.0 mm_log: 1.2.0 mm_log_set_maxlvl: 1.2.0 mm_mapfile: 1.2.0 mm_mkdir: 1.2.0 mm_nanosleep: 1.2.0 mm_open: 1.2.0 mm_opendir: 1.2.0 mm_path_from_basedir: 1.2.0 mm_pipe: 1.2.0 mm_poll: 1.2.0 mm_print_lasterror: 1.2.0 mm_profile_get_data: 1.2.0 mm_profile_print: 1.2.0 mm_profile_reset: 1.2.0 mm_raise_error_full: 1.2.0 mm_raise_error_vfull: 1.2.0 mm_raise_from_errno_full: 1.2.0 mm_read: 1.2.0 mm_readdir: 1.2.0 mm_readlink: 1.2.0 mm_recv: 1.2.0 mm_recv_multimsg: 1.2.0 mm_recvmsg: 1.2.0 mm_relative_sleep_ms: 1.2.0 mm_relative_sleep_ns: 1.2.0 mm_relative_sleep_us: 1.2.0 mm_remove: 1.2.0 mm_rename: 1.2.0 mm_rewinddir: 1.2.0 mm_rmdir: 1.2.0 mm_save_errorstate: 1.2.0 mm_seek: 1.2.0 mm_send: 1.2.0 mm_send_multimsg: 1.2.0 mm_sendmsg: 1.2.0 mm_set_errorstate: 1.2.0 mm_setenv: 1.2.0 mm_setsockopt: 1.2.0 mm_shm_open: 1.2.0 mm_shm_unlink: 1.2.0 mm_shutdown: 1.2.0 mm_socket: 1.2.0 mm_spawn: 1.2.0 mm_stat: 1.4.0 mm_strerror: 1.2.0 mm_strerror_r: 1.2.0 mm_symlink: 1.2.0 mm_thr_cond_broadcast: 1.2.0 mm_thr_cond_deinit: 1.2.0 mm_thr_cond_init: 1.2.0 mm_thr_cond_signal: 1.2.0 mm_thr_cond_timedwait: 1.2.0 mm_thr_cond_wait: 1.2.0 mm_thr_create: 1.2.0 mm_thr_detach: 1.2.0 mm_thr_join: 1.2.0 mm_thr_mutex_consistent: 1.2.0 mm_thr_mutex_deinit: 1.2.0 mm_thr_mutex_init: 1.2.0 mm_thr_mutex_lock: 1.2.0 mm_thr_mutex_trylock: 1.2.0 mm_thr_mutex_unlock: 1.2.0 mm_thr_once: 1.2.0 mm_thr_self: 1.2.0 mm_tic: 1.2.0 mm_toc: 1.2.0 mm_toc_label: 1.2.0 mm_unlink: 1.2.0 mm_unmap: 1.2.0 mm_unsetenv: 1.2.0 mm_utimens: 1.4.0 mm_wait_process: 1.2.0 mm_write: 1.2.0 mmlib-1.4.2/mmpack/specs000066400000000000000000000006161435717460000151400ustar00rootroot00000000000000name: mmlib version: file(../VERSION) maintainer: nicolas.bourdaud@gmail.com url: https://github.com/mmlabs-mindmaze/mmlib licenses: [ Apache-2.0 ] copyright: Copyright (C) 2012-2020 MindMaze Holdings SA description: | mmlib is the general purpose library providing helper functions to other module. It provides facilities for logging, error reporting and basic geometry. build-system: meson mmlib-1.4.2/po/000077500000000000000000000000001435717460000132435ustar00rootroot00000000000000mmlib-1.4.2/po/.gitignore000066400000000000000000000001321435717460000152270ustar00rootroot00000000000000Makevars.template POTFILES Rules-quot stamp-po Makefile.in.in *.sed *.gmo *.sin *.header mmlib-1.4.2/po/LINGUAS000066400000000000000000000000121435717460000142610ustar00rootroot00000000000000fr de it mmlib-1.4.2/po/Makevars000066400000000000000000000035001435717460000147350ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = Mindmaze SA # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = # do not regenerate po files unconditionnaly PO_DEPENDS_ON_POT = no mmlib-1.4.2/po/POTFILES.in000066400000000000000000000001071435717460000150160ustar00rootroot00000000000000# List of source files which contain translatable strings. src/error.c mmlib-1.4.2/po/de.po000066400000000000000000000021221435717460000141700ustar00rootroot00000000000000# French translation for mmlib # Copyright (C) 2017 Mindmaze SA # This file is distributed under the same license as the mmlib package. # Sébastien Lasserre , 2017. # msgid "" msgstr "" "Project-Id-Version: mmlib 1.2.0\n" "Report-Msgid-Bugs-To: nicolas.bourdaud@mindmaze.com\n" "POT-Creation-Date: 2020-03-10 17:34+0100\n" "PO-Revision-Date: 2020-03-10 17:40+0100\n" "Last-Translator: Nicolas Bourdaud \n" "Language-Team: project managment \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2.1\n" #: src/error.c:42 msgid "The device has been disconnected." msgstr "Das Gerät wurde getrennt." #: src/error.c:43 msgid "Object in wrong state" msgstr "Objekt hat falschen Status" #: src/error.c:44 msgid "Object not found" msgstr "Objekt nicht gefunden" #: src/error.c:45 msgid "Bad format" msgstr "Falsches Format" #: src/error.c:47 msgid "Specified hostname cannot be resolved" msgstr "Angegebener Hostname kann nicht aufgelöst werden" mmlib-1.4.2/po/fr.po000066400000000000000000000034341435717460000142160ustar00rootroot00000000000000# French translation for mmlib # Copyright (C) 2017 Mindmaze SA # This file is distributed under the same license as the mmlib package. # Sébastien Lasserre , 2017. # msgid "" msgstr "" "Project-Id-Version: mmlib 1.2.0\n" "Report-Msgid-Bugs-To: nicolas.bourdaud@mindmaze.com\n" "POT-Creation-Date: 2020-03-10 17:34+0100\n" "PO-Revision-Date: 2020-03-10 17:36+0100\n" "Last-Translator: Nicolas Bourdaud \n" "Language-Team: Project management team \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2.1\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/error.c:42 msgid "The device has been disconnected." msgstr "L'appareil a été déconnecté." #: src/error.c:43 msgid "Object in wrong state" msgstr "État erroné de l'objet" #: src/error.c:44 msgid "Object not found" msgstr "Objet non identifié" #: src/error.c:45 msgid "Bad format" msgstr "Format erroné" #: src/error.c:47 msgid "Specified hostname cannot be resolved" msgstr "Le nom d'hôte spécifié ne peut pas être résolu" #~ msgid "User unknown" #~ msgstr "Utilisateur inconnu" #~ msgid "Wrong password" #~ msgstr "Mot de passe erroné" #~ msgid "Too many entities have been requested" #~ msgstr "Trop d'entités ont été requises" #~ msgid "Calibration needed" #~ msgstr "Calibration requise" #~ msgid "" #~ "Hand trackers not detected.\n" #~ "Please ensure the USB dongle is connected \n" #~ "and the sensors are switched on" #~ msgstr "" #~ "Hand Trackers non détectés.\n" #~ "Vérifiez que le dongle USB est connecté\n" #~ " et que les capteurs sont allumés." #~ msgid "Communication error with camera hardware." #~ msgstr "Erreur de communication avec la caméra." mmlib-1.4.2/po/it.po000066400000000000000000000034411435717460000142210ustar00rootroot00000000000000# French translation for mmlib # Copyright (C) 2017 Mindmaze SA # This file is distributed under the same license as the mmlib package. # Sébastien Lasserre , 2017. # msgid "" msgstr "" "Project-Id-Version: mmlib 1.2.0\n" "Report-Msgid-Bugs-To: nicolas.bourdaud@mindmaze.com\n" "POT-Creation-Date: 2020-03-10 17:34+0100\n" "PO-Revision-Date: 2020-03-10 17:41+0100\n" "Last-Translator: Nicolas Bourdaud \n" "Language-Team: project management \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/error.c:42 msgid "The device has been disconnected." msgstr "Il dispositivo è stato disconnesso." #: src/error.c:43 msgid "Object in wrong state" msgstr "Oggetto in uno stato non corretto" #: src/error.c:44 msgid "Object not found" msgstr "Oggetto non trovato" #: src/error.c:45 msgid "Bad format" msgstr "Formato non corretto" #: src/error.c:47 msgid "Specified hostname cannot be resolved" msgstr "Il nome host specificato non può essere risolto" #~ msgid "User unknown" #~ msgstr "Utente sconosciuto" #~ msgid "Wrong password" #~ msgstr "Password errata" #~ msgid "Too many entities have been requested" #~ msgstr "Troppi elementi richiesti" #~ msgid "Calibration needed" #~ msgstr "Calibrazione necessaria" #~ msgid "" #~ "Hand trackers not detected.\n" #~ "Please ensure the USB dongle is connected \n" #~ "and the sensors are switched on" #~ msgstr "" #~ "HandTracker non rilevati.\n" #~ "Assicurarsi che il supporto USB sia collegato\n" #~ "e i sensori siano attivi" #~ msgid "Communication error with camera hardware." #~ msgstr "Errore di comunicazione con l'hardware della fotocamera." mmlib-1.4.2/po/meson.build000066400000000000000000000006361435717460000154120ustar00rootroot00000000000000# use meson internationalization module, ie that generates/updates po and pot # files i18n = import('i18n') i18n.gettext(meson.project_name(), args: [ '--keyword=_', '--keyword=N_', '--copyright-holder=Mindmaze SA', '--package-name=' + meson.project_name(), '--package-version=' + meson.project_version(), '--msgid-bugs-address=nicolas.bourdaud@gmail.com', ] ) mmlib-1.4.2/po/mmlib.pot000066400000000000000000000015751435717460000150770ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Mindmaze SA # This file is distributed under the same license as the mmlib package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: mmlib 1.2.0\n" "Report-Msgid-Bugs-To: nicolas.bourdaud@mindmaze.com\n" "POT-Creation-Date: 2020-03-10 17:34+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: src/error.c:42 msgid "The device has been disconnected." msgstr "" #: src/error.c:43 msgid "Object in wrong state" msgstr "" #: src/error.c:44 msgid "Object not found" msgstr "" #: src/error.c:45 msgid "Bad format" msgstr "" #: src/error.c:47 msgid "Specified hostname cannot be resolved" msgstr "" mmlib-1.4.2/read_version.py000066400000000000000000000002061435717460000156550ustar00rootroot00000000000000#!/usr/bin/env python3 from pathlib import Path from sys import stdout stdout.write(open(Path(__file__).parent / 'VERSION').read()) mmlib-1.4.2/src/000077500000000000000000000000001435717460000134145ustar00rootroot00000000000000mmlib-1.4.2/src/Makefile.am000066400000000000000000000061671435717460000154620ustar00rootroot00000000000000eol= AM_CPPFLAGS = \ -DLOCALEDIR=\"$(localedir)\" \ -DSYSCONFDIR=\"$(sysconfdir)\" \ -DLIBEXECDIR='"$(pkglibexecdir)"' \ $(eol) AM_CFLAGS = $(MM_WARNFLAGS) include_HEADERS = \ mmlog.h \ mmerrno.h \ mmprofile.h \ mmpredefs.h \ mmlib.h \ mmtime.h \ mmsysio.h \ mmthread.h \ mmdlfcn.h \ mmargparse.h \ $(eol) noinst_LTLIBRARIES = libmmlib-internal-wrapper.la lib_LTLIBRARIES = libmmlib.la pkglibexec_PROGRAMS = libmmlib_la_SOURCES = libmmlib_la_LIBADD = libmmlib-internal-wrapper.la libmmlib_la_LDFLAGS = \ $(AM_LDFLAGS) \ -no-undefined \ -version-info $(CURRENT):$(REVISION):$(AGE) \ $(eol) libmmlib_la_LDFLAGS += \ -Wl,--version-script -Wl,$(srcdir)/libmmlib.map \ $(eol) libmmlib_internal_wrapper_la_SOURCES = \ mmlog.h log.c \ nls-internals.h \ mmerrno.h error.c \ mmprofile.h profile.c \ mmlib.h \ alloc.c \ utils.c \ mmargparse.h argparse.c \ mmtime.h time.c \ mmsysio.h \ file.c file-internal.h \ socket-internal.h \ socket.c \ mmthread.h \ mmdlfcn.h dlfcn.c \ $(eol) libmmlib_internal_wrapper_la_LIBADD = \ @LTLIBINTL@ \ $(DL_LIB) \ $(eol) if OS_TYPE_POSIX libmmlib_internal_wrapper_la_SOURCES += \ time-posix.c \ file-posix.c \ shm-posix.c \ process-posix.c \ local-ipc-posix.c \ thread-posix.c \ socket-posix.c \ $(eol) libmmlib_internal_wrapper_la_LIBADD += \ $(CLOCK_LIB) \ $(SHM_LIB) \ $(PTHREAD_LIB) \ $(eol) endif if OS_TYPE_WIN32 AM_CPPFLAGS += \ -DWIN32_LEAN_AND_MEAN \ -D_WIN32_WINNT=_WIN32_WINNT_WIN8 \ -DMMLIB_API=API_EXPORTED \ -D__USE_MINGW_ANSI_STDIO=1 \ $(eol) libmmlib_internal_wrapper_la_SOURCES += \ time-win32.c \ clock-win32.h clock-win32.c \ utils-win32.h utils-win32.c \ file-win32.c \ shm-win32.c \ process-win32.c \ local-ipc-win32.h local-ipc-win32.c \ thread-win32.c \ atomic-win32.h \ mutex-lockval.h \ lock-referee-proto.h \ pshared-lock.h pshared-lock.c \ socket-win32.h socket-win32.c \ env-win32.c \ startup-win32.c \ volume-win32.h volume-win32.c \ $(eol) libmmlib_internal_wrapper_la_LIBADD += \ -lpowrprof \ -lws2_32 \ $(eol) if LOCKSERVER_IN_MMLIB_DLL libmmlib_internal_wrapper_la_SOURCES += \ lock-referee-server.c \ $(eol) else #!LOCKSERVER_IN_MMLIB_DLL pkglibexec_PROGRAMS += lock-referee lock_referee_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DMMLOG_MODULE_NAME=\"lock-referee\" \ $(eol) lock_referee_SOURCES = \ lock-referee-proto.h \ lock-referee-server.c \ clock-win32.h clock-win32.c \ $(eol) lock_referee_LDADD = \ -lpowrprof \ -ladvapi32 \ $(eol) endif #!LOCKSERVER_IN_MMLIB_DLL endif #OS_TYPE_WIN32 # check all the source files for correct coding style .PHONY: checkstyle checkstyle: ../tools/uncrustify.cfg cd $(srcdir) && uncrustify -c ../tools/uncrustify.cfg -l C -q --check \ $(libmmlib_internal_wrapper_la_SOURCES) .PHONY: fixstyle fixstyle: ../tools/uncrustify.cfg cd $(srcdir) && uncrustify -c ../tools/uncrustify.cfg -l C -q --replace --no-backup \ $(libmmlib_internal_wrapper_la_SOURCES) .PHONY: spelling spelling: $(libmmlib_internal_wrapper_la_SOURCES) $(AM_V_at) codespell -x $(top_srcdir)/tools/codespell-ignore $^ .PHONY: api-compat-test api-compat-test: ../tools/api-compat-test.sh $(include_HEADERS) $^ mmlib-1.4.2/src/alloc.c000066400000000000000000000074521435717460000146620ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmlib.h" #include "mmerrno.h" #include #include "mmpredefs.h" #ifdef HAVE__ALIGNED_MALLOC #include #endif static void* internal_aligned_alloc(size_t alignment, size_t size) { void * ptr = NULL; #if defined (HAVE_POSIX_MEMALIGN) int ret = posix_memalign(&ptr, alignment, size); if (ret) { errno = ret; ptr = NULL; } #elif defined (HAVE_ALIGNED_ALLOC) ptr = aligned_alloc(alignment, size); #elif defined (HAVE__ALIGNED_MALLOC) if (!MM_IS_POW2(alignment) || (alignment < sizeof(void*))) { ptr = NULL; errno = EINVAL; } else { ptr = _aligned_malloc(size, alignment); } #else # error Cannot find aligned allocation primitive #endif /* if defined (HAVE_POSIX_MEMALIGN) */ return ptr; } /** * mm_aligned_alloc() - Allocate memory on a specified alignment boundary. * @alignment: alignment value, must be a power of 2 * @size: size of the requested memory allocation * * This allocates a block of @size bytes whose address is a multiple of * @alignment. * * Use mm_aligned_free() to deallocate data returned by mm_aligned_alloc(). * * Returns: A pointer to the memory block that was allocated in case of * success. Otherwise NULL is returned and error state set accordingly */ API_EXPORTED void* mm_aligned_alloc(size_t alignment, size_t size) { void * ptr = internal_aligned_alloc(alignment, size); if (!ptr) { mm_raise_from_errno("Cannot allocate buffer " "(alignment=%zu, size=%zu)", alignment, size); return NULL; } return ptr; } /** * mm_aligned_free() - Free memory allocated with mm_aligned_alloc() * @ptr: data to dellocate * * This function cause the space pointed to by @ptr to be deallocated. If * ptr is a NULL pointer, no action occur (this is not an error). Otherwise * the behavior is undefined if the space has not been allocated with * mm_aligned_alloc(). */ API_EXPORTED void mm_aligned_free(void* ptr) { #ifdef HAVE__ALIGNED_MALLOC _aligned_free(ptr); #else free(ptr); #endif } /** * _mm_malloca_on_heap() - heap memory allocation version of mm_malloca() * @size: size of memory to be allocated * * Function called when mm_malloca() cannot allocate on stack because @size is * too big. The allocation will be attempted on heap. * * NOTE: although this is function is exported, this should not be used * anywhere excepting by the mm_malloca() macro. * * Return: the pointer on allocated memory in case of success. The return value * is then ensured to be of value MM_STK_ALIGN modulo (2*MM_STK_ALIGN). This * particularity will be used to recognize when a memory block has been * allocated on stack or on heap. In case of failure (likely due to @size too * large), NULL is returned. */ API_EXPORTED void* _mm_malloca_on_heap(size_t size) { char * ptr; size_t alloc_size; // Increase allocated size to guarantee alignment requirement alloc_size = size + 2*MM_STK_ALIGN; if (alloc_size < size) { mm_raise_error(ENOMEM, "size=%zu is too big", size); return NULL; } // Allocate memory block ptr = internal_aligned_alloc(2*MM_STK_ALIGN, alloc_size); if (ptr == NULL) { mm_raise_from_errno("malloca_on_heap(%zu) failed", alloc_size); return NULL; } // Get pointer aligned on MM_STK_ALIGN modulo (2*MM_STK_ALIGN) ptr += MM_STK_ALIGN; return ptr; } /** * _mm_freea_on_heap() - deallocate memory when mm_malloca() has used heap * @ptr: memory block to deallocate * * Function called when mm_freea() has detected that @ptr has been allocated on * heap. * * NOTE: although this is function is exported, this should not be used * anywhere excepting by the mm_freea() macro. */ API_EXPORTED void _mm_freea_on_heap(void* ptr) { char* base = ptr; mm_aligned_free(base - MM_STK_ALIGN); } mmlib-1.4.2/src/argparse.c000066400000000000000000001240231435717460000153660ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "file-internal.h" #include "mmargparse.h" #include "mmerrno.h" #include "mmlib.h" #include "mmsysio.h" #include #include #include #include #include #include #include #include #define OPT_INDENT_LEN 30 #define LINE_MAXLENGTH 80 #define IGNORE_KEY 0 #define VALUE_NAME_MAXLEN 16 #define LONGOPT_NAME_MAXLEN 32 #define OPT_SYNOPSIS_MAXLEN (VALUE_NAME_MAXLEN + LONGOPT_NAME_MAXLEN+16) static const struct mm_arg_opt help_opt = { .name = "h|help", .flags = MM_OPT_NOVAL, .desc = "print this message and exit", }; struct value_type_name { int type; const char* name; }; static const struct value_type_name typenames[] = { {.type = MM_OPT_STR, .name = "string"}, {.type = MM_OPT_INT, .name = "int"}, {.type = MM_OPT_LLONG, .name = "long long"}, {.type = MM_OPT_UINT, .name = "unsigned int"}, {.type = MM_OPT_ULLONG, .name = "unsigned long long"}, }; static const struct mm_arg_opt* find_opt(const struct mm_arg_parser* parser, int key, const char* name, int namelen); /** * get_value_type_name() - get string describing type name * @type: MM_OPT_* flags specifying a type * * Return: The type string, or "unknown" if not found. */ static const char* get_value_type_name(int type) { int i; for (i = 0; i < MM_NELEM(typenames); i++) { if (typenames[i].type == type) return typenames[i].name; } return "unknown"; } /** * get_first_token_length() - get length of the first substring * @str: null-terminated string to split * @breakch: character at which @str must be split * * This function search for the first occurrence of @breakch and report the * length string if it were cut at this position. In any case, @str will * not be modified. * * Return: the length of the first substring found if breaking @str at * @breakch. If @breakch cannot be found, the full length of string will be * returned. In both case, the reported length excludes @breakch or the * null-termination. */ static int get_first_token_length(const char* str, char breakch) { char ch; int len = 0; ch = str[0]; while (ch != '\0' && ch != breakch) ch = str[++len]; return len; } /** * is_valid_short_opt_key() - check option key name belong to valid set * @ch: character of short option key * * a valid key must be a letter in ascii set (upper or lower case) * * Return: true if key is valid, false otherwise */ static bool is_valid_short_opt_key(int ch) { return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')); } /** * is_valid_long_opt_name() - check long option name is valid * @name: null terminated string of option name (without --) * @stop_at_equal: if true, stop inspecting @name once '=' is found * * a valid name contains only lower case letter in ascii set, numeric * characters and hyphens. * * Return: true if @name is valid, false otherwise */ static bool is_valid_long_opt_name(const char* name, bool stop_at_equal) { int i; char ch; // long name must start with letter ch = name[0]; if (ch < 'a' || ch > 'z') return false; for (i = 1; name[i]; i++) { ch = name[i]; if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-')) continue; if (ch == '=' && stop_at_equal) break; return false; } // Final check: name must not be too short return (i >= 2) ? true : false; } /** * is_arg_an_option() - check string has a format of option * @arg: null terminated string to test * * Return: true is @arg has the format of an option, false otherwise */ static bool is_arg_an_option(const char* arg) { if (!arg || arg[0] != '-') return false; // Check arg has short name option format if (arg[1] != '-' && arg[1] != '\0') return is_valid_short_opt_key(arg[1]); // Check arg has long name option format if (arg[2] != '-' && arg[2] != '\0') return is_valid_long_opt_name(arg+2, true); return false; } /** * is_char_valid_for_value_name() - check a char is valid for value name * @ch: character to check * * Return: true if @ch is valid, false otherwise */ static bool is_char_valid_for_value_name(char ch) { if ((ch >= 'A' && ch <= 'Z') || (ch == '-') || (ch == '_')) return true; return false; } /************************************************************************* * * * Help printing functions * * * *************************************************************************/ /** * validate_value_name() - check whether string is suitable as value name * @str: string to try * * Inspect the next character of @str and test whether they form a suitable * name for a value (in option description). A suitable name : * - is composed of capitals and '-' and '_' character, * - contains at least one valid character * - is not longer than VALUE_NAME_MAXLEN * * Since the value name is referenced with '@' prefixed in description, * this function is meant to be called when : * * 1. option description is scanned and no value name has been found yet. * 2. a '@' has been found in description * * Return: the length of the name if a suitable name has been found, 0 * otherwise */ static int validate_value_name(const char* str) { int i; for (i = 0; (i < VALUE_NAME_MAXLEN) && (str[i] != '\0'); i++) { if (is_char_valid_for_value_name(str[i])) continue; // Value name must be at least one character if (i > 1) return i; } // name is too long, so invalid return 0; } /** * match_value() - test whether the string match a value name * @str: null-terminated string to test * @valname: value name to test * @namelen: length of @valname * * Test if @str match @valname up to @namelen character AND if the next * character in @str if NOT character valid for a value name. * * Return: true in case of match, false otherwise */ static bool match_value(const char* str, const char* valname, int namelen) { int i; for (i = 0; i < namelen; i++) { if (valname[i] != str[i]) return false; } // The match must be greedy, so if name could continue, we don't // have a match if (is_char_valid_for_value_name(str[namelen])) return false; return true; } /** * copy_opt_desc() - copy option description while guessing value name * @dst: destination buffer where modified description is copied * @src: description buffer as it is in &mm_arg_opt.desc * @buffer: buffer that will hold the value name if found * * Copy option description from @src to @src while trying to guess the name * of option value as referred in the description. The value name will be * the first occurrence of a token of the form '@VALUENAME'. If one is * found, @buffer will be filled with the value name (null-terminated, but * without '@') and all occurrence of '@VALUENAME' in @src will be replace * in @dst by 'VALUENAME'. */ static void copy_opt_desc(char* dst, const char* src, char* buffer) { int namelen = 0; char* valname = NULL; const char* start = src; while (*src) { if ((*src == '@') && ((start == src) || !isalnum(src[-1]))) { if (!valname) { namelen = validate_value_name(src+1); if (namelen) { // Store value name memcpy(buffer, src+1, namelen); buffer[namelen] = '\0'; valname = buffer; // Copy value name to destination memcpy(dst, valname, namelen); dst += namelen; src += namelen+1; continue; } } else { if (match_value(src+1, valname, namelen)) { // Copy value name to destination memcpy(dst, valname, namelen); dst += namelen; src += namelen+1; continue; } } } *dst++ = *src++; } *dst = '\0'; } /** * print_text_wrapped() - print wrapped text on stream with alignment * @line_maxlen: maximum length of a line when printing * @text: text to print * @align_len: number of column to skip before printing @text * @header: header to print only the first line (in alignment * zone). This may be NULL and would be treated as "" * in such a case. * @stream: output stream on @text must be written * * This function print @text on standard error ensuring that each displayed * line will not be longer than @line_maxlen, wrapping text if necessary at * whitespace boundaries. The wrapping will respect the linefeed set in * @text. In addition the content of @text will be displayed after applying * an indent of @align_len character. Moreover, if @header is not null, it * will be printed on the first line displayed, in the indentation zone. * * As an example, here is how the LOREM_IPSUM string would be displayed * with print_text_wrapped(60, LOREM_IPSUM, 10, "head_str") : * * head_str Lorem ipsum dolor sit amet, consectetur adipiscing * elit, sed do eiusmod tempor incididunt ut labore * et dolore magna aliqua. Ut enim ad minim veniam, * quis nostrud exercitation ullamco laboris nisi ut * aliquip ex ea commodo consequat... */ static void print_text_wrapped(int line_maxlen, const char* text, int align_len, const char* header, FILE* stream) { int len, textline_maxlen; if (!header) header = ""; textline_maxlen = line_maxlen - align_len; do { // Get the length of the next part to print len = get_first_token_length(text, '\n'); // Check whether the text is too long and must be wrapped if (len >= textline_maxlen) { // Find a good place to split the string len = textline_maxlen; while (text[len] != ' ') { if (--len == 0) { len = textline_maxlen; break; } } } fprintf(stream, "%-*s%.*s\n", align_len, header, len, text); // Skip first linefeed since it has been displayed // already by the previous fprintf() if (text[len] == '\n') len++; // skip spaces while (text[len] == ' ') len++; text += len; header = ""; } while (*text != '\0'); } /** * print_synopsis() - print program synopsis on stream * @parser: argument parser * @stream: output stream on which the synopsis must be written * * Print on the standard error the synopsis of the program. If * @parser->execname is not set, "PROGRAM" will be used instead. For each * line in @parser->args_doc, a synopsis line is expanded. If * @parser->args_doc is NULL, a generic synopsis in printed. */ static void print_synopsis(const struct mm_arg_parser* parser, FILE* stream) { int len = 0; const char* args_doc; const char* execname; // Get program name from parser or use generic program if NULL execname = parser->execname; if (!execname) execname = "PROGRAM"; // Get synopsis lines from parser or use a generic one if NULL args_doc = parser->args_doc; if (!args_doc) args_doc = "[options] args..."; // For each line in args_doc, print a usage line while (args_doc[0] != '\0') { // Get length of next argument list doc (terminated by \n) len = get_first_token_length(args_doc, '\n'); fprintf(stream, " %s %.*s\n", execname, len, args_doc); // Skip '\n' character while (args_doc[len] == '\n') len++; // Update to point to the next synopsis line args_doc += len; } } /** * set_option_synopsis() - format an option synopsis string * @synopsis: buffer receiving the option string * @opt: option parser * @valname: the value name as it must appear in the synopsis. If NULL * and if option accepts value, the string "VALUE" is used * instead. * * Return: length of synopsis (excluding null termination character) */ static int set_option_synopsis(char* synopsis, const struct mm_arg_opt* opt, const char* valname) { char* str = synopsis; int key = mm_arg_opt_get_key(opt); const char* name = mm_arg_opt_get_name(opt); int reqflags = opt->flags & MM_OPT_REQMASK; // Insert small indentation *str++ = ' '; *str++ = ' '; // Add short option name synopsis if (key) { if (reqflags == MM_OPT_NOVAL) str += sprintf(str, "-%c", key); else if (reqflags == MM_OPT_NEEDVAL) str += sprintf(str, "-%c %s", key, valname); else str += sprintf(str, "-%c [%s]", key, valname); } // Add separator if short and long name are provided if (key && name) { *str++ = ','; *str++ = ' '; } // Add long name option synopsis if (name) { if (reqflags == MM_OPT_NOVAL) str += sprintf(str, "--%s", name); else if (reqflags == MM_OPT_NEEDVAL) str += sprintf(str, "--%s=%s", name, valname); else str += sprintf(str, "--%s[=%s]", name, valname); } // return the number of characters written in synopsis return str - synopsis; } /** * print_option() - print option doc on stream * @opt: option whose documentation must be printed * @stream: output stream on which the option doc must be written */ static void print_option(const struct mm_arg_opt* opt, FILE* stream) { char synopsis[OPT_SYNOPSIS_MAXLEN]; char type_doc[VALUE_NAME_MAXLEN+64]; char value_name[VALUE_NAME_MAXLEN] = "VALUE"; const char * opt_desc; char* desc; int len, desc_len; int type = mm_arg_opt_get_type(opt); int reqflags = opt->flags & MM_OPT_REQMASK; bool is_positive; opt_desc = opt->desc ? opt->desc : ""; desc_len = strlen(opt_desc); desc = mm_malloca(desc_len + sizeof(type_doc)); copy_opt_desc(desc, opt_desc, value_name); // Set option synopsis len = set_option_synopsis(synopsis, opt, value_name); // Write synopsis on its own line if too long if (len >= OPT_INDENT_LEN) { fprintf(stream, "%s\n", synopsis); synopsis[0] = '\0'; } // Append value type specification if not string type if (type != MM_OPT_STR && reqflags != MM_OPT_NOVAL) { is_positive = (type == MM_OPT_UINT || type == MM_OPT_ULLONG); sprintf(type_doc, "%s%s must be a%s integer.", desc_len ? " " : "", value_name, is_positive ? " non negative" : "n"); strcat(desc, type_doc); } print_text_wrapped(LINE_MAXLENGTH, desc, OPT_INDENT_LEN, synopsis, stream); mm_freea(desc); } /** * print_help() - print usage help on stream * @parser: argument parser whose help must be printed * @stream: output stream on which usage must be written */ static void print_help(const struct mm_arg_parser* parser, FILE* stream) { int i; const char* doc = parser->doc; fprintf(stream, "Usage:\n"); print_synopsis(parser, stream); if (doc) { fputc('\n', stream); print_text_wrapped(LINE_MAXLENGTH, doc, 0, NULL, stream); fputc('\n', stream); } fprintf(stream, "\nOptions:\n"); for (i = 0; i < parser->num_opt; i++) print_option(&parser->optv[i], stream); print_option(&help_opt, stream); } /************************************************************************* * * * Completion helper functions * * * *************************************************************************/ static int is_completing(const struct mm_arg_parser* parser) { return parser->flags & MM_ARG_PARSER_COMPLETION; } /** * complete_longopt() - print long option completion candidate * @opt: pointer to opt * @len: length of str_start * @name_start: beginning of option name supplied on cmdline * * If beginning of long option name of @opt match @str_start, then print on * standard output the synopsis of the long option form of @opt. */ static void complete_longopt(const struct mm_arg_opt* opt, int len, const char* name_start) { const char* lname = mm_arg_opt_get_name(opt); // Filter completion candidate: the supplied argument must match the // beginning of option name if (!lname || strncmp(name_start, lname, len)) return; switch (opt->flags & MM_OPT_REQMASK) { case MM_OPT_NOVAL: printf("--%s\n", lname); break; case MM_OPT_NEEDVAL: printf("--%s=\n", lname); break; case MM_OPT_OPTVAL: printf("--%s=\n", lname); printf("--%s\n", lname); break; } } /** * complete_longopts() - generate list of long option for completion * @parser: argument parser configuration * @arg: beginning of argument supplied on cmdline that should be * completed. To be passed without leading "--" (can be NULL) * * Print on standard output the list long options compatible with the partial * option name specified by @arg. If @arg is NULL, it is assumed to * be empty. * * Return: always MM_ARGPARSE_COMPLETE */ static int complete_longopts(const struct mm_arg_parser* parser, const char* arg) { int i, len; if (arg == NULL) arg = ""; len = strlen(arg); for (i = 0; i < parser->num_opt; i++) complete_longopt(&parser->optv[i], len, arg); complete_longopt(&help_opt, len, arg); return MM_ARGPARSE_COMPLETE; } /** * complete_shortopts() - generate list of short option for completion * @arg: beginning of argument supplied on cmdline that should be * completed. To be passed without leading "-" (can be NULL) * @parser: argument parser configuration * * Return: always MM_ARGPARSE_COMPLETE */ static int complete_shortopts(const struct mm_arg_parser* parser, const char* arg) { int i, key, len; if (arg == NULL) arg = ""; len = strlen(arg); // Verify that all previous short option in the argument are // recognized. If not, do not propose any completion. for (i = 0; i < len; i++) { if (!find_opt(parser, arg[i], NULL, 0)) return MM_ARGPARSE_COMPLETE; } // If there is already options in argument, propose only the current // argument as completion, this will make completion add space and // move to a new argument if (len != 0) { printf("-%s\n", arg); return MM_ARGPARSE_COMPLETE; } // We have currently an empty argument, loop over all option // providing short key display tham as completion proposal for (i = 0; i < parser->num_opt; i++) { key = mm_arg_opt_get_key(&parser->optv[i]); if (key) printf("-%c\n", key); } printf("-h\n"); return MM_ARGPARSE_COMPLETE; } /** * complete_opt_value() - gen list of option value acceptable for option * @parser: argument parser configuration * @opt: option whose value is being completed * @arg: beginning of value supplied on cmdline * * Return: always MM_ARGPARSE_COMPLETE */ static int complete_opt_value(const struct mm_arg_parser* parser, const struct mm_arg_opt* opt, const char* arg) { int rv, flags; union mm_arg_val value = {.str = arg}; if (parser->cb) { rv = parser->cb(opt, value, parser->cb_data, MM_ARG_OPT_COMPLETION); if (rv) return MM_ARGPARSE_COMPLETE; } if (opt->flags & (MM_OPT_FILEPATH | MM_OPT_DIRPATH)) { flags = 0; if (opt->flags & MM_OPT_FILEPATH) flags |= MM_DT_REG | MM_DT_DIR; if (opt->flags & MM_OPT_DIRPATH) flags |= MM_DT_DIR; mm_arg_complete_path(arg, flags, NULL, NULL); } else if (arg) { printf("%s\n", arg); } return MM_ARGPARSE_COMPLETE; } /************************************************************************* * * * Argument processing functions * * * *************************************************************************/ /** * match_opt_key_or_name() - test whether a name or key match an option * @opt: option to try * @key: key to test against @opt. If @key is not set to IGNORE_KEY, * the matching is done based on short option key. Otherwise * the matching is done based on long name option (using @name * and @namelen) * @name: string of name to test against long name of @opt. This * string does NOT need to be null terminated * @namelen: length of @name string * * Return: true if supplied key or name match @opt, false otherwise */ static bool match_opt_key_or_name(const struct mm_arg_opt* opt, int key, const char* name, int namelen) { const char* opt_name; // If key is not IGNORE_KEY, the matching MUST be done based on key if (key != IGNORE_KEY) return (key == mm_arg_opt_get_key(opt)); // Do matching based on long since key is IGNORE_KEY opt_name = mm_arg_opt_get_name(opt); if (opt_name != NULL && !strncmp(opt_name, name, namelen) && opt_name[namelen] == '\0') return true; return false; } /** * find_opt() - find the option that match supplied key or name * @parser: argument parser * @key: key to test against the different option of @parser. * matching is based on key or name depending on whether @key * is set to IGNORE_KEY or not, see match_opt_key_or_name(). * @name: string of name to test against long name options. This * string does NOT need to be null terminated * @namelen: length of @name string * * Search among the options of @parser to find a matching one. If @key is * 'h' or @name is "help", this function will print the usage on standard * error and exit. * * Return: pointer to option if found among the configured options of * @parser. NULL if option is not found. Please note that is @key or @name * appears to be a request to display help, the function does not return. */ static const struct mm_arg_opt* find_opt(const struct mm_arg_parser* parser, int key, const char* name, int namelen) { int i, num_opt; const struct mm_arg_opt* opt; // Test in user provided options num_opt = parser->num_opt; opt = parser->optv; for (i = 0; i < num_opt; i++, opt++) { if (match_opt_key_or_name(opt, key, name, namelen)) return opt; } // Check option is not help if (match_opt_key_or_name(&help_opt, key, name, namelen)) { return &help_opt; } return NULL; } /** * print_opt_error() - print message error related to an option * @opt: option relative to which the error has been detected * @msg: string containing the message. * * Print a error message on standard error by prefixing the option name * before displaying @msg. */ static void print_opt_error(const struct mm_arg_opt* opt, const char* msg, ...) { va_list args; const char* name = mm_arg_opt_get_name(opt); int key = mm_arg_opt_get_key(opt); if (name && key) fprintf(stderr, "Option -%c|--%s ", key, name); else if (name) fprintf(stderr, "Option --%s ", name); else fprintf(stderr, "Option -%c ", key); va_start(args, msg); vfprintf(stderr, msg, args); va_end(args); fputc('\n', stderr); } /** * cast_ll_to_argval() - copy long long value into proper field of argval * @opt: option parser specifying which cast to perform * @argval: pointer to argval union that will receive the result * @llval: long long value to cast * * Return: 0 in case of success, -1 otherwise (cast problem) */ static int cast_ll_to_argval(const struct mm_arg_opt* opt, union mm_arg_val * argval, long long llval) { int type = mm_arg_opt_get_type(opt); switch (type) { case MM_OPT_LLONG: argval->ll = llval; break; case MM_OPT_INT: if (llval < INT_MIN || llval > INT_MAX) goto error; argval->i = llval; break; case MM_OPT_UINT: if (llval < 0 || llval > UINT_MAX) goto error; argval->ui = llval; break; default: errno = EINVAL; return -1; } return 0; error: errno = ERANGE; return -1; } /** * check_value_is_positive() - verify string is not negative integer * @str: string for value * * Return: 0 if @str does not represent a negative value, -1 otherwise with * errno set to ERANGE. */ static int check_value_is_positive(const char* str) { // Skip whitespace while (isspace(*str)) str++; // assert leading non whitespace character is not minus if (*str == '-') { errno = ERANGE; return -1; } return 0; } /** * conv_str_to_argval() - convert string into suitable type for option * @opt: option specifying which conversion must be performed. * @argval: pointer to argval union that will receive the result * @value: string of the value to convert * * Return: 0 in case of success, -1 otherwise */ static int conv_str_to_argval(const struct mm_arg_opt* opt, union mm_arg_val * argval, const char* value) { int type = mm_arg_opt_get_type(opt); long long llval = 0; // shut up false warning about uninitialized // llval when type == MM_OPT_ULLONG char* endptr; const char* valtype; int prev_err = errno; if (type == MM_OPT_STR) { argval->str = value; return 0; } // Value type is not string, hence it requires a conversion. So now // ensure that value is not empty if (!value || value[0] == '\0') { errno = EINVAL; goto error; } // Convert all strings as long long excepting if requesting // ulonglong errno = 0; if (type == MM_OPT_ULLONG) { // Prevent to convert negative value (strtoull() would // accept them) if (check_value_is_positive(value)) { errno = ERANGE; goto error; } argval->ull = strtoull(value, &endptr, 0); } else { llval = strtoll(value, &endptr, 0); } // Check the whole string has been used for conversion if (*endptr != '\0') { errno = EINVAL; goto error; } if (errno != 0) goto error; // If not requesting ulonglong, do final conversion of longlong to // requested type if (type != MM_OPT_ULLONG && cast_ll_to_argval(opt, argval, llval)) goto error; errno = prev_err; return 0; error: valtype = get_value_type_name(type); print_opt_error(opt, "accepting %s value type has received an " "invalid value \"%s\" (%s)", valtype, value, strerror(errno)); return -1; } /** * mm_arg_opt_set_value() - set value to supplied pointer * @opt: option whose value has to be set * @val: value to set. Can be NULL if case of string type option. * * Set value if @opt->strptr (or any aliased pointer) is not NULL. In such * case, the conversion is performed (if needed) according to the type * specified in @opt->flags. * * Return: 0 in case of success, -1 otherwise */ static int mm_arg_opt_set_value(const struct mm_arg_opt* opt, union mm_arg_val val) { int type = mm_arg_opt_get_type(opt); // Ignore setting value if no pointer has been set if (!opt->val.sptr) return 0; switch (type) { case MM_OPT_STR: *opt->val.sptr = val.str; break; case MM_OPT_INT: *opt->val.iptr = val.i; break; case MM_OPT_UINT: *opt->val.uiptr = val.ui; break; case MM_OPT_LLONG: *opt->val.llptr = val.ll; break; case MM_OPT_ULLONG: *opt->val.ullptr = val.ull; break; default: print_opt_error(opt, "has unknown value type"); return -1; } return 0; } /** * process_opt_value() - process argument value * @opt: matched option * @value: string of value if one has been supplied, NULL otherwise * @parser: parser used * * Return: 0 in case of success, or MM_ARGPARSE_ERROR (-1) if a validation issue * has occurred and MM_ARGPARSE_STOP (-2) if early stop has been requested. */ static int process_opt_value(const struct mm_arg_opt* opt, const char* value, const struct mm_arg_parser* parser) { void* cb_data = parser->cb_data; mm_arg_callback cb = parser->cb; int reqflags = opt->flags & MM_OPT_REQMASK; union mm_arg_val argval; int rv; // If the recognized option is the help option added internally, just // print help and return stop if (opt == &help_opt) { if (is_completing(parser)) return 0; print_help(parser, stdout); return MM_ARGPARSE_STOP; } if ((reqflags == MM_OPT_NOVAL) && value) { print_opt_error(opt, "does not accept any value."); return MM_ARGPARSE_ERROR; } if ((reqflags == MM_OPT_NEEDVAL) && !value) { print_opt_error(opt, "needs value."); return MM_ARGPARSE_ERROR; } if (!value) value = opt->defval; // Convert string value to argval union and run callback if one is // present if ((rv = conv_str_to_argval(opt, &argval, value)) < 0 || (cb && (rv = cb(opt, argval, cb_data, 0)) < 0)) return rv; // set value if specified in option parser return mm_arg_opt_set_value(opt, argval); } /** * process_short_opt() - find and parses option assuming short key * @parser: argument parser to use * @opts: string of concatenated options (without -) * @next_arg: pointer to next argument if present, NULL otherwise * @next_is_last: set to one if @next_arg is the last argument of cmdline * * Return: a non-negative number indicating the number of next argument to * skip in case of success. -1 in case of failure */ static int process_short_opt(const struct mm_arg_parser* parser, const char* opts, const char* next_arg, int next_is_last) { const struct mm_arg_opt* opt_parser; const char* value = NULL; int move_arg_index = 0; int rv, reqflags; if (is_completing(parser) && !next_arg) return complete_shortopts(parser, opts); while (opts[0] != '\0') { opt_parser = find_opt(parser, opts[0], NULL, 0); if (!opt_parser) { fprintf(stderr, "Unsupported option -%c\n", opts[0]); return -1; } // It is allowed to interpret the next argument as value // only if the option key is the last one of the list reqflags = opt_parser->flags & MM_OPT_REQMASK; if ((reqflags != MM_OPT_NOVAL) && (opts[1] == '\0') && !is_arg_an_option(next_arg)) { value = next_arg; move_arg_index = 1; if (is_completing(parser) && next_is_last) return complete_opt_value(parser, opt_parser, value); } rv = process_opt_value(opt_parser, value, parser); if (rv < 0) return rv; opts++; } // Advance in argument list if value was return move_arg_index; } /** * process_long_opt() - find and parses option assuming long option * @parser: argument parser to use * @arg: argument supplied (without --) * @do_complete: execute completion * * Return: 0 in case of success, -1 otherwise */ static int process_long_opt(const struct mm_arg_parser* parser, const char* arg, int do_complete) { const char* name; const char* value; int namelen, rv; const struct mm_arg_opt* opt; // Set the name and value token name = arg; namelen = get_first_token_length(arg, '='); value = (arg[namelen] == '=') ? arg+namelen+1 : NULL; // If we are in the mode of autocompletion mode, generate // the list of long options if we are still not writing value if (do_complete && arg[namelen] != '=') { complete_longopts(parser, arg); return MM_ARGPARSE_COMPLETE; } // Search for a matching option opt = find_opt(parser, IGNORE_KEY, name, namelen); if (!opt) { if (do_complete) return MM_ARGPARSE_COMPLETE; fprintf(stderr, "Unsupported option --%.*s\n", namelen, arg); return MM_ARGPARSE_ERROR; } if (do_complete) return complete_opt_value(parser, opt, value); // Process the found option if ((rv = process_opt_value(opt, value, parser)) < 0) return rv; return 0; } /** * validate_options() - validate options settings * @parser: argument parser to validate * * Return: 0 in case of success, -1 otherwise */ static int validate_options(const struct mm_arg_parser* parser) { int i, key; const char* lname; const struct mm_arg_opt* opt; for (i = 0; i < parser->num_opt; i++) { opt = &parser->optv[i]; // Ensure the name field is set if (!opt->name || opt->name[0] == '\0') { fprintf(stderr, "name in mm_arg_opt must be set\n"); return -1; } key = mm_arg_opt_get_key(opt); lname = mm_arg_opt_get_name(opt); // Validate option key and/or long option name if ((key && !is_valid_short_opt_key(key)) || (lname && !is_valid_long_opt_name(lname, false))) { fprintf(stderr, "invalid short or long name for " "option %s\n", opt->name); return -1; } } return 0; } /** * early_stop_parsing() - Stop argument parsing * @parser: argument parser configuration * @retval: code returned indicating why parsing is interrupted * * This function exits program if MM_ARG_PARSER_NOEXIT is not set in * @parser->flags. If the parsing has been interrupted because of parsing error * (@retval == -1), a small reminder how to use help is reported to stderr. * * Return: @retval if MM_ARG_PARSER_NOEXIT is set in @parser->flags (otherwise, * the function call exit) */ static int early_stop_parsing(const struct mm_arg_parser* parser, int retval) { int exitcode = EXIT_SUCCESS; if (retval == MM_ARGPARSE_ERROR) { fprintf(stderr, "Use -h or --help to display usage.\n"); exitcode = EXIT_FAILURE; } if (parser->flags & MM_ARG_PARSER_NOEXIT) return retval; exit(exitcode); } /** * mm_arg_parse() - parse command-line options * @parser: argument parser configuration * @argc: argument count as passed to main() * @argv: argument array as passed to main() * * This functions parses the arguments in argv, of length argc, as provided * by the argument of main() using the configuration set in @parser. The * supported options are specified in the @parser->optv array. Even if it is * not set in @parser->optv, a parser always supports the "-h" or "--help" * option. If encountered, the program usage will be printed on standard * output and the process will exit with EXIT_SUCCESS code (This behaviour * can be overridden if "h|help" is explicitly defined as option in * @parser->optv). If the parsing operation fails (because of invalid option * or value), the error diagnostic will be printed on standard error and the * process will exit with EXIT_FAILURE code. In other case, the parsing will * continued until "--" or a non optional argument is encountered. * * The value of @parser->flags is a OR-combination of any number of the * following flags : * * - %MM_ARG_PARSER_NOEXIT: the process will not exit in case of help printing * nor in case of error but mm_arg_parse() will return respectively * MM_ARGPARSE_STOP and MM_ARGPARSE_ERROR. * - %MM_ARG_PARSER_COMPLETION: the parser is invoked for being used in shell * completion script. In case of unknown option, the completed candidate * options are printed on standard output (in a format suitable for bash * completion script). * * There are 2 non-exclusive ways to get the values of the option supplied * on command line * * #. setting the struct mm_arg_opt->*ptr field to the data that must be set * when an option is found and parsed. * #. using the callback function @parser->cb and data @parser->cb_data. * * Return: a non negative value indicating the index of the first non-option * argument when argument parsing has been successfully finished. Additionally * if MM_ARG_PARSER_NOEXIT is set in @parser->flags : * * - MM_ARGPARSE_ERROR (-1): an error of argument parsing or validation occurred * - MM_ARGPARSE_STOP (-2): help display has been requested or early parsing * stop has been requested by callback. * - MM_ARGPARSE_COMPLETE (-3): parser was in completion mode and the last * argument has been completed (completion candidates have been printed on * output). */ API_EXPORTED int mm_arg_parse(const struct mm_arg_parser* parser, int argc, char* argv[]) { const char * arg, * next_arg; int index, r, do_complete; if (validate_options(parser)) return early_stop_parsing(parser, MM_ARGPARSE_ERROR); do_complete = 0; for (index = 1; index < argc; index++) { arg = argv[index]; // If opt has not the format of option, we must stop // processing options if (arg[0] != '-') break; // if arg has form "-string", process as short option if (is_valid_short_opt_key(arg[1])) { next_arg = (index+1 < argc) ? argv[index+1] : NULL; r = process_short_opt(parser, arg+1, next_arg, index+2 == argc); if (r < 0) return early_stop_parsing(parser, r); index += r; continue; } // If completion is enabled, run it on last argument if ((index == argc-1) && is_completing(parser)) do_complete = 1; // Complete if we have an incomplete option if ((arg[1] == '\0') && do_complete) { mm_arg_parse_complete(parser, arg); return early_stop_parsing(parser, MM_ARGPARSE_COMPLETE); } if (arg[1] != '-') break; // if arg is "--", position the argument index to the next // argument and stop processing options if ((arg[2] == '\0') && !do_complete) return index+1; // arg has the form of "--string", process as long option if ((r = process_long_opt(parser, arg+2, do_complete)) < 0) return early_stop_parsing(parser, r); } return index > argc ? argc : index; } /** * mm_arg_optv_parse() - parse command-line options * @optn: number of mm_arg_opt elements in optv * @optv: pointer to mm_arg_opt array * @argc: argument count as passed to main() * @argv: argument array as passed to main() * * This function wraps around mm_arg_parse and it takes care to create and * initialize a minimal mm_arg_parser structure. * It can be useful when no extended usage documentation is needed, as it * just provides the standard help function. * * Return: a non negative value indicating the index of the first non-option * argument when argument parsing has been successfully finished. */ API_EXPORTED int mm_arg_optv_parse(int optn, const struct mm_arg_opt* optv, int argc, char* argv[]) { struct mm_arg_parser parser = { .optv = optv, .num_opt = optn > 0 ? optn : 0, .execname = argv[0] }; return mm_arg_parse(&parser, argc, argv); } /** * mm_arg_parse_complete() - print list of opts of arg parser for completion * @parser: argument parser configuration * @arg: beginning of argument (can be NULL) * * This function print on standard output the list of option accepted by * parser and whose beginning match @arg (if supplied). This list is the * same format as bash compgen command. * * Return: 0 in case of success, MM_ARGPARSE_ERROR otherwise (validation * error occurred) */ API_EXPORTED int mm_arg_parse_complete(const struct mm_arg_parser* parser, const char* arg) { int len; if (validate_options(parser)) return early_stop_parsing(parser, MM_ARGPARSE_ERROR); if (!arg) return 0; len = strlen(arg); // Short option completion is triggered by "" and "-something" if (len < 1 || arg[0] == '-') complete_shortopts(parser, len >= 1 ? arg+1 : ""); // Long option completion is triggered by "", "-", "--something" if ((len == 0) || (len == 1 && arg[0] == '-') || (len >= 2 && arg[0] == '-' && arg[1] == '-')) complete_longopts(parser, len >= 2 ? arg+2 : ""); return 0; } /** * mm_arg_complete_path() - complete argument as path * @arg: beginning of argument (must not be NULL) * @type_mask: Combination of MM_DT_* flags indicating the desired file * @cb: user supplied completion callback (can be NULL) * @cb_data: pointer passed to @cb if it is not NULL * * This function print on standard output the list of path whose beginning * match @arg and whose type match the mask specified by type_mask. This * list has the same format as bash compgen command. * * More path candidates can be filtered out by supplying a callback through * the @cb argument. If not NULL, this function will be called for each * completion candidate which will be discarded if the callback does not * return 1. * * Return: 0 in case of success, -1 otherwise */ API_EXPORTED int mm_arg_complete_path(const char* arg, int type_mask, mm_arg_complete_path_cb cb, void* cb_data) { MM_DIR* dir; const struct mm_dirent* dirent; char * dirpath, * base; const char * disp_dir, * name; int i, arglen, baselen, type, isdir; int rv = -1; if (arg == NULL) return mm_raise_error(EINVAL, "arg pointer must not be NULL"); arglen = strlen(arg); base = mm_malloca(arglen + 1); dirpath = mm_malloca(arglen + 2); if (!base || !dirpath) goto exit; // If arg is written as a folder, start search from it, otherwise, // we compute its parent dir and use the basename as filter if (arglen == 0 || is_path_separator(arg[arglen-1])) { strcpy(dirpath, arglen ? arg : "./"); strcpy(base, ""); baselen = 0; } else { i = mm_dirname(dirpath, arg); dirpath[i++] = '/'; dirpath[i] = '\0'; baselen = mm_basename(base, arg); } // If argument has no separator, don't prepend "./" artificially to // completion candidates. disp_dir = ""; for (i = 0; i < arglen; i++) { if (is_path_separator(arg[i])) disp_dir = dirpath; } dir = mm_opendir(dirpath); if (!dir) goto exit; while (1) { dirent = mm_readdir(dir, NULL); if (!dirent) break; name = dirent->name; type = dirent->type; // Discard if base does not match the beginning of filename if (strncmp(name, base, baselen) != 0 || is_wildcard_directory(name)) continue; // Discard if a callback did not return 1 if (cb && cb(name, dirpath, type, cb_data) != 1) continue; // If type does not match expected, just discard if (!(type & type_mask)) continue; // Display candidate. If it a dir, append a "/" isdir = (type & MM_DT_DIR); printf("%s%s%s\n", disp_dir, name, isdir ? "/" : ""); } mm_closedir(dir); rv = 0; exit: mm_freea(dirpath); mm_freea(base); return rv; } /** * mm_arg_is_completing() - indicates whether shell completion is running * * The function indicates if command completion has been requested through * %MMLIB_CMD_COMPLETION environment variable: by convention command * completion is requested if this variable is set (no matter the value). * * Please note that you are not forced to use this environment variable to * trigger command completion. You may use any environment variable or any * other mechanism of your choosing. If mm_arg_parse() is used to parse * options, completion will run only if the flag %MM_ARG_PARSER_COMPLETION * is set in the flags field of struct mm_arg_parser. However on the other * hand, there is little reason not to use %MMLIB_CMD_COMPLETION environment * variable. Hence if a code is mm_arg_parse() and using command completion * through its executable, it is invited to set %MM_ARG_PARSER_COMPLETION if * mm_arg_is_completing() returns 1. * * Return: 1 if completion is running, 0 otherwise. */ API_EXPORTED int mm_arg_is_completing(void) { static int is_completing = -1; if (is_completing >= 0) return is_completing; is_completing = mm_getenv("MMLIB_CMD_COMPLETION", NULL) ? 1 : 0; return is_completing; } mmlib-1.4.2/src/atomic-win32.h000066400000000000000000000035061435717460000160050ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef ATOMIC_WIN32_H #define ATOMIC_WIN32_H #include #include /************************************************************************** * * * Define set of atomic macro/function * * * **************************************************************************/ /** * atomic_cmp_exchange() - atomically swap if expected value is in object * @obj: pointer of int64_t to change * @expected: pointer to expected value in input, receive the previous value * of *@obj in output * @desired: new value that must be swapped into @obj * * Return: If @expected equal @obj, the swap occurs and true is returned. * Otherwise false. */ static inline bool atomic_cmp_exchange(volatile int64_t* obj, int64_t* expected, int64_t desired) { int64_t prev_val; bool has_swapped; prev_val = _InterlockedCompareExchange64(obj, desired, *expected); has_swapped = (*expected == prev_val) ? true : false; *expected = prev_val; return has_swapped; } // Same as atomic_fetch_add() of C11's stdatomic.h #ifndef atomic_fetch_add #define atomic_fetch_add(obj, arg) (_InterlockedExchangeAdd64(obj, arg)) #endif // Same as atomic_fetch_sub() of C11's stdatomic.h #ifndef atomic_fetch_sub #define atomic_fetch_sub(obj, arg) (_InterlockedExchangeAdd64(obj, -arg)) #endif // Same as atomic_fetch_sub() but return value is ignored #define atomic_sub(obj, arg) ((void)_InterlockedExchangeAdd64(obj, -arg)) // Same as atomic_load() of C11's stdatomic.h #ifndef atomic_load #define atomic_load(obj) (*obj) #endif #endif /* ifndef ATOMIC_WIN32_H */ mmlib-1.4.2/src/clock-win32.c000066400000000000000000000145051435717460000156200ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmtime.h" #include "clock-win32.h" #include "mmpredefs.h" #include "utils-win32.h" #include #include #include #include #include #include #include #include /************************************************************************** * Clock cycle counters * **************************************************************************/ //Not defined outside of DDK, but documented typedef struct _PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; #define PICOSEC_IN_SEC 1000000000000LL #define MHZ_IN_HZ 1000000 static int64_t cpu_cycle_freq; static int64_t cpu_cycle_tick_psec; // Time of a cpu cycle in picosecond static void determine_cpu_cycle_rate(void) { int num_proc; SYSTEM_INFO sysinfo; PROCESSOR_POWER_INFORMATION* proc_pwr_info; // Get number of logical processor to allocate proc_pwr_info array size // to the right one GetSystemInfo(&sysinfo); num_proc = sysinfo.dwNumberOfProcessors; // Get CPU information. // When requesting 'ProcessorInformation', the output receives one // PROCESSOR_POWER_INFORMATION structure for each processor that is // installed on the system proc_pwr_info = _alloca(num_proc*sizeof(*proc_pwr_info)); CallNtPowerInformation(ProcessorInformation, NULL, 0, proc_pwr_info, num_proc*sizeof(*proc_pwr_info)); // Compute CPU max clock rate and tick length // On x86 processors supporting invariant TSC, the TSC clock rate is // this one cpu_cycle_freq = proc_pwr_info[0].MaxMhz * MHZ_IN_HZ; cpu_cycle_tick_psec = PICOSEC_IN_SEC / cpu_cycle_freq; } static void convert_cpu_cycle_to_ts(int64_t cpu_cycle, struct mm_timespec* ts) { int64_t sec, nsec; sec = cpu_cycle / cpu_cycle_freq; cpu_cycle -= sec * cpu_cycle_freq; nsec = (cpu_cycle * cpu_cycle_tick_psec)/1000; ts->tv_sec = (time_t)sec; ts->tv_nsec = (long)nsec; } static void gettimespec_tsc(struct mm_timespec* ts) { unsigned int tsc_aux; int64_t tsc; tsc = __rdtscp(&tsc_aux); convert_cpu_cycle_to_ts(tsc, ts); } static void getres_tsc(struct mm_timespec* res) { long nsec; nsec = (long)(cpu_cycle_tick_psec/1000); if (nsec == 0) nsec = 1; res->tv_sec = 0; res->tv_nsec = nsec; } /************************************************************************** * QPC based clock * **************************************************************************/ static LONGLONG qpc_tick_freq; static int nsec_in_qpc_tick; static void init_qpc(void) { LARGE_INTEGER qpc_freq; QueryPerformanceFrequency(&qpc_freq); qpc_tick_freq = qpc_freq.QuadPart; nsec_in_qpc_tick = (int)((1.0 / qpc_freq.QuadPart)*1.0e9); } static void gettimespec_qpc(struct mm_timespec* ts) { LARGE_INTEGER count; LONGLONG sec, nsec; QueryPerformanceCounter(&count); sec = count.QuadPart / qpc_tick_freq; nsec = (count.QuadPart - sec*qpc_tick_freq)*nsec_in_qpc_tick; ts->tv_sec = (time_t)sec; ts->tv_nsec = (long)nsec; } static void getres_qpc(struct mm_timespec* res) { res->tv_sec = 0; res->tv_nsec = nsec_in_qpc_tick; } /************************************************************************** * monotonic clock * **************************************************************************/ #define EAX_REG_INDEX 0 #define EBX_REG_INDEX 1 #define ECX_REG_INDEX 2 #define EDX_REG_INDEX 3 #define NUM_REG 4 // To get the value that must be passed to cpuid, see the ISA documentation #define CPUID_LEAF_EXTENDED 0x80000001 #define RDTSCP_EDX_MASK (1<<27) #define CPUID_LEAF_TSC 0x80000007 #define INVARIANT_TSC_EDX_MASK (1<<8) static bool monotonic_use_tsc; static void init_monotonic_clock(void) { int cpu_regs[NUM_REG]; bool rdtscp_supported = false; bool is_tsc_invariant = false; // Check support for rdtscp instruction __cpuid(cpu_regs, CPUID_LEAF_EXTENDED); if (cpu_regs[EDX_REG_INDEX] & RDTSCP_EDX_MASK) rdtscp_supported = true; // Check that processor provides invariant TSC __cpuid(cpu_regs, CPUID_LEAF_TSC); if (cpu_regs[EDX_REG_INDEX] & INVARIANT_TSC_EDX_MASK) is_tsc_invariant = true; // Most modern processors support invariant TSC and rdtscp instruction if (is_tsc_invariant && rdtscp_supported) monotonic_use_tsc = true; else monotonic_use_tsc = false; } LOCAL_SYMBOL void gettimespec_monotonic_w32(struct mm_timespec* ts) { if (monotonic_use_tsc) gettimespec_tsc(ts); else gettimespec_qpc(ts); } LOCAL_SYMBOL void getres_monotonic_w32(struct mm_timespec* res) { if (monotonic_use_tsc) getres_tsc(res); else getres_qpc(res); } /************************************************************************** * other clocks * **************************************************************************/ LOCAL_SYMBOL void gettimespec_wallclock_w32(struct mm_timespec* ts) { FILETIME curr; GetSystemTimePreciseAsFileTime(&curr); filetime_to_timespec(curr, ts); } LOCAL_SYMBOL void getres_wallclock_w32(struct mm_timespec* res) { // GetSystemTimePreciseAsFileTime() express time in term of number of // 100-nanosecond intervals res->tv_sec = 0; res->tv_nsec = 100; } LOCAL_SYMBOL void gettimespec_thread_w32(struct mm_timespec* ts) { ULONG64 cycles; QueryThreadCycleTime(GetCurrentThread(), &cycles); convert_cpu_cycle_to_ts((int64_t)cycles, ts); } LOCAL_SYMBOL void getres_thread_w32(struct mm_timespec* res) { getres_tsc(res); } LOCAL_SYMBOL void gettimespec_process_w32(struct mm_timespec* ts) { ULONG64 cycles; QueryProcessCycleTime(GetCurrentProcess(), &cycles); convert_cpu_cycle_to_ts((int64_t)cycles, ts); } LOCAL_SYMBOL void getres_process_w32(struct mm_timespec* res) { getres_tsc(res); } /************************************************************************** * Clocks initialization * **************************************************************************/ MM_CONSTRUCTOR(init_clocks_win32) { determine_cpu_cycle_rate(); init_qpc(); init_monotonic_clock(); } mmlib-1.4.2/src/clock-win32.h000066400000000000000000000010501435717460000156140ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef CLOCK_WIN32_H #define CLOCK_WIN32_H #include "mmtime.h" void gettimespec_wallclock_w32(struct mm_timespec* ts); void gettimespec_monotonic_w32(struct mm_timespec* ts); void gettimespec_thread_w32(struct mm_timespec* ts); void gettimespec_process_w32(struct mm_timespec* ts); void getres_wallclock_w32(struct mm_timespec* res); void getres_monotonic_w32(struct mm_timespec* res); void getres_thread_w32(struct mm_timespec* res); void getres_process_w32(struct mm_timespec* res); #endif /* ifndef CLOCK_WIN32_H */ mmlib-1.4.2/src/dlfcn.c000066400000000000000000000165361435717460000146610ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmdlfcn.h" #include "mmerrno.h" #include "mmlib.h" #include static mm_dynlib_t* arch_dlopen(const char * path, int flags); static void arch_dlclose(mm_dynlib_t* handle); static void* arch_dlsym(mm_dynlib_t* handle, const char* symbol); /************************************************************************** * * * Architecture specific * * * **************************************************************************/ #ifdef _WIN32 /************************************************************************** * Windows version * **************************************************************************/ #include static mm_dynlib_t* arch_dlopen(const char * path, int flags) { HMODULE handle; (void)flags; if (path == NULL) handle = GetModuleHandle(NULL); else handle = LoadLibrary(path); if (!handle) { mm_raise_error(EIO, "Can't open dynamic library %s", path); return NULL; } return (mm_dynlib_t*)handle; } static void arch_dlclose(mm_dynlib_t* handle) { FreeLibrary((HMODULE)handle); } static void* arch_dlsym(mm_dynlib_t* handle, const char* symbol) { union { void* ptr; FARPROC proc; } addr; /* Use union to allow cast between func pointer and void* */ addr.proc = GetProcAddress((HMODULE)handle, symbol); if (!addr.proc) { mm_raise_error(MM_ENOTFOUND, "symbol (%s) could not be found " "in dynamic library (h=%p): %s", symbol, handle); return NULL; } return addr.ptr; } #elif HAVE_DLOPEN /************************************************************************** * POSIX version * **************************************************************************/ #include static mm_dynlib_t* arch_dlopen(const char * path, int flags) { void* handle; int dlflags = 0; // Default is lazy binding if (flags & MM_LD_NOW) dlflags |= RTLD_NOW; else dlflags |= RTLD_LAZY; handle = dlopen(path, dlflags); if (!handle) { mm_raise_error(ELIBEXEC, "Can't open dynamic library %s " "(mode %08x): %s", path, dlflags, dlerror()); return NULL; } return handle; } static void arch_dlclose(mm_dynlib_t* handle) { dlclose(handle); } static void* arch_dlsym(mm_dynlib_t* handle, const char* symbol) { void* ptr = dlsym(handle, symbol); if (!ptr) { mm_raise_error(MM_ENOTFOUND, "symbol (%s) could not be found " "in dynamic library (h=%p): %s", symbol, handle, dlerror()); return NULL; } return ptr; } #else /************************************************************************** * Other version * **************************************************************************/ #error "dynamic loading facility unknown" #endif /* ifdef _WIN32 */ /************************************************************************** * * * API implementation * * * **************************************************************************/ /** * mm_dl_fileext() - get usual shared library extension * * Return: the usual shared library extension of the platform. */ API_EXPORTED const char* mm_dl_fileext(void) { static const char dynlib_ext[] = LT_MODULE_EXT; return dynlib_ext; } /** * mm_dlopen() - Load library dynamically * @path: path of the library to load * @flags: flags controlling how the library is loaded * * This function makes the symbols (function identifiers and data object * identifiers) in the shared library specified by @path available to the * calling process. A successful mm_dlopen() returns an handle which the * caller may use on subsequent calls to mm_dlsym() and mm_dlclose(). * * If @path is NULL, this will return a handle to the main program. * * The behavior of the function can be controlled by @flags which must be a * OR-combination of the following flags: * * MM_LD_LAZY * Relocations shall be performed at an implementation-defined time, * ranging from the time of the mm_dlopen() call until the first reference * to a given symbol occurs. Currently, this has no effect on Windows * platform. * * MM_LD_NOW * All necessary relocations shall be performed when shared library is * first loaded. This may waste some processing if relocations are * performed for symbols that are never referenced. This behavior may be * useful for applications that need to know that all symbols referenced * during execution will be available before mm_dlopen() returns. * Currently this is the only possible behavior for Windows platform. * * MM_LD_APPEND_EXT * If set, mm_dlopen() append automatically the usual file extension * of a shared library (OS dependent) to @path and load this file instead. * This flag allows one to write code that is fully platform independent. * * Return: In case of success, mm_dlopen() return a non-NULL handle. * Otherwise NULL is returned and error state is set accordingly. */ API_EXPORTED mm_dynlib_t* mm_dlopen(const char* path, int flags) { size_t len; char* path_ext; mm_dynlib_t* hnd; if ((flags & MM_LD_NOW) && (flags & MM_LD_LAZY)) { mm_raise_error(EINVAL, "MM_LD_NOW and MM_LD_LAZY flags cannot " "be set at the same time."); return NULL; } if (path == NULL) return arch_dlopen(NULL, flags); len = strlen(path); path_ext = mm_malloca(len+sizeof(LT_MODULE_EXT)); if (!path_ext) return NULL; // Form dynamic library filename strcpy(path_ext, path); if (flags & MM_LD_APPEND_EXT) strcat(path_ext, LT_MODULE_EXT); hnd = arch_dlopen(path_ext, flags); mm_freea(path_ext); return hnd; } /** * mm_dlclose() - Close an handle of an dynamic library * @handle: handle of library to close * * this informs the system that the library specified by handle is no longer * needed by the application. Once the handle has been closed, an * application should assume that any symbols (function identifiers and data * object identifiers) made visible using @handle, are no longer available * to the process. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly */ API_EXPORTED void mm_dlclose(mm_dynlib_t* handle) { if (!handle) return; arch_dlclose(handle); } /** * mm_dlsym() - get the address of a symbol from library handle * @handle: handle of library * @symbol: function or a data object identifier * * This obtain the address of a symbol (a function identifier or a data * object identifier) defined in the symbol table identified by the handle * argument. * * Return: pointer to the symbol if found, NULL otherwise with error state * set acccordingly */ API_EXPORTED void* mm_dlsym(mm_dynlib_t* handle, const char* symbol) { if (!handle || !symbol) { mm_raise_error(EINVAL, "invalid handle (%p) or symbol (%s) " " arguments", handle, symbol); return NULL; } return arch_dlsym(handle, symbol); } mmlib-1.4.2/src/env-win32.c000066400000000000000000000352421435717460000153160ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmlib.h" #include "utils-win32.h" #include #include #include #include /************************************************************************** * * * Environment strings cache * * * **************************************************************************/ /** * struct envstr - environment string entry * @str: buffer holder the env string (of the form "name=value") * @namelen: length of the name string (ie, offset of '=' character) * @str_maxlen: allocated size in @str */ struct envstr { char* str; int namelen; int str_maxlen; }; /** * struct envcache - structure holding all encountered UTF-8 env strings * @max_arrlen: allocated number of element in @array * @arrlen: number of element used in @array * @array: buffer of environment strings * @envp: cache of NULL terminated array to environment strings */ struct envcache { int max_arrlen; int arrlen; struct envstr* array; char** envp; }; /** * envstr_init() - environment string initialization * @entry: env string to initialize * @name: name of the environment variable in UTF-8 * * Return: 0 in case of success, -1 with errno set otherwise */ static int envstr_init(struct envstr* entry, const char* name) { int str_maxlen, namelen; // Allocate string buffer to be large enough to hold variable name // and decent value namelen = strlen(name); str_maxlen = namelen + 64; entry->str = malloc(str_maxlen); if (!entry->str) return -1; // Initialize the rest of the fields entry->namelen = namelen; entry->str_maxlen = str_maxlen; // Initialize string buffer with "name=", the value part of the // string will be set later memcpy(entry->str, name, namelen); entry->str[namelen] = '='; entry->str[namelen+1] = '\0'; return 0; } /** * envstr_deinit() - cleanup environment string * @entry: environment string to cleanup (may be NULL) * * This function is idempotent */ static void envstr_deinit(struct envstr* entry) { if (!entry) return; free(entry->str); *entry = (struct envstr) {.str = NULL}; } /** * envstr_get_value() - get pointer of the value part of environment string * @entry: environment string whose value must be retrieved * * Return: pointer to the value part in the environment string */ static char* envstr_get_value(struct envstr* entry) { // The value part start right after the '=' after the name return entry->str + entry->namelen+1; } /** * envstr_resize_value() - resize buffer to accommodate a size of value * @entry: environment string whose buffer must be resized if needed * @value_len: size of value (excluding null termination) that must be * accommodated * * Return: 0 in case of success, -1 with errno set otherwise */ static int envstr_resize_value(struct envstr* entry, int value_len) { char* str; int envstr_len; // The new size must be large enough for variable name, '=', value // and terminating nul character envstr_len = value_len + entry->namelen + 2; // If the allocated string is not large enough, realloc if (entry->str_maxlen < envstr_len) { str = realloc(entry->str, envstr_len); if (!str) return -1; entry->str = str; entry->str_maxlen = envstr_len; } return 0; } /** * envstr_set_value() - set value part of an environment string * @entry: environment string whose value must be modified * @value: new value string to set * * This function will reallocate the string buffer if necessary to * accommodate the new value string * * Return: 0 in case of success, -1 otherwise with errno set */ static int envstr_set_value(struct envstr* entry, const char* value) { int value_len; // Estimate the new size needed and resize string buffer value_len = strlen(value); if (envstr_resize_value(entry, value_len)) return -1; // Copy the new value in the value part of the string memcpy(envstr_get_value(entry), value, value_len+1); return 0; } /** * envcache_deinit() - cleanup the environment cache * @cache: cache to cleanup */ static void envcache_deinit(struct envcache* cache) { int i; for (i = 0; i < cache->arrlen; i++) envstr_deinit(&cache->array[i]); free(cache->envp); free(cache->array); *cache = (struct envcache) {.array = NULL}; } /** * envcache_find_entry() - find the named environment string in the cache * @cache: cache to search * @name: name of the environment variable to search */ static struct envstr* envcache_find_entry(struct envcache* cache, const char* name) { int i, namelen; namelen = strlen(name); for (i = 0; i < cache->arrlen; i++) { if (namelen != cache->array[i].namelen) continue; if (!memcmp(cache->array[i].str, name, namelen)) return &cache->array[i]; } return NULL; } /** * envcache_create_entry() - create an entry in the cache for the named env var * @cache: cache in which the environment string must be created * @name: name of the environment variable * * Return: pointer to the created environment string in case of success, * NULL otherwise with errno set */ static struct envstr* envcache_create_entry(struct envcache* cache, const char* name) { int max_arrlen; struct envstr * entry, * array; array = cache->array; max_arrlen = cache->max_arrlen; // Resize the array if not large enough to accommodate the new entry if (cache->arrlen+1 > max_arrlen) { max_arrlen = (max_arrlen == 0) ? 32 : max_arrlen*2; array = realloc(array, max_arrlen * sizeof(*array)); if (!array) return NULL; cache->array = array; cache->max_arrlen = max_arrlen; } // Get the new entry to use and initialize it entry = &array[cache->arrlen++]; if (envstr_init(entry, name)) { cache->arrlen--; return NULL; } return entry; } /** * envcache_remove_entry() - remove environment string from cache * @cache: environment cache to update * @entry: pointer to environment string to remove (may be NULL) */ static void envcache_remove_entry(struct envcache* cache, struct envstr* entry) { int index; if (!entry) return; envstr_deinit(entry); // Remove the entry from the array index = entry - cache->array; memmove(cache->array + index, cache->array + index + 1, (cache->arrlen - index -1)*sizeof(*entry)); cache->arrlen--; } /** * envcache_update_envp() - update environ strings array * @cache: environment cache to update * * return: the new environ strings array in case of success, NULL otherwise */ static char** envcache_update_envp(struct envcache* cache) { int i; char** envp; envp = realloc(cache->envp, (cache->arrlen+1)*sizeof(*envp)); if (!envp) return NULL; for (i = 0; i < cache->arrlen; i++) envp[i] = cache->array[i].str; envp[cache->arrlen] = NULL; cache->envp = envp; return envp; } /************************************************************************** * * * UTF-8 encoded environment manipulation * * * **************************************************************************/ /** * get_u16env() - get UTF-16 environment value from UTF-8 name * @name_u8: name of the variable in UTF-8 * * Return: the value in UTF-16 of the environment variable named @name_u8 if * present, NULL otherwise. */ static char16_t* get_u16env(const char* name_u8) { char16_t * name_u16, * value_u16; int u16_len; // Estimate the size of the variable name in UTF-16. If the name in // invalid, we consider (rightfully) that the environment does not // contains such a name u16_len = get_utf16_buffer_len_from_utf8(name_u8); if (u16_len < 0) return NULL; // Get UTF-16 env value from temporary UTF-16 variable name name_u16 = mm_malloca(u16_len * sizeof(*name_u16)); conv_utf8_to_utf16(name_u16, u16_len, name_u8); value_u16 = _wgetenv(name_u16); mm_freea(name_u16); return value_u16; } // global UTF-8 environment strings cache static struct envcache utf8_env_cache; // Destructor of the UTF-8 cache. This function is called when the program // terminates or the library is unloaded (in case of dynamic load) MM_DESTRUCTOR(utf8_environment_cache_cleanup) { envcache_deinit(&utf8_env_cache); } /** * update_environment_cache() - update cache from "key=val" string in UTF-16 * @cache: environment cache to update * @envstr_u16: "key=val" string in UTF-16 */ static void update_environment_cache(struct envcache* cache, const char16_t* envstr_u16) { int len8; char * envstr_u8, * name, * value; struct envstr* entry; // Get temporary UTF-8 env string from envstr_u16. len8 = get_utf8_buffer_len_from_utf16(envstr_u16); envstr_u8 = mm_malloca(len8); conv_utf16_to_utf8(envstr_u8, len8, envstr_u16); // Get pointer of name and value (terminate name by '\0') name = envstr_u8; value = strchr(envstr_u8, '='); *value = '\0'; value++; // Create new or get existing entry with name entry = envcache_find_entry(cache, name); if (!entry) { entry = envcache_create_entry(cache, name); if (!entry) goto exit; } envstr_set_value(entry, value); exit: mm_freea(envstr_u8); } /** * get_environ_utf8() - Get array of environment strings in UTF-8 * * This function update the environment cache with all environment variable * sets in the process. * * Return: NULL terminated array of the environment string in UTF-8. In * case of failure (memory allocation issues), NULL is returned. */ LOCAL_SYMBOL char** get_environ_utf8(void) { struct envcache* cache = &utf8_env_cache; LPWCH env_strw; char16_t* env16; env_strw = GetEnvironmentStringsW(); if (env_strw == NULL) return NULL; // Update the environment cache with all key=value strings present // in environment for (env16 = env_strw; *env16 != L'\0'; env16 += wcslen(env16)+1) update_environment_cache(cache, env16); FreeEnvironmentStringsW(env_strw); return envcache_update_envp(cache); } /** * getenv_utf8() - get value in UTF-8 of an environment variable * @name: name (UTF-8 encoded) of the environment variable * * This function is the same as getenv() of the ISO C excepting it has been * made to support UTF-8 on Windows. * * Return: pointer to UTF-8 string containing the value for the specified * name. If the specified name cannot be found in the environment of the * calling process, a null pointer is returned. */ LOCAL_SYMBOL char* getenv_utf8(const char* name) { int value_len; char16_t* val_u16; struct envstr* entry; char* value; struct envcache* cache = &utf8_env_cache; // Fast path: if environment is in cache, just return the cached value entry = envcache_find_entry(cache, name); if (entry) return envstr_get_value(entry); // See if the environment exist in CRT, if not, NULL value is // returned val_u16 = get_u16env(name); if (!val_u16) return NULL; // Allocate an entry in the cache entry = envcache_create_entry(cache, name); if (!entry) return NULL; // Resize the entry value for the required string size after // conversion to UTF-8 value_len = get_utf8_buffer_len_from_utf16(val_u16); if (envstr_resize_value(entry, value_len-1)) return NULL; // Perform the actual UTF-16->UTF-8 conversion of the value string value = envstr_get_value(entry); conv_utf16_to_utf8(value, value_len, val_u16); return value; } /** * setenv_utf8() - set value in UTF-8 of an environment variable * @name: name (UTF-8 encoded) of the environment variable * @value: UTF-8 encoded value * @overwrite: non-zero if existing variable must be overwritten * * This function is the same as setenv() of the ISO C excepting it has been * made to support UTF-8 on Windows. * * Return: Upon successful completion, zero is returned. Otherwise, -1 and * errno set to indicate the error, and the environment will be unchanged. */ LOCAL_SYMBOL int setenv_utf8(const char* name, const char* value, int overwrite) { struct envstr* entry; char16_t* str_u16; int name_u16_len, value_u16_len, retval = -1; struct envcache* cache = &utf8_env_cache; // Estimate the size of components of the environment string in UTF-16 name_u16_len = get_utf16_buffer_len_from_utf8(name); value_u16_len = get_utf16_buffer_len_from_utf8(value); if (name_u16_len <= 0 || value_u16_len < 0) { errno = (name_u16_len == 0) ? EILSEQ : EINVAL; return -1; } // Get UTF-16 env value from temporary UTF-16 variable name. After // this block, str_u16 contains only the variable name in UTF-16 str_u16 = mm_malloca((name_u16_len + value_u16_len)*sizeof(*str_u16)); conv_utf8_to_utf16(str_u16, name_u16_len, name); // Skip setting env if the variable exist and overwrite is null if (!overwrite && _wgetenv(str_u16)) { retval = 0; goto exit; } // Find cache entry or allocate if not found if (!(entry = envcache_find_entry(cache, name)) && !(entry = envcache_create_entry(cache, name))) goto exit; // Store the UTF-8 value in the cache if (envstr_set_value(entry, value)) goto exit; // Complete UTF-16 with "=value" part and push it in CRT env // (name_u16_len has terminating nul character) str_u16[name_u16_len-1] = L'='; conv_utf8_to_utf16(str_u16 + name_u16_len, value_u16_len, value); if (_wputenv(str_u16)) { envcache_remove_entry(cache, entry); goto exit; } retval = 0; exit: mm_freea(str_u16); return retval; } /** * unsetenv_utf8() - remove an environment variable * @name: UTF-8 encoded name of environment variable to remove * * This function is the same as unsetenv() of the ISO C excepting it has * been made to support UTF-8 on Windows. * * Return: 0 in case of success, -1 otherwise environment unchanged and * errno set accordingly */ LOCAL_SYMBOL int unsetenv_utf8(const char* name) { struct envstr* entry; int name_u16_len, retval; char16_t* str_u16; struct envcache* cache = &utf8_env_cache; // Remove matching entry from cache if any entry = envcache_find_entry(cache, name); envcache_remove_entry(cache, entry); // Estimate the size of components of the environment string in UTF-16 // (name_u16_len has terminating nul character) name_u16_len = get_utf16_buffer_len_from_utf8(name); if (name_u16_len < 0) { errno = EINVAL; return -1; } // Allocate temporary string set to "name=" and push it with // _wputenv(). This way, the environment variable named "name" will // be removed. str_u16 = mm_malloca(name_u16_len+1); conv_utf8_to_utf16(str_u16, name_u16_len, name); str_u16[name_u16_len-1] = L'='; str_u16[name_u16_len] = L'\0'; retval = _wputenv(str_u16); mm_freea(str_u16); return retval; } mmlib-1.4.2/src/error-internal.h000066400000000000000000000012331435717460000165270ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef ERROR_INTERNAL_H #define ERROR_INTERNAL_H struct error_info { int flags; // flags to finetune error handling int errnum; // error class (standard and mmlib errno value) char extended_id[64]; // message to display to end user if has not // been caught before char module[32]; // module that has generated the error char location[256]; // which function/file/line has generated the // error char desc[256]; // message intended to developer }; struct error_info* get_thread_last_error(void); #endif /* ERROR_INTERNAL_H */ mmlib-1.4.2/src/error.c000066400000000000000000000372511435717460000147210ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "error-internal.h" #include "mmerrno.h" #include "mmlog.h" #include #include #include #include #include #include #include "nls-internals.h" #ifndef thread_local # if defined (__GNUC__) # define thread_local __thread # elif defined (_MSC_VER) # define thread_local __declspec(thread) # else # error Do not know how to specify thread local attribute # endif #endif struct errmsg_entry { int errnum; const char* msg; }; /************************************************************************** * * * Messages definition * * * **************************************************************************/ static const struct errmsg_entry error_tab[] = { {.errnum = MM_EDISCONNECTED, .msg = N_("The device has been disconnected.")}, {.errnum = MM_EWRONGSTATE, .msg = N_("Object in wrong state")}, {.errnum = MM_ENOTFOUND, .msg = N_("Object not found")}, {.errnum = MM_EBADFMT, .msg = N_("Bad format")}, {.errnum = MM_ENONAME, .msg = N_( "Specified hostname cannot be resolved")}, }; #define NUM_ERROR_ENTRY MM_NELEM(error_tab) /************************************************************************** * * * Implementation * * * **************************************************************************/ MM_CONSTRUCTOR(translation) { _domaindir(LOCALEDIR); } static const char* get_mm_errmsg(int errnum) { int i; if ((errnum < error_tab[0].errnum) || (errnum > error_tab[NUM_ERROR_ENTRY-1].errnum)) return NULL; for (i = 0; i < NUM_ERROR_ENTRY; i++) { if (errnum == error_tab[i].errnum) return _(error_tab[i].msg); } return NULL; } /** * mm_strerror() - Get description for error code * @errnum: error to describe * * This function maps the error number in @errnum to a locale-dependent * error message string and return a pointer to it. * * mm_strerror() function is not be thread-safe. The application must not * modify the string returned. The returned string pointer might be * invalidated or the string content might be overwritten by a subsequent * call to mm_strerror(), strerror(), or by subsequent call to strerror_l() * in the same thread. * * Return: pointer to the generated message string. */ API_EXPORTED const char* mm_strerror(int errnum) { const char* msg; msg = get_mm_errmsg(errnum); if (!msg) msg = strerror(errnum); return msg; } #ifdef _WIN32 static int strerror_r(int errnum, char * strerrbuf, size_t buflen) { return strerror_s(strerrbuf, buflen, errnum); } #endif /** * mm_strerror_r() - Get description for error code (reentrant) * @errnum: error to describe * @buf: buffer to which the description should be written * @buflen: buffer size of @buf * * Return: 0 is in case of success, -1 otherwise. */ API_EXPORTED int mm_strerror_r(int errnum, char * buf, size_t buflen) { const char* msg; size_t msglen, trunclen; msg = get_mm_errmsg(errnum); if (!msg) return strerror_r(errnum, buf, buflen); if (buflen < 1) { errno = ERANGE; return -1; } msglen = strlen(msg)+1; trunclen = (buflen < msglen) ? buflen : msglen; memcpy(buf, msg, trunclen-1); buf[trunclen-1] = '\0'; if (trunclen != msglen) { errno = ERANGE; return -1; } return 0; } /****************************************************************** * * * Error state API * * * ******************************************************************/ #ifndef _WIN32 //implementation of this function for win32 is in thread-win32.c LOCAL_SYMBOL struct error_info* get_thread_last_error(void) { // info of the last error IN THE THREAD static thread_local struct error_info last_error; return &last_error; } #endif /* ifndef _WIN32 */ /** * mm_error_set_flags() - set the error reporting behavior * @flags: the flags to add * @mask: mask applied on the flags * * This function allows one to modify the behavior when an error is raised with * mm_raise_error() and similar function. Normally when an error is raised, the * usual behavior is to set the error and its details in a thread local * variable (error state) and to log it. The aspect of this behavior can be * modified depending on @mask which must be an OR-combination of the following * flags : * * MM_ERROR_IGNORE * error are silently ignored... Thread error state will not be changed * and no log will be produced. * * MM_ERROR_NOLOG * log will not be produced when an error is raised. * * The aspect of the error raising behavior is controlled by the flags set * in @flags combined with @mask. In other words, the aspect of behavior * mentioned in the previous list is modified if the corresponding bit is * set in @mask and the alternate behavior is respectively used or not * depending on the corresponding bit is set or not in @flags. If a bit is * unset in @mask, the same behavior is kept as before the call to * mm_error_set_flags(). MM_ERROR_ALL_ALTERNATE is defined to modify all * possible behavior aspect. * * The return variable can be used to restore the previous state. * * For example use previous = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_NOLOG) * to stop logging errors. The previous variable will contain the original * flag variable state, which can then be restored using * mm_error_set_flags(previous, MM_ERROR_NOLOG). * * Return: the previous state flags */ API_EXPORTED int mm_error_set_flags(int flags, int mask) { struct error_info* state = get_thread_last_error(); int previous; previous = state->flags; state->flags = (mask & flags) | (~mask & previous); return previous; } /** * mm_raise_error_vfull() - set and log an error using a va_list * @errnum: error class number * @module: module name * @func: function name at the origin of the error * @srcfile: filename of source code at the origin of the error * @srcline: line number of file at the origin of the error * @extid: extended error id (identifier of a specific error case) * @desc_fmt: description intended for developer (vprintf-like extensible) * @args: va_list of arguments for @desc * * Exactly the same as mm_raise_error_full() but using a va_list to pass * argument to the format passed in @desc. * * Return: always -1. */ API_EXPORTED int mm_raise_error_vfull(int errnum, const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc_fmt, va_list args) { struct error_info* state; int flags; if (!module) module = "unknown"; if (!func) func = "unknown"; if (!srcfile) srcfile = "unknown"; if (!extid) extid = ""; state = get_thread_last_error(); // Check that error should not be ignored if (state->flags & MM_ERROR_IGNORE) return -1; // Copy the fields that don't need formatting state->errnum = errnum; strncpy(state->module, module, sizeof(state->module)-1); strncpy(state->extended_id, extid, sizeof(state->extended_id)-1); // format source location field snprintf(state->location, sizeof(state->location), "%s() in %s:%i", func, srcfile, srcline); // format description vsnprintf(state->desc, sizeof(state->desc), desc_fmt, args); // Set errno for backward compatibility, ie case of module that has // been updated to use mm_error* but whose client code (user of this // module) is not using yet mm_error* if (errnum != 0) errno = errnum; if (state->flags & MM_ERROR_NOLOG) return -1; // Log error but ignore any error that could occur while logging: // either ways there would be nothing that can be done about it, but // more importantly we do not want to overwrite the error being set // by the user. flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); mm_log(MM_LOG_ERROR, module, "%s (%s)", state->desc, state->location); mm_error_set_flags(flags, MM_ERROR_IGNORE); return -1; } /** * mm_raise_error_full() - set and log an error (function backend) * @errnum: error class number * @module: module name * @func: function name at the origin of the error * @srcfile: filename of source code at the origin of the error * @srcline: line number of file at the origin of the error * @extid: extended error id (identifier of a specific error case) * @desc_fmt: description intended for developer (printf-like extensible) * * This function is the actual function invoked by the mm_raise_error() and * mm_raise_error_with_extid() macros. You are advised to use the macros instead * unless you want to build your own wrapper. * * Return: always -1. */ API_EXPORTED int mm_raise_error_full(int errnum, const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc_fmt, ...) { int ret; va_list args; va_start(args, desc_fmt); ret = mm_raise_error_vfull(errnum, module, func, srcfile, srcline, extid, desc_fmt, args); va_end(args); return ret; } /** * mm_raise_from_errno_full() - set and log an error (function backend) * @module: module name * @func: function name at the origin of the error * @srcfile: filename of source code at the origin of the error * @srcline: line number of file at the origin of the error * @extid: extended error id (identifier of a specific error case) * @desc_fmt: description intended for developer (printf-like extensible) * * This function is the actual function invoked by the mm_raise_from_errno() * macro. You are advised to use the macros instead unless you want to build * your own wrapper. * * Return: always -1. */ API_EXPORTED int mm_raise_from_errno_full(const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc_fmt, ...) { int ret; va_list args; char new_fmt[256]; snprintf(new_fmt, sizeof(new_fmt) - 1, "%s ; %s", desc_fmt, strerror(errno)); new_fmt[sizeof(new_fmt) - 1] = 0; va_start(args, desc_fmt); ret = mm_raise_error_vfull(errno, module, func, srcfile, srcline, extid, new_fmt, args); va_end(args); return ret; } /** * mm_save_errorstate() - Save the error state on an opaque data holder * @state: data holder of the error state * * Use this function to save the current error state to data holder pointed by * @state. The content of @state may be copied around even between threads and * different processes. * * Return: 0 (cannot fail) * * The reciprocal of this function is mm_set_errorstate(). */ API_EXPORTED int mm_save_errorstate(struct mm_error_state* state) { struct error_info* last_error = get_thread_last_error(); assert(sizeof(*state) >= sizeof(*last_error)); memcpy(state, last_error, sizeof(*last_error)); return 0; } /** * mm_set_errorstate() - Save the error state of the calling thread * @state: pointer to the data holding of the error state * * Use this function to restore the error state of the calling thread from the * information pointed by @state. Combined with mm_save_errorstate(), you * may : * * - handle an error from a called function and recover the error state before * the failed function * - copy the error state of a failed function whose call may have been * offloaded to a different thread or even different process * * The reciprocal of this function is mm_save_errorstate(). * * Return: 0 (cannot fail) */ API_EXPORTED int mm_set_errorstate(const struct mm_error_state* state) { struct error_info* last_error = get_thread_last_error(); assert(sizeof(*state) >= sizeof(*last_error)); memcpy(last_error, state, sizeof(*last_error)); // Set errno for backward compatibility, ie case of module that has // been updated to use mm_error* but whose client code (user of this // module) is not using yet mm_error* errno = last_error->errnum; return 0; } /** * mm_print_lasterror() - display last error info on standard output * @info: string describing the context where the error has been * encountered. It can be enriched by variable argument in the * printf-like style. It may be NULL, in such a case, only the * error state is described. */ API_EXPORTED void mm_print_lasterror(const char* info, ...) { struct error_info* last_error = get_thread_last_error(); va_list args; // Print context info if supplied if (info) { va_start(args, info); vprintf(info, args); va_end(args); printf("\n"); } // No error state is set, not in errno if (!last_error->errnum && !errno) { printf("No error found in the state\n"); return; } // No error state is set, but something is in errno if (!last_error->errnum && errno) { printf("Error only found in errno: %i, %s\n", errno, mm_strerror(errno)); return; } // Print the error state printf("Last error reported:\n" "\terrnum=%i : %s\n" "\tmodule: %s\n" "\tlocation: %s\n" "\tdescription: %s\n" "\textented_id: %s\n", last_error->errnum, mm_strerror(last_error->errnum), last_error->module, last_error->location, last_error->desc, last_error->extended_id); } /** * mm_get_lasterror_number() - get error number of last error in the thread * * Return: the error number (0 if no error has been set in the thread) */ API_EXPORTED int mm_get_lasterror_number(void) { return get_thread_last_error()->errnum; } /** * mm_get_lasterror_desc() - get error description of last error in thread * * Return: the error description ("" if no error) */ API_EXPORTED const char* mm_get_lasterror_desc(void) { return get_thread_last_error()->desc; } /** * mm_get_lasterror_location() - get file location of last error in the thread * * Return: the file location that is at the origin of the error in the format * "filename:linenum" ("" if no error) */ API_EXPORTED const char* mm_get_lasterror_location(void) { return get_thread_last_error()->location; } /** * mm_get_lasterror_extid() - get error extended id of last error in the thread * * This function provides the extended id of the last error set in the calling * thread. The extended id is a string identifier destinated for the UI layer * of the software stack to identify a error specific situation(See explanation * in mm_raise_error_with_extid()). * * Please note that not all error report are supposed to report an error * extended id (they actually should be a minority). If none has been provided * when the last error has been set, the extid provided by this function will * be NULL. * * Return: the error extended id if one has been set by the last error, NULL * otherwise. */ API_EXPORTED const char* mm_get_lasterror_extid(void) { struct error_info* last_error = get_thread_last_error(); // Don't return an empty string if extid is not set if (last_error->extended_id[0] == '\0') return NULL; return last_error->extended_id; } /** * mm_get_lasterror_module() - module at the source the last error in the thread * * Return: the module name that is at the origin of the error ("" if no error) */ API_EXPORTED const char* mm_get_lasterror_module() { return get_thread_last_error()->module; } mmlib-1.4.2/src/file-internal.h000066400000000000000000000011001435717460000163060ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef FILE_INTERNAL_H #define FILE_INTERNAL_H static inline int is_path_separator(char c) { #if defined (_WIN32) return (c == '\\' || c == '/'); #else return (c == '/'); #endif } /* use to skip "." and ".." directories */ static inline int is_wildcard_directory(const char * name) { int len; for (len = 0; (len <= 2) && (name[len] != '\0'); len++) { if (name[len] != '.') return 0; } return (len == 1 || len == 2); } int copy_internal(const char* src, const char* dst, int flags, int mode); #endif /* FILE_INTERNAL_H */ mmlib-1.4.2/src/file-posix.c000066400000000000000000001110061435717460000156360ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif // Needed for copy_file_range if available #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "mmlib.h" #include "mmerrno.h" #include "mmpredefs.h" #include "mmsysio.h" #include "file-internal.h" #include #include #include #include #include #include #include #include #include #include #include #if HAVE_LINUX_FS_H # include #endif /** * mm_open() - Open file * @path: path to file to open * @oflag: control flags how to open the file * @mode: access permission bits is file is created * * This function creates an open file description that refers to a file and * a file descriptor that refers to that open file description. The file * descriptor is used by other I/O functions to refer to that file. The * @path argument points to a pathname naming the file. * * The file status flags and file access modes of the open file description * are set according to the value of @oflag, which is constructed by a * bitwise-inclusive OR of flags from the following list. It must specify * exactly one of the first 3 values. * * %O_RDONLY * Open for reading only. * %O_WRONLY * Open for writing only. * %O_RDWR * Open for reading and writing. The result is undefined if this flag is * applied to a FIFO. * * Any combination of the following may be used * * %O_APPEND * If set, the file offset shall be set to the end of the file prior to * each write. * %O_CREAT * If the file exists, this flag has no effect except as noted under * %O_EXCL below. Otherwise, the file is created as a regular file; the * user ID of the file shall be set to the effective user ID of the * process; the group ID of the file shall be set to the group ID of the * file's parent directory or to the effective group ID of the process; * and the access permission bits of the file mode are set by the @mode * argument modified by a bitwise AND with the umask of the process. This * @mode argument does not affect whether the file is open for reading, * writing, or for both. * %O_TRUNC * If the file exists and is a regular file, and the file is successfully * opened %O_RDWR or %O_WRONLY, its length is truncated to 0, and the * mode and owner are unchanged. The result of using O_TRUNC without * either %O_RDWR or %O_WRONLY is undefined. * %O_EXCL * If %O_CREAT and %O_EXCL are set, mm_open() fails if the file exists. * The check for the existence of the file and the creation of the file if * it does not exist is atomic with respect to other threads executing * mm_open() naming the same filename in the same directory with %O_EXCL * and %O_CREAT set. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_open(const char* path, int oflag, int mode) { int fd; // Make file opened by mm_open automatically non inheritable oflag |= O_CLOEXEC; fd = open(path, oflag, mode); if (fd < 0) return mm_raise_from_errno("open(%s, %08x) failed", path, oflag); return fd; } /** * mm_rename() - rename a file * @oldpath: old pathname of the file * @newpath: new pathname of the file * * The mm_rename() function changes the name of a file. @oldpath is the path of * the file to be renamed, and @newpath is the new pathname of the file. * * If @newpath corresponds to the path of an existing file/directory, then it is * removed and @oldpath is renamed to @newpath. Therefore, write access * permission is required for both the directory containing @oldpath and the * directory containing @newpath. Note that, in case @newpath corresponds to the * path of an existing directory, this directory is required to be empty. * * If either @oldpath or @newpath is a path of a symbolic link, mm_rename() * operates on the symbolic link itself. * * If @oldpath and @newpath are identical paths mm_rename() returns successfully * and performs no other action. * * mm_rename() will non-trivially fail if: * - Either @oldpath or @newpath refers to a path whose final component is * either dot or dot-dot. * - @newpath is a path toward a non empty directory. * - @newpath contains a subpath (different from the path) toward a non * existing directory. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_rename(const char * oldpath, const char * newpath) { if (rename(oldpath, newpath) != 0) return mm_raise_from_errno("rename %s into %s failed", oldpath, newpath); return 0; } /** * mm_close() - Close a file descriptor * @fd: file descriptor to close * * This function deallocates the file descriptor indicated by @fd, ie it * makes the file descriptor available for return by subsequent calls to * mm_open() or other system functions that allocate file descriptors. * * If a memory mapped file or a shared memory object remains referenced at * the last close (that is, a process has it mapped), then the entire * contents of the memory object persists until the memory object becomes * unreferenced. If this is the last close of a memory mapped file or a * shared memory object and the close results in the memory object becoming * unreferenced, and the memory object has been unlinked, then the memory * object will be removed. * * If @fd refers to a socket, mm_close() causes the socket to be destroyed. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_close(int fd) { if (fd == -1) return 0; if (close(fd) < 0) return mm_raise_from_errno("close(%i) failed", fd); return 0; } /** * mm_read() - Reads data from a file descriptor * @fd: file descriptor to read from * @buf: storage location for data * @nbyte: maximum size to read * * mm_read() attempts to read @nbyte bytes from the file associated with the * open file descriptor, @fd, into the buffer pointed to by @buf. * * On files that support seeking (for example, a regular file), the * mm_read() starts at a position in the file given by the file offset * associated with @fd. The file offset will incremented by the number of * bytes actually read. * No data transfer will occur past the current * end-of-file. If the starting position is at or after the end-of-file, 0 * is returned. * * If @fd refers to a socket, mm_read() shall be equivalent to mm_recv() * with no flags set. * * Return: Upon successful completion, a non-negative integer is returned * indicating the number of bytes actually read. Otherwise, -1 is returned * and error state is set accordingly */ API_EXPORTED ssize_t mm_read(int fd, void* buf, size_t nbyte) { ssize_t rsz; rsz = read(fd, buf, nbyte); if (rsz < 0) return mm_raise_from_errno("read(%i, ...) failed", fd); return rsz; } /** * mm_write() - Write data to a file descriptor * @fd: file descriptor to write to * @buf: storage location for data * @nbyte: amount of data to write * * The mm_write() function attempts to write @nbyte bytes from the buffer * pointed to by @buf to the file associated with the open file descriptor, * @fd. * * On a regular file or other file capable of seeking, the actual writing of * data shall proceed from the position in the file indicated by the file * offset associated with @fd. Before successful return from mm_write(), the * file offset is incremented by the number of bytes actually written. * * If the %O_APPEND flag of the file status flags is set, the file offset is set * to the end of the file prior to each write and no intervening file * modification operation will occur between changing the file offset and the * write operation. * * Write requests to a pipe or FIFO shall be handled in the same way as a * regular file with the following exceptions * * - there is no file offset associated with a pipe, hence each write request * shall append to the end of the pipe. * - write requests of pipe buffer size bytes or less will not be interleaved * with data from other processes doing writes on the same pipe. * - a write request may cause the thread to block, but on normal completion it * shall return @nbyte. * * Return: Upon successful completion, a non-negative integer is returned * indicating the number of bytes actually written. Otherwise, -1 is returned * and error state is set accordingly */ API_EXPORTED ssize_t mm_write(int fd, const void* buf, size_t nbyte) { ssize_t rsz; rsz = write(fd, buf, nbyte); if (rsz < 0) return mm_raise_from_errno("write(%i, ...) failed", fd); return rsz; } /** * mm_dup() - duplicate an open file descriptor * @fd: file descriptor to duplicate * * This function creates a new file descriptor referencing the same file * description as the one referenced by @fd. * * Note that the two file descriptors point to the same file. They will share * the same file pointer. * * Return: a non-negative integer representing the new file descriptor in case * of success. The return file descriptor value is then guaranteed to be the * lowest available at the time of the call. In case of error, -1 is returned * with error state set accordingly. */ API_EXPORTED int mm_dup(int fd) { int newfd; newfd = dup(fd); if (newfd < 0) return mm_raise_from_errno("dup(%i) failed", fd); return newfd; } /** * mm_dup2() - duplicate an open file descriptor to a determined file descriptor * @fd: file descriptor to duplicate * @newfd: file descriptor number that will become the duplicate * * This function duplicates an open file descriptor @fd and assign it to the * file descriptor @newfd. In other word, this function is similar to mm_dup() * but in case of success, the returned value is ensured to be @newfd. * * Return: a non-negative integer representing the new file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_dup2(int fd, int newfd) { if (dup2(fd, newfd) < 0) return mm_raise_from_errno("dup2(%i, %i) failed", fd, newfd); return newfd; } /** * mm_pipe() - creates an interprocess channel * @pipefd: array of two in receiving the read and write endpoints * * The mm_pipe() function creates a pipe and place two file descriptors, one * each into the arguments @pipefd[0] and @pipefd[1], that refer to the open * file descriptions for the read and write ends of the pipe. Their integer * values will be the two lowest available at the time of the mm_pipe() call. * * Data can be written to the file descriptor @pipefd[1] and read from the file * descriptor @pipefd[0]. A read on the file descriptor @pipefd[0] shall access * data written to the file descriptor @pipefd[1] on a first-in-first-out * basis. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_pipe(int pipefd[2]) { if (pipe(pipefd) < 0) return mm_raise_from_errno("pipe() failed"); return 0; } /** * mm_unlink() - remove a directory entry * @path: location to remove from file system * * The mm_unlink() function removes a link to a file. If @path names a symbolic * link, it removes the symbolic link named by @path and does not affect any * file or directory named by the contents of the symbolic link. Otherwise, * mm_unlink() remove the link named by the pathname pointed to by @path and * decrements the link count of the file referenced by the link. * * When the file's link count becomes 0 and no process has the file open, the * space occupied by the file will be freed and the file will no longer be * accessible. If one or more processes have the file open when the last link * is removed, the link will be removed before mm_unlink() returns, but the * removal of the file contents is postponed until all references to the * file are closed (ie when all file descriptors referencing it are closed). * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. * * NOTE: On Windows platform, it is usually believed that an opened file is not * permitted to be deleted. This is not true. This is only due to the fact that * many libraries/application open file missing the right share mode * (FILE_SHARE_DELETE). If you access the file through mmlib APIs, you will be * able to unlink your file before it is closed (even if memory mapped...). */ API_EXPORTED int mm_unlink(const char* path) { if (unlink(path) < 0) return mm_raise_from_errno("unlink(%s) failed", path); return 0; } /** * mm_link() - create a hard link to a file * @oldpath: existing path for the file to link * @newpath: new path of the file * * The mm_link() function creates a new link (directory entry) for the existing * file, @oldpath. * * The @oldpath argument points to a pathname naming an existing file. The * @newpath argument points to a pathname naming the new directory entry to be * created. The mm_link() function create atomically a new link for the * existing file and the link count of the file shall be incremented by one. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_link(const char* oldpath, const char* newpath) { if (link(oldpath, newpath) < 0) return mm_raise_from_errno("link(%s, %s) failed", oldpath, newpath); return 0; } /** * mm_symlink() - create a symbolic link to a file * @oldpath: existing path for the file to link * @newpath: new path of the file * * The mm_link() function creates a new symbolinc link for the existing file, * @oldpath. The @oldpath argument do not need to point to a pathname naming an * existing file. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_symlink(const char* oldpath, const char* newpath) { if (symlink(oldpath, newpath) < 0) return mm_raise_from_errno("symlink(%s, %s) failed", oldpath, newpath); return 0; } /** * mm_check_access() - verify access to a file * @path: path of file * @amode: access mode to check (OR-combination of _OK flags) * * This function verify the calling process can access the file located at * @path according to the bits pattern specified in @amode which can be a * OR-combination of the %R_OK, %W_OK, %X_OK to indicate respectively the read, * write or execution access to a file. If @amode is F_OK, only the existence * of the file is checked. * * Return: * - 0 if the file can be accessed * - %ENOENT if a component of @path does not name an existing file * - %EACCESS if the file cannot be access with the mode specified in @amode * - -1 in case of error (error state is then set accordingly) */ API_EXPORTED int mm_check_access(const char* path, int amode) { int prev_error = errno; int ret, errnum; ret = access(path, amode); if (ret) { // If errno is EACCESS or ENOENT, this is not a real error, // but just that either the file do not exist, either the // mode requested could not be obtained. Hence, revert back // the previous errno errnum = errno; if (errnum == EACCES || errnum == ENOENT) { errno = prev_error; return errnum; } // If not EACCESS, the error is a real one and worth being // reported return mm_raise_from_errno("access(\"%s\", %02x) failed", path, amode); } // File exist and requested mode could be granted return 0; } /** * mm_isatty() - test whether a file descriptor refers to a terminal * @fd: File descriptor to test * * Return: 1 if @fd refers to a terminal, 0 if not. If @fd is not a valid * file descriptor, -1 is returned and error state is set accordingly. */ API_EXPORTED int mm_isatty(int fd) { int rv; int prev_err = errno; rv = isatty(fd); if (rv == 0) { if (errno != EINVAL && errno != ENOTTY) return mm_raise_from_errno("isatty(%i) failed", fd); // If errno is EINVAL or ENOTTY, fd is actually not a tty, // but this is not an error, thus we restore errno as it was errno = prev_err; } return rv; } struct mm_dirstream { DIR * dir; struct mm_dirent * dirent; }; /** * mm_chdir() - change working directory * @path: path to new working directory * * The mm_chdir() function causes the directory named by the pathname pointed * to by the @path argument to become the current working directory; that is, * the starting point for path searches for pathnames that are not absolute. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_chdir(const char* path) { if (chdir(path) != 0) return mm_raise_from_errno("chdir(%s) failed", path); return 0; } /** * mm_getcwd() - get the pathname of the current working directory * @buf: pointer to buffer where to write pathname or NULL * @size: size of buffer pointed to by @buf if not NULL * * The mm_getcwd() function places an absolute pathname of the current * working directory in the array pointed to by @buf, and return @buf. The * pathname copied to the array contains no components that are symbolic * links. The @size argument is size of the buffer pointed to by @buf. * * If @buf is NULL, space is allocated as necessary to store the pathname. * In such a case, @size argument is ignored. This space may later be freed * with free(). * * Return: a pointer to a string containing the pathname of the current * working directory in case of success. Otherwise NULL is returned and * error state is set accordingly. */ API_EXPORTED char* mm_getcwd(char* buf, size_t size) { char* path; // Needed for glibc's getcwd() to allocate the needed size if (!buf) size = 0; path = getcwd(buf, size); if (!path) { if (errno == ERANGE) mm_raise_error(ERANGE, "buffer too short for " "holding current directory path"); else mm_raise_from_errno("can't get current directory"); } return path; } /** * mm_rmdir() - remove a directory * @path: path to the directory to remove * * The mm_rmdir() function removes the directory named by the pathname pointed * to by the @path argument. It only works on empty directories. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_rmdir(const char* path) { if (rmdir(path) != 0) return mm_raise_from_errno("rmdir(%s) failed", path); return 0; } static void conv_native_to_mm_stat(struct mm_stat* buf, const struct stat* native_stat) { *buf = (struct mm_stat) { .dev = native_stat->st_dev, .ino = native_stat->st_ino, .uid = native_stat->st_uid, .gid = native_stat->st_gid, .mode = native_stat->st_mode, .nlink = native_stat->st_nlink, .size = native_stat->st_size, .nblocks = native_stat->st_blocks, .atime = native_stat->st_atime, .ctime = native_stat->st_ctime, .mtime = native_stat->st_mtime, }; // Accommodate for end of string to be consistent with mm_readlink() if (S_ISLNK(buf->mode)) buf->size += 1; } /** * mm_fstat() - get file status from file descriptor * @fd: file descriptor * @buf: pointer to mm_stat structure to fill * * This function obtains information about an open file associated with the * file descriptor @fd, and writes it to the area pointed to by @buf. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_fstat(int fd, struct mm_stat* buf) { struct stat native_stat; if (fstat(fd, &native_stat) < 0) return mm_raise_from_errno("fstat(%i) failed", fd); conv_native_to_mm_stat(buf, &native_stat); return 0; } /** * mm_stat() - get file status from file path * @path: path of file * @buf: pointer to mm_stat structure to fill * @flags: 0 or MM_NOFOLLOW * * This function obtains information about an file located by @path, and writes * it to the area pointed to by @buf. If @path refers to a symbolic link, the * information depents on the value of @flags. If @flags is 0, the information * returned will be the one of the target of symbol link. Otherwise, if * MM_NOFOLLOW is set in @flags, the information will be the one of the * symbolic link itself. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_stat(const char* path, struct mm_stat* buf, int flags) { struct stat native_stat; if (flags & MM_NOFOLLOW) { if (lstat(path, &native_stat) < 0) return mm_raise_from_errno("lstat(%s) failed", path); } else { if (stat(path, &native_stat) < 0) return mm_raise_from_errno("stat(%s) failed", path); } conv_native_to_mm_stat(buf, &native_stat); return 0; } /** * mm_futimens() - set file access and modification times of an opened file * @fd: file descriptor of an open file whose times must be changed * @ts: array of 2 timestamps (access and modification time). If NULL, * both time are set to current time. * * This set the access and modification times of a file associated with the * file description @fd to the values of the @ts argument. The file's relevant * timestamp is set to the greatest value supported by the file system that is * not greater than the specified time. * * The @ts argument is an array of two timespec structures. The first array * member @ts[0] represents the date and time of last access, and the second * member @ts[1] represents the date and time of last modification. The times * in the mmm_timespec structure are measured in seconds and nanoseconds since * the Epoch. * * If the tv_nsec field of a mm_timespec structure has the special value * UTIME_NOW, the file's relevant timestamp is set to the greatest value * supported by the file system that is not greater than the current time. If * the tv_nsec field has the special value UTIME_OMIT, the file's relevant * timestamp is not changed. In either case, the tv_sec field shall be ignored. * * If the @ts argument is a null pointer, both the access and modification * timestamps are set to the greatest value supported by the file system that * is not greater than the current time. * * Return: 0 in case of success, -1 otherwise with error state set. */ API_EXPORTED int mm_futimens(int fd, const struct mm_timespec ts[2]) { if (futimens(fd, (const struct timespec*)ts)) return mm_raise_from_errno("futimens(%i) failed", fd); return 0; } /** * mm_utimens() - set file access and modification times * @path: path to file whose times must be changed * @ts: array of 2 timestamps (access and modification time). If NULL, * both time are set to current time. * @flags 0 or MM_NOFOLLOW * * This does the same as mm_futimens() excepting that file is referred through * its file path while with mm_futimens() it must be referred though a file * descriptor. * * See documentation of mm_futimens() for the documentation of @ts argument. * * If @flags is 0, the time are set to the target if @path refers to a symlink. * Otherwise, if MM_NOFOLLOW is set in @flags, the times of the symlink itself * are set. * * Return: 0 in case of success, -1 otherwise with error state set. */ API_EXPORTED int mm_utimens(const char* path, const struct mm_timespec ts[2], int flags) { int uflags = 0; if (flags & ~MM_NOFOLLOW) return mm_raise_error(EINVAL, "invalid flags (0x%08x)", flags); if (flags & MM_NOFOLLOW) uflags |= AT_SYMLINK_NOFOLLOW; if (utimensat(AT_FDCWD, path, (const struct timespec*)ts, uflags)) return mm_raise_from_errno("utimensat(%s) failed", path); return 0; } /** * mm_readlink() - read value of a symbolic link * @path: pathname of symbolic link * @buf: buffer receiving the value * @bufsize: length of @buf * * mm_readlink() places the contents of the symbolic link @path in the buffer * @buf, which has size @bufsize. It does append a null byte to @buf. If @buf * is too small to hold the contents, error will be returned. The required size * for the buffer can be obtained from &struct stat.filesize value returned by * a call to mm_stat() on the link. * * Return: 0 in case of success, -1 otherwise with error state set. */ API_EXPORTED int mm_readlink(const char* path, char* buf, size_t bufsize) { ssize_t rsz; rsz = readlink(path, buf, bufsize); if (rsz < 0) return mm_raise_from_errno("readlink(%s) failed", path); if (rsz == (ssize_t)bufsize) return mm_raise_error(EOVERFLOW, "target too large"); buf[rsz] = '\0'; return 0; } static int get_file_type(int dirfd, const char* path) { int rv; struct stat stat; rv = fstatat(dirfd, path, &stat, AT_SYMLINK_NOFOLLOW); if (rv != 0) return rv; switch (stat.st_mode & S_IFMT) { case S_IFIFO: return MM_DT_FIFO; case S_IFCHR: return MM_DT_CHR; case S_IFBLK: return MM_DT_BLK; case S_IFDIR: return MM_DT_DIR; case S_IFREG: return MM_DT_REG; case S_IFLNK: return MM_DT_LNK; case S_IFSOCK: return MM_DT_SOCK; default: return MM_DT_UNKNOWN; } } #define RECURSION_MAX 100 /** * mm_remove_rec() - internal helper to recursively clean given folder * @dirfd: directory file descriptor to clean * @flags: option flag to return on error * @rec_lvl: maximum recursion level * * Many error return values are *explicitly* skipped. * Since this is a recursive removal, we should not stop when we encounter * a forbidden file or folder. This except if the @flag contains MM_FAILONERROR. * * Return: 0 on success, -1 on error */ static int mm_remove_rec(int dirfd, int flags, int rec_lvl) { int rv, exit_value; int newdirfd; int type; DIR * dir = NULL; const struct dirent * dp; int unlink_flag; exit_value = -1; if (UNLIKELY(rec_lvl < 0)) return mm_raise_error(EOVERFLOW, "Too many levels of recurion"); if ((dir = fdopendir(dirfd)) == NULL) { close(dirfd); if (flags & MM_FAILONERROR) return exit_value; else return 0; } while ((dp = readdir(dir)) != NULL) { type = get_file_type(dirfd, dp->d_name); if (type > 0 && (flags & type) == 0) continue; // only consider filtered files /* skip "." and ".." directories */ if (is_wildcard_directory(dp->d_name)) continue; if (type == MM_DT_DIR) { /* remove the inside of the folder */ newdirfd = openat(dirfd, dp->d_name, O_CLOEXEC, 0); if (newdirfd == -1) { if (flags & MM_FAILONERROR) goto exit; else continue; } rv = mm_remove_rec(newdirfd, flags, rec_lvl - 1); if (rv != 0 && (flags & MM_FAILONERROR)) goto exit; /* try to remove the folder again * it MAY have been cleansed by the recursive remove * call */ (void) unlinkat(dirfd, dp->d_name, AT_REMOVEDIR); } unlink_flag = (type == MM_DT_DIR) ? AT_REMOVEDIR : 0; rv = unlinkat(dirfd, dp->d_name, unlink_flag); if (rv != 0 && (flags & MM_FAILONERROR)) goto exit; } exit_value = 0; exit: closedir(dir); return exit_value;; } /** * mm_remove() - remove a file if of type authorized in flag list * @path: path to the directory to remove * @flags: bitflag of authorized filetypes that can be removed * and removal option * * The mm_remove() function removes a file if its type is authorized in given * type flag argument. It also can remove files recursively. * * The @flag express whether the call is recursive and the recursivity behavior. * If the MM_RECURSIVE flag is set, then the call will be recursive. * Additionally, if MM_FAILONERROR is set, the removal operation will stop on * the first failure it will encounter. Otherwise, it will ignore all the errors * on any file or folder, and only return whether the call could be completed * with full success, or any number of possible error. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_remove(const char* path, int flags) { int dirfd, rv, error_flags; int type = get_file_type(AT_FDCWD, path); if (type < 0) return mm_raise_from_errno("unable to get %s filetype", path); if ((flags & type) == 0) return mm_raise_error(EPERM, "failed to remove %s: " "invalid type", path); /* recusivly try to empty the folder */ if (flags & MM_RECURSIVE && type == MM_DT_DIR) { if ((dirfd = mm_open(path, O_DIRECTORY, 0)) == -1) { return mm_raise_from_errno("recursive mm_remove(%s) failed: " "cannot open directory", path); } error_flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_NOLOG); rv = mm_remove_rec(dirfd, flags, RECURSION_MAX); mm_error_set_flags(error_flags, MM_ERROR_NOLOG); if (rv != 0 && !(flags & MM_FAILONERROR)) { return mm_raise_from_errno( "recursive mm_remove(%s) failed", path); } /* allow rmdir(".") when called with the recursive flag only */ if (is_wildcard_directory(path)) return rv; } if (type == MM_DT_DIR) return mm_rmdir(path); else return mm_unlink(path); } /** * mm_opendir() - open a directory stream * @path: path to directory * * The mm_opendir() function opens a directory stream corresponding to the * directory named by the @path argument. The directory stream is positioned * at the first entry. * * Return: A pointer usable with mm_readdir() on success, to be closed using * mm_closedir(). In case of error, NULL is returned and an error state is * set accordingly. */ API_EXPORTED MM_DIR* mm_opendir(const char* path) { DIR * dir; MM_DIR * d; d = malloc(sizeof(*d)); if (d == NULL) { mm_raise_from_errno("opendir(%s) failed", path); return NULL; } dir = opendir(path); if (dir == NULL) { free(d); mm_raise_from_errno("opendir(%s) failed", path); return NULL; } d->dir = dir; d->dirent = NULL; return d; } /** * mm_closedir() - close a directory stream * @dir: directory stream to close * * The mm_closedir() function closes the directory stream referred to by the * argument @dir. Upon return, the value of @dir may no longer point to an * accessible object of the type MM_DIR. */ API_EXPORTED void mm_closedir(MM_DIR* dir) { if (dir == NULL) return; closedir(dir->dir); free(dir->dirent); free(dir); } /** * mm_rewinddir() - reset a directory stream to its beginning * @dir: directory stream to rewind * * The mm_rewinddir() function resets the position of the directory stream to * which @dir refers to the beginning of the directory. It causes the directory * stream to refer to the current state of the corresponding directory, as a * call to mm_opendir() would have done. */ API_EXPORTED void mm_rewinddir(MM_DIR* dir) { if (dir == NULL) { mm_raise_error(EINVAL, "mm_rewinddir() does not accept NULL pointers"); return; } rewinddir(dir->dir); } /** * mm_readdir() - read current entry from directory stream and advance it * @d: directory stream to read * @status: if not NULL, will contain whether readdir returned on error or * end of dir * * The type MM_DIR represents a directory stream, which is an ordered sequence * of all the directory entries in a particular directory. Directory entries * present the files they contain, which may be added or removed from it * asynchronously to the operation of mm_readdir(). * * The mm_readdir() function returns a pointer to a structure representing the * directory entry at the current position in the directory stream specified by * the argument @dir, and position the directory stream at the next entry which * will be valid until the next call to mm_readdir() with the same @dir * argument. It returns a NULL pointer upon reaching the end of the directory * stream. * * The @status argument is optional. It can be provided to gather information on * why the call to mm_readdir() returned NULL. Most of the time, this will * happen on end-of-dir, in which case status will be 0. However this is not * always the case - eg. if a required internal allocation fails - and then * status is filled with a negative value. * * Return: pointer to the file entry if directory stream has not reached the * end. NULL otherwise. In such a case and if an error has occurred and error * state is set accordingly and if @status is not NULL, pointed variable * will be set to -1. */ API_EXPORTED const struct mm_dirent* mm_readdir(MM_DIR* d, int * status) { size_t reclen, namelen; struct dirent* rd; if (status != NULL) *status = -1; if (d == NULL) { mm_raise_error(EINVAL, "mm_readdir() does not accept NULL pointers"); return NULL; } rd = readdir(d->dir); if (rd == NULL) { if (status != NULL) *status = 0; return NULL; } /* expand mm_dirent structure if needed */ namelen = strlen(rd->d_name) + 1; reclen = sizeof(*d->dirent) + namelen; if (UNLIKELY(d->dirent == NULL || d->dirent->reclen < reclen)) { void * tmp = realloc(d->dirent, reclen); if (tmp == NULL) { mm_raise_from_errno("failed to alloc required memory"); return NULL; } d->dirent = tmp; d->dirent->reclen = reclen; } switch (rd->d_type) { case DT_FIFO: d->dirent->type = MM_DT_FIFO; break; case DT_CHR: d->dirent->type = MM_DT_CHR; break; case DT_DIR: d->dirent->type = MM_DT_DIR; break; case DT_BLK: d->dirent->type = MM_DT_BLK; break; case DT_REG: d->dirent->type = MM_DT_REG; break; case DT_LNK: d->dirent->type = MM_DT_LNK; break; case DT_SOCK: d->dirent->type = MM_DT_SOCK; break; default: d->dirent->type = MM_DT_UNKNOWN; break; } strncpy(d->dirent->name, rd->d_name, namelen); if (status != NULL) *status = 0; return d->dirent; } #define COPYBUFFER_SIZE (1024*1024) // 1MiB static int clone_fd_fallback(int fd_in, int fd_out) { size_t wbuf_sz; char * buffer, * wbuf; ssize_t rsz, wsz; int rv = -1; buffer = malloc(COPYBUFFER_SIZE); if (!buffer) return mm_raise_from_errno("unable to alloc transfer buffer"); do { // Perform read operation rsz = mm_read(fd_in, buffer, COPYBUFFER_SIZE); if (rsz < 0) goto exit; // Do write of what has been read, possibly chunked if transfer // got interrupted wbuf = buffer; wbuf_sz = rsz; while (wbuf_sz) { wsz = mm_write(fd_out, wbuf, wbuf_sz); if (wsz < 0) goto exit; wbuf += wsz; wbuf_sz -= wsz; } } while (rsz != 0); rv = 0; exit: free(buffer); return rv; } static int clone_fd_try_cow(int fd_in, int fd_out) { #if HAVE_COPY_FILE_RANGE ssize_t rsz; ssize_t written = 0; int err, prev_errno = errno; do { rsz = copy_file_range(fd_in, NULL, fd_out, NULL, SSIZE_MAX, 0); if (rsz > 0) written += rsz; } while (rsz > 0); if (rsz < 0) { err = errno; if (err == ENOSYS || err == EXDEV) { errno = prev_errno; return clone_fd_fallback(fd_in, fd_out); } return mm_raise_from_errno("copy_file_range failed"); } // copy_file_range may return 0 instead of error in case of // some filesystem. If no data has been written so far, try // copy fallback. return written ? 0 : clone_fd_fallback(fd_in, fd_out); #else return clone_fd_fallback(fd_in, fd_out); #endif /* if HAVE_COPY_FILE_RANGE */ } static int clone_fd_force_cow(int fd_in, int fd_out) { #ifdef FICLONE if (ioctl(fd_in, FICLONE, fd_out)) return mm_raise_from_errno("Failed to clone file desc"); return 0; #else (void)fd_in; (void)fd_out; return mm_raise_error(ENOTSUP, "COW is not supported on platform"); #endif } static int copy_symlink(const char* src, const char* dst) { int rv = 0; size_t tgt_sz; char* target = NULL; struct stat buf; if (lstat(src, &buf)) return mm_raise_from_errno("lstat(%s) failed", src); tgt_sz = buf.st_size + 1; target = mm_malloca(tgt_sz); if (!target) return -1; if (mm_readlink(src, target, tgt_sz) || mm_symlink(target, dst)) rv = -1; mm_freea(target); return rv; } static int clone_srcfd(int fd_in, const char* dst, int flags, int mode) { int fd_out = -1; int rv = -1; int saved_errno; fd_out = mm_open(dst, O_WRONLY|O_CREAT|O_EXCL, mode); if (fd_out == -1) return -1; switch (flags & (MM_NOCOW & MM_FORCECOW)) { case MM_FORCECOW: rv = clone_fd_force_cow(fd_in, fd_out); break; case MM_NOCOW: rv = clone_fd_fallback(fd_in, fd_out); break; default: rv = clone_fd_try_cow(fd_in, fd_out); } if (rv) { saved_errno = errno; unlink(dst); errno = saved_errno; } mm_close(fd_out); return rv; } LOCAL_SYMBOL int copy_internal(const char* src, const char* dst, int flags, int mode) { int fd_in = -1; int rv = -1; int src_oflags; int err, prev_err = errno; src_oflags = O_RDONLY; if (flags & MM_NOFOLLOW) src_oflags |= O_NOFOLLOW; fd_in = open(src, src_oflags, 0); if (fd_in == -1) { err = errno; if ((flags & MM_NOFOLLOW) && (err == ELOOP)) { errno = prev_err; return copy_symlink(src, dst); } return mm_raise_from_errno("Cannot open %s", src); } rv = clone_srcfd(fd_in, dst, flags, mode); mm_close(fd_in); return rv; } mmlib-1.4.2/src/file-win32.c000066400000000000000000001352771435717460000154560ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmsysio.h" #include "mmerrno.h" #include "mmlib.h" #include "file-internal.h" #include "local-ipc-win32.h" #include "socket-win32.h" #include "utils-win32.h" #include "volume-win32.h" #include "mmlog.h" #include #include #include #include #include #include #include #include #define NUM_ATTEMPT 256 #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02 #endif struct mm_dirstream { HANDLE hdir; int find_first_done; // has folder been though FindFirstFile()? struct mm_dirent * dirent; char dirname[]; }; static int win32_temp_path_len; static char win32_temp_path[(MAX_PATH + 1)]; MM_CONSTRUCTOR(win32_temp_path) { int rv; rv = GetTempPath(MAX_PATH, win32_temp_path); if (rv != 0 && rv < MAX_PATH) { win32_temp_path_len = strlen(win32_temp_path); } else { win32_temp_path[0] = 0; win32_temp_path_len = 0; } } /** * mmlib_read() - perform a read operation using local implementation * @hnd: handle on which to read * @buf: buffer to hold the data to read * @nbyte: number of byte to read * * Perform read assuming file type on which ReadFile() is possible without * passing special flags or not requiring transforming the data. * * Return: number of byte read in case of success. Otherwise -1 is returned and * error state is set accordingly */ static ssize_t mmlib_read(HANDLE hnd, void* buf, size_t nbyte) { DWORD read_sz; if (!ReadFile(hnd, buf, nbyte, &read_sz, NULL)) { // If write end is closed and pipe is empty, this is not an // error. A success must be returned with 0 byte read. if (GetLastError() == ERROR_BROKEN_PIPE) return 0; return -1; } return read_sz; } /** * mmlib_write() - perform a write operation using local implementation * @hnd: handle on which to write * @buf: buffer to hold the data to write * @nbyte: number of byte to write * * Perform write assuming file type on which WriteFile() is possible without * passing special flags or not requiring transforming the data. * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t mmlib_write(HANDLE hnd, const void* buf, size_t nbyte) { DWORD written_sz; if (!WriteFile(hnd, buf, nbyte, &written_sz, NULL)) return -1; return written_sz; } /** * console_read() - read UTF-8 from console * @hnd: console handle * @buf: buffer that should hold the console input * @nbyte: size of @buf * * Return: In case of success, a non negative number corresponding to the * number of the byte read. -1 in case of failure with error state set */ static ssize_t console_read(HANDLE hnd, char* buf, size_t nbyte) { DWORD nchar16_read; size_t nchar16; char16_t* buf16; int rsz = -1; // Allocate supplementary buffer to hold UTF-16 data from console nchar16 = nbyte / sizeof(char16_t); buf16 = mm_malloca(nchar16*sizeof(char16_t)); // Read console input in UTF-16 if (!ReadConsoleW(hnd, buf16, nchar16, &nchar16_read, NULL)) goto exit; // Convert in console data in UTF-8 rsz = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf16, nchar16_read, buf, nbyte, NULL, NULL); exit: mm_freea(buf16); return rsz; } /** * console_write() - write UTF-8 to console * @hnd: handle on which to write * @buf: UTF-8 string to be written on console * @nbyte: size of @buf * * Return: In case of success, a non negative number corresponding to the * number of the byte written. -1 in case of failure with error state set */ static ssize_t console_write(HANDLE hnd, const char* buf, size_t nbyte) { DWORD nchar16_written; int nchar16; char16_t* buf16 = NULL; ssize_t rsz = -1; // Allocate temporary buffers buf16 = mm_malloca(2*nbyte*sizeof(*buf16)); // Convert UTF-8 into UTF-16 nchar16 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, buf, nbyte, buf16, 2*nbyte); if (nchar16 < 0) goto exit; // Write the UTF-16 sequence to console if (!WriteConsoleW(hnd, buf16, nchar16, &nchar16_written, NULL)) goto exit; // Convert the number of UTF-16 code unit written into number of bytes // in the original UTF-8 sequence rsz = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf16, nchar16_written, NULL, -1, NULL, NULL); exit: mm_freea(buf16); return rsz; } /** * hnd_write_binary() - perform a binary write operation on handle * @hnd: handle on which to write * @fd_info: file desriptor mmlib info * @buf: buffer to hold the data to write * @nbyte: number of byte to write * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t hnd_write_binary(HANDLE hnd, int fd_info, const void* buf, size_t nbyte) { switch (fd_info & FD_TYPE_MASK) { case FD_TYPE_NORMAL: case FD_TYPE_PIPE: return mmlib_write(hnd, buf, nbyte); case FD_TYPE_CONSOLE: return console_write(hnd, buf, nbyte); case FD_TYPE_SOCKET: return sock_hnd_write(hnd, buf, nbyte); case FD_TYPE_IPCDGRAM: return ipc_hnd_write(hnd, buf, nbyte); default: abort(); } } /** * hnd_write_binary() - perform a binary write operation on handle * @hnd: handle on which to write * @fd_info: file desriptor mmlib info * @buf: buffer to hold the data to write * @nbyte: number of byte to write * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t hnd_write_text(HANDLE hnd, int fd_info, const char* buf, size_t sz) { char* buf_crlf = NULL; int i, len_crlf; ssize_t rsz; buf_crlf = mm_malloca(2*sz); if (!buf_crlf) return -1; // Copy buf to buf_crlf with LF -> CRLF expansion for (i = 0, len_crlf = 0; i < (int)sz; i++) { if (buf[i] == '\n') buf_crlf[len_crlf++] = '\r'; buf_crlf[len_crlf++] = buf[i]; } rsz = hnd_write_binary(hnd, fd_info, buf_crlf, len_crlf); // Remove from count the inserted CR in each CRLF. This way, we really // have the size of the part of @buf which has been written for (i = 1; i < len_crlf; i++) { if (buf_crlf[i-1] == '\r' && buf_crlf[i] == '\n') rsz--; } mm_freea(buf_crlf); return rsz; } /** * hnd_write() - perform a generic write operation on handle * @hnd: handle on which to write * @fd_info: file desriptor info * @buf: buffer to hold the data to write * @sz: number of byte to write * * This write operation switch between text or binary mode depending on content pointed to by @pinfo. * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t hnd_write(HANDLE hnd, int fd_info, const void* buf, size_t sz) { if ((fd_info & FD_FLAG_TEXT)) return hnd_write_text(hnd, fd_info, buf, sz); return hnd_write_binary(hnd, fd_info, buf, sz); } /** * hnd_read_binary() - perform a binary read operation on handle * @hnd: handle on which to read * @fd_info: file desriptor mmlib info * @buf: buffer to hold the data to read * @nbyte: number of byte to read * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t hnd_read_binary(HANDLE hnd, int fd_info, void* buf, size_t nbyte) { switch (fd_info & FD_TYPE_MASK) { case FD_TYPE_NORMAL: case FD_TYPE_PIPE: return mmlib_read(hnd, buf, nbyte); case FD_TYPE_CONSOLE: return console_read(hnd, buf, nbyte); case FD_TYPE_SOCKET: return sock_hnd_read(hnd, buf, nbyte); case FD_TYPE_IPCDGRAM: return ipc_hnd_read(hnd, buf, nbyte); default: abort(); } } static ssize_t conv_crlf_to_lf(int* pinfo, char* buf, ssize_t rsz, size_t maxsz) { int fd_info = *pinfo; char prev_ch, curr_ch; char* dst = buf; const char* src = buf; const char* src_end = src + rsz; // Beginning of CRLF -> LF depends whether a CR is carried. if (fd_info & FD_FLAG_CARRY) { prev_ch = fd_info_get_carry_char(fd_info); *pinfo = fd_info = fd_info_drop_carry(fd_info); } else { prev_ch = *src++; } // Perform CRLF -> LF conversion for (; src < src_end; prev_ch = curr_ch, src++) { curr_ch = *src; if ((prev_ch == '\r') && (curr_ch == '\n')) continue; *dst++ = prev_ch; } if ((prev_ch == '\r') || (dst == buf + maxsz)) *pinfo = fd_info_set_carry_char(fd_info, prev_ch); else *dst++ = prev_ch; return dst - buf; } /** * hnd_read() - perform a generic read operation on handle * @hnd: handle on which to read * @fd_info: file desriptor mmlib info * @buf: buffer to hold the data to read * @nbyte: number of byte to read * * Return: number of byte written in case of success. Otherwise -1 is returned * and error state is set accordingly. */ static ssize_t hnd_read(HANDLE hnd, int* restrict pinfo, void* buf, size_t nbyte) { int fd_info = *pinfo; ssize_t rsz; rsz = hnd_read_binary(hnd, fd_info, buf, nbyte); if (rsz <= 0) return rsz; if (fd_info & FD_FLAG_TEXT) rsz = conv_crlf_to_lf(pinfo, buf, rsz, nbyte); return rsz; } /* doc in posix implementation */ API_EXPORTED int mm_open(const char* path, int oflag, int mode) { HANDLE hnd; int fdinfo, fd = -1; struct local_secdesc lsd; struct w32_create_file_options opts; if (set_w32_create_file_options(&opts, oflag)) return -1; if (local_secdesc_init_from_mode(&lsd, mode)) { mm_raise_from_w32err("can't create security descriptor"); goto exit; } hnd = open_handle(path, opts.access_mode, opts.creation_mode, lsd.sd, opts.file_attribute); if (hnd == INVALID_HANDLE_VALUE) { mm_raise_from_w32err("Can't get handle for %s", path); goto exit; } fdinfo = FD_TYPE_NORMAL; if (oflag & O_APPEND) fdinfo |= FD_FLAG_APPEND; if (wrap_handle_into_fd(hnd, &fd, fdinfo)) { CloseHandle(hnd); goto exit; } exit: local_secdesc_deinit(&lsd); return fd; } /* doc in posix implementation */ API_EXPORTED int mm_close(int fd) { if (fd == -1) return 0; if (_close(fd) < 0) return mm_raise_from_errno("_close(%i) failed", fd); set_fd_info(fd, FD_TYPE_UNKNOWN); return 0; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_read(int fd, void* buf, size_t nbyte) { HANDLE hnd; int fd_info; ssize_t rsz; if (unwrap_handle_from_fd(&hnd, fd)) return -1; fd_info = get_fd_info_checked(fd); if (fd_info < 0) return mm_raise_error(EBADF, "Invalid file descriptor: %i", fd); rsz = hnd_read(hnd, &fd_info, buf, nbyte); if (rsz < 0) mm_raise_from_w32err("reading from fd=%i failed", fd); return rsz; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_write(int fd, const void* buf, size_t nbyte) { HANDLE hnd; int fd_info; ssize_t rsz; if (unwrap_handle_from_fd(&hnd, fd)) return -1; fd_info = get_fd_info_checked(fd); if (fd_info < 0) return mm_raise_error(EBADF, "Invalid file descriptor: %i", fd); // If file is opened in append mode, we must reset file pointer to // the end. if (fd_info & FD_FLAG_APPEND) { if (!SetFilePointer(hnd, 0, NULL, FILE_END)) { mm_raise_from_w32err("handle is opened in append mode " "but can't seek file end"); return -1; } } rsz = hnd_write(hnd, fd_info, buf, nbyte); if (rsz < 0) mm_raise_from_w32err("writing to fd=%i failed", fd); return rsz; } /* doc in posix implementation */ API_EXPORTED int mm_dup(int fd) { int newfd; newfd = _dup(fd); if (newfd < 0) return mm_raise_from_errno("_dup(%i) failed", fd); set_fd_info(newfd, get_fd_info(fd)); return newfd; } /* doc in posix implementation */ API_EXPORTED int mm_dup2(int fd, int newfd) { if (_dup2(fd, newfd) < 0) return mm_raise_from_errno("_dup2(%i, %i) failed", fd, newfd); set_fd_info(newfd, get_fd_info(fd)); return newfd; } /* doc in posix implementation */ API_EXPORTED int mm_pipe(int pipefd[2]) { HANDLE wr_hnd, rd_hnd; if (!CreatePipe(&rd_hnd, &wr_hnd, NULL, 16*MM_PAGESZ)) return mm_raise_from_w32err("Cannot create pipe"); if (wrap_handle_into_fd(rd_hnd, &pipefd[0], FD_TYPE_PIPE)) { CloseHandle(rd_hnd); CloseHandle(wr_hnd); return -1; } if (wrap_handle_into_fd(wr_hnd, &pipefd[1], FD_TYPE_PIPE)) { _close(pipefd[0]); CloseHandle(wr_hnd); return -1; } return 0; } /* doc in posix implementation */ API_EXPORTED int mm_isatty(int fd) { int info; info = get_fd_info_checked(fd); if (info == -1) { mm_raise_error(EBADF, "Invalid file descriptor (%i)", fd); return -1; } return (info & FD_FLAG_ISATTY) == FD_FLAG_ISATTY; } /** * scrub_user_trash_path() - attempt to clean remainings of deleted files * @trash_buffer: Buffer to use to prepare full path of file to be * deleted. It must be initialized with the trash prefix * to use to search for file delete by mmlib. Allocated * size must be at least MAX_PATH+1 long. * @prefix_len: length of prefix in @trash_buffer (excluding '\0') * * NOTE: This function assumes this is legal to write on @trash_prefix between * index @prefix_len and MAX_PATH+1 */ static void scrub_user_trash_path(char16_t* trash_buffer, int prefix_len) { WIN32_FIND_DATAW find_data; HANDLE find_hnd; // Start search file matching .mmlib*.deleted in prefix wcscpy(trash_buffer + prefix_len, L".mmlib-*.deleted"); find_hnd = FindFirstFileW(trash_buffer, &find_data); if (find_hnd == INVALID_HANDLE_VALUE) return; // Loop over the file matching the pattern and try delete them. We do // not bother it fails. do { wcscpy(trash_buffer + prefix_len, find_data.cFileName); DeleteFileW(trash_buffer); } while (FindNextFileW(find_hnd, &find_data)); FindClose(find_hnd); } /** * rename_file_to_trash_from_handle() - rename a file to trash volume folder * @hnd: handle of the file opened for deletion * * This renames the file opened through @hnd to be renamed to the recycle bin * folder of its volume: the recycle bin is a folder present in each mounted * volume and a writeable folder is dedicated in it for each logged user. Hence * the file renaming should never failed as long as the filename used is * unique. To this end, the file to removed is renamed following a pattern * based on its unique file id on the volume. * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error. */ static int rename_file_to_trash_from_handle(HANDLE hnd) { FILE_ID_INFO id_info = {0}; const struct volume* vol; union { char buffer[2*(MAX_PATH+1) + sizeof(FILE_RENAME_INFO)]; FILE_RENAME_INFO info; } rename_buffer; FILE_RENAME_INFO* info = &rename_buffer.info; size_t info_sz; mm_ino_t ino; int rlen; // Get file id and deduce volume of the handle and volume trash prefix if (get_file_id_info_from_handle(hnd, &id_info) || !(vol = get_volume_from_dev(id_info.VolumeSerialNumber)) || (rlen = volume_get_trash_prefix_u16(vol, info->FileName)) < 0) { return -1; } scrub_user_trash_path(info->FileName, rlen); // Some version of mingw64-crt defines FILE_ID_128 as 2 ULONGLONG // fields, some as BYTE[16]. Copy explicitly to mm_ino_t avoid // us any trouble memcpy(&ino, &id_info.FileId, sizeof(ino)); // append path of file renamed in trash (named after its file id) swprintf(info->FileName + rlen, MAX_PATH - rlen, L".mmlib-%016llx%016llx.deleted", ino.id_high, ino.id_low); info->FileName[MAX_PATH] = L'\0'; // ensure filename is terminated // Finalize the structure of rename operation and perform it info->RootDirectory = NULL; info->ReplaceIfExists = FALSE; info->FileNameLength = wcslen(info->FileName); info_sz = sizeof(*info); info_sz += sizeof(info->FileName[0]) * info->FileNameLength; if (!SetFileInformationByHandle(hnd, FileRenameInfo, info, info_sz)) return -1; return 0; } /* doc in posix implementation */ API_EXPORTED int mm_rename(const char* oldpath, const char* newpath) { /* TODO: make the verifications for the directories (returns an error * in case the directory is not empty). */ int newpath_u16_len; FILE_RENAME_INFO * info; size_t sz; HANDLE hndold = INVALID_HANDLE_VALUE; HANDLE hndnew = INVALID_HANDLE_VALUE; int rv = -1; DWORD flags, access; newpath_u16_len = get_utf16_buffer_len_from_utf8(newpath); if (newpath_u16_len < 0) return mm_raise_from_w32err("invalid UTF-8 sequence"); sz = newpath_u16_len * sizeof(char16_t) + sizeof(*info); info = mm_malloca(sz); if (!info) return -1; // FILE_FLAG_BACKUP_SEMANTICS is needed to open directories flags = FILE_FLAG_BACKUP_SEMANTICS; access = DELETE | FILE_WRITE_ATTRIBUTES; hndold = open_handle(oldpath, access, OPEN_EXISTING, NULL, flags); if (hndold == INVALID_HANDLE_VALUE) goto exit; conv_utf8_to_utf16(info->FileName, newpath_u16_len, newpath); info->RootDirectory = NULL; info->ReplaceIfExists = FALSE; info->FileNameLength = newpath_u16_len; // Try to rename the file oldpath to newpath. In case the renaming did // not work because a file named newpath was already used, then put it // in the trash and retry if (!SetFileInformationByHandle(hndold, FileRenameInfo, info, sz)) { hndnew = CreateFileW(info->FileName, access, FILE_SHARE_ALL, NULL, OPEN_EXISTING, flags, NULL); if ((hndnew == INVALID_HANDLE_VALUE) || (rename_file_to_trash_from_handle(hndnew) == -1) || !SetFileInformationByHandle(hndold, FileRenameInfo, info, sz)) { goto exit; } } rv = 0; exit: mm_freea(info); safe_closehandle(hndold); safe_closehandle(hndnew); return rv; } /** * delete_file_from_handle() - delete file opened by handle * @hnd: handle of the file to be deleted opened with the appropriate * access DELETE|FILE_WRITE_ATTRIBUTES * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error (this is meant to be done by the caller which has more * context, in particular the filename). */ static int delete_file_from_handle(HANDLE* hnd) { FILE_DISPOSITION_INFO dispose = {.DeleteFile = TRUE}; // Make file path available immediately even if file object is not // removed yet if (rename_file_to_trash_from_handle(hnd)) return -1; // Indicate kernel that file object should deleted. This will be // defered to when all handle to file object will be closed. we don't // mind to fail file has already been renamed: the file should be // eventually deleted afterwards anyway SetFileInformationByHandle(hnd, FileDispositionInfo, &dispose, sizeof(dispose)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_unlink(const char* path) { DWORD flags, access; HANDLE hnd; int rv = 0; // Do not follow symlink (Hence FILE_FLAG_OPEN_REPARSE_POINT). Also // FILE_FLAG_BACKUP_SEMANTICS is added to handle symlink to folder. flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS; access = DELETE | FILE_WRITE_ATTRIBUTES; hnd = open_handle(path, access, OPEN_EXISTING, NULL, flags); if (hnd == INVALID_HANDLE_VALUE) return mm_raise_from_w32err("Can't get handle for %s", path); if (delete_file_from_handle(hnd)) rv = mm_raise_from_w32err("Can't delete %s", path); CloseHandle(hnd); return rv; } #define TOKEN_ACCESS \ ( TOKEN_IMPERSONATE | TOKEN_QUERY \ | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ) /* * from http://blog.aaronballman.com/2011/08/how-to-check-access-rights/ */ static int test_access(SECURITY_DESCRIPTOR* sd, DWORD access_rights) { HANDLE proc_htoken = INVALID_HANDLE_VALUE; HANDLE imp_htoken = INVALID_HANDLE_VALUE; HANDLE hproc = GetCurrentProcess(); GENERIC_MAPPING mapping; PRIVILEGE_SET privileges = {0}; DWORD granted = 0, priv_len = sizeof(privileges); BOOL result = FALSE; int rv = -1; // Setup mapping to File generic rights mapping.GenericRead = FILE_GENERIC_READ; mapping.GenericWrite = FILE_GENERIC_WRITE; mapping.GenericExecute = FILE_GENERIC_EXECUTE; mapping.GenericAll = FILE_ALL_ACCESS; MapGenericMask(&access_rights, &mapping); // Get obtained a token suitable for impersonation and check access if (!OpenProcessToken(hproc, TOKEN_ACCESS, &proc_htoken) || !DuplicateToken(proc_htoken, SecurityImpersonation, &imp_htoken) || !AccessCheck(sd, imp_htoken, access_rights, &mapping, &privileges, &priv_len, &granted, &result)) { goto exit; } rv = (result == TRUE) ? 0 : EACCES; exit: safe_closehandle(imp_htoken); safe_closehandle(proc_htoken); return rv; } /* doc in posix implementation */ API_EXPORTED int mm_check_access(const char* path, int amode) { struct local_secdesc lsd; int rv = -1; HANDLE hnd; DWORD w32err, access_rights; hnd = open_handle_for_metadata(path, false); if (hnd == INVALID_HANDLE_VALUE) { // If we receive file not found, this is not an error and // must be reported as return value w32err = GetLastError(); if (w32err == ERROR_PATH_NOT_FOUND || w32err == ERROR_FILE_NOT_FOUND) return ENOENT; return mm_raise_from_w32err("Can't get handle for %s", path); } // If only file presence is tested, we skip the access test if (amode == F_OK) { rv = 0; goto exit; } if (local_secdesc_init_from_handle(&lsd, hnd)) goto exit; access_rights = 0; access_rights |= (amode & R_OK) ? GENERIC_READ : 0; access_rights |= (amode & W_OK) ? GENERIC_WRITE : 0; access_rights |= (amode & X_OK) ? GENERIC_EXECUTE : 0; rv = test_access(lsd.sd, access_rights); local_secdesc_deinit(&lsd); exit: if (rv < 0) mm_raise_from_w32err("Failed to test access of %s", path); CloseHandle(hnd); return rv; } /* doc in posix implementation */ API_EXPORTED int mm_link(const char* oldpath, const char* newpath) { int oldpath_u16_len, newpath_u16_len; char16_t * oldpath_u16, * newpath_u16; int retval = 0; // Get the length (in byte) of the string when converted in UTF-8 oldpath_u16_len = get_utf16_buffer_len_from_utf8(oldpath); newpath_u16_len = get_utf16_buffer_len_from_utf8(newpath); if (oldpath_u16_len < 0 || newpath_u16_len < 0) return mm_raise_from_w32err("invalid UTF-8 sequence"); // temporary alloc of the UTF-16 string oldpath_u16 = mm_malloca(oldpath_u16_len*sizeof(*oldpath_u16)); newpath_u16 = mm_malloca(newpath_u16_len*sizeof(*newpath_u16)); // Do actual UTF-8 -> UTF-16 conversion conv_utf8_to_utf16(oldpath_u16, oldpath_u16_len, oldpath); conv_utf8_to_utf16(newpath_u16, newpath_u16_len, newpath); if (!CreateHardLinkW(newpath_u16, oldpath_u16, NULL)) { mm_raise_from_w32err("CreateHardLinkW(%s, %s) failed", newpath, oldpath); retval = -1; } mm_freea(newpath_u16); mm_freea(oldpath_u16); return retval; } /* doc in posix implementation */ API_EXPORTED int mm_symlink(const char* oldpath, const char* newpath) { int oldpath_u16_len, newpath_u16_len; char16_t * oldpath_u16, * newpath_u16; int retval = 0; int err; DWORD flags, file_attrs; // Get the length (in byte) of the string when converted in UTF-8 oldpath_u16_len = get_utf16_buffer_len_from_utf8(oldpath); newpath_u16_len = get_utf16_buffer_len_from_utf8(newpath); if (oldpath_u16_len < 0 || newpath_u16_len < 0) return mm_raise_from_w32err("invalid UTF-8 sequence"); // Stack alloc the UTF-16 string oldpath_u16 = mm_malloca(oldpath_u16_len*sizeof(*oldpath_u16)); newpath_u16 = mm_malloca(newpath_u16_len*sizeof(*newpath_u16)); // Do actual UTF-8 -> UTF-16 conversion conv_utf8_to_utf16(oldpath_u16, oldpath_u16_len, oldpath); conv_utf8_to_utf16(newpath_u16, newpath_u16_len, newpath); flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; // Make directory symlink if target is detected as such. If target // (specified by oldpath) does not exist, the symlink will be // assumed to be symlink to a file. This is not a major problem to // do this assumption because having a file symlink pointing to a // directory is still functional to a certain extent (only opening // the target folder handle seems not functional) file_attrs = GetFileAttributesW(oldpath_u16); if ((file_attrs != INVALID_FILE_ATTRIBUTES) && (file_attrs & FILE_ATTRIBUTE_DIRECTORY)) flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; err = CreateSymbolicLinkW(newpath_u16, oldpath_u16, flags); if (!err && GetLastError() == ERROR_INVALID_PARAMETER) { /* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE was introduced * in 2017-03. If it is not recognized, try again without it */ flags &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; err = CreateSymbolicLinkW(newpath_u16, oldpath_u16, flags); } if (!err) { mm_raise_from_w32err("CreateSymbolicLinkW(%s, %s) failed", newpath, oldpath); retval = -1; } mm_freea(newpath_u16); mm_freea(oldpath_u16); return retval; } /* doc in posix implementation */ API_EXPORTED int mm_chdir(const char* path) { int rv, path_u16_len; char16_t* path_u16; path_u16_len = get_utf16_buffer_len_from_utf8(path); if (path_u16_len < 0) return mm_raise_from_w32err("Invalid UTF-8 path"); path_u16 = mm_malloca(path_u16_len * sizeof(*path_u16)); if (path_u16 == NULL) return mm_raise_from_w32err("Failed to alloc required memory!"); conv_utf8_to_utf16(path_u16, path_u16_len, path); rv = _wchdir(path_u16); mm_freea(path_u16); if (rv != 0) return mm_raise_from_errno("chdir(%s) failed", path); return rv; } /* doc in posix implementation */ API_EXPORTED char* mm_getcwd(char* buf, size_t size) { char* path = NULL; char16_t* path_u16; int path_u8_len, ret, path_u16_len; path_u16_len = 256; while (1) { path_u16 = malloc(path_u16_len*sizeof(*path_u16)); if (!path_u16) { mm_raise_from_errno("Buffer allocation failed"); return NULL; } // Try to get the copy the currdir to buffer (if buffer is NULL // or too short, the return value will be larger than provided // and will represent the needed size) ret = GetCurrentDirectoryW(path_u16_len, path_u16); if (ret == 0) { mm_raise_from_w32err("Failed to get current dir"); goto exit; } if (ret < path_u16_len) break; path_u16_len = ret; free(path_u16); } path_u8_len = get_utf8_buffer_len_from_utf16(path_u16); // If buf argument is supplied, we must check that the size is // sufficient if (buf && (int)size < path_u8_len) { mm_raise_error(ERANGE, "Buffer too short for holding " "current directory path"); goto exit; } // If buf argument is NULL, the buffer of necessary size must be // allocatted and returned if (!buf) { size = path_u8_len; buf = malloc(path_u8_len); if (!buf) { mm_raise_from_errno("Buffer allocation failed"); goto exit; } } conv_utf16_to_utf8(buf, size, path_u16); path = buf; exit: free(path_u16); return path; } /* doc in posix implementation */ API_EXPORTED int mm_rmdir(const char* path) { int rv, path_u16_len; char16_t* path_u16; path_u16_len = get_utf16_buffer_len_from_utf8(path); if (path_u16_len < 0) return mm_raise_from_w32err("Invalid UTF-8 path"); path_u16 = mm_malloca(path_u16_len * sizeof(*path_u16)); if (path_u16 == NULL) return mm_raise_from_w32err("Failed to alloc required memory!"); conv_utf8_to_utf16(path_u16, path_u16_len, path); rv = _wrmdir(path_u16); mm_freea(path_u16); if (rv != 0) return mm_raise_from_errno("rmdir(%s) failed", path); return rv; } /* * Take care: FILE_ATTRIBUTE_ARCHIVE can be added to almost any type of file * here we only consider it if left alone, and then consider it a regular file */ static int translate_filetype(DWORD type) { if (type & FILE_ATTRIBUTE_REPARSE_POINT) { return MM_DT_LNK; } else if (type & FILE_ATTRIBUTE_DIRECTORY) { return MM_DT_DIR; } else if ((type & FILE_ATTRIBUTE_NORMAL) || (type & FILE_ATTRIBUTE_ARCHIVE)) { return MM_DT_REG; } else { return MM_DT_UNKNOWN; } } static int win32_unlinkat(const char * prefix, const char * name, int type) { int rv; int len; char * path; /* path + "/" + name + "\0" */ len = strlen(prefix) + 1 + strlen(name) + 1; path = mm_malloca(len); *path = '\0'; strcat(path, prefix); strcat(path, "/"); strcat(path, name); if (type == MM_DT_DIR) rv = mm_rmdir(path); else rv = mm_unlink(path); mm_freea(path); return rv; } #define RECURSION_MAX 100 /** * mm_remove_rec() - internal helper to recursively clean given folder * @prefix: tracks the relative prefix path from the original callpoint * @d: pointer to the current MM_DIR structure to clean * @flags: option flag to return on error * @rec_lvl: maximum recursion level * * Many error return values are *explicitly* skipped. * Since this is a recursive removal, we should not stop when we encounter * a forbidden file or folder. This except if the @flag contains MM_FAILONERROR. * * Return: 0 on success, -1 on error */ static int mm_remove_rec(const char * prefix, MM_DIR * d, int flags, int rec_lvl) { int rv, status; unsigned int type; MM_DIR * newdir; const struct mm_dirent * dp; if (UNLIKELY(rec_lvl < 0)) return mm_raise_error(EOVERFLOW, "Too many levels of recurion"); while ((dp = mm_readdir(d, &status)) != NULL) { if (status != 0) return -1; type = d->dirent->type; if (type > 0 && (flags & type) == 0) continue; // only consider filtered files /* skip "." and ".." directories */ if (is_wildcard_directory(d->dirent->name)) continue; /* try removing the file or folder */ if (type == MM_DT_DIR) { /* remove the inside of the folder */ int len; char * newdir_path; /* newdir_path + "/" + name + "\0" */ len = strlen(prefix) + 1 + strlen(d->dirent->name) + 1; newdir_path = mm_malloca(len); if (newdir_path == NULL) return -1; *newdir_path = '\0'; strcat(newdir_path, prefix); strcat(newdir_path, "/"); strcat(newdir_path, d->dirent->name); newdir = mm_opendir(newdir_path); if (newdir == NULL) { if (flags & MM_FAILONERROR) { mm_freea(newdir_path); return -1; } else { continue; } } rv = mm_remove_rec(newdir_path, newdir, flags, rec_lvl - 1); mm_freea(newdir_path); mm_closedir(newdir); if (rv != 0 && (flags & MM_FAILONERROR)) return -1; } rv = win32_unlinkat(prefix, d->dirent->name, type); if (rv != 0 && (flags & MM_FAILONERROR)) return -1; } if (GetLastError() == ERROR_NO_MORE_FILES) return 0; return -1; } /* doc in posix implementation */ API_EXPORTED int mm_remove(const char* path, int flags) { int rv, error_flags; MM_DIR * dir; int type = -1; DWORD attrs; attrs = GetFileAttributes(path); if (attrs != INVALID_FILE_ATTRIBUTES) type = translate_filetype(attrs); if (type < 0) return mm_raise_from_w32err("unable to get %s filetype", path); if (flags & MM_RECURSIVE) { flags |= MM_DT_DIR; if (type == MM_DT_DIR) { dir = mm_opendir(path); error_flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_NOLOG); rv = mm_remove_rec(path, dir, flags, RECURSION_MAX); mm_error_set_flags(error_flags, MM_ERROR_NOLOG); mm_closedir(dir); if (rv != 0 && !(flags & MM_FAILONERROR)) { return mm_raise_from_errno( "recursive mm_remove(%s) failed", path); } /* allow rmdir(".") when called with the recursive flag * only */ if (is_wildcard_directory(path)) return rv; } } if ((flags & type) == 0) return mm_raise_error(EPERM, "failed to remove %s: " "invalid type", path); if (type == MM_DT_DIR) return mm_rmdir(path); else return mm_unlink(path); } /** * win32_find_file() - helper to handle the conversion between utf8 and 16 * @dir: pointer to a MM_DIR structure * * NOTE: windows Prototype are: * HANDLE FindFirstFileW(path, ...) * bool FindFirstFileW(HANDLE, ...) * * This function hides the difference by always returning a HANDLE like * FindFirstFile() and storing the HANDLE and path within the MM_DIR * structure. * * Return: 0 on success, -1 on error * The caller should call GetLastError() and investigate the issue itself * */ static int win32_find_file(MM_DIR * dir) { size_t reclen, namelen; int path_u16_len; char16_t* path_u16; WIN32_FIND_DATAW find_dataw; HANDLE hdir; if (dir == NULL) { mm_raise_error(EINVAL, "Does not accept NULL arguments"); return -1; } path_u16_len = get_utf16_buffer_len_from_utf8(dir->dirname); if (path_u16_len < 0) { mm_raise_from_w32err("Invalid UTF-8 path"); return -1; } path_u16 = mm_malloca(path_u16_len * sizeof(*path_u16)); if (path_u16 == NULL) { mm_raise_from_w32err("Failed to alloc required memory!"); return -1; } conv_utf8_to_utf16(path_u16, path_u16_len, dir->dirname); if (!dir->find_first_done) { hdir = dir->hdir = FindFirstFileW(path_u16, &find_dataw); dir->find_first_done = 1; } else { if (!FindNextFileW(dir->hdir, &find_dataw)) hdir = INVALID_HANDLE_VALUE; else hdir = dir->hdir; } mm_freea(path_u16); /* do not copy the result to the dirent structure * let the caller check the errors */ if (hdir == INVALID_HANDLE_VALUE) return -1; namelen = get_utf8_buffer_len_from_utf16(find_dataw.cFileName); reclen = sizeof(*dir->dirent) + namelen; if (dir->dirent == NULL || dir->dirent->reclen != reclen) { void * tmp = realloc(dir->dirent, reclen); if (tmp == NULL) return mm_raise_from_errno("cannot alloc dirent"); dir->dirent = tmp; dir->dirent->reclen = reclen; } dir->dirent->type = translate_filetype(find_dataw.dwFileAttributes); conv_utf16_to_utf8(dir->dirent->name, namelen, find_dataw.cFileName); return 0; } /* doc in posix implementation */ API_EXPORTED MM_DIR* mm_opendir(const char* path) { int len; MM_DIR * d; len = strlen(path) + 3; // concat with "/*\0" d = malloc(sizeof(*d) + len); if (d == NULL) goto error; *d = (MM_DIR) {.hdir = INVALID_HANDLE_VALUE}; strcpy(d->dirname, path); strcat(d->dirname, "/*"); /* call FindFirstFile() to ensure that the given path * is meaningful, and to keep ithe folder opened */ if (win32_find_file(d)) goto error; return d; error: mm_raise_from_errno("opendir(%s) failed", path); mm_closedir(d); return NULL; } /* doc in posix implementation */ API_EXPORTED void mm_closedir(MM_DIR* dir) { if (dir == NULL) return; if (dir->hdir != INVALID_HANDLE_VALUE) FindClose(dir->hdir); free(dir->dirent); free(dir); } /* doc in posix implementation */ API_EXPORTED void mm_rewinddir(MM_DIR* dir) { if (dir == NULL) { mm_raise_error(EINVAL, "Does not accept NULL arguments"); return; } FindClose(dir->hdir); win32_find_file(dir); } /* doc in posix implementation */ API_EXPORTED const struct mm_dirent* mm_readdir(MM_DIR* d, int * status) { if (d == NULL) { if (status != NULL) *status = -1; mm_raise_error(EINVAL, "Does not accept NULL arguments"); return NULL; } if (status != NULL) *status = 0; if (win32_find_file(d)) { if (GetLastError() == ERROR_NO_MORE_FILES) return NULL; if (status != NULL) *status = -1; mm_raise_from_errno("readdir() failed to find next file"); return NULL; } return d->dirent; } /************************************************************************* * * * stat like function implementation * * * *************************************************************************/ /** * reparse_data_create() - allocate and get reparse data from handle * HANDLE: handle of an open reparse point * * Return: initialized REPARSE_DATA_BUFFER in case of success, NULL * otherwise. Must be cleanup with free() when you don't need it any longer */ static REPARSE_DATA_BUFFER* get_reparse_data(HANDLE hnd) { REPARSE_DATA_BUFFER* rep; DWORD io_retsz; rep = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); if (!rep) return NULL; // Get reparse point data if (!DeviceIoControl(hnd, FSCTL_GET_REPARSE_POINT, NULL, 0, rep, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &io_retsz, NULL)) { free(rep); return NULL; } return rep; } /** * get_target_u16_from_reparse_data() - extract symlink target from data * rep: reparse point data read from a symlink * * Return: UTF-16 string (null terminated) of the target if @rep is the * reparse point data of a symlink, NULL otherwise. The return pointer is * valid for the lifetime of @rep. */ static char16_t* get_target_u16_from_reparse_data(REPARSE_DATA_BUFFER* rep) { char* buf8; char16_t * trgt_u16; int trgt_u16_len, buf8_len, buf8_offset; if (rep->ReparseTag != IO_REPARSE_TAG_SYMLINK) return NULL; // Get UTF-16 path of target buf8 = (char*)rep->SymbolicLinkReparseBuffer.PathBuffer; buf8_offset = rep->SymbolicLinkReparseBuffer.SubstituteNameOffset; buf8_len = rep->SymbolicLinkReparseBuffer.SubstituteNameLength; trgt_u16 = (char16_t*)(buf8 + buf8_offset); trgt_u16_len = buf8_len / sizeof(*trgt_u16); trgt_u16[trgt_u16_len] = L'\0'; return trgt_u16; } /** * get_symlink_target_strlen() - Get size of target string of symlink * hnd: handle of an open symlink * * Return: size of the UTF-8 string of the target including null * terminator. */ static size_t get_symlink_target_strlen(HANDLE hnd) { REPARSE_DATA_BUFFER* rep; char16_t * trgt_u16; size_t len = 0; rep = get_reparse_data(hnd); if (!rep) return 0; trgt_u16 = get_target_u16_from_reparse_data(rep); if (trgt_u16) len = get_utf8_buffer_len_from_utf16(trgt_u16); free(rep); return len; } /** * get_stat_from_handle() - fill stat info from opened file handle * @hnd: file handle * @buf: stat info to fill * * Return: 0 in case of success, -1 otherwise. BE CAREFUL, this function * does not set error state. Only win32 last error is set. It is expected * to raise the corresponding in the caller (which will have more context) */ LOCAL_SYMBOL int get_stat_from_handle(HANDLE hnd, struct mm_stat* buf) { FILE_ATTRIBUTE_TAG_INFO attr_tag; FILE_ID_INFO id_info; BY_HANDLE_FILE_INFORMATION info; struct local_secdesc lsd; int type; if (!GetFileInformationByHandle(hnd, &info) || local_secdesc_init_from_handle(&lsd, hnd)) { return -1; } if (!GetFileInformationByHandleEx(hnd, FileAttributeTagInfo, &attr_tag, sizeof(attr_tag)) || !GetFileInformationByHandleEx(hnd, FileIdInfo, &id_info, sizeof(id_info))) { DWORD id[4]; attr_tag = (FILE_ATTRIBUTE_TAG_INFO) { .FileAttributes = info.dwFileAttributes, }; // Convert 2 DWORDs file ID into FILE_ID_128 id[0] = info.nFileIndexLow; id[1] = info.nFileIndexHigh; id[2] = 0; id[3] = 0; memcpy(&id_info.FileId, id, sizeof(id)); id_info.VolumeSerialNumber = info.dwVolumeSerialNumber; } // translate_filetype() consider all reparse point as symlink. Here // we can use dwReserved0 field to distinguish type if (attr_tag.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { switch (attr_tag.ReparseTag) { case IO_REPARSE_TAG_SYMLINK: type = S_IFLNK; break; case IO_REPARSE_TAG_MOUNT_POINT: type = S_IFDIR; break; default: type = S_IFREG; break; } } else { switch (translate_filetype(attr_tag.FileAttributes)) { case MM_DT_DIR: type = S_IFDIR; break; case MM_DT_LNK: type = S_IFLNK; break; case MM_DT_FIFO: type = S_IFIFO; break; case MM_DT_CHR: type = S_IFCHR; break; default: type = S_IFREG; break; } } buf->mode = type | local_secdesc_get_mode(&lsd); buf->nlink = info.nNumberOfLinks; if (type == S_IFLNK) { buf->size = get_symlink_target_strlen(hnd); } else { buf->size = ((mm_off_t)info.nFileSizeHigh) * MAXDWORD; buf->size += info.nFileSizeLow; } buf->atime = filetime_to_time(info.ftLastAccessTime); buf->ctime = filetime_to_time(info.ftCreationTime); buf->mtime = filetime_to_time(info.ftLastWriteTime); buf->dev = id_info.VolumeSerialNumber; memcpy(&buf->ino, &id_info.FileId, sizeof(buf->ino)); local_secdesc_deinit(&lsd); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_stat(const char* path, struct mm_stat* buf, int flags) { HANDLE hnd; int rv = 0; hnd = open_handle_for_metadata(path, flags & MM_NOFOLLOW); if (hnd == INVALID_HANDLE_VALUE) return mm_raise_from_w32err("Can't open %s", path); // Get stat info from open file handle if (get_stat_from_handle(hnd, buf)) rv = mm_raise_from_w32err("Can't get stat of %s", path); CloseHandle(hnd); return rv; } /* doc in posix implementation */ API_EXPORTED int mm_fstat(int fd, struct mm_stat* buf) { HANDLE hnd; if (unwrap_handle_from_fd(&hnd, fd)) return -1; if (get_stat_from_handle(hnd, buf)) return mm_raise_from_w32err("Can't get stat of fd=%i", fd); return 0; } static int hnd_utimens(HANDLE hnd, const struct mm_timespec ts[2]) { FILETIME aft; FILETIME mft; FILETIME now; if (!ts || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) GetSystemTimePreciseAsFileTime(&now); // Set access filetime if (!ts || ts[0].tv_nsec == UTIME_NOW) aft = now; else if (ts[0].tv_nsec == UTIME_OMIT) aft = (FILETIME) {0}; else aft = timespec_to_filetime(ts[0]); // Set modification filetime if (!ts || ts[1].tv_nsec == UTIME_NOW) mft = now; else if (ts[1].tv_nsec == UTIME_OMIT) mft = (FILETIME) {0}; else mft = timespec_to_filetime(ts[1]); return SetFileTime(hnd, NULL, &aft, &mft) ? 0 : -1; } /* doc in posix implementation */ API_EXPORTED int mm_futimens(int fd, const struct mm_timespec ts[2]) { HANDLE hnd, hnd_new; int rv = -1; if (unwrap_handle_from_fd(&hnd, fd)) return -1; hnd_new = ReOpenFile(hnd, FILE_WRITE_ATTRIBUTES, FILE_SHARE_ALL, 0); if (hnd_new == INVALID_HANDLE_VALUE) goto exit; rv = hnd_utimens(hnd_new, ts); CloseHandle(hnd_new); exit: if (rv) mm_raise_from_w32err("Cannot change times of fd %i", fd); return rv; } /* doc in posix implementation */ API_EXPORTED int mm_utimens(const char* path, const struct mm_timespec ts[2], int flags) { HANDLE hnd; int rv; if (flags & ~MM_NOFOLLOW) return mm_raise_error(EINVAL, "invalid flags (0x%08x)", flags); hnd = open_handle(path, FILE_WRITE_ATTRIBUTES, OPEN_EXISTING, NULL, flags); if (hnd == INVALID_HANDLE_VALUE) return mm_raise_from_w32err("Cannot open %s", path); rv = hnd_utimens(hnd, ts); if (rv) mm_raise_from_w32err("Cannot change times of %s", path); CloseHandle(hnd); return rv; } /* doc in posix implementation */ API_EXPORTED int mm_readlink(const char* path, char* buf, size_t bufsize) { REPARSE_DATA_BUFFER* rep; HANDLE hnd = INVALID_HANDLE_VALUE; char16_t* trgt_u16; int rv = 0; hnd = open_handle_for_metadata(path, true); if (hnd == INVALID_HANDLE_VALUE) return mm_raise_from_w32err("Can't open %s", path); // Get reparse point data rep = get_reparse_data(hnd); if (!rep) { rv = mm_raise_from_w32err("Can't get data of %s", path); goto exit; } // Extract target string from reparse point data trgt_u16 = get_target_u16_from_reparse_data(rep); if (!trgt_u16) { rv = mm_raise_error(EINVAL, "%s is not a symlink", path); goto exit; } // Try convert symlink target UTF-16->UTF-8 if (conv_utf16_to_utf8(buf, bufsize, trgt_u16) < 0) rv = mm_raise_error(EOVERFLOW, "target too large"); exit: free(rep); CloseHandle(hnd); return rv; } #define COPYBUFFER_SIZE (1024*1024) // 1MiB static int clone_hnd_fallback(HANDLE hnd_src, HANDLE hnd_dst) { size_t wbuf_sz; char * buffer, * wbuf; ssize_t rsz, wsz; int rv = -1; buffer = malloc(COPYBUFFER_SIZE); if (!buffer) return -1; do { // Perform read operation rsz = mmlib_read(hnd_src, buffer, COPYBUFFER_SIZE); if (rsz < 0) goto exit; // Do write of what has been read, possibly chunked if transfer // got interrupted wbuf = buffer; wbuf_sz = rsz; while (wbuf_sz) { wsz = mmlib_write(hnd_dst, wbuf, wbuf_sz); if (wsz < 0) goto exit; wbuf += wsz; wbuf_sz -= wsz; } } while (rsz != 0); rv = 0; exit: free(buffer); return rv; } static int clone_hnd_force_cow(HANDLE hnd_src, HANDLE hnd_dst) { (void)hnd_src; (void)hnd_dst; SetLastError(ERROR_NOT_SUPPORTED); return -1; } static int copy_hnd_symlink(HANDLE hnd, const char* dst) { REPARSE_DATA_BUFFER* rep = NULL; const char16_t* target; char16_t* dst16 = NULL; DWORD flags; int dst16_len, rv = -1; dst16_len = get_utf16_buffer_len_from_utf8(dst); if (dst16_len < 0) goto exit; dst16 = mm_malloca(dst16_len * sizeof(*dst16)); if (dst16 == NULL) goto exit; conv_utf8_to_utf16(dst16, dst16_len, dst); rep = get_reparse_data(hnd); if (!rep) goto exit; target = get_target_u16_from_reparse_data(rep); flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; if (!CreateSymbolicLinkW(dst16, target, flags)) { if (GetLastError() != ERROR_INVALID_PARAMETER || !CreateSymbolicLinkW(dst16, target, 0)) goto exit; } rv = 0; exit: mm_freea(dst16); free(rep); return rv; } static int clone_src_hnd(HANDLE hnd_src, const char* dst, int flags, int mode) { HANDLE hnd_dst; struct local_secdesc lsd; int rv = -1; DWORD w32err; if (local_secdesc_init_from_mode(&lsd, mode)) return -1; hnd_dst = open_handle(dst, GENERIC_WRITE|DELETE|FILE_WRITE_ATTRIBUTES, CREATE_NEW, lsd.sd, FILE_ATTRIBUTE_NORMAL); local_secdesc_deinit(&lsd); if (hnd_dst == INVALID_HANDLE_VALUE) return -1; switch (flags & (MM_NOCOW & MM_FORCECOW)) { case MM_FORCECOW: rv = clone_hnd_force_cow(hnd_src, hnd_dst); break; case MM_NOCOW: default: rv = clone_hnd_fallback(hnd_src, hnd_dst); } // Delete incomplete destination file in case of failure while keeping // reported system error if (rv) { w32err = GetLastError(); delete_file_from_handle(hnd_dst); SetLastError(w32err); } CloseHandle(hnd_dst); return rv; } LOCAL_SYMBOL int copy_internal(const char* src, const char* dst, int flags, int mode) { int rv = -1; HANDLE hnd_src; DWORD attrs; FILE_ATTRIBUTE_TAG_INFO tag_info; attrs = FILE_ATTRIBUTE_NORMAL; attrs |= flags & MM_NOFOLLOW ? FILE_FLAG_OPEN_REPARSE_POINT : 0; hnd_src = open_handle(src, GENERIC_READ, OPEN_EXISTING, NULL, attrs); // Force to open handle to identify EISDIR error case which is aliased // to access denied error if FILE_FLAG_BACKUP_SEMANTICS is not used. if ((hnd_src == INVALID_HANDLE_VALUE) && (GetLastError() == ERROR_ACCESS_DENIED)) { hnd_src = open_handle_for_metadata(src, flags & MM_NOFOLLOW); } if (hnd_src == INVALID_HANDLE_VALUE) return mm_raise_from_w32err("Cannot open %s", src); if (!GetFileInformationByHandleEx(hnd_src, FileAttributeTagInfo, &tag_info, sizeof(tag_info))) goto exit; if ((tag_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && (tag_info.ReparseTag == IO_REPARSE_TAG_SYMLINK)) rv = copy_hnd_symlink(hnd_src, dst); else if (tag_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) SetLastError(ERROR_DIRECTORY_NOT_SUPPORTED); else rv = clone_src_hnd(hnd_src, dst, flags, mode); exit: CloseHandle(hnd_src); if (rv) mm_raise_from_w32err("Fail to copy %s to %s", src, dst); return rv; } mmlib-1.4.2/src/file.c000066400000000000000000000203271435717460000145030ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmlib.h" #include "mmerrno.h" #include "mmsysio.h" #include "file-internal.h" #include #include #include #include #ifdef _WIN32 # include # include # include "utils-win32.h" #else # include # include #endif #ifdef _WIN32 # ifdef lseek # undef lseek # endif # define lseek _lseeki64 # define fsync _commit static int ftruncate(int fd, mm_off_t size) { errno_t ret; ret = _chsize_s(fd, size); if (ret != 0) { errno = ret; return -1; } return 0; } #endif //_WIN32 /** * mm_fsync() - synchronize changes to a file * @fd: file description to synchronize * * This requests that all data for the open file descriptor named by @fd is * to be transferred to the storage device associated with the file * described by @fd. The mm_fsync() function does not return until the * system has completed that action or until an error is detected. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_fsync(int fd) { if (fsync(fd) < 0) return mm_raise_from_errno("fsync(%i) failed", fd); return 0; } /** * mm_seek() - change file offset * @fd: file descriptor * @offset: delta * @whence: how the @offset affect the file offset * * This function sets the file offset for the open file description associated * with the file descriptor @fd, as follows depending on the value in @whence * * %SEEK_SET * the file offset shall be set to @offset bytes. * %SEEK_CUR * the file offset shall be set to its current location plus @offset. * %SEEK_END * the file offset shall be set to the size of the file plus @offset. * * Return: in case of success, the resulting offset location as measured in * bytes from the beginning of the file. Otherwise -1 with error state set * accordingly. */ API_EXPORTED mm_off_t mm_seek(int fd, mm_off_t offset, int whence) { mm_off_t loc; loc = lseek(fd, offset, whence); if (loc < 0) return mm_raise_from_errno("lseek(%i, %lli, %i) failed", fd, offset, whence); return loc; } /** * mm_ftruncate() - truncate/resize a file to a specified length * @fd: file descriptor of the file to resize * @length: new length of the file * * If @fd refers to a regular file, mm_ftruncate() cause the size of the file * to be truncated to @length. If the size of the file previously exceeded * @length, the extra data shall no longer be available to reads on the file. * If the file previously was smaller than this size, mm_ftruncate() increases * the size of the file. If the file size is increased, the extended area will * appear as if it were zero-filled. The value of the seek pointer shall not be * modified by a call to mm_ftruncate(). * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_ftruncate(int fd, mm_off_t length) { if (ftruncate(fd, length) < 0) return mm_raise_from_errno("ftruncate(%i, %lli) failed", fd, length); return 0; } /** * internal_dirname() - quick implementation of dirname() * @path: the path to get the dir of * * This MAY OR MAY NOT modify in-place the given path so as to transform path * to contain its dirname. * * Return: a pointer to path. * If no dirname can be extracted from the path, it will return "." instead. * * Example: * dirname("/usr/lib/") -> "/usr\0lib/" * dirname("usr") -> "." * * Note: windows does not provide dirname, nor memrchr */ static char* internal_dirname(char * path) { char * c = path + strlen(path) - 1; /* skip the last chars if they're not a path */ while (c > path && is_path_separator(*c)) c--; while (--c > path) { if (is_path_separator(*c)) { /* remove consecutive separators (if any) */ while (c > path && is_path_separator(*c)) { *c = '\0'; c--; } return path; } } return "."; } static int internal_mkdir(const char* path, int mode) { #ifndef _WIN32 return mkdir(path, mode); #else int rv, path_u16_len; char16_t* path_u16; (void) mode; // permission management is not supported on windows path_u16_len = get_utf16_buffer_len_from_utf8(path); if (path_u16_len < 0) return mm_raise_from_w32err("Invalid UTF-8 path"); path_u16 = mm_malloca(path_u16_len * sizeof(*path_u16)); if (path_u16 == NULL) return mm_raise_from_w32err("Failed to alloc required memory!"); conv_utf8_to_utf16(path_u16, path_u16_len, path); rv = _wmkdir(path_u16); mm_freea(path_u16); return rv; #endif /* ifndef _WIN32 */ } static int mm_mkdir_rec(char* path, int mode) { int rv; int len, len_orig; rv = internal_mkdir(path, mode); if (errno == EEXIST) return 0; else if (rv == 0 || errno != ENOENT) return rv; /* prevent recursion: dirname(".") == "." */ if (is_wildcard_directory(path)) return 0; len_orig = strlen(path); rv = mm_mkdir_rec(internal_dirname(path), mode); /* restore the dir separators removed by internal_dirname() */ len = strlen(path); while (len < len_orig && path[len] == '\0') path[len++] = '/'; if (rv != 0) return -1; return internal_mkdir(path, mode); } /** * mm_mkdir() - creates a directory * @path: path of the directory to create * @mode: permission to use for directory creation * @flags: creation flags * * The mm_mkdir() function creates a new directory with name @path. The file * permission bits of the new directory shall be initialized from @mode. These * file permission bits of the @mode argument are modified by the process' file * creation mask. * * The function will fail if the parent directory does not exist unless @flags * contains MM_RECURSIVE which is this case, the function will try to * recursively create the missing parent directories (using the file * permission). * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_mkdir(const char* path, int mode, int flags) { int rv, len; char * tmp_path; rv = internal_mkdir(path, mode); if (flags & MM_RECURSIVE && rv != 0) { // when recursive, do not raise an error when dir already // present if (errno == EEXIST) return 0; else if (errno != ENOENT) return mm_raise_from_errno("mkdir(%s) failed", path); len = strlen(path); tmp_path = mm_malloca(len + 1); strcpy(tmp_path, path); rv = mm_mkdir_rec(tmp_path, 0777); mm_freea(tmp_path); } if (rv != 0) mm_raise_from_errno("mkdir(%s) failed", path); return rv; } /** * mm_copy() - copy source to destination * @src: path to file to copy from * @dst: path to destination * @flags: combination of flags controlling the copy * @mode: access permission bits of created file * * Copies the content of @src into @dst. If the @dst already exists, the * function will fail. @mode will be used as permission mask for the created * file. * * @flags must be constructed by a bitwise-inclusive OR of flags from the * following list (maybe none): * * %MM_NOFOLLOW * If @src is a symbolic link the copy is done on the symbolic link itself, * not the target, thus producing a symbolic link in @dst. * %MM_NOCOW * Prevents to perform a copy-on-write clone of the source content (reflink), * even if the filesystem allows it. * %MM_FORCECOW * Ensures the copy is performed through a copy-on-write clone of the source * content (reflink), leading to failure if the filesystem or the conditions * do not allow it. * * Note that %MM_NOCOW and %MM_FORCECOW affect only the copy of a regular * file. They will be ignored in the case of symlink used as source with * %MM_NOFOLLOW flag. * * If @src is neither a regular file or symbolic link, the function will fail. * * Return: 0 in case of success, -1 otherwise with error state set accordingly */ API_EXPORTED int mm_copy(const char* src, const char* dst, int flags, int mode) { if (flags & ~(MM_NOFOLLOW|MM_NOCOW|MM_FORCECOW)) return mm_raise_error(EINVAL, "invalid flags (0x%08x)", flags); if ((flags & MM_NOCOW) && (flags & MM_FORCECOW)) return mm_raise_error(EINVAL, "MM_NOCOW and MM_FORCECOW " "cannot be set together"); return copy_internal(src, dst, flags, mode); } mmlib-1.4.2/src/libmmlib.map000066400000000000000000000061431435717460000157060ustar00rootroot00000000000000/* Please name the version node according to the first 2 digit of version that * introduce the listed symbol within a soname. * * When SONAME bump is introduced, all symbols that are kept in the new version * must be grouped within the same version named after the version node using * the new soname. * * Example: * -------- * The version of library "somelib" being compiled is currently 2.6.3. The * library exposes the following symbols: * - foo (introduced in 0.9.1) * - bar (introduced in 1.2.3) * - afunc (introduced in 2.0.0) * - bfunc (introduced in 2.3.1) * * The version script should be: * * SOMELIB_2.0 { * global: * foo; * bar; * afunc; * local: *; * }; * * SOMELIB_2.3 { * global: * bfunc; * } SOMELIB_2.0; * */ MMLIB_1.0 { global: _mm_freea_on_heap; _mm_malloca_on_heap; mm_accept; mm_aligned_alloc; mm_aligned_free; mm_anon_shm; mm_basename; mm_bind; mm_chdir; mm_check_access; mm_close; mm_closedir; mm_connect; mm_copy; mm_create_sockclient; mm_dirname; mm_dl_fileext; mm_dlclose; mm_dlopen; mm_dlsym; mm_dup2; mm_dup; mm_error_set_flags; mm_execv; mm_freeaddrinfo; mm_fstat; mm_fsync; mm_ftruncate; mm_futimens; mm_get_basedir; mm_get_environ; mm_get_lasterror_desc; mm_get_lasterror_extid; mm_get_lasterror_location; mm_get_lasterror_module; mm_get_lasterror_number; mm_getaddrinfo; mm_getcwd; mm_getenv; mm_getnameinfo; mm_getpeername; mm_getres; mm_getsockname; mm_getsockopt; mm_gettime; mm_isatty; mm_link; mm_listen; mm_mapfile; mm_mkdir; mm_nanosleep; mm_open; mm_opendir; mm_path_from_basedir; mm_pipe; mm_poll; mm_print_lasterror; mm_raise_error_full; mm_raise_error_vfull; mm_raise_from_errno_full; mm_read; mm_readdir; mm_readlink; mm_recv; mm_recv_multimsg; mm_recvmsg; mm_relative_sleep_ms; mm_relative_sleep_ns; mm_relative_sleep_us; mm_remove; mm_rename; mm_rewinddir; mm_rmdir; mm_save_errorstate; mm_seek; mm_send; mm_send_multimsg; mm_sendmsg; mm_set_errorstate; mm_setenv; mm_setsockopt; mm_shm_open; mm_shm_unlink; mm_shutdown; mm_socket; mm_spawn; mm_stat; mm_symlink; mm_unlink; mm_unmap; mm_unsetenv; mm_utimens; mm_wait_process; mm_write; mm_arg_complete_path; mm_arg_is_completing; mm_arg_optv_parse; mm_arg_parse; mm_arg_parse_complete; mm_ipc_connect; mm_ipc_connected_pair; mm_ipc_recvmsg; mm_ipc_sendmsg; mm_ipc_srv_accept; mm_ipc_srv_create; mm_ipc_srv_destroy; mm_log; mm_log_set_maxlvl; mm_profile_get_data; mm_profile_print; mm_profile_reset; mm_strerror; mm_strerror_r; mm_thr_cond_broadcast; mm_thr_cond_deinit; mm_thr_cond_init; mm_thr_cond_signal; mm_thr_cond_timedwait; mm_thr_cond_wait; mm_thr_create; mm_thr_detach; mm_thr_join; mm_thr_mutex_consistent; mm_thr_mutex_deinit; mm_thr_mutex_init; mm_thr_mutex_lock; mm_thr_mutex_trylock; mm_thr_mutex_unlock; mm_thr_once; mm_thr_self; mm_tic; mm_toc; mm_toc_label; local: *; }; mmlib-1.4.2/src/local-ipc-posix.c000066400000000000000000000213761435717460000165740ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmsysio.h" #include "mmerrno.h" #include #include #include #include #include #include #define BACKLOG_LENGTH 5 struct mm_ipc_srv { int listenfd; }; /** * mm_ipc_srv_create() - Create a IPC server * @addr: path to which the server must listen * * This creates a server instance that will listen to the path specified by * argument @path. This path does not have necessarily a connection with the * filesystem pathnames. However it obey to the same syntax. * * Only one IPC server instance can listen to same address. If there is * already another server, this function will fail. * * Return: pointer to IPC server in case of success. NULL otherwise with * error state set accordingly */ API_EXPORTED struct mm_ipc_srv* mm_ipc_srv_create(const char* addr) { int fd = -1; struct sockaddr_un address = {.sun_family = AF_UNIX}; struct mm_ipc_srv* srv; if (strlen(addr) > (sizeof(address.sun_path) - 1)) { mm_raise_error(ENAMETOOLONG, "server name too long"); return NULL; } // Copy the socket address. It must start with a null byte ('\0') to // obtain an abstract socket address. Without it would be bound to the // filesystem (which we do not want) address.sun_path[0] = '\0'; strncpy(address.sun_path+1, addr, sizeof(address.sun_path) - 1); if (!(srv = malloc(sizeof(*srv))) || (fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0 || bind(fd, (struct sockaddr*)&address, sizeof(address)) < 0 || listen(fd, BACKLOG_LENGTH) < 0) { mm_raise_from_errno("Fail create listening socket on %s", addr); mm_close(fd); free(srv); return NULL; } srv->listenfd = fd; return srv; } /** * mm_ipc_srv_destroy() - Destroy IPC server * @srv: server to destroy * * This function destroy the server referenced to by @srv. The path to which * server was listening become available for new call to mm_ipc_srv_create(). * However, if there were accepted connection still opened, it is * unspecified whether the name will be available before all connection are * closed or not. If there were client connection pending that had not been * accepted by the server yet, those will be dropped. * * Destroying a server does not affect accepted connections which will * survive until they are specifically closed with mm_close(). */ API_EXPORTED void mm_ipc_srv_destroy(struct mm_ipc_srv* srv) { if (!srv) return; mm_close(srv->listenfd); free(srv); } /** * mm_ipc_srv_accept() - accept a incoming connection * @srv: IPC server * * This function extracts the first connection on the queue of pending * connections, and allocate a new file descriptor for that connection (the * lowest number available). If there are no connection pending, the * function will block until one arrives. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_ipc_srv_accept(struct mm_ipc_srv* srv) { int fd; fd = accept(srv->listenfd, NULL, NULL); if (fd == -1) { mm_raise_from_errno("Failed to accept connection"); return -1; } return fd; } /** * mm_ipc_connect() - connect a client to an IPC server * @addr: path to which the client must connect * * Client-side counterpart of mm_ipc_srv_accept(), this functions attempts to * connect to a server listening to @addr if there are any. If one is found, * it allocates a new file descriptor for that connection (the lowest number * available). If there are no server listening to @addr, the function will * block until one server does. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_ipc_connect(const char* addr) { int fd; size_t len; struct sockaddr_un address = {.sun_family = AF_UNIX}; address.sun_path[0] = '\0'; /* do not use strncpy() to prevent invalid stringop-truncation warning: * this is not a string that needs to be null-terminated */ len = strnlen(addr, sizeof(address.sun_path) - 1); memcpy(address.sun_path + 1, addr, len); if ((fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0 || connect(fd, (struct sockaddr*)&address, sizeof(address)) < 0) { mm_raise_from_errno("Failed to connect to local IPC server"); mm_close(fd); return -1; } return fd; } /** * mm_ipc_sendmsg() - send message to ICP endpoint * @fd: file descriptor of an IPC connection endpoint * @msg: IPC message * * If space is not available at the sending endpoint to hold the message to be * transmitted, the function will block until space is available. The * message sent in @msg is a datagram: either all could have been * transmitted, either none and the function would fail. * * File descriptors can also be transmitted to the receiving endpoint along a * message with the @msg->fds and @msg->num_fds fields. The file descriptors * listed here are duplicated for the process holding the other endpoint. * * Return: the number of bytes sent in case of success, -1 otherwise with * error state set accordingly. */ API_EXPORTED ssize_t mm_ipc_sendmsg(int fd, const struct mm_ipc_msg* msg) { ssize_t rsz; struct msghdr dgram = { .msg_iov = msg->iov, .msg_iovlen = msg->num_iov, }; size_t fd_array_len = msg->num_fds*sizeof(int); char cbuf[CMSG_SPACE(fd_array_len)]; if (msg->num_fds > 0) { struct cmsghdr * cmsg; dgram.msg_control = cbuf; dgram.msg_controllen = sizeof(cbuf); cmsg = CMSG_FIRSTHDR(&dgram); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(fd_array_len); memcpy(CMSG_DATA(cmsg), msg->fds, fd_array_len); dgram.msg_controllen = cmsg->cmsg_len; } rsz = sendmsg(fd, &dgram, 0); if (rsz < 0) mm_raise_from_errno("Failed to receive datagram"); return rsz; } /** * mm_ipc_recvmsg() - recv message from IPC endpoint * @fd: file descriptor of an IPC connection endpoint * @msg: IPC message * * This function receives a message. The message received is a datagram: if * the data received is smaller that requested, the function will return a * smaller message and its size will be reported by the return value. * Controversy if a message is too long to fit in the supplied buffers in * @msg->iov, the excess bytes will be discarded and the flag %MSG_TRUNC * will be set in @msg->flags. * * You can receive file descriptors along with the message in @msg->fds and * @msg->num_fds fields. Similarly to the data message, if the buffer * holding the file descriptor is too small, the files descriptor in excess * will be discarded (implicitly closing them, ensuring no descriptor leak to * occur) and the flag %MSG_CTRUNC will be set in @msg->flags. * * Return: the number of bytes received in case of success, -1 otherwise with * error state set accordingly. */ API_EXPORTED ssize_t mm_ipc_recvmsg(int fd, struct mm_ipc_msg* msg) { ssize_t rsz; int i, num_fd, passed_fd; size_t fd_array_len = msg->num_fds_max*sizeof(int); const unsigned char* cmsg_data; char cbuf[CMSG_SPACE(fd_array_len)]; struct msghdr dgram = { .msg_iov = msg->iov, .msg_iovlen = msg->num_iov, .msg_control = cbuf, .msg_controllen = sizeof(cbuf), }; struct cmsghdr * cmsg; // Get datagram from socket rsz = recvmsg(fd, &dgram, MSG_CMSG_CLOEXEC); if (rsz <= 0) { mm_raise_from_errno("Failed to receive datagram"); return -1; } msg->flags = dgram.msg_flags & (MSG_TRUNC | MSG_CTRUNC); // Read ancillary data of the datagram and get file descriptor that // might be sent along the datagram cmsg = CMSG_FIRSTHDR(&dgram); if (cmsg && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { // use of memcpy necessary because CMSG_DATA // does not have type alignment guarantees num_fd = (cmsg->cmsg_len - CMSG_LEN(0))/sizeof(int); if (num_fd <= msg->num_fds_max) msg->num_fds = num_fd; else msg->num_fds = msg->num_fds_max; cmsg_data = CMSG_DATA(cmsg); memcpy(msg->fds, cmsg_data, msg->num_fds*sizeof(int)); // Close any fd that could not have been passed in mm_ipc_msg for (i = msg->num_fds_max; i < num_fd; i++) { memcpy(&passed_fd, cmsg_data + i * sizeof(int), sizeof(int)); close(passed_fd); msg->flags |= MSG_CTRUNC; } } return rsz; } /** * mm_ipc_connected_pair() - create a pair of connected IPC endpoints * @fds: array receiving the file descriptor of the 2 endpoints * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_ipc_connected_pair(int fds[2]) { int rv; rv = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds); if (rv != 0) { mm_raise_from_errno("Failed to connect to local IPC server"); return -1; } return 0; } mmlib-1.4.2/src/local-ipc-win32.c000066400000000000000000000324651435717460000163750ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmsysio.h" #include "mmerrno.h" #include "utils-win32.h" #include "clock-win32.h" #include "local-ipc-win32.h" #include #include #include #include #define PIPE_PREFIX "\\\\.\\pipe\\" #define MAX_PIPENAME 256 #define MAX_DATA_SIZE MM_PAGESZ #define BUFSIZE MAX_DATA_SIZE #define MAX_ATTEMPTS 16 /** * struct fd_data - data to serialize file descriptor information * @hnd: WIN32 handle of the file * @fd_info: mmlib fd info of the associated file */ struct fd_data { HANDLE hnd; int fd_info; }; /** * struct ancillary_data - structure for serializing several of file descriptor * @num_fds: number of file descriptor information that are serialized * @array: array of file descriptor information of length @num_fds. */ struct ancillary_data { int num_fds; struct fd_data array[]; }; struct mm_ipc_srv { HANDLE hpipe; char pipe_name[MAX_PIPENAME]; }; /* doc in posix implementation */ API_EXPORTED struct mm_ipc_srv* mm_ipc_srv_create(const char* addr) { HANDLE hpipe; DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE; DWORD pipe_mode = PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS; char pipe_name[MAX_PIPENAME]; struct mm_ipc_srv* srv; if (strlen(addr) > (MAX_PIPENAME - strlen(PIPE_PREFIX))) { mm_raise_error(ENAMETOOLONG, "server name too long"); return NULL; } // Format actual named pipe name (must start with prefix) snprintf(pipe_name, sizeof(pipe_name), PIPE_PREFIX "%s", addr); if (!(srv = malloc(sizeof(*srv)))) { mm_raise_from_errno("Failed to alloc server data for %s", addr); return NULL; } // Create the server named pipe hpipe = CreateNamedPipe(pipe_name, open_mode, pipe_mode, PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, NULL); if (hpipe == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_ACCESS_DENIED) { mm_raise_error(EADDRINUSE, "addr %s already in use " "in a named pipe server", addr); } else { mm_raise_from_w32err("Failed to create server " "Named pipe at %s", addr); } free(srv); return NULL; } srv->hpipe = hpipe; strcpy(srv->pipe_name, pipe_name); return srv; } /* doc in posix implementation */ API_EXPORTED void mm_ipc_srv_destroy(struct mm_ipc_srv* srv) { if (!srv) return; CloseHandle(srv->hpipe); free(srv); } /* doc in posix implementation */ API_EXPORTED int mm_ipc_srv_accept(struct mm_ipc_srv* srv) { DWORD open_mode = PIPE_ACCESS_DUPLEX; DWORD pipe_mode = PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS; HANDLE hpipe; int fd; // connect to client, It is harmless if error pipe connected is // returned if (!ConnectNamedPipe(srv->hpipe, NULL) && (GetLastError() != ERROR_PIPE_CONNECTED)) { mm_raise_from_w32err("Failed to connect client"); return -1; } // Wrap into a file descriptor if (wrap_handle_into_fd(srv->hpipe, &fd, FD_TYPE_IPCDGRAM)) return -1; // Create the new listening server named pipe instance hpipe = CreateNamedPipe(srv->pipe_name, open_mode, pipe_mode, PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL); if (hpipe == INVALID_HANDLE_VALUE) { mm_raise_from_w32err("Failed to create new listening Named pipe"); return -1; } // Replace the connected pipe instance by the new listening pipe srv->hpipe = hpipe; return fd; } /* doc in posix implementation */ API_EXPORTED int mm_ipc_connect(const char* addr) { HANDLE hpipe = INVALID_HANDLE_VALUE; char pipe_name[MAX_PIPENAME]; int fd; // Format actual named pipe name (must start with prefix) snprintf(pipe_name, sizeof(pipe_name), PIPE_PREFIX "%s", addr); // Connect named pipe to server do { if (WaitNamedPipe(pipe_name, NMPWAIT_WAIT_FOREVER) == 0 && GetLastError() == ERROR_FILE_NOT_FOUND) { // server does not exist mm_raise_from_w32err("Server %s not found\n", addr); return -1; } hpipe = CreateFile(pipe_name, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hpipe == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_PIPE_BUSY) continue; mm_raise_from_w32err("Connection to %s failed", addr); return -1; } } while (hpipe == INVALID_HANDLE_VALUE); // Wrap into a file descriptor if (wrap_handle_into_fd(hpipe, &fd, FD_TYPE_IPCDGRAM)) { CloseHandle(hpipe); return -1; } return fd; } /** * open_peer_process_handle() - get win32 handle of peer process of pipe * @hpipe: handle of named pipe instance * * Return: HANDLE of process in case of success, INVALID_HANDLE_VALUE * otherwise with error state set accordingly. */ static HANDLE open_peer_process_handle(HANDLE hpipe) { HANDLE dst_proc; ULONG pid; DWORD flags; BOOL r = TRUE; // Get PID of the other end of the named pipe r &= GetNamedPipeInfo(hpipe, &flags, NULL, NULL, NULL); if (flags & PIPE_SERVER_END) r &= GetNamedPipeClientProcessId(hpipe, &pid); else r &= GetNamedPipeServerProcessId(hpipe, &pid); if (r == FALSE) { mm_raise_from_w32err("Failed to get endpoint process id"); return INVALID_HANDLE_VALUE; } // Get process handle from pid dst_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid); if (dst_proc == INVALID_HANDLE_VALUE) mm_raise_from_w32err("Cannot open endpoint process handle"); return dst_proc; } /** * write_ancillary_data() - write the fd passed into an ancillary data * @data: ancillary_data that must be written * @msg: message data containing the fd that must be passed * @hpipe: handle to pipe to which the message is going to be sent * * Return: size of ancillary data in case of success, -1 otherwise with * error state set accordingly */ static ssize_t write_ancillary_data(struct ancillary_data* data, const struct mm_ipc_msg* msg, HANDLE hpipe) { HANDLE dst_proc, src_proc; HANDLE hnd, dst_hnd; int i; ssize_t retsize; data->num_fds = msg->num_fds; retsize = sizeof(*data); if (msg->num_fds == 0) return retsize; // Get current and peer process handle src_proc = GetCurrentProcess(); dst_proc = open_peer_process_handle(hpipe); if (dst_proc == INVALID_HANDLE_VALUE) return -1; // Duplicate WIN32 handle to send into the other endpoint process for (i = 0; i < msg->num_fds; i++) { if (unwrap_handle_from_fd(&hnd, msg->fds[i]) || !DuplicateHandle(src_proc, hnd, dst_proc, &dst_hnd, 0, FALSE, DUPLICATE_SAME_ACCESS)) { mm_raise_from_w32err("Failed to duplicate handle"); while (--i >= 0) { CloseHandle(data->array[i].hnd); } retsize = -1; break; } // Store file descriptor info in element of ancillary data data->array[i].hnd = dst_hnd; data->array[i].fd_info = get_fd_info(msg->fds[i]); retsize += sizeof(data->array[i]); } CloseHandle(dst_proc); return retsize; } /** * serialize_msg() - serialize IPC message for consumption in WriteFile*() * @hpipe: handle to which the message is intended * @msg: user provided scatter/gather buffer to be serialize * @msg_data: buffer receiving the serialized message * @p_hdr_sz: pointer to a variable receiving the ancillary data size * * Return: total size of the serialized message in case of success, -1 * otheriwse with error state set accordingly */ static ssize_t serialize_msg(HANDLE hpipe, const struct mm_ipc_msg* msg, void* msg_data, size_t* p_hdr_sz) { char* buff = msg_data; size_t len; int i, truncated; ssize_t total_sz; total_sz = write_ancillary_data(msg_data, msg, hpipe); if (total_sz < 0) return -1; *p_hdr_sz = total_sz; buff = msg_data; buff += total_sz; truncated = 0; for (i = 0; i < msg->num_iov && !truncated; i++) { len = msg->iov[i].iov_len; if (len + total_sz >= MAX_DATA_SIZE) { len = MAX_DATA_SIZE - total_sz; // Force break at the end of iteration truncated = 1; } memcpy(buff, msg->iov[i].iov_base, len); buff += len; total_sz += len; } return total_sz; } /** * read_ancillary_data() - get the fd passed from an ancillary data * @data: ancillary_data that must be read * @msg: message data that will contain the fd passed * * Return: size of ancillary data in case of success, -1 otherwise with * error state set accordingly */ static size_t read_ancillary_data(const struct ancillary_data* data, struct mm_ipc_msg* msg) { int i; HANDLE hnd; int fd_info; // Wrap handles in the ancillary data of datagram for (i = 0; i < data->num_fds && i < msg->num_fds_max; i++) { hnd = data->array[i].hnd; fd_info = data->array[i].fd_info; if (wrap_handle_into_fd(hnd, &msg->fds[i], fd_info)) break; } msg->num_fds = i; // Close handle that could not be wrapped for (; i < data->num_fds; i++) CloseHandle(data->array[i].hnd); if (msg->num_fds < data->num_fds) msg->flags |= MSG_CTRUNC; return sizeof(*data) + data->num_fds * sizeof(data->array[0]); } /** * deserialize_msg() - deserialize IPC message read with ReadFile*() * @buff_sz: size of the serialized message buffer * @msg_data: serialized message buffer * @msg: user provided scatter/gather data receiving the message * * Return: size of the data excluding the ancillary data */ static ssize_t deserialize_msg(size_t buff_sz, const void* msg_data, struct mm_ipc_msg* msg) { const char* buff; size_t len, msg_sz; int i, truncated; // Reset flags msg->flags = 0; // Read passed file descriptor data and set buff pointer passed to // this ancillary data len = read_ancillary_data(msg_data, msg); buff = msg_data; buff += len; buff_sz -= len; truncated = 0; msg_sz = buff_sz; for (i = 0; i < msg->num_iov && !truncated; i++) { len = msg->iov[i].iov_len; if (len > buff_sz) { len = buff_sz; msg->flags |= MSG_TRUNC; // Force break at the end of iteration truncated = 1; } memcpy(msg->iov[i].iov_base, buff, len); buff += len; buff_sz -= len; } return msg_sz; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_ipc_sendmsg(int fd, const struct mm_ipc_msg* msg) { HANDLE hpipe; DWORD send_sz; ssize_t len; size_t hdr_sz; uintptr_t msg_data[MAX_DATA_SIZE]; if (unwrap_handle_from_fd(&hpipe, fd) || (len = serialize_msg(hpipe, msg, msg_data, &hdr_sz)) < 0) return -1; if (!WriteFile(hpipe, msg_data, (DWORD)len, &send_sz, NULL)) return mm_raise_from_w32err("WriteFile failed"); return send_sz - hdr_sz; } LOCAL_SYMBOL ssize_t ipc_hnd_write(HANDLE hpipe, const void* buf, size_t nbyte) { DWORD send_sz; ssize_t len; size_t hdr_sz; struct iovec iov[] = {{.iov_base = (void*)buf, .iov_len = nbyte}}; struct mm_ipc_msg ipcmsg = {.iov = iov, .num_iov = 1}; uintptr_t msg_data[MAX_DATA_SIZE]; if ((len = serialize_msg(hpipe, &ipcmsg, msg_data, &hdr_sz)) < 0) return -1; if (!WriteFile(hpipe, msg_data, (DWORD)len, &send_sz, NULL)) return -1; return send_sz - hdr_sz; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_ipc_recvmsg(int fd, struct mm_ipc_msg* msg) { HANDLE hpipe; DWORD recv_sz; uintptr_t msg_data[MAX_DATA_SIZE]; if (unwrap_handle_from_fd(&hpipe, fd)) return -1; if (!ReadFile(hpipe, msg_data, sizeof(msg_data), &recv_sz, NULL)) return mm_raise_from_w32err("ReadFile failed"); return deserialize_msg(recv_sz, msg_data, msg); } LOCAL_SYMBOL ssize_t ipc_hnd_read(HANDLE hpipe, void* buf, size_t nbyte) { DWORD recv_sz; struct iovec iov[] = {{.iov_base = buf, .iov_len = nbyte}}; struct mm_ipc_msg ipcmsg = {.iov = iov, .num_iov = 1}; uintptr_t msg_data[MAX_DATA_SIZE]; if (!ReadFile(hpipe, msg_data, sizeof(msg_data), &recv_sz, NULL)) return -1; return deserialize_msg(recv_sz, msg_data, &ipcmsg); } /* doc in posix implementation */ API_EXPORTED int mm_ipc_connected_pair(int fds[2]) { DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE; DWORD pipe_mode = PIPE_TYPE_MESSAGE | PIPE_REJECT_REMOTE_CLIENTS; char name[MAX_PIPENAME]; int srv_fd, client_fd; HANDLE hsrv, hclient; struct mm_timespec ts; int attempt; // Try and retry to create a named pipe server handle using a // new pipe name until one can be used (fail on other errors) attempt = 0; do { // generate a name based on thread ID and current time gettimespec_monotonic_w32(&ts); snprintf(name, sizeof(name), PIPE_PREFIX "anon-%lu%lx%lx", get_tid(), (long)ts.tv_sec, (long)ts.tv_nsec); hsrv = CreateNamedPipe(name, open_mode, pipe_mode, 1, BUFSIZE, BUFSIZE, 0, NULL); if (hsrv == INVALID_HANDLE_VALUE) { // retry if we couldn't create a first instance if (GetLastError() == ERROR_ACCESS_DENIED && ++attempt < MAX_ATTEMPTS) continue; mm_raise_from_w32err("Can't create named pipe"); return -1; } } while (hsrv == INVALID_HANDLE_VALUE); if (wrap_handle_into_fd(hsrv, &srv_fd, FD_TYPE_IPCDGRAM)) { CloseHandle(hsrv); return -1; } // Create connected client pipe endpoint hclient = CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hclient == INVALID_HANDLE_VALUE) { mm_raise_from_w32err("Can't connect named pipe"); mm_close(srv_fd); // This closes hsrv implicitly return -1; } if (wrap_handle_into_fd(hclient, &client_fd, FD_TYPE_IPCDGRAM)) { CloseHandle(hclient); mm_close(srv_fd); // This closes hsrv implicitly return -1; } fds[0] = srv_fd; fds[1] = client_fd; return 0; } mmlib-1.4.2/src/local-ipc-win32.h000066400000000000000000000004131435717460000163660ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef LOCAL_IPC_WIN32_H #define LOCAL_IPC_WIN32_H #include ssize_t ipc_hnd_read(HANDLE hpipe, void* buf, size_t nbyte); ssize_t ipc_hnd_write(HANDLE hpipe, const void* buf, size_t nbyte); #endif /* LOCAL_IPC_WIN32_H */ mmlib-1.4.2/src/lock-referee-proto.h000066400000000000000000000040631435717460000172740ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef LOCK_REFEREE_PROTO_H #define LOCK_REFEREE_PROTO_H #include "mmtime.h" #include "mmthread.h" #include #include #define referee_pipename "\\\\.\\pipe\\mmlib-lock-referee" enum { LOCKREF_OP_WAKE, LOCKREF_OP_WAIT, LOCKREF_OP_INITLOCK, LOCKREF_OP_GETROBUST, LOCKREF_OP_CLEANUP, LOCKREF_OP_CLEANUP_DONE, LOCKREF_OP_ERROR, }; #define WAITCLK_FLAG_MONOTONIC (MM_THR_PSHARED << 1) #define WAITCLK_FLAG_REALTIME (MM_THR_PSHARED << 2) #define WAITCLK_MASK (WAITCLK_FLAG_MONOTONIC | WAITCLK_FLAG_REALTIME) #define SRV_TIMEOUT_MS 200 struct lockref_req_msg { int opcode; union { struct lockref_reqdata_wake { int num_wakeup; int64_t key; int64_t val; } wake; struct lockref_reqdata_wait { int64_t key; int64_t val; int clk_flags; struct mm_timespec timeout; } wait; struct lockref_reqdata_getrobust { int num_keys; } getrobust; struct lockref_reqdata_cleanup_done { int64_t key; int num_wakeup; } cleanup; }; }; struct lockref_resp_msg { int respcode; union { int64_t key; HANDLE hmap; int timedout; }; }; struct robust_data { DWORD thread_id; int64_t attempt_key; int is_waiter; int num_locked; int num_locked_max; int64_t locked_keys[]; }; struct dead_thread { int is_waiter; DWORD tid; }; struct mutex_cleanup_job { int in_progress; int num_dead; struct dead_thread deadlist[]; }; static inline HANDLE create_srv_first_pipe(void) { HANDLE hnd; DWORD open_mode, pipe_mode; open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; pipe_mode = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS; hnd = CreateNamedPipe(referee_pipename, open_mode, pipe_mode, PIPE_UNLIMITED_INSTANCES, MM_PAGESZ, MM_PAGESZ, SRV_TIMEOUT_MS, NULL); return hnd; } #ifdef LOCKSERVER_IN_MMLIB_DLL void* lockserver_thread_routine(void* arg); #endif #endif /* ifndef LOCK_REFEREE_PROTO_H */ mmlib-1.4.2/src/lock-referee-server.c000066400000000000000000001244341435717460000174370ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include "lock-referee-proto.h" #include "mmpredefs.h" #include "clock-win32.h" #include "mmtime.h" #include #include #include #include #include #include #define LOCK_LIST_INITIAL_LEN 128 #define NSEC_IN_MSEC (NS_IN_SEC / MS_IN_SEC) #define ROUND_UP(x, y) ((((x)+(y)-1) / (y)) * (y)) #ifndef DWORD_MAX #define DWORD_MAX 0xFFFFFFFF #endif struct queue_node { struct queue_node* prev; struct queue_node* next; }; struct queue { struct queue_node* first; struct queue_node* last; }; struct list_node { struct list_node* prev; struct list_node* next; }; struct list { struct list_node head; }; struct thread_client { struct queue_node lock_node; HANDLE pipe; int num_pending_request; bool being_destroyed; int64_t wakeup_val; int64_t waiting_lock_key; struct lockref_req_msg msg_read; struct lockref_resp_msg msg_write; OVERLAPPED overlapped_read; OVERLAPPED overlapped_write; struct robust_data* robust_data; struct list_node timeout_node; struct mm_timespec wait_timeout; }; #define GET_TC_FROM_OVERLAPPED_READ(lpo) ((struct thread_client*)(((char*)lpo)-offsetof(struct thread_client, overlapped_read))) #define GET_TC_FROM_OVERLAPPED_WRITE(lpo) ((struct thread_client*)(((char*)lpo)-offsetof(struct thread_client, overlapped_write))) #define GET_TC_FROM_LOCK_NODE(node) ((struct thread_client*)(((char*)node)-offsetof(struct thread_client, lock_node))) #define GET_TC_FROM_TIMEOUT_NODE(node) ((struct thread_client*)(((char*)node)-offsetof(struct thread_client, timeout_node))) struct lock { int64_t key; int64_t max_wakeup_val; int nwaiter; struct queue waiters_queue; struct mutex_cleanup_job* job; HANDLE job_mapping_hnd; }; struct lock_array { int num_used; int num_max; struct lock* sorted_array; }; struct lockref_server { int is_init; HANDLE connect_evt; OVERLAPPED conn_overlapped; HANDLE pipe; SECURITY_ATTRIBUTES sec_attr; struct lock_array watched_locks; struct list realtime_timeout_list; struct list monotonic_timeout_list; int num_thread_client; int quit; }; static struct lockref_server server = {.is_init = 0}; static struct lock* lockref_server_get_lock(struct lockref_server* srv, int64_t key, bool create); static struct list* lockref_server_get_timeout_list(struct lockref_server* srv, int clk_flags); static void lockref_server_update_thread_count(struct lockref_server* srv, int adjustment); static void thread_client_wakeup(struct thread_client* tc, bool timedout); static void thread_client_process_timeout(struct thread_client* tc); static void thread_client_do_cleanup_job(struct thread_client* tc, HANDLE cleanup_mapping_hnd); static void thread_client_queue_read(struct thread_client* tc); static void thread_client_queue_write(struct thread_client* tc); static HANDLE thread_client_add_robust_data(struct thread_client* tc, int locked_list_size); static void thread_client_destroy( struct thread_client* tc); static struct thread_client* thread_client_create(HANDLE pipe); /** * gen_pshared_key() - generate a key for process shared lock * * The generated is made in a way that it is unique during the lifetime of the * server. Even if it is restarted, the probability to regenerate the same key * that a previous server instance is very close to 0. * * Return: the generated key */ static int64_t gen_pshared_key(void) { static uint32_t last_value = 0; int64_t key; FILETIME curr; key = last_value++; key <<= 32; GetSystemTimeAsFileTime(&curr); key |= curr.dwLowDateTime; return key; } static DWORD clamp_i64_to_dword(int64_t i64val) { if (i64val > DWORD_MAX) return DWORD_MAX; if (i64val < 0) return 0; return (DWORD)i64val; } static HANDLE dup_handle_for_pipeclient(HANDLE hpipe, HANDLE src_hnd) { HANDLE dst_proc, src_proc; HANDLE dst_hnd; ULONG pid; // Get PID of the other end of the named pipe GetNamedPipeClientProcessId(hpipe, &pid); // Get process handle of source and destination for handle passing src_proc = GetCurrentProcess(); dst_proc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid); if (dst_proc == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE; if (!DuplicateHandle(src_proc, src_hnd, dst_proc, &dst_hnd, 0, FALSE, DUPLICATE_SAME_ACCESS)) { dst_hnd = INVALID_HANDLE_VALUE; } CloseHandle(dst_proc); return dst_hnd; } static size_t get_robust_data_size(int num_keys) { struct robust_data* data; size_t len; static size_t pagesize; if (num_keys <= 0) num_keys = 1; // Get page size if (!pagesize) { SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); pagesize = sysinfo.dwPageSize; } // Get the minimal size to map the robust data with at least // locked_list_size element in locked_keys array, round up to the next // multiple of page size len = sizeof(*data) + num_keys*sizeof(data->locked_keys[0]); return ROUND_UP(len, pagesize); } /************************************************************************** * * * Lock manipulation * * * **************************************************************************/ /** * append_to_queue() - add the specified node to the end of the queue * @queue: queue to update * @node: node to add */ static void append_to_queue(struct queue* queue, struct queue_node* node) { struct queue_node* last = queue->last; if (!queue->first) queue->first = node; else last->next = node; node->prev = last; node->next = NULL; queue->last = node; } /** * drop_from_queue() - remove the specified node from the queue * @queue: queue to update * @node: node to remove */ static void drop_from_queue(struct queue* queue, struct queue_node* node) { struct queue_node *prev, *next; prev = node->prev; next = node->next; if (prev) prev->next = next; else queue->first = next; if (next) next->prev = prev; else queue->last = prev; } /** * lock_init() - initialize the internals of a newly created lock * @lock: pointer to lock to initialize * @key: key associated with the lock */ static void lock_init(struct lock* lock, int64_t key) { *lock = (struct lock) { .key = key, .job_mapping_hnd = INVALID_HANDLE_VALUE, }; } /** * lock_add_waiter() - add a thread waiting for the lock * @lock: lock to which the thread must be adding in wait list * @tc: thread client to turn as a waiter * * Return: true if the thread client is now actually waiting for lock, false if * the thread client is not waiting due to immediate wakeup. */ static bool lock_add_waiter(struct lock* lock, struct thread_client* tc) { // Check that lock has not been unheld in advance if ((++lock->nwaiter <= 0) && (lock->max_wakeup_val >= tc->wakeup_val)) { thread_client_wakeup(tc, false); return false; } // Append thread client to the end of lk_arr of waiters append_to_queue(&lock->waiters_queue, &tc->lock_node); return true; } /** * lock_wake_waiters() - wakeup some threads waiting for a lock * @lock: lock for which the thread are waiting * @num: number of thread to wake up * @val: wakeup minimal value * * Select @num thread waiting for the lock referenced by @lock and whose wakeup * condition is fulfilled and wake them up. The wakeup condition corresponds to * a wakeup value of a thread client being equal or smaller to @val. * * Due to race for going to sleep, it might be possible that the current number * of thread waiting for the lock is smaller than the number of thread to * wakeup. This is not a issue since the lock will store in advance the wakeup * condition and adjust the number of thread that must sleep. When the missing * waiters will actually request to wait for lock, they will be immediately * waken up. */ static void lock_wake_waiters(struct lock* lock, int num, int64_t val) { struct thread_client *tc; struct queue_node* node; // Adjust in advance the wakeup, it will be here if we don't find // enough waiter to wake lock->nwaiter -= num; if (lock->max_wakeup_val < val) lock->max_wakeup_val = val; // Wakeup num first thread whose wakeup condition match for (node = lock->waiters_queue.first; node && num; node = node->next) { tc = GET_TC_FROM_LOCK_NODE(node); if (tc->wakeup_val > val) continue; // Remove from waiter queue and wake it up drop_from_queue(&lock->waiters_queue, &tc->lock_node); thread_client_wakeup(tc, false); num--; } } /** * lock_drop_waiter() - remove a thread waiting for a lock * @lock: lock for which the thread are waiting * @tc: thread client to drop from the waiter queue * * This function is meant to be used when a thread client is being destroyed or * a wait has timedout. It will remove it from the waiter queue of @lock. * Contrary to lock_wake_waiters(), this does not wake up the thread. */ static void lock_drop_waiter(struct lock* lock, struct thread_client* tc) { // Remove from waiter queue and adjust the wakeup count lock->nwaiter--; drop_from_queue(&lock->waiters_queue, &tc->lock_node); } /** * lock_create_cleanup_job() - create a cleanup job associated with a lock * @lock: lock associated to which the cleanup job must be created * * This function creates a cleanup job associated with @lock. This structure is * mean to keep count of dead thread that were in relation with the lock at the * moment of their death. It is a special data that is meant to be shared with * a client process which will do the actual cleanup. As such it is not memory * allocated as usual but created through a file mapping. * * Return: 0 in case of success, -1 otherwise. */ static int lock_create_cleanup_job(struct lock* lock) { HANDLE hnd; struct mutex_cleanup_job* job; hnd = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MM_PAGESZ, NULL); if (hnd == INVALID_HANDLE_VALUE) return -1; // Map into memory job = MapViewOfFile(hnd, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (!job) { CloseHandle(hnd); return -1; } job->num_dead = 0; lock->job = job; lock->job_mapping_hnd = hnd; return 0; } /** * lock_destroy_cleanup_job() - destroy a cleanup job associated with a lock * @lock: lock whose cleanup job must be destroyed */ static void lock_destroy_cleanup_job(struct lock* lock) { if (!lock->job) return; UnmapViewOfFile(lock->job); CloseHandle(lock->job_mapping_hnd); lock->job_mapping_hnd = INVALID_HANDLE_VALUE; lock->job = NULL; } /** * lock_start_or_remove_cleanup_job() - start pending cleanup job or remove empty one * @lock: lock whose cleanup job must be inspected */ static void lock_start_or_remove_cleanup_job(struct lock* lock) { struct mutex_cleanup_job* job = lock->job; struct thread_client* tc; if (!job || job->in_progress) return; if (job->num_dead == 0) { lock_destroy_cleanup_job(lock); return; } // Start it if there is something to do and a waiter is available for it if (lock->waiters_queue.first) { lock->job->in_progress = 1; tc = GET_TC_FROM_LOCK_NODE(lock->waiters_queue.first); thread_client_do_cleanup_job(tc, lock->job_mapping_hnd); } } /** * lock_report_cleanup_job_done() - report a mutex cleanup has been finished * @lock: lock whose cleanup job is done * @num_wakeup: number of thread that must be wakeup * * Called when a thread has finished a mutex cleanup job, this mark the job has * done making it possibly eligible for removal during the next garbage * collection. */ static void lock_report_cleanup_job_done(struct lock* lock) { struct mutex_cleanup_job* job = lock->job; job->in_progress = 0; } /** * lock_report_dead() - add a thread reported as dead in a lock * @lock: lock to which the dead muyst be reported * @is_waiter: flag from thread robust data indicating it was waiting * @thread_id: thread id of the dead thread * * Notify @lock that the thread whose ID was @thread_id and which has been * detected as dead was in relation with the lock. The lock can then create or * update a cleanup job to submit later to a thread client which will allows * one to undo the possible change the dead has made to the shared lock. */ static void lock_report_dead(struct lock* lock, int is_waiter, DWORD thread_id) { struct mutex_cleanup_job* job; int i; // Create a cleanup job associated with the lock if none were available // before if (!lock->job) { if (lock_create_cleanup_job(lock)) return; } job = lock->job; // Add the thread to the cleanup job i = job->num_dead++; job->deadlist[i].tid = thread_id; job->deadlist[i].is_waiter = is_waiter; } /** * lock_is_unused() - check that a lock still need to be referenced * @lock: lock to check * * Return: true if it is actually unused, false otherwise. */ static bool lock_is_unused(const struct lock* lock) { if (!lock->nwaiter && !lock->waiters_queue.first && !lock->job) return true; return false; } /************************************************************************** * * * Lock array manipulation * * * **************************************************************************/ #define KEY_NOTFOUND_FLAG 0x80000000 /** * search_key_index() - get the index of lock of given key in the list * @locks: sorted array of locks * @len: number of element in @locks * @key: key of lock to search * * Return: the index if lock using @key is found. Otherwise the position * where to insert the key (insert before) is provided by a negative value * (-index is returned means that lock must be inserted before position * index) */ static int search_key_index(const struct lock* locks, int len, int64_t key) { int m, l, h; // Binary search in the space of the sorted list of lock keys l = 0; h = len-1; while (l <= h) { m = (l+h)/2; if (key == locks[m].key) return m; if (key < locks[m].key) { h = m-1; } else { l = m+1; } } // Not found... The location of insertion (before) is given by l return l | KEY_NOTFOUND_FLAG; } /** * lock_array_get_lock() - get the lock (maybe new) of the specified key * @lk_arr: lock_array in which the corresponding lock should be found * @key: pshared key corresponding to the lock to find * @create: flag indicating whether the lock must be created if not found * * Return: pointer to the lock found or newly created. */ static struct lock* lock_array_get_lock(struct lock_array* lk_arr, int64_t key, bool create) { int index, len; struct lock* locks = lk_arr->sorted_array; index = search_key_index(locks, lk_arr->num_used, key); // Handle the case when lock must be inserted. if (index & KEY_NOTFOUND_FLAG) { if (!create) return NULL; index &= ~KEY_NOTFOUND_FLAG; // Resize lock array if there is no space to add one if (UNLIKELY(lk_arr->num_used+1 > lk_arr->num_max)) { lk_arr->num_max *= 2; locks = realloc(locks, lk_arr->num_max*sizeof(*locks)); if (!locks) return NULL; lk_arr->sorted_array = locks; } // Actually insert the new lock and initialize it lk_arr->num_used++; len = lk_arr->num_used - index; memmove(locks+index+1, locks+index, len*sizeof(*locks)); lock_init(&locks[index], key); } return locks + index; } /** * lock_array_drop_unused() - drop the locks that are not used any longer * @lk_arr: lock array whose locks should be checked */ static void lock_array_drop_unused(struct lock_array* lk_arr) { int src, dst; struct lock* locks = lk_arr->sorted_array; // Copy element (lock) wise. Skip copy if lock is unused for (src = 0, dst = 0; src < lk_arr->num_used; src++, dst++) { // Check that the lock is still in use if (lock_is_unused(&locks[src])) { dst--; continue; } // No modification so far, so skip copy if (src == dst) continue; locks[dst] = locks[src]; } lk_arr->num_used += (dst - src); } /** * lock_array_update_cleanup_jobs() - start or remove cleanup job of locks * @lk_arr: lock array * * This function will inspect the cleanup job of each lock (if any) and start * the cleanup job if something has to be done, or remove it if nothing has to * be done. */ static void lock_array_update_cleanup_jobs(struct lock_array* lk_arr) { int i; struct lock* locks = lk_arr->sorted_array; for (i = 0; i < lk_arr->num_used; i++) lock_start_or_remove_cleanup_job(&locks[i]); } /** * lock_array_deinit_() - deallocate internal of a lock array * @lk_arr: lock array to deinit */ static void lock_array_deinit(struct lock_array* lk_arr) { free(lk_arr->sorted_array); *lk_arr = (struct lock_array){0}; } /** * lock_array_init() - Initialize a lock array * @lk_arr: lock array to initialize * * Return: 0 in case of success, -1 otherwise */ static int lock_array_init(struct lock_array* lk_arr) { size_t arrsize; *lk_arr = (struct lock_array){.num_max = LOCK_LIST_INITIAL_LEN}; arrsize = lk_arr->num_max*sizeof(*lk_arr->sorted_array); lk_arr->sorted_array = calloc(1, arrsize); if (!lk_arr->sorted_array) return -1; return 0; } /************************************************************************** * * * Timeout list manipulation * * * **************************************************************************/ static void detach_list_node(struct list_node* node) { if (node->next) { node->next->prev = node->prev; node->next = NULL; } if (node->prev) { node->prev->next = node->next; node->prev = NULL; } } static void insert_list_node_after(struct list_node* node_to_insert, struct list_node* after) { node_to_insert->prev = after; node_to_insert->next = after->next; if (after->next) after->next->prev = node_to_insert; after->next = node_to_insert; } /** * timeout_list_add_waiter() - add a thread client to a timeout list * @timeout_list: list to update * @tc: thread client to add * * When adding a thread, the element is inserted in the middle in the list such * a way the list is kept sorted according to the timeout, ie the first element * in the list is always the one whose timeout is the most immediate. */ static void timeout_list_add_waiter(struct list* timeout_list, struct thread_client *tc) { struct list_node *node, *next; struct thread_client* next_tc; struct mm_timespec* timeout = &tc->wait_timeout; node = &timeout_list->head; next = node->next; // Find the position of thread in the list of increasing timeout // After this loop, the thread must be inserted after node while (next) { next_tc = GET_TC_FROM_TIMEOUT_NODE(next); if (mm_timediff_ns(timeout, &next_tc->wait_timeout) < 0) break; node = next; next = next->next; } insert_list_node_after(&tc->timeout_node, node); } /** * timeout_list_update() - wakeup thread whose timeout is elapsed * @timeout_list: list to update * @now: timestamp indicating current time * * Return: the time difference in nanosecond from @now to the timeout of the * next thread client node remaining in the list after update. If the timeout * list is empty INT64_MAX is returned. */ static int64_t timeout_list_update(struct list* timeout_list, struct mm_timespec* now) { struct thread_client* tc; struct list_node *node, *next; int64_t diff_ns; for (node = timeout_list->head.next; node != NULL; node = next) { tc = GET_TC_FROM_TIMEOUT_NODE(node); diff_ns = mm_timediff_ns(&tc->wait_timeout, now); if (diff_ns >= 0) return diff_ns; // Store the next node now, because, waking thread client up // will remove it from its timeout list (hence, node->next // will become NULL during wakeup). next = node->next; thread_client_process_timeout(tc); } // No waiter left with this clock base return INT64_MAX; } /************************************************************************** * * * Thread connection handler * * * **************************************************************************/ /** * DOC: Thread client communication * * The communication with the thread client is handle asynchronously over the * connected named pipe. For each thread to handle, there is a connected pipe. * The whole communication resolves around handling the few request it can * make : * * - LOCKREF_OP_INITLOCK: request a lock to be initialized (ie, a key to be * generated). The response will contain the generated key. * * - LOCKREF_OP_WAIT: the thread has requested to wait on the specified key. * The response will not be issued until the thread must be waken up * * - LOCKREF_OP_WAKE: The thread request to wakeup a certain number of thread * waiting for the specified key. This request do not get any response from * the server. * * - LOCKREF_OP_GETROBUST: request the lock server to provide robust data to * the client. This data will shared though memory map between client and * server. * * - LOCKREF_OP_CLEANUP_DONE: response to a request initiated by the server. * The thread client notifies that the mutex cleanup job that has been * requested is finished. * * Since the communication is done asynchronously, the request and response * data are local to the thread_client struct. The read or write request is * queued and later the associated completion callback will be executed. * Logically the client thread can be engaged in only one request/response * exchange at a time, so normally, it would be correct to requeue a read when * the completion of a write happens and same for write when read complete. * * However, the only wait to detect the other endpoint of a named pipe being * closed is to read from it. Consequently there is always a read being queued * (whenever a read complete a new one is queued) and the write (response) are * done in parallel. There is no problem because only one request can run at a * time. */ static void thread_client_handle_req_wake(struct thread_client* tc) { int64_t key, val; int num_wakeup; struct lock* lock; // Get parameters from incoming message key = tc->msg_read.wake.key; val = tc->msg_read.wake.val; num_wakeup = tc->msg_read.wake.num_wakeup; // Find the lock and do the wakeup on it lock = lockref_server_get_lock(&server, key, true); lock_wake_waiters(lock, num_wakeup, val); // Send wake done ack tc->msg_write.respcode = LOCKREF_OP_WAKE; thread_client_queue_write(tc); } static void thread_client_handle_req_wait(struct thread_client* tc) { int64_t key, val; int clk_flags; struct mm_timespec* timeout; struct lock* lock; struct list* timeout_list; bool is_waiting; // Get parameters from incoming message key = tc->msg_read.wait.key; val = tc->msg_read.wait.val; clk_flags = tc->msg_read.wait.clk_flags; timeout = &tc->msg_read.wait.timeout; // Add thread client to the waiter queue of the lock. If it returns // immediatemely, the lock has been waken up is advance and the thread // client is not waiting, thus must not be added to the timeout list tc->wakeup_val = val; lock = lockref_server_get_lock(&server, key, true); is_waiting = lock_add_waiter(lock, tc); if (!is_waiting) return; // Find timeout list if applicable timeout_list = lockref_server_get_timeout_list(&server, clk_flags); if (!timeout_list) return; // Add thread client to the timeout list tc->wait_timeout = *timeout; timeout_list_add_waiter(timeout_list, tc); } static void thread_client_handle_req_initlock(struct thread_client* tc) { int64_t key; key = gen_pshared_key(); // Reply with the generated key tc->msg_write.key = key; tc->msg_write.respcode = LOCKREF_OP_INITLOCK; thread_client_queue_write(tc); } static void thread_client_handle_req_getrobust(struct thread_client* tc) { int num_keys; HANDLE client_hmap; // Get parameters from incoming message num_keys = tc->msg_read.getrobust.num_keys; client_hmap = thread_client_add_robust_data(tc, num_keys); // Send robust data in reply (or report failure) if (client_hmap != INVALID_HANDLE_VALUE) { tc->msg_write.respcode = LOCKREF_OP_GETROBUST; tc->msg_write.hmap = client_hmap; } else { tc->msg_write.respcode = LOCKREF_OP_ERROR; } thread_client_queue_write(tc); } static void thread_client_handle_req_cleanup_done(struct thread_client* tc) { int64_t key; int num_wakeup; struct lock* lock; // Get parameters from incoming message key = tc->msg_read.cleanup.key; num_wakeup = tc->msg_read.cleanup.num_wakeup; lock = lockref_server_get_lock(&server, key, false); lock_report_cleanup_job_done(lock); lock_wake_waiters(lock, num_wakeup, 1); } /** * read_completed() - callback when asyncIO on client pipe read a request * @errcode: win32 error code in case of failure of the read * @xfer_sz: amount of data that has been read * @lpo: pointer to the overlapped array used in the async read */ static VOID CALLBACK read_completed(DWORD errcode, DWORD xfer_sz, LPOVERLAPPED lpo) { struct thread_client* tc = GET_TC_FROM_OVERLAPPED_READ(lpo); tc->num_pending_request--; // If any problem occurs, it is likely to be due to the client has // closed the connection (willingly or not), ie, errcode=109. Even if // is something different, the only sensible thing to do is to close // the connection if (errcode || xfer_sz < sizeof(tc->msg_read) || tc->being_destroyed) { thread_client_destroy(tc); return; } // Handle request switch(tc->msg_read.opcode) { case LOCKREF_OP_WAKE: thread_client_handle_req_wake(tc); break; case LOCKREF_OP_WAIT: thread_client_handle_req_wait(tc); break; case LOCKREF_OP_INITLOCK: thread_client_handle_req_initlock(tc); break; case LOCKREF_OP_GETROBUST: thread_client_handle_req_getrobust(tc); break; case LOCKREF_OP_CLEANUP_DONE: thread_client_handle_req_cleanup_done(tc); break; default: tc->msg_write.respcode = LOCKREF_OP_ERROR; thread_client_queue_write(tc); break; } // immediately requeue a read to handle the next request. This harmless // since write and read use different buffer and it allows use to // detect when a client get disconnected even if we don't have written // any response to the current request yet (this is the case of wait // request) thread_client_queue_read(tc); } /** * thread_client_queue_read() - queue a read asynchronously * @tc: thread client with which the read must be done * * This start an asynchronous read from the associated pipe in @tc->msg_read. */ static void thread_client_queue_read(struct thread_client* tc) { BOOL res; if (tc->being_destroyed) return; res = ReadFileEx(tc->pipe, &tc->msg_read, sizeof(tc->msg_read), &tc->overlapped_read, read_completed); if (!res) { // Failure path thread_client_destroy(tc); return; } tc->num_pending_request++; } /** * write_completed() - callback when asyncIO on client pipe write a response * @errcode: win32 error code in case of failure of the write * @xfer_sz: amount of data that has been written * @lpo: pointer to the overlapped array used in the async write */ static VOID CALLBACK write_completed(DWORD errcode, DWORD xfer_sz, LPOVERLAPPED lpo) { struct thread_client* tc = GET_TC_FROM_OVERLAPPED_WRITE(lpo); tc->num_pending_request--; if (errcode || xfer_sz < sizeof(tc->msg_write) || tc->being_destroyed) thread_client_destroy(tc); } /** * thread_client_queue_write() - queue a write asynchronously * @tc: thread client with which the write must be done * * This start an asynchronous write with the associated pipe with the data in * @tc->msg_write. Prior to this call, the content of @tc->msg_write must be * set appropriately. */ static void thread_client_queue_write(struct thread_client* tc) { BOOL res; if (tc->being_destroyed) return; res = WriteFileEx(tc->pipe, &tc->msg_write, sizeof(tc->msg_write), &tc->overlapped_write, write_completed); if (!res) { // Failure path thread_client_destroy(tc); return; } tc->num_pending_request++; } /** * thread_client_wakeup() - wake a thread that was waiting * @tc: thread client to wakeup * @timedout: true if the client is waken up due to timeout * * This function must be used on a thread client that has issue previously a * wait request. This will issue the response to its wait request which in * effect will wake the thread up since it was waiting for the response. * * This function is meant to be called as soon as the thread client has been * removed from the waiter queue of the lock it was waiting for, so by * lock_wake_waiters() or lock_add_waiter() if it has been wake up in * advance. */ static void thread_client_wakeup(struct thread_client* tc, bool timedout) { // Remove thread client from timeout list (if any) detach_list_node(&tc->timeout_node); tc->waiting_lock_key = 0; // wake up correspond to the reply to the wait request tc->msg_write.respcode = LOCKREF_OP_WAIT; tc->msg_write.timedout = timedout ? 0 : 1; thread_client_queue_write(tc); } /** * thread_client_process_timeout() - do necessary wakeup when a wait is timed out * @tc: thread client that wait engaged in a timed wait */ static void thread_client_process_timeout(struct thread_client* tc) { struct lock* lock; // Find the lock the thread is waiting for lock = lockref_server_get_lock(&server, tc->waiting_lock_key, false); // Remove from lock waiter queue and wake it up lock_drop_waiter(lock, tc); thread_client_wakeup(tc, true); } /** * thread_client_do_cleanup_job() - send cleanup job request to thread client * @tc: thread client to use * @cleanup_mapping_hnd: handle to file mapping of the cleanup job */ static void thread_client_do_cleanup_job(struct thread_client* tc, HANDLE cleanup_mapping_hnd) { HANDLE client_hmap; // Generate a file mapping handle usable by client client_hmap = dup_handle_for_pipeclient(tc->pipe, cleanup_mapping_hnd); if (client_hmap == INVALID_HANDLE_VALUE) return; // Send request to thread client tc->msg_write.respcode = LOCKREF_OP_CLEANUP; tc->msg_write.hmap = client_hmap; thread_client_queue_write(tc); } /** * thread_client_add_robust_data() - create robust data for a thread client * @tc: thread client whose robust data must be created * @num_keys: minimal number of locked keys that the robust data must support * * Return: handle to the file mapping of the robust data that must be passed to * the thread client. In case of failure, INVALID_HANDLE_VALUE is returned. */ static HANDLE thread_client_add_robust_data(struct thread_client* tc, int num_keys) { size_t data_len, keylocked_max; HANDLE hmap = INVALID_HANDLE_VALUE; HANDLE client_hmap = INVALID_HANDLE_VALUE; // Create a file mapping backed by the system paging file data_len = get_robust_data_size(num_keys); hmap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, data_len, NULL); if (hmap == INVALID_HANDLE_VALUE) goto exit; // Map into memory tc->robust_data = MapViewOfFile(hmap, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (!tc->robust_data) goto exit; // Set maximum number of key that a thread a locked at the same time // according to the size of mapped robust_data keylocked_max = (data_len - sizeof(*tc->robust_data))/sizeof(tc->robust_data->locked_keys[0]); tc->robust_data->num_locked_max = keylocked_max; // Generate a file mapping handle usable by client client_hmap = dup_handle_for_pipeclient(tc->pipe, hmap); exit: // Cleanup if (hmap != INVALID_HANDLE_VALUE) CloseHandle(hmap); return client_hmap; } /** * thread_client_cleanup_robust_data() - process robust data and destroy it * @tc: thread being destroy and whose robust data must be processed * * This function, meant to be called when the thread client is destroy, analyse * the robust data (if any) and will notify the lock referenced in it that the * thread has been destroyed. */ static void thread_client_cleanup_robust_data(struct thread_client* tc) { int i; struct robust_data* rdata; struct lock* lock; int64_t key; DWORD tid; rdata = tc->robust_data; if (!rdata) return; tid = rdata->thread_id; // Cleanup all mutex that dead is owning for (i = 0; i < rdata->num_locked; i++) { key = tc->robust_data->locked_keys[i]; lock = lockref_server_get_lock(&server, key, true); lock_report_dead(lock, 0, tid); } // Cleanup mutex that dead is attempting to lock key = rdata->attempt_key; if (key != 0) { lock = lockref_server_get_lock(&server, key, true); lock_report_dead(lock, rdata->is_waiter, tid); } UnmapViewOfFile(tc->robust_data); tc->robust_data = NULL; } /** * thread_client_destroy() - destroy a thread client * @tc: thread client to destroy * * This function is meant to be called when the client endpoint of thread pipe * appears to be closed (or if a problem occurs). This will drop the thread * client from any waiter queue it might belong to. */ static void thread_client_destroy(struct thread_client* tc) { int64_t key; struct lock* lock; tc->being_destroyed = true; // Do actual cleanup only when all request have returned if (tc->num_pending_request) return; // Remove thread from any wait list key = tc->waiting_lock_key; if (key) { lock = lockref_server_get_lock(&server, key, false); lock_drop_waiter(lock, tc); // Remove thread client from timeout list (if any) detach_list_node(&tc->timeout_node); } thread_client_cleanup_robust_data(tc); CloseHandle(tc->pipe); free(tc); lockref_server_update_thread_count(&server, -1); } /** * thread_client_create() - create a thread client * @pipe: handle to the newly connected pipe * * This creates a new thread client structure to handle communication with the * client thread. This function must be used when a new pipe gets connected. * * Return: the pointer to the new thread client */ static struct thread_client* thread_client_create(HANDLE pipe) { struct thread_client* tc; tc = malloc(sizeof(*tc)); if (!tc) return NULL; *tc = (struct thread_client) {.pipe = pipe}; lockref_server_update_thread_count(&server, 1); return tc; } /************************************************************************** * * * lockref server * * * **************************************************************************/ static void deinit_pipe_security_attrs(SECURITY_ATTRIBUTES* sec_attr) { PSECURITY_DESCRIPTOR sd; PACL acl; BOOL has_acl, default_acl; if (!sec_attr->lpSecurityDescriptor) return; sd = sec_attr->lpSecurityDescriptor; GetSecurityDescriptorDacl(sd, &has_acl, &acl, &default_acl); if (has_acl && !default_acl) LocalFree(acl); LocalFree(sd); *sec_attr = (SECURITY_ATTRIBUTES){0}; } static int init_pipe_security_attrs(SECURITY_ATTRIBUTES* sec_attr) { EXPLICIT_ACCESS ea[2] = {{0}}; PSECURITY_DESCRIPTOR sd; PACL acl; int i; for (i = 0; i < MM_NELEM(ea); i++) { ea[i].grfAccessMode = SET_ACCESS; ea[i].grfAccessPermissions = GENERIC_READ | FILE_WRITE_DATA; ea[i].grfInheritance = NO_INHERITANCE; ea[i].Trustee.TrusteeForm = TRUSTEE_IS_SID; } // Everyone can create and use a client endpoint of pipe ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.ptstrName = "EVERYONE"; // Only the current user can create a new instance server endpoint ea[1].grfAccessPermissions |= FILE_CREATE_PIPE_INSTANCE; ea[1].Trustee.TrusteeType = TRUSTEE_IS_USER; ea[1].Trustee.ptstrName = "CURRENT_USER"; SetEntriesInAcl(MM_NELEM(ea), ea, NULL, &acl); sd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE); *sec_attr = (SECURITY_ATTRIBUTES){0}; sec_attr->nLength = sizeof(SECURITY_ATTRIBUTES); sec_attr->bInheritHandle = FALSE; sec_attr->lpSecurityDescriptor = sd; return 0; } static HANDLE create_srv_new_pipe(SECURITY_ATTRIBUTES* sec_attr) { HANDLE hnd; DWORD open_mode, pipe_mode; open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; pipe_mode = PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS; hnd = CreateNamedPipe(referee_pipename, open_mode, pipe_mode, PIPE_UNLIMITED_INSTANCES, MM_PAGESZ, MM_PAGESZ, SRV_TIMEOUT_MS, sec_attr); return hnd; } static int setup_initial_pipe(HANDLE pipe, SECURITY_ATTRIBUTES* sec_attr) { PACL dacl; BOOL is_dacl_present, is_dacl_default; if ( !GetSecurityDescriptorDacl(sec_attr->lpSecurityDescriptor, &is_dacl_present, &dacl, &is_dacl_default) || !SetSecurityInfo(pipe, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, dacl, NULL)) return -1; return 0; } static int lockref_server_handle_new_connection(struct lockref_server* srv) { struct thread_client* tc; tc = thread_client_create(srv->pipe); if (!tc) return -1; thread_client_queue_read(tc); // Create a new server pipe to replace the one that just has been // connected srv->pipe = create_srv_new_pipe(&srv->sec_attr); if (srv->pipe == INVALID_HANDLE_VALUE) return -1; return 0; } static int lockref_server_queue_connect(struct lockref_server* srv) { if (ConnectNamedPipe(srv->pipe, &srv->conn_overlapped)) return -1; switch(GetLastError()) { case ERROR_IO_PENDING: return 1; case ERROR_PIPE_CONNECTED: return SetEvent(srv->connect_evt) ? 0 : -1; } return -1; } static struct lock* lockref_server_get_lock(struct lockref_server* srv, int64_t key, bool create) { return lock_array_get_lock(&srv->watched_locks, key, create); } static struct list* lockref_server_get_timeout_list(struct lockref_server* srv, int clk_flags) { switch(clk_flags & WAITCLK_MASK) { case 0: return NULL; case WAITCLK_FLAG_MONOTONIC: return &srv->monotonic_timeout_list; case WAITCLK_FLAG_REALTIME: return &srv->realtime_timeout_list; default: // Invalid parameter, just ignore the timeout return NULL; } } static void lockref_server_garbage_collection(struct lockref_server* srv) { lock_array_drop_unused(&srv->watched_locks); lock_array_update_cleanup_jobs(&srv->watched_locks); } static int64_t lockref_server_update_next_timeout_ns(struct lockref_server* srv) { struct mm_timespec now; int64_t diff_ns, timeout = INT64_MAX; // Wake up timed out waits based on wallclock gettimespec_wallclock_w32(&now); diff_ns = timeout_list_update(&srv->realtime_timeout_list, &now); if (timeout > diff_ns) timeout = diff_ns; // Wake up timed out waits based on monotonic gettimespec_monotonic_w32(&now); diff_ns = timeout_list_update(&srv->monotonic_timeout_list, &now); if (timeout > diff_ns) timeout = diff_ns; return timeout; } static void lockref_server_update_thread_count(struct lockref_server* srv, int adjustment) { srv->num_thread_client += adjustment; if (srv->num_thread_client != 0) srv->quit = 0; else srv->quit = 1; } static void lockref_server_mainloop(struct lockref_server* srv) { int io_pending; DWORD wait_res, byte_ret, timeout_ms; int64_t timeout_ns; BOOL ret; io_pending = lockref_server_queue_connect(srv); if (io_pending < 0) goto failure; while (!srv->quit) { timeout_ns = lockref_server_update_next_timeout_ns(srv); timeout_ms = clamp_i64_to_dword(timeout_ns / NSEC_IN_MSEC); wait_res = WaitForSingleObjectEx(srv->connect_evt, timeout_ms, TRUE); switch(wait_res) { case 0: if (io_pending) { ret = GetOverlappedResult(srv->pipe, &srv->conn_overlapped, &byte_ret, FALSE); if (!ret) goto failure; } if (lockref_server_handle_new_connection(srv)) goto failure; io_pending = lockref_server_queue_connect(srv); if (io_pending < 0) goto failure; case WAIT_IO_COMPLETION: case WAIT_TIMEOUT: break; default: goto failure; } lockref_server_garbage_collection(srv); } return; failure: fprintf(stderr, "Failed to get connected instance: %lu\n", GetLastError()); } static void lockref_server_deinit(struct lockref_server* srv) { if (!srv->is_init) return; deinit_pipe_security_attrs(&srv->sec_attr); if (srv->pipe != INVALID_HANDLE_VALUE) CloseHandle(srv->pipe); if (srv->connect_evt != INVALID_HANDLE_VALUE) CloseHandle(srv->connect_evt); } static int lockref_server_init(struct lockref_server* srv, HANDLE pipe) { HANDLE evt = NULL; evt = CreateEvent(NULL, TRUE, TRUE, NULL); if (evt == NULL) goto failure; if (init_pipe_security_attrs(&srv->sec_attr) || lock_array_init(&srv->watched_locks) || setup_initial_pipe(pipe, &srv->sec_attr)) goto failure; srv->connect_evt = evt; srv->pipe = pipe; srv->conn_overlapped.hEvent = evt; srv->is_init = 1; return 0; failure: if (evt != NULL) CloseHandle(evt); if (pipe != INVALID_HANDLE_VALUE) CloseHandle(pipe); deinit_pipe_security_attrs(&srv->sec_attr); lock_array_deinit(&srv->watched_locks); return -1; } static int run_lockserver(HANDLE pipehnd) { setbuf(stderr, NULL); if (lockref_server_init(&server, pipehnd)) return -1; lockref_server_mainloop(&server); lockref_server_deinit(&server); return 0; } #ifndef LOCKSERVER_IN_MMLIB_DLL int main(int argc, char* argv[]) { HANDLE pipehnd; unsigned long long ull; char *hnd_str, *endptr; if (argc > 1) { hnd_str = argv[1]; ull = strtoull(hnd_str, &endptr, 16); // Check the whole string has been used for conversion if (*endptr != '\0') { fprintf(stderr, "pipe arg badly formatted: %s\n", hnd_str); return EXIT_FAILURE; } pipehnd = (HANDLE) ull; } else { pipehnd = create_srv_first_pipe(); if (pipehnd == INVALID_HANDLE_VALUE) { fprintf(stderr, "Failed to create server pipe: %lu\n", GetLastError()); return EXIT_FAILURE; } } if (run_lockserver(pipehnd)) return EXIT_FAILURE; return EXIT_SUCCESS; } #else LOCAL_SYMBOL void* lockserver_thread_routine(void* arg) { HANDLE pipehnd; (void)arg; pipehnd = create_srv_first_pipe(); if (pipehnd == INVALID_HANDLE_VALUE) return NULL; run_lockserver(pipehnd); return NULL; } #endif mmlib-1.4.2/src/log.c000066400000000000000000000125401435717460000143430ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmsysio.h" #include "mmlog.h" // Define STDERR_FILENO if not (may happen with some compiler for Windows) #ifndef STDERR_FILENO # define STDERR_FILENO 2 #endif #ifdef _MSC_VER # define restrict __restrict # define ssize_t int #endif #if _WIN32 # if HAS_LOCALTIME_S # define localtime_r(time, tm) localtime_s((tm), (time)) # else # define localtime_r(time, tm) do {*(tm) = *(localtime(time));} while (0) # endif #endif #ifndef MM_LOG_LINE_MAXLEN #define MM_LOG_LINE_MAXLEN 256 #endif static int maxloglvl = MM_LOG_INFO; static const char* const loglevel[] = { [MM_LOG_FATAL] = "FATAL", [MM_LOG_ERROR] = "ERROR", [MM_LOG_WARN] = "WARN", [MM_LOG_INFO] = "INFO", [MM_LOG_DEBUG] = "DEBUG", }; #define NLEVEL (sizeof(loglevel)/sizeof(loglevel[0])) MM_CONSTRUCTOR(init_log) { int i; const char* envlvl; envlvl = getenv("MM_LOG_MAXLEVEL"); if (!envlvl) return; for (i = 0; i < (int)NLEVEL; i++) { if (!strcmp(loglevel[i], envlvl)) { maxloglvl = i; return; } } // If we reach each, unknown level has been set through environment // In that case, it should be equivalent to no log maxloglvl = MM_LOG_NONE; } /** * format_log_str() - generate log string on supplied buffer * @buff: buffer that must receive the log string * @blen: maximum size of @buffer * @lvl: level of the log line * @location: module name at the origin of the log * @msg: format controlling the log message * @args: argument list of supplied for @msg * * This function generates the log string according to @lvl, @location and * the supplied message that must be formatted with respect to the format * @msg and the argument list in @args. * * NOTE: The string is meant to be written as such to log file with write() * system call. In consequence, please pay attention that the log string * written in @buffer IS NOT null terminated! * * Return: the number of byte written on @buffer. */ static size_t format_log_str(char* restrict buff, size_t blen, int lvl, const char* restrict location, const char* restrict msg, va_list args) { time_t ts; struct tm tm; size_t len, rlen; rlen = blen; // format time stamp ts = time(NULL); localtime_r(&ts, &tm); len = strftime(buff, blen, "%d/%m/%y %H:%M:%S", &tm); buff += len; rlen -= len; // format message header message len = snprintf(buff, rlen-1, " %-5s %-16s : ", loglevel[lvl], location); buff += len; rlen -= len; // Format provided info and append end of line len = vsnprintf(buff, rlen, msg, args); len = (len < rlen-1) ? len : rlen-1; // handle truncation case buff[len++] = '\n'; rlen -= len; // Return the length of string without null terminator return blen - rlen; } /** * mm_log() - Add a formatted message to the log file * @lvl: log level. * @location: origin of the log message. * @msg: log message. * * Writes an entry in the log following the suggested format by the mmlib * standard: date, time, origin, level, message. The origin part is specified * by the @location parameter. The severity part is defined by the @lvl * parameter which must one of this value listed from the most critical to the * least one: * MM_LOG_FATAL * MM_LOG_ERROR * MM_LOG_WARN * MM_LOG_INFO * MM_LOG_DEBUG * * The message part of log entry is formed according the format specified by the * @msg parameters which convert the formatting and conversion of the optional * argument list of the function. As the format specified by @msg follows the * one of the @sprintf function. * * If the parameter lvl is less critical than the environment variable * @MM_LOG_MAXLEVEL, the log entry will not be written to log and simply * ignored. * * Usually, users do not call mm_log() directly but use one of the following * macros: mm_log_fatal(), mm_log_error(), mm_log_warn(), mm_log_info(), * mm_log_debug() * * Return: None * * ENVIRONMENT: You can control the output on the log at runtime using the * environment variable @MM_LOG_MAXLEVEL. It specifies the maximum level of * severity that must be written on the log. It must be set to one of these * values: * NONE * FATAL * ERROR * WARN * INFO * DEBUG * * A value different from the one listed above, the maximum level output on the * log is WARN. * * mm_log() is thread-safe. * * See: sprintf(), mm_log_fatal(), mm_log_error(), mm_log_warn(), * mm_log_info(), mm_log_debug() */ API_EXPORTED void mm_log(int lvl, const char* location, const char* msg, ...) { ssize_t r; size_t len; char* cbuf; va_list args; char buff[MM_LOG_LINE_MAXLEN]; // Do not log something higher than the max level set by environment if (lvl > maxloglvl || lvl < 0) return; // Format log string onto buffer va_start(args, msg); len = format_log_str(buff, sizeof(buff), lvl, location, msg, args); va_end(args); // Write message on log file descriptor cbuf = buff; do { if ((r = mm_write(STDERR_FILENO, cbuf, len)) < 0) return; len -= r; cbuf += r; } while (len); } /** * mm_log_set_maxlvl() - set maximum log level * @lvl: log level to set * * Return: previous log level */ API_EXPORTED int mm_log_set_maxlvl(int lvl) { int rv = maxloglvl; maxloglvl = lvl; return rv; } mmlib-1.4.2/src/meson.build000066400000000000000000000102131435717460000155530ustar00rootroot00000000000000public_headers = files( 'mmargparse.h', 'mmdlfcn.h', 'mmerrno.h', 'mmlib.h', 'mmlog.h', 'mmpredefs.h', 'mmprofile.h', 'mmsysio.h', 'mmthread.h', 'mmtime.h', ) install_headers(public_headers) mmlib_sources = files( 'alloc.c', 'argparse.c', 'dlfcn.c', 'error.c', 'file.c', 'file-internal.h', 'log.c', 'mmargparse.h', 'mmdlfcn.h', 'mmerrno.h', 'mmlib.h', 'mmlog.h', 'mmprofile.h', 'mmsysio.h', 'mmthread.h', 'mmtime.h', 'nls-internals.h', 'profile.c', 'socket.c', 'time.c', 'utils.c', ) cflags = [] ldflags = [] link_deps = [] dependencies = [] if nls_state == 'enabled' cflags += '-DENABLE_NLS' if not cc.has_function('ngettext') dependencies += cc.find_library('intl', required : true) endif endif if host_machine.system() == 'windows' mmlib_sources += files( 'atomic-win32.h', 'clock-win32.c', 'clock-win32.h', 'env-win32.c', 'file-win32.c', 'local-ipc-win32.c', 'local-ipc-win32.h', 'lock-referee-proto.h', 'mutex-lockval.h', 'process-win32.c', 'pshared-lock.c', 'pshared-lock.h', 'shm-win32.c', 'socket-win32.c', 'socket-win32.h', 'startup-win32.c', 'thread-win32.c', 'time-win32.c', 'utils-win32.c', 'utils-win32.h', 'volume-win32.c', 'volume-win32.h', ) libpowrprof = cc.find_library('powrprof', required : true) libws2_32 = cc.find_library('ws2_32', required : true) cflags += [ '-DMMLIB_API=API_EXPORTED', '-D__USE_MINGW_ANSI_STDIO=1', ] dependencies += [libpowrprof, libws2_32] else mmlib_sources += files( 'file-posix.c', 'local-ipc-posix.c', 'process-posix.c', 'shm-posix.c', 'socket-posix.c', 'thread-posix.c', 'time-posix.c', ) libthread = dependency('threads', required : true) libdl = cc.find_library('dl', required : true) librt = cc.find_library('rt', required : true) mmlib_map = meson.current_source_dir() + '/libmmlib.map' ldflags += '-Wl,--version-script,' + mmlib_map link_deps += mmlib_map dependencies += [libthread, libdl, librt] endif lock_referee_sources = [] if host_machine.system() == 'windows' if get_option('lock-server-process') lock_referee_sources += files( 'lock-referee-proto.h', 'lock-referee-server.c', 'clock-win32.h', 'clock-win32.c', ) libadvapi32 = cc.find_library('advapi32', required : true) executable('lock-referee', lock_referee_sources, c_args : cflags + ['-DMMLOG_MODULE_NAME="lock-referee"'], include_directories : configuration_inc, install : true, install_dir : get_option('libexecdir'), dependencies : [libpowrprof, libadvapi32], ) else add_project_arguments('-DLOCKSERVER_IN_MMLIB_DLL=1', language : 'c') mmlib_sources += files('lock-referee-server.c') endif # lock-server-process endif # windows # follow semantic versionning # https://semver.org/ # * MAJOR version when you make incompatible API changes, # * MINOR version when you add functionality in a backwards-compatible manner # * PATCH version when you make backwards-compatible bug fixes. major = '1' minor = '2' patch = '2' version = major + '.' + minor + '.' + patch mmlib_static = static_library('mmlib-static', mmlib_sources, c_args : cflags, install : false, include_directories : configuration_inc, dependencies : [dependencies], ) mmlib = shared_library('mmlib', mmlib_sources, c_args : cflags, link_args : ldflags, link_depends : link_deps, install : true, version : version, include_directories : configuration_inc, dependencies : [dependencies], ) import('pkgconfig').generate(mmlib) mmlib-1.4.2/src/mmargparse.h000066400000000000000000000240321435717460000157240ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMARGPARSE_H #define MMARGPARSE_H #include #include "mmpredefs.h" #ifdef __cplusplus extern "C" { #endif #define MM_OPT_NOVAL 0x00 #define MM_OPT_OPTVAL 0x01 #define MM_OPT_NEEDVAL 0x02 #define MM_OPT_REQMASK 0x03 #define MM_OPT_STR 0x00 #define MM_OPT_INT 0x10 #define MM_OPT_LLONG 0x20 #define MM_OPT_UINT 0x90 #define MM_OPT_ULLONG 0xA0 #define MM_OPT_TYPEMASK 0xF0 #define MM_OPT_FILEPATH 0x100 #define MM_OPT_DIRPATH 0x200 #define MM_OPT_OPTSTR (MM_OPT_OPTVAL | MM_OPT_STR) #define MM_OPT_NEEDSTR (MM_OPT_NEEDVAL | MM_OPT_STR) #define MM_OPT_OPTINT (MM_OPT_OPTVAL | MM_OPT_INT) #define MM_OPT_NEEDINT (MM_OPT_NEEDVAL | MM_OPT_INT) #define MM_OPT_OPTLLONG (MM_OPT_OPTVAL | MM_OPT_LLONG) #define MM_OPT_NEEDLLONG (MM_OPT_NEEDVAL | MM_OPT_LLONG) #define MM_OPT_OPTUINT (MM_OPT_OPTVAL | MM_OPT_UINT) #define MM_OPT_NEEDUINT (MM_OPT_NEEDVAL | MM_OPT_UINT) #define MM_OPT_OPTULLONG (MM_OPT_OPTVAL | MM_OPT_ULLONG) #define MM_OPT_NEEDULLONG (MM_OPT_NEEDVAL | MM_OPT_ULLONG) #define MM_OPT_OPTFILE (MM_OPT_OPTSTR | MM_OPT_FILEPATH) #define MM_OPT_NEEDFILE (MM_OPT_NEEDSTR | MM_OPT_FILEPATH) #define MM_OPT_OPTDIR (MM_OPT_OPTSTR | MM_OPT_DIRPATH) #define MM_OPT_NEEDDIR (MM_OPT_NEEDSTR | MM_OPT_DIRPATH) /** * union mm_arg_val - generic holder of argument value * @str: pointer to string value * @i: signed integer value * @ll: signed long long value * @ui: unsigned integer value * @ull: unsigned long long value * * &union mm_arg_val is the data holder to pass the value of an argument when a * argument callback function is called. The field to use to manipulate the * value depends on the type indicated &mm_arg_opt.flags of the option supplied * in the callback. This type can be retrieved by the helper * mm_arg_opt_get_type(). */ union mm_arg_val { const char* str; int i; long long ll; unsigned int ui; unsigned long long ull; }; /** * struct mm_arg_opt - option parser configuration * @name: option names, see description * @flags: conversion type and requirement flags, see options flags * @defval: default value is option is supplied without value * @val: union for the various types * @val.sptr: pointer to pointer to string, used if type is MM_OPT_STR * @val.iptr: pointer to integer, used if type is MM_OPT_INT * @val.llptr: pointer to long long, used if type is MM_OPT_LLONG * @val.uiptr: pointer to unsigned integer, used if type is MM_OPT_UINT * @val.ullptr: pointer to unsigned long long, used if type is MM_OPT_ULLONG * @desc: description of the option printed when usage is displayed * * This structure defines what an option supports and how its value must be * interpreted. @name defines both the short option key and long name. The * short key ("c") must be a single character of lower or upper case letter * in ascii character set. The long option name ("long-name") must contains * only ascii lower case letter, digit or hyphens. @name must adhere to one * the 3 following formats * * - "c|long-name": option can be referred by "-c" or "--long-name" * - "c": option can be referred only by short option "-c" * - "long-name": option can be referred only by long option "--long-name" * * The parameter @flags determine whether a value for an option: * * - %MM_OPT_NOVAL: value is forbidden * - %MM_OPT_OPTVAL: value is optional and @defval will be used if not set. * - %MM_OPT_NEEDVAL: value is mandatory * * it also determines what is the supported type of a value (if not * forbidden): * * - %MM_OPT_STR: value is string (then @val.sptr used) * - %MM_OPT_INT: value is int (then @val.iptr used) * - %MM_OPT_UINT: value is unsigned int (then @val.uiptr used) * - %MM_OPT_LLONG: value is long long (then @val.llptr used) * - %MM_OPT_ULLONG: value is unsigned long long (then @val.ullptr used) * * the ptr fields specified in parenthesis indicates that if the * corresponding field is not NULL, it will receive the value specified * * @desc provides the documentation of the option. It will be displayed if * usage is requested. Please note that if an option accepts a value * (optional or mandatory), the value name displayed in option synopsis * will be VALUE, unless, @desc contains one of more occurrence of string * following "@NAMEOFVALUE" pattern where NAMEOFVALUE is a string made of * only upper case letter and hyphens or underscore and of length less than * 16 characters. If such pattern can be found NAMEOFVALUE will be used for * synopsis and all occurrence of "@NAMEOFVALUE" will be replaced by * "NAMEOFVALUE" in description. */ struct mm_arg_opt { const char* name; int flags; const char* defval; union { const char** sptr; int* iptr; long long* llptr; unsigned int* uiptr; unsigned long long* ullptr; } val; const char* desc; }; #define MM_ARGPARSE_ERROR -1 #define MM_ARGPARSE_STOP -2 #define MM_ARGPARSE_COMPLETE -3 #define MM_ARG_OPT_COMPLETION (1 << 0) /** * typedef mm_arg_callback() - prototype of argument parser callback * @opt: pointer to matching option * @value: value set for option. The field of the union to use is * determined with mm_arg_opt_get_type(@opt). * @data: user pointer provided for hold state while running parser * @state: flags indicating the state of option parsing. * * The flags in @state indicates special calling context. It can be a * combination of the following flags : * * %MM_ARG_OPT_COMPLETION: the option value is being completed. The value * passed is then always a string, no matter is @opt->value. Also its * content may be incomplete. If the callback want to provide completion * candidates, it has to write them on standard output, each candidate on * its own line. If it returns MM_ARGPARSE_COMPLETE, no further completion * proposal will be added. This flag cannot appear if the flag * %MM_ARG_PARSER_COMPLETION has not been set in argument parser. * * Return: 0 in case of success, MM_ARGPARSE_ERROR (-1) if an error has been * detected, MM_ARGPARSE_STOP (-2) if early exit is requested like with help * display or MM_ARGPARSE_COMPLETE (-3) if value has been completed. */ typedef int (* mm_arg_callback)(const struct mm_arg_opt* opt, union mm_arg_val value, void* data, int state); /** * typedef mm_arg_complete_path_cb() - prototype of path completion callback * @name: basename of path candidate * @dir: directory of path candidate. * @type: file type of path candidate (one of the MM_DT_* definition) * @data: user pointer provided when calling path completion * * Return: 1 if path candidate must be kept, 0 if it must be discarded. */ typedef int (* mm_arg_complete_path_cb)(const char* name, const char* dir, int type, void* data); #define MM_ARG_PARSER_NOEXIT (1 << 0) #define MM_ARG_PARSER_COMPLETION (1 << 1) /** * struct mm_arg_parser - argument parser configuration * @flags: flags to change behavior of parsing (MM_ARG_PARSER_*) * @num_opt: number of element in @optv. * @optv: array of option supported. Please note that @optv does not * need to provide an option "h|help" since argument parser add * such option automatically which will trigger usage printing. * You may however provide such option in @optv array to * override the behaviour when this option is encountered. * @args_doc: lines of synopsis of program usage (excluding program name). * This can support multiple line (like for different case of * invocation). Can be NULL. * @doc: document of the program. Can be NULL * @execname: name of executable. You are invited to set it to argv[0]. If * NULL, "PROGRAM" will be used instead for synopsis * @cb_data: user provided data callback * @cb: callback called whenever an option is recognised and parsed. * This callback is optional and can be set to NULL if * unneeded. */ struct mm_arg_parser { int flags; int num_opt; const struct mm_arg_opt* optv; const char* doc; const char* args_doc; const char* execname; void* cb_data; mm_arg_callback cb; }; MMLIB_API int mm_arg_parse(const struct mm_arg_parser* parser, int argc, char* argv[]); MMLIB_API int mm_arg_optv_parse(int optn, const struct mm_arg_opt* optv, int argc, char* argv[]); MMLIB_API int mm_arg_parse_complete(const struct mm_arg_parser* parser, const char* arg); MMLIB_API int mm_arg_complete_path(const char* arg, int type_mask, mm_arg_complete_path_cb cb, void* cb_data); MMLIB_API int mm_arg_is_completing(void); /** * mm_arg_opt_get_key() - get short key of an option * @opt: option whose key must be returned * * Return: short key code used to recognised @opt if used, 0 if @opt cannot * be matched by short key. */ static inline int mm_arg_opt_get_key(const struct mm_arg_opt* opt) { char sep = opt->name[1]; return (sep == '|' || sep == '\0') ? opt->name[0] : 0; } /** * mm_arg_opt_get_name() - get long option name * @opt: option whose long name must be returned * * Return: the long name of opt if supported, NULL otherwise. */ static inline const char* mm_arg_opt_get_name(const struct mm_arg_opt* opt) { int offset; char sep = opt->name[1]; // Check the option is not identified solely with short key if (sep == '\0') return NULL; // The long name start depends whether the option can be identified // by short key or not offset = (sep == '|') ? 2 : 0; return opt->name + offset; } /** * mm_arg_opt_get_type() - get type of value supported by an option * @opt: option whose value type must be returned * * Return: MM_OPT_STR, MM_OPT_INT, MM_OPT_UINT, MM_OPT_LLONG or MM_OPT_ULLONG */ static inline int mm_arg_opt_get_type(const struct mm_arg_opt* opt) { return opt->flags & MM_OPT_TYPEMASK; } #ifdef __cplusplus } #endif #endif /* ifndef MMARGPARSE_H */ mmlib-1.4.2/src/mmdlfcn.h000066400000000000000000000010501435717460000152010ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMDLFCN_H #define MMDLFCN_H #include "mmpredefs.h" #ifdef __cplusplus extern "C" { #endif typedef struct mm_dynlib mm_dynlib_t; #define MM_LD_LAZY (1 << 0) #define MM_LD_NOW (1 << 1) #define MM_LD_APPEND_EXT (1 << 2) MMLIB_API mm_dynlib_t* mm_dlopen(const char* path, int flags); MMLIB_API void mm_dlclose(mm_dynlib_t* handle); MMLIB_API void* mm_dlsym(mm_dynlib_t* handle, const char* symbol); MMLIB_API const char* mm_dl_fileext(void); #ifdef __cplusplus } #endif #endif /* ifndef MMDLFCN_H */ mmlib-1.4.2/src/mmerrno.h000066400000000000000000000202561435717460000152510ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMERRNO_H #define MMERRNO_H #include #include #include #include #include "mmpredefs.h" /** * DOC: error codes * * The error code should be seen as an error class that describes roughly * the type of error encountered. Depending on their context, some layer can * handle a specific error reported by their callee (maybe raised in much * lower layer). It will thus use the code to filter the error it handle and * propagate to the upper layers the others. * * mmlib defines a number of error in addition to the usual one * defined in errno.h by the system to address the case not covered by the * system error. In the following table, you will find the list of typical * error that you will most likely use (or receive) * * EINVAL: * Invalid argument. This is used to indicate various kinds of problems * with passing the wrong argument to a library function. * * ENOSYS: * Function not implemented. This indicates that the function called is not * implemented at all, either in the C library, in the operating * system or in a library (typically you try to access a set of * functionality that are disabled by a compilation flag). When you get * this error, you can be sure that this particular function will always * fail with ENOSYS unless you install a new version of the operating * system. * * ENOTSUP: * Not supported. A function returns this error when certain parameter * values are valid, but the functionality they request is not available. * This can mean that the function does not implement a particular command * or option value or flag bit at all. For functions that operate on some * object given in a parameter, such as a file descriptor or a port, it * might instead mean that only that specific object (file descriptor, * port, etc.) is unable to support the other parameters given; different * file descriptors might support different ranges of parameter values. If * the entire function is not available at all in the implementation, it * returns ENOSYS instead. * * ETIMEDOUT: * An operation with a specified timeout received no response during the * timeout period. * * EPIPE: * Broken pipe; there is no process reading from the other end of a pipe. * Every library function that returns this error code also generates a * SIGPIPE signal; this signal terminates the program if not handled or * blocked. Thus, your program will never actually see EPIPE unless it has * handled or blocked SIGPIPE. * * ENOENT: * No such file or directory. This is a “file doesn’t exist” error for * ordinary files that are referenced in contexts where they are expected * to already exist. * * EACCES: * Permission denied; the file permissions do not allow the attempted * operation. * * ERANGE: * Insufficient storage was supplied to contain the data. * * EBUSY: * Resource busy; a resource that can’t be shared is already in use. * * EIO: * Input/output error; usually used for physical read or write errors. * * MM_EDISCONNECTED: * Hardware is disconnected or turned off * * MM_ENOTFOUND: * An entity that are requested cannot be found. * * MM_EBADFMT: * The format of a file or data is not the one expected. * * MM_NONAME: * The specified hostname cannot be resolved */ #define MM_EDISCONNECTED 1000 #define MM_EWRONGSTATE 1003 #define MM_ENOTFOUND 1005 #define MM_EBADFMT 1006 #define MM_ENONAME 1010 // Surprisingly some compilers targeting windows fail to define // ENOTRECOVERABLE error code while they define EOWNERDEAD #ifdef _WIN32 # ifndef ENOTRECOVERABLE # define ENOTRECOVERABLE 127 # endif # ifndef ENOMSG # define ENOMSG 122 # endif #endif #define MM_ERROR_IGNORE 0x01 #define MM_ERROR_NOLOG 0x02 #define MM_ERROR_ALL_ALTERNATE 0xffffffff #define MM_ERROR_SET 0xffffffff #define MM_ERROR_UNSET 0x00000000 struct mm_error_state { char data[1024]; }; #ifdef __cplusplus extern "C" { #endif MMLIB_API const char* mm_strerror(int errnum); MMLIB_API int mm_strerror_r(int errnum, char * buf, size_t buflen); /** * mm_raise_error() - set and log an error * @errnum: error class number * * mm_raise_error() takes an extensible number of arguments (printf-like) * and usually receives one string describing the issue. * * Set the state of an error in the running thread. If @errnum is 0, the * thread error state will kept untouched. * * Although this can be used like a function, this is a macro which will * enrich the error state with the origin of the error (module, function, * source code file and line number). * * This must be called the closest place where the error is detected. Ie, if * you call a function that sets an error, you should not raise an error * yourself, because your callee has failed and done so already, the only * thing that might be done in this case would be adding a log line (if * necessary). On the other side, if you call function from third party, * the error state will of course not be set, it will then be your * responsibility once you detect the third-party call has failed to set * the error state using mm_raise_error(). * * Return: always -1. */ #define mm_raise_error(errnum, ...) \ mm_raise_error_full(errnum, MM_LOG_MODULE_NAME, __func__, __FILE__, \ __LINE__, NULL, __VA_ARGS__) #define mm_raise_from_errno(...) \ mm_raise_from_errno_full(MM_LOG_MODULE_NAME, __func__, __FILE__, \ __LINE__, NULL, __VA_ARGS__) /** * mm_raise_error_with_extid() - set and log an error with an extended error id * @errnum: error class number * @extid: extended error id (identifier of a specific error case) * * Same as with mm_raise_error(), this accepts an extensible number of * arguments. * * Same as mm_raise_error() with an extended error id set to @extid. If @extid * is NULL, the effect is exactly the same as calling mm_raise_error(). * * An extended error id is a string identifier that is meant to inform the * layer that interact with the enduser (like therapist) about the reason of * the error. With this identifier, the UI layer can display a error * message that makes sense to the end user. Example, the cameralink camera can * detect a HW problem due to ESD. When this happens, the acquisition driver * will set an hardware error class (like MM_ECAMERROR) with an extid set to * "clcam-esd-detected". The UI of the final product will recognise the * extended identifier and display to the appropriate message (with the * right language) that make sense in the context of the usage of the * product and maybe what the enduser has to do. * * Return: always -1. */ #define mm_raise_error_with_extid(errnum, extid, ...) \ mm_raise_error_full(errnum, MM_LOG_MODULE_NAME, __func__, __FILE__, \ __LINE__, extid, __VA_ARGS__) MMLIB_API int mm_raise_error_full(int errnum, const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc, ...); MMLIB_API int mm_raise_error_vfull(int errnum, const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc, va_list args); MMLIB_API int mm_raise_from_errno_full(const char* module, const char* func, const char* srcfile, int srcline, const char* extid, const char* desc_fmt, ...); MMLIB_API int mm_error_set_flags(int flags, int mask); MMLIB_API int mm_save_errorstate(struct mm_error_state* state); MMLIB_API int mm_set_errorstate(const struct mm_error_state* state); MMLIB_API void mm_print_lasterror(const char* info, ...); MMLIB_API int mm_get_lasterror_number(void); MMLIB_API const char* mm_get_lasterror_desc(void); MMLIB_API const char* mm_get_lasterror_location(void); MMLIB_API const char* mm_get_lasterror_extid(void); MMLIB_API const char* mm_get_lasterror_module(void); #ifdef __cplusplus } #endif #endif /* MMERRNO */ mmlib-1.4.2/src/mmlib.h000066400000000000000000000145231435717460000146720ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMLIB_H #define MMLIB_H #include #include #include "mmpredefs.h" // declare alloca() macro #ifdef _WIN32 # include # ifndef alloca # define alloca _alloca # endif #else # include #endif #ifdef __cplusplus extern "C" { #endif /** * enum mm_known_dir - identifier of base folder * @MM_HOME: home folder of user * @MM_DATA_HOME: user-specific data files * @MM_CONFIG_HOME: user-specific configuration files * @MM_CACHE_HOME: user-specific non-essential (cached) data * @MM_RUNTIME_DIR: where user-specific runtime files and other * objects should be placed. * @MM_NUM_DIRTYPE: do not use (for internals of mmlib only) */ enum mm_known_dir { MM_HOME, MM_DATA_HOME, MM_CONFIG_HOME, MM_CACHE_HOME, MM_RUNTIME_DIR, MM_NUM_DIRTYPE, }; /** * enum mm_env_action - used for setenv action argument * @MM_ENV_PRESERVE: preserve environment if set * @MM_ENV_OVERWRITE: overwrite environment * @MM_ENV_PREPEND: prepend to environment * @MM_ENV_APPEND: append to environment * * @MM_ENV_MAX: internal */ enum mm_env_action { MM_ENV_PRESERVE = 0, MM_ENV_OVERWRITE = 1, MM_ENV_PREPEND = 2, MM_ENV_APPEND = 3, MM_ENV_MAX }; MMLIB_API const char* mm_getenv(const char* name, const char* default_value); MMLIB_API int mm_setenv(const char* name, const char* value, int action); MMLIB_API int mm_unsetenv(const char* name); MMLIB_API char const* const* mm_get_environ(void); MMLIB_API const char* mm_get_basedir(enum mm_known_dir dirtype); MMLIB_API char* mm_path_from_basedir(enum mm_known_dir dirtype, const char* suffix); MMLIB_API int mm_basename(char* dst, const char* path); MMLIB_API int mm_dirname(char* dst, const char* path); MMLIB_API void* mm_aligned_alloc(size_t alignment, size_t size); MMLIB_API void mm_aligned_free(void* ptr); /************************************************************************* * * * stack allocation * * * *************************************************************************/ #ifdef __BIGGEST_ALIGNMENT__ # define MM_STK_ALIGN __BIGGEST_ALIGNMENT__ #else # define MM_STK_ALIGN 16 #endif #define MM_STACK_ALLOC_THRESHOLD 2048 // Do not use those function directly. There are meant ONLY for use in // mm_malloca() and mm_freea() MMLIB_API void* _mm_malloca_on_heap(size_t size); MMLIB_API void _mm_freea_on_heap(void* ptr); /** * mm_aligned_alloca() - allocates memory on the stack with alignment * @alignment: alignment, must be a power of two * @size: size of memory to be allocated * * This macro allocates @size bytes from the stack and the returned pointer * is ensured to be aligned on @alignment boundaries (if @alignment is a * power of two). If size is 0, mm_aligned_alloca() allocates a zero-length * item and returns a unique pointer to that item. * * Please note that more than @size byte are consumed from the stack (even in * case of if @size is 0). This is due to the overhead necessary for having * an aligned allocated memory block. * * The lifetime of the allocated object ends just before the calling * function returns to its caller. This is so even when mm_aligned_alloca() * is called within a nested block. * * WARNING: If is NOT safe to try allocate more than a page size on the * stack. In general it is even recommended to limit allocation under half * of a page. Ignoring this put the program under the thread of more than * simply a stack overflow: there will be a risk that the stack overflow * will not be detected and the execution to continue while corrupting both * heap and stack... For safe stack allocation, use mm_malloca()/mm_freea(). * * Return: the pointer to the allocated space in case of success. Otherwise, * ie, if @alignment is not a power of two, NULL is returned. */ #define mm_aligned_alloca(alignment, size) \ ( (alignment) & (alignment-1) \ ? NULL \ : (void*)( ((uintptr_t)alloca((size)+(alignment)-1) + (alignment)-1) \ & ~(uintptr_t)((alignment)-1))) /** * mm_malloca() - safely allocates memory on the stack * @size: size of memory to be allocated * * This macro allocates @size bytes from the stack if not too big (lower or * equal to MM_STACK_ALLOC_THRESHOLD) or on the heap. The returned pointer * is ensured to be aligned on a boundary suitable for any data type. If * @size is 0, mm_malloca() allocates a zero-length item and returns a valid * pointer to that item. * * Use this macro as a safer replacement of alloca() or Variable Length * Array (VLA). * * Return: pointer to the allocated space in case success, NULL otherwise. * The allocation might fail if the requested size is larger that the system * memory (or the OS do not overcommit and is running out of memory). In * case of successful allocation, the returned pointer must be passed to * mm_freea() before calling function returns to its caller. */ #define mm_malloca(size) \ ( (size) > MM_STACK_ALLOC_THRESHOLD \ ? _mm_malloca_on_heap(size) \ : mm_aligned_alloca(2*MM_STK_ALIGN, (size))) /** * mm_freea() - free memory allocated with mm_malloca() * @ptr: memory object allocated by mm_malloca() * * This function free memory allocated by mm_malloca(). Please note that * when this function returns, the memory might not be reusable yet. If @ptr * has been allocated on stack, the memory will be reclaimed (hence * reusable) only when the function that has called mm_malloca() for * allocating @ptr will return to its caller. */ static inline void mm_freea(void* ptr) { // The type of allocation is recognised from the alignment of the // @ptr address. If @ptr is: // - NULL: the allocation failed // - 0 mod (2*MM_STK_ALIGN): allocated on stack // - MM_STK_ALIGN mod (2*MM_STK_ALIGN): allocated on heap if ((uintptr_t)ptr & (2*MM_STK_ALIGN-1)) _mm_freea_on_heap(ptr); } /* * On windows, strcasecmp() is not defined, but _stricmp() is and performs the * exact same function. Since strcasecmp() is standard C and _stricmp() isn't, * choose to define based on strcasecmp's name. */ #ifdef _WIN32 #define mm_strcasecmp _stricmp #else #define mm_strcasecmp strcasecmp #endif #ifdef __cplusplus } #endif #endif /* ifndef MMLIB_H */ mmlib-1.4.2/src/mmlog.h000066400000000000000000000036471435717460000147120ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMLOG_H #define MMLOG_H #include "mmpredefs.h" #include #include #define MM_LOG_NONE -1 #define MM_LOG_FATAL 0 #define MM_LOG_ERROR 1 #define MM_LOG_WARN 2 #define MM_LOG_INFO 3 #define MM_LOG_DEBUG 4 #ifndef MM_LOG_MAXLEVEL # define MM_LOG_MAXLEVEL MM_LOG_DEBUG #endif #if defined __cplusplus #define MM_LOG_VOID_CAST static_cast < void > #else #define MM_LOG_VOID_CAST (void) #endif #if MM_LOG_MAXLEVEL >= MM_LOG_FATAL #define mm_log_fatal(...) \ mm_log(MM_LOG_FATAL, MM_LOG_MODULE_NAME, __VA_ARGS__) #else #define mm_log_fatal(...) MM_LOG_VOID_CAST(0) #endif #if MM_LOG_MAXLEVEL >= MM_LOG_ERROR #define mm_log_error(...) \ mm_log(MM_LOG_ERROR, MM_LOG_MODULE_NAME, __VA_ARGS__) #else #define mm_log_error(...) MM_LOG_VOID_CAST(0) #endif #if MM_LOG_MAXLEVEL >= MM_LOG_WARN #define mm_log_warn(...) \ mm_log(MM_LOG_WARN, MM_LOG_MODULE_NAME, __VA_ARGS__) #else #define mm_log_warn(...) MM_LOG_VOID_CAST(0) #endif #if MM_LOG_MAXLEVEL >= MM_LOG_INFO #define mm_log_info(...) \ mm_log(MM_LOG_INFO, MM_LOG_MODULE_NAME, __VA_ARGS__) #else #define mm_log_info(...) MM_LOG_VOID_CAST(0) #endif #if MM_LOG_MAXLEVEL >= MM_LOG_DEBUG #define mm_log_debug(...) \ mm_log(MM_LOG_DEBUG, MM_LOG_MODULE_NAME, __VA_ARGS__) #else #define mm_log_debug(...) MM_LOG_VOID_CAST(0) #endif #define mm_crash(...) \ do { \ char msg[256]; /* size of max length of log line */ \ snprintf(msg, sizeof(msg), __VA_ARGS__); \ mm_log(MM_LOG_FATAL, MM_LOG_MODULE_NAME, \ "%s (%s() in %s:%i)", \ msg, __func__, __FILE__, __LINE__); \ abort(); \ } while (0) #define mm_check(expr) \ do { \ if (UNLIKELY(!(expr))) { \ mm_crash("mm_check(" #expr ") failed. "); \ } \ } while (0) #ifdef __cplusplus extern "C" { #endif MMLIB_API void mm_log(int lvl, const char* location, const char* msg, ...); MMLIB_API int mm_log_set_maxlvl(int lvl); #ifdef __cplusplus } #endif #endif /*MMLOG_H*/ mmlib-1.4.2/src/mmpredefs.h000066400000000000000000000103651435717460000155540ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef MMPREDEFS_H #define MMPREDEFS_H /* Attributes of imported symbols from mmlib */ #ifndef MMLIB_API # ifdef _WIN32 # define MMLIB_API __declspec(dllimport) # else # define MMLIB_API # endif #endif /* Double expansion is the usual trick to expand a preprocessor macro argument into a string */ #define MM_XSTRINGIFY(arg) #arg #define MM_STRINGIFY(arg) MM_XSTRINGIFY(arg) /* Setup the module name (MM_LOG_MODULE_NAME) as it appears in the log. If it is unset, it set it to a reasonable default. It is allowed to reset MM_LOG_MODULE_NAME in the source code: this will change the module name used for the next invocation of any mm_log_* macro. */ #ifndef MM_LOG_MODULE_NAME # ifdef PACKAGE_NAME # define MM_LOG_MODULE_NAME PACKAGE_NAME # else # define MM_LOG_MODULE_NAME "unknown" # endif #endif /* Macros MM_CONSTRUCTOR() / MM_DESTRUCTOR() to declare constructors and destructors. Those function are typically called before and after main in case of normal executable, or at time of load/unload in case of dynamic module (ie in the case of dlopen()/LoadLibrary()) */ #if defined(__GNUC__) # define MM_CONSTRUCTOR(name) static void __attribute__((constructor)) name ## _ctor(void) # define MM_DESTRUCTOR(name) static void __attribute__((destructor)) name ## _dtor(void) #elif defined(_MSC_VER) #include # define MM_CONSTRUCTOR(name) \ static void name ## _ctor(void); \ static int name ## _ctor_wrapper(void) { name ## _ctor(); return 0; } \ __pragma(section(".CRT$XCU",read)) \ __declspec(allocate(".CRT$XCU")) static int (* array_ctor_ ## name)(void) = name ## _ctor_wrapper; \ static void name ## _ctor(void) # define MM_DESTRUCTOR(name) \ static void name ## _dtor(void); \ static int name ## _reg_dtor_wrapper(void) { atexit(name ## _dtor); return 0; } \ __pragma(section(".CRT$XCU",read)) \ __declspec(allocate(".CRT$XCU")) static int (* array_dtor_ ## name)(void) = name ## _reg_dtor_wrapper; \ static void name ## _dtor(void) #else # warning MM_CONSTRUCTOR() cannot be defined #endif /* Define LIKELY() and UNLIKELY() macros to help compiler to optimize the right conditional branch. DO NOT ABUSE OF THEM. If you use it, you need to be sure that is the correct branching to optimize. In doubt, let the compiler do its guess. */ #if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) # ifndef LIKELY # define LIKELY(x) __builtin_expect(!!(x), 1) # endif # ifndef UNLIKELY # define UNLIKELY(x) __builtin_expect(!!(x), 0) # endif #else // most likely msvc # ifndef LIKELY # define LIKELY(x) (x) # endif # ifndef UNLIKELY # define UNLIKELY(x) (x) # endif #endif /* Define NOINLINE attribute */ #ifndef NOINLINE # if defined(__GNUC__) # define NOINLINE __attribute__ ((noinline)) # elif defined (_MSC_VER) # define NOINLINE __declspec(noinline) # else # define NOINLINE # endif #endif /* * Define nonnull attribute for function parameters */ /* for all attributes, or as parameter attribute with clang */ #ifndef NONNULL # if defined(__GNUC__) # define NONNULL __attribute__((nonnull)) # else # define NONNULL # endif #endif /* for specifying a single attribute. function attribute only */ #ifndef NONNULL_ARGS # if defined(__GNUC__) # define NONNULL_ARGS(...) __attribute__((nonnull(__VA_ARGS__))) # else # define NONNULL_ARGS # endif #endif /* * Define macro to deprecate symbol * Usage: * // Use of struct old_data is warned * struct MM_DEPRECATED old_data { * ... * }; * * // call to old_func() is warned * MM_DEPRECATED int old_func(int a, void* b); * * // use of old_variable is warned * MM_DEPRECATED int old_variable; */ #if defined(__GNUC__) # define MM_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) # define MM_DEPRECATED __declspec(deprecated) #else # define MM_DEPRECATED #endif /* Macros to get the number of element in a C array. */ #define MM_NELEM(arr) ((int)(sizeof(arr)/sizeof(arr[0]))) /* * Return true if ival is a power of 2 OR null */ #define MM_IS_POW2(ival) (!((ival) & ((ival)-1))) /* * Macros to get the page size * Overload by compiling with -DMM_PAGESZ or by defining it before. */ #ifndef MM_PAGESZ # define MM_PAGESZ 0x1000 #endif #endif mmlib-1.4.2/src/mmprofile.h000066400000000000000000000015731435717460000155650ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMPROFILE_H #define MMPROFILE_H #include "mmpredefs.h" #define PROF_CURR 0x01 #define PROF_MIN 0x02 #define PROF_MAX 0x04 #define PROF_MEAN 0x08 #define PROF_MEDIAN 0x10 #define PROF_DEFAULT (PROF_MIN|PROF_MAX|PROF_MEAN|PROF_MEDIAN) #define PROF_FORCE_NSEC 0x100 #define PROF_FORCE_USEC 0x200 #define PROF_FORCE_MSEC 0x300 #define PROF_FORCE_SEC 0x400 #define PROF_RESET_CPUCLOCK 0x01 #define PROF_RESET_KEEPLABEL 0x02 #include #ifdef __cplusplus extern "C" { #endif MMLIB_API void mm_tic(void); MMLIB_API void mm_toc(void); MMLIB_API void mm_toc_label(const char* label); MMLIB_API int mm_profile_print(int mask, int fd); MMLIB_API void mm_profile_reset(int reset_flags); MMLIB_API int64_t mm_profile_get_data(int measure_point, int type); #ifdef __cplusplus } #endif #endif /* ifndef MMPROFILE_H */ mmlib-1.4.2/src/mmsysio.h000066400000000000000000000367301435717460000152760ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMSYSIO_H #define MMSYSIO_H #include #include #include #include #include #ifdef _WIN32 /* * WARNING * Windows enforces include of after * However, what gets included when you do the actual include of * depends on some macros of your project (eg. WIN32_LEAN_AND_MEAN), so * mmsysio.h cannot do this for you. * Should you stumble on warning such as this one: * #warning Please include winsock2.h before windows.h * The easiest way around is probably for you to re-order your includes. * Other platforms NEVER enforce include order, so this will not have an * impact there. * * More info here: * https://docs.microsoft.com/en-us/windows/win32/winsock/creating-a-basic-winsock-application */ #include #include #include #include #else #include #include #include #include #include #include #include #endif /* ifdef _WIN32 */ #include "mmpredefs.h" #include "mmtime.h" #ifdef _WIN32 /** * struct iovec - structure for scatter/gather I/O. * @iov_base: Base address of a memory region for input or output * @iov_len: The size of the memory pointed to by @iov_base * * Note: on win32 this is guaranteed to alias to WSABUF */ struct iovec { unsigned long iov_len; void* iov_base; }; // Not defined on windows platform, so we can keep the standard name // without mm_ prefix typedef unsigned long long uid_t; typedef unsigned long long gid_t; #ifdef _MSC_VER # ifndef _SSIZE_T_DEFINED # define _SSIZE_T_DEFINED # undef ssize_t # ifdef _WIN64 typedef __int64 ssize_t; # else typedef int ssize_t; # endif /* _WIN64 */ # endif /* _SSIZE_T_DEFINED */ #endif typedef long long mm_off_t; typedef unsigned long long mm_dev_t; typedef struct { unsigned long long id_low; unsigned long long id_high; } mm_ino_t; #else /* _WIN32 */ typedef off_t mm_off_t; typedef dev_t mm_dev_t; typedef ino_t mm_ino_t; #endif /* _WIN32 */ /** * mm_ino_equal() - test equality between two mm_ino_t * @a: first mm_ino_t operand * @b: second mm_ino_t operand * * Return: 1 if equal, 0 otherwise */ static inline int mm_ino_equal(mm_ino_t a, mm_ino_t b) { #ifdef _WIN32 return ((a.id_low == b.id_low) && (a.id_high == b.id_high)); #else return (a == b); #endif } #ifdef __cplusplus extern "C" { #endif /************************************************************************** * File manipulation * **************************************************************************/ #ifdef _WIN32 #ifndef O_RDONLY #define O_RDONLY _O_RDONLY #endif #ifndef O_WRONLY #define O_WRONLY _O_WRONLY #endif #ifndef O_RDWR #define O_RDWR _O_RDWR #endif #ifndef O_APPEND #define O_APPEND _O_APPEND #endif #ifndef O_CREAT #define O_CREAT _O_CREAT #endif #ifndef O_TRUNC #define O_TRUNC _O_TRUNC #endif #ifndef O_EXCL #define O_EXCL _O_EXCL #endif #ifndef S_IFMT #define S_IFMT _S_IFMT #endif #ifndef S_IFDIR #define S_IFDIR _S_IFDIR #endif #ifndef S_IFREG #define S_IFREG _S_IFREG #endif #ifndef S_IFLNK #define S_IFLNK (_S_IFREG|_S_IFCHR) #endif #define S_ISTYPE(mode, mask) (((mode) & S_IFMT) == (mask)) #ifndef S_ISDIR #define S_ISDIR(mode) S_ISTYPE((mode), S_IFDIR) #endif #ifndef S_ISREG #define S_ISREG(mode) S_ISTYPE((mode), S_IFREG) #endif #ifndef S_ISLNK #define S_ISLNK(mode) S_ISTYPE((mode), S_IFLNK) #endif #ifndef S_IRUSR #define S_IRUSR _S_IREAD #endif #ifndef S_IWUSR #define S_IWUSR _S_IWRITE #endif #ifndef S_IXUSR #define S_IXUSR _S_IEXEC #endif #ifndef S_IRWXU #define S_IRWXU (S_IRUSR|S_IWUSR|S_IXUSR) #endif #ifndef F_OK #define F_OK 0x00 #endif #ifndef X_OK #define X_OK 0x01 #endif #ifndef W_OK #define W_OK 0x02 #endif #ifndef R_OK #define R_OK 0x04 #endif #define UTIME_NOW (-1L) #define UTIME_OMIT (-2L) #endif /* _WIN32 */ /* file types returned when scanning a directory */ #define MM_DT_UNKNOWN 0 #define MM_DT_FIFO (1 << 1) #define MM_DT_CHR (1 << 2) #define MM_DT_BLK (1 << 3) #define MM_DT_DIR (1 << 4) #define MM_DT_REG (1 << 5) #define MM_DT_LNK (1 << 6) #define MM_DT_SOCK (1 << 7) #define MM_DT_ANY (0XFF) #define MM_RECURSIVE (1 << 31) #define MM_FAILONERROR (1 << 30) #define MM_NOFOLLOW (1 << 29) #define MM_NOCOW (1 << 28) #define MM_FORCECOW (1 << 27) /** * struct mm_stat - file status data * @dev: Device ID of device containing file * @ino: File serial number * @mode: Mode of file (Indicate file type and permission) * @nlink: Number of hard links to the file * @uid: Currently unused * @gid: Currently unused * @size: For regular files, the file size in bytes. * For symbolic links, the length in bytes of the UTF-8 * pathname contained in the symbolic link (including null * termination). * @atime: time of last access * @nblocks: Currently unused * @mtime: time of last modification * @ctime: time of last status change */ struct mm_stat { mm_dev_t dev; mm_ino_t ino; mode_t mode; int nlink; uid_t uid; gid_t gid; mm_off_t size; time_t atime; size_t nblocks; time_t mtime; time_t ctime; }; MMLIB_API int mm_open(const char* path, int oflag, int mode); MMLIB_API int mm_rename(const char* oldpath, const char * newpath); MMLIB_API int mm_close(int fd); MMLIB_API int mm_fsync(int fd); MMLIB_API ssize_t mm_read(int fd, void* buf, size_t nbyte); MMLIB_API ssize_t mm_write(int fd, const void* buf, size_t nbyte); MMLIB_API mm_off_t mm_seek(int fd, mm_off_t offset, int whence); MMLIB_API int mm_ftruncate(int fd, mm_off_t length); MMLIB_API int mm_fstat(int fd, struct mm_stat* buf); MMLIB_API int mm_stat(const char* path, struct mm_stat* buf, int flags); MMLIB_API int mm_futimens(int fd, const struct mm_timespec ts[2]); MMLIB_API int mm_utimens(const char* path, const struct mm_timespec ts[2], int flags); MMLIB_API int mm_check_access(const char* path, int amode); MMLIB_API int mm_isatty(int fd); MMLIB_API int mm_dup(int fd); MMLIB_API int mm_dup2(int fd, int newfd); MMLIB_API int mm_pipe(int pipefd[2]); MMLIB_API int mm_unlink(const char* path); MMLIB_API int mm_link(const char* oldpath, const char* newpath); MMLIB_API int mm_symlink(const char* oldpath, const char* newpath); MMLIB_API int mm_readlink(const char* path, char* buf, size_t bufsize); MMLIB_API int mm_copy(const char* src, const char* dst, int flags, int mode); MMLIB_API int mm_mkdir(const char* path, int mode, int flags); MMLIB_API int mm_chdir(const char* path); MMLIB_API char* mm_getcwd(char* buffer, size_t size); MMLIB_API int mm_rmdir(const char* path); MMLIB_API int mm_remove(const char* path, int flags); /************************************************************************** * Directory navigation * **************************************************************************/ typedef struct mm_dirstream MM_DIR; struct mm_dirent { size_t reclen; /* this record length */ int type; /* file type (see above) */ int id; /* reserved for later use */ #ifndef __cplusplus char name[]; /* Null-terminated filename (C flexible array)*/ #else char name[1]; /* Null-terminated filename (C++ compatibility)*/ #endif }; MMLIB_API MM_DIR* mm_opendir(const char* path); MMLIB_API void mm_closedir(MM_DIR* dir); MMLIB_API void mm_rewinddir(MM_DIR* dir); MMLIB_API const struct mm_dirent* mm_readdir(MM_DIR* dir, int * status); /************************************************************************** * Process spawning * **************************************************************************/ #ifdef _WIN32 typedef DWORD mm_pid_t; #else typedef pid_t mm_pid_t; #endif /** * struct mm_remap_fd - file descriptor mapping for child creation * @child_fd: file descriptor in the child * @parent_fd: file descriptor in the parent process to @child_fd must be * mapped. If @child_fd must be specifically closed in the * child, @parent_fd can be set to -1; * * Use in combination of mm_spawn(), this structure is meant to be in an * array that define the file descriptor remapping in child. */ struct mm_remap_fd { int child_fd; int parent_fd; }; #define MM_SPAWN_DAEMONIZE 0x00000001 #define MM_SPAWN_KEEP_FDS 0x00000002 // Keep all inheritable fd in child MMLIB_API int mm_spawn(mm_pid_t* child_pid, const char* path, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp); MMLIB_API int mm_execv(const char* path, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp); #define MM_WSTATUS_CODEMASK 0x000000FF #define MM_WSTATUS_EXITED 0x00000100 #define MM_WSTATUS_SIGNALED 0x00000200 MMLIB_API int mm_wait_process(mm_pid_t pid, int* status); /************************************************************************** * memory mapping * **************************************************************************/ #define MM_MAP_READ 0x00000001 #define MM_MAP_WRITE 0x00000002 #define MM_MAP_EXEC 0x00000004 #define MM_MAP_SHARED 0x00000008 #define MM_MAP_RDWR (MM_MAP_READ | MM_MAP_WRITE) #define MM_MAP_PRIVATE 0x00000000 MMLIB_API void* mm_mapfile(int fd, mm_off_t offset, size_t len, int mflags); MMLIB_API int mm_unmap(void* addr); MMLIB_API int mm_shm_open(const char* name, int oflag, int mode); MMLIB_API int mm_anon_shm(void); MMLIB_API int mm_shm_unlink(const char* name); /************************************************************************** * Interprocess communication * **************************************************************************/ /** * struct mm_ipc_msg - structure for IPC message * @iov: scatter/gather array * @fds: array of file descriptor to pass/receive * @num_iov: number of element in @iov * @num_fds: number of file descriptors in @fds * @flags: flags on received message * @num_fds_max: maximum number of file descriptors in @fds * @reserved: reserved for future use (must be NULL) */ struct mm_ipc_msg { struct iovec* iov; int* fds; int num_iov; int num_fds; int flags; int num_fds_max; void* reserved; }; struct mm_ipc_srv; MMLIB_API struct mm_ipc_srv* mm_ipc_srv_create(const char* addr); MMLIB_API void mm_ipc_srv_destroy(struct mm_ipc_srv* srv); MMLIB_API int mm_ipc_srv_accept(struct mm_ipc_srv* srv); MMLIB_API int mm_ipc_connect(const char* addr); MMLIB_API int mm_ipc_connected_pair(int fds[2]); MMLIB_API ssize_t mm_ipc_sendmsg(int fd, const struct mm_ipc_msg* msg); MMLIB_API ssize_t mm_ipc_recvmsg(int fd, struct mm_ipc_msg* msg); /************************************************************************** * Network communication * **************************************************************************/ struct addrinfo; #if defined (_WIN32) /** * struct msghdr - structure for socket message * @msg_name: optional address * @msg_namelen: size of address * @msg_iov: scatter/gather array * @msg_iovlen: number of element in @msg_iov (beware of type, see NOTE) * @msg_control: Ancillary data * @msg_controllen: length of ancillary data * @msg_flags: flags on received message * * NOTE: * Although Posix mandates that @msg_iovlen is int, many platform do not * respect this: it is size_t on Linux, unsigned int on Darwin, int on * freebsd. It is safer to consider @msg_iovlen is defined as size_t in * struct (avoid one hole due to alignment on 64bits platform) and always * manipulate as int (forcing the cast). */ struct msghdr { void* msg_name; socklen_t msg_namelen; struct iovec* msg_iov; size_t msg_iovlen; void* msg_control; socklen_t msg_controllen; int msg_flags; }; # define SHUT_RD 0 # define SHUT_WR 1 # define SHUT_RDWR 2 // The following constants are defined on Windows platform from ws2tcpip.h // if _WIN32_WINNT is defined higher to 0x0600 (corresponding roughly to // windows vista release). It is way below the support threshold of mmlib. // We can then assume their availability. ws2tcpip.h is included at the top // of this file but we cannot expect _WIN32_WINNT to be defined in the // project using mmlib. Hence we define the constants to their right values // if they are not defined yet. # ifndef AI_NUMERICSERV # define AI_NUMERICSERV 0x00000008 # endif # ifndef AI_ALL # define AI_ALL 0x00000100 # endif # ifndef AI_ADDRCONFIG # define AI_ADDRCONFIG 0x00000400 # endif # ifndef AI_V4MAPPED # define AI_V4MAPPED 0x00000800 # endif #endif /* _WIN32 */ /** * struct mm_sock_multimsg - structure for transmitting multiple messages * @msg: message * @datalen: number of received byte for @msg * * This should alias with struct mm_sghdr on system supporting recvmmsg() */ struct mm_sock_multimsg { struct msghdr msg; unsigned int datalen; }; MMLIB_API int mm_socket(int domain, int type, int protocol); MMLIB_API int mm_bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen); MMLIB_API int mm_getsockname(int sockfd, struct sockaddr * addr, socklen_t * addrlen); MMLIB_API int mm_getpeername(int sockfd, struct sockaddr * addr, socklen_t * addrlen); MMLIB_API int mm_listen(int sockfd, int backlog); MMLIB_API int mm_accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); MMLIB_API int mm_connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); MMLIB_API int mm_setsockopt(int sockfd, int level, int optname, const void * optval, socklen_t optlen); MMLIB_API int mm_getsockopt(int sockfd, int level, int optname, void * optval, socklen_t* optlen); MMLIB_API int mm_shutdown(int sockfd, int how); MMLIB_API ssize_t mm_send(int sockfd, const void * buffer, size_t length, int flags); MMLIB_API ssize_t mm_recv(int sockfd, void * buffer, size_t length, int flags); MMLIB_API ssize_t mm_sendmsg(int sockfd, const struct msghdr* msg, int flags); MMLIB_API ssize_t mm_recvmsg(int sockfd, struct msghdr* msg, int flags); MMLIB_API int mm_send_multimsg(int sockfd, int vlen, struct mm_sock_multimsg * msgvec, int flags); MMLIB_API int mm_recv_multimsg(int sockfd, int vlen, struct mm_sock_multimsg * msgvec, int flags, struct mm_timespec * timeout); MMLIB_API int mm_getaddrinfo(const char * node, const char * service, const struct addrinfo * hints, struct addrinfo ** res); MMLIB_API int mm_getnameinfo(const struct sockaddr * addr, socklen_t addrlen, char * host, socklen_t hostlen, char * serv, socklen_t servlen, int flags); MMLIB_API void mm_freeaddrinfo(struct addrinfo * res); MMLIB_API int mm_create_sockclient(const char* uri); #if defined (_WIN32) struct mm_pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ }; #else #define mm_pollfd pollfd #endif MMLIB_API int mm_poll(struct mm_pollfd * fds, int nfds, int timeout_ms); #ifdef __cplusplus } #endif #endif /* ifndef MMSYSIO_H */ mmlib-1.4.2/src/mmthread.h000066400000000000000000000051331435717460000153700ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMTHREAD_H #define MMTHREAD_H #include #include "mmtime.h" #include "mmpredefs.h" #ifndef _WIN32 #include typedef pthread_mutex_t mm_thr_mutex_t; typedef pthread_cond_t mm_thr_cond_t; typedef pthread_once_t mm_thr_once_t; typedef pthread_t mm_thread_t; #define MM_THR_MTX_INITIALIZER PTHREAD_MUTEX_INITIALIZER /* deprecated */ #define MM_THR_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER #define MM_THR_COND_INITIALIZER PTHREAD_COND_INITIALIZER #define MM_THR_ONCE_INIT PTHREAD_ONCE_INIT #else // _WIN32 #include typedef union { struct mm_thr_mutex_pshared { int flag; int padding; int64_t lock; int64_t pshared_key; } pshared; struct mm_thr_mutex_swr { int flag; int padding; void * srw_lock; } srw; } mm_thr_mutex_t; typedef union { struct mm_thr_cond_pshared { int flag; int padding; int64_t pshared_key; int64_t waiter_seq; int64_t wakeup_seq; } pshared; struct mm_thr_cond_swr { int flag; int padding; void* cv; } srw; } mm_thr_cond_t; typedef int mm_thr_once_t; #define MM_THR_MTX_INITIALIZER {0} /* deprecated */ #define MM_THR_MUTEX_INITIALIZER {0} #define MM_THR_COND_INITIALIZER {0} #define MM_THR_ONCE_INIT 0 typedef struct mm_thread* mm_thread_t; #endif // !_WIN32 #define MM_THR_PSHARED 0x00000001 #define MM_THR_WAIT_MONOTONIC 0x00000002 #ifdef __cplusplus extern "C" { #endif MMLIB_API int mm_thr_mutex_init(mm_thr_mutex_t* mutex, int flags); MMLIB_API int mm_thr_mutex_lock(mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_mutex_trylock(mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_mutex_consistent(mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_mutex_unlock(mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_mutex_deinit(mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_cond_init(mm_thr_cond_t* cond, int flags); MMLIB_API int mm_thr_cond_wait(mm_thr_cond_t* cond, mm_thr_mutex_t* mutex); MMLIB_API int mm_thr_cond_timedwait(mm_thr_cond_t* cond, mm_thr_mutex_t* mutex, const struct mm_timespec* abstime); MMLIB_API int mm_thr_cond_signal(mm_thr_cond_t* cond); MMLIB_API int mm_thr_cond_broadcast(mm_thr_cond_t* cond); MMLIB_API int mm_thr_cond_deinit(mm_thr_cond_t* cond); MMLIB_API int mm_thr_once(mm_thr_once_t* once, void (* once_routine)(void)); MMLIB_API int mm_thr_create(mm_thread_t* thread, void* (*proc)(void*), void* arg); MMLIB_API int mm_thr_join(mm_thread_t thread, void** value_ptr); MMLIB_API int mm_thr_detach(mm_thread_t thread); MMLIB_API mm_thread_t mm_thr_self(void); #ifdef __cplusplus } #endif #endif /* ifndef MMTHREAD_H */ mmlib-1.4.2/src/mmtime.h000066400000000000000000000141511435717460000150570ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MMTIME_H #define MMTIME_H #include #include #include #include "mmpredefs.h" /** * DOC: clock types * * MM_CLK_REALTIME * system-wide clock measuring real time. This clock is affected by * discontinuous jumps in the system time (e.g., if the system * administrator manually changes the clock), and by the incremental * adjustments performed by NTP. * * MM_CLK_MONOTONIC * represents monotonic time since some unspecified starting point. This * clock is not affected by discontinuous jumps in the system time (e.g., * if the system administrator manually changes the clock), but it may by * affected by the incremental adjustments performed by NTP. In other * word, this clock may be sped up/slowed down by the kernel as necessary * to match real time through NTP to ensure that 1s with MM_CLK_MONOTONIC is * really 1s. * * MM_CLK_CPU_PROCESS * Per-process CPU-time clock (measures CPU time consumed by all threads * in the process). * * MM_CLK_CPU_THREAD * Thread-specific CPU-time clock. * * MM_CLK_MONOTONIC_RAW * Similar to MM_CLK_MONOTONIC, but provides access to a raw * hardware-based time that is not subject to NTP adjustments. On modern * CPU, this clock is often based on the cycle counter of the CPU (when a * reliable one is available) which makes it a good basis for code * profile. */ #ifndef _WIN32 # define MM_CLK_REALTIME CLOCK_REALTIME # define MM_CLK_MONOTONIC CLOCK_MONOTONIC # define MM_CLK_CPU_PROCESS CLOCK_PROCESS_CPUTIME_ID # define MM_CLK_CPU_THREAD CLOCK_THREAD_CPUTIME_ID # ifdef CLOCK_MONOTONIC_RAW # define MM_CLK_MONOTONIC_RAW CLOCK_MONOTONIC_RAW # else # define MM_CLK_MONOTONIC_RAW CLOCK_MONOTONIC # endif #else // WIN32 # define MM_CLK_REALTIME 0 # define MM_CLK_MONOTONIC 1 # define MM_CLK_CPU_PROCESS 2 # define MM_CLK_CPU_THREAD 3 # define MM_CLK_MONOTONIC_RAW 4 #endif /* ifndef _WIN32 */ #ifdef __cplusplus extern "C" { #endif #ifdef _MSC_VER typedef int clockid_t; #endif /** * struct mm_timespec - interval broken down into seconds and nanoseconds. * @tv_sec: whole seconds (valid values are >= 0) * @tv_nsec: nanoseconds (valid values are [0, 999999999]) * * This structure is meant to be binary compatible with the struct timespec * defined on the platform while avoid the issue of missing declaration of * struct timespec (if compiled with old C standard < C11 without posix * feature) or multiple definition of it (in certain circumstances, on Win32 * with mingw using pthread.h). */ struct mm_timespec { time_t tv_sec; long tv_nsec; }; MMLIB_API int mm_gettime(clockid_t clock_id, struct mm_timespec * ts); MMLIB_API int mm_getres(clockid_t clock_id, struct mm_timespec * res); MMLIB_API int mm_nanosleep(clockid_t clock_id, const struct mm_timespec * ts); MMLIB_API int mm_relative_sleep_ms(int64_t duration_ms); MMLIB_API int mm_relative_sleep_us(int64_t duration_us); MMLIB_API int mm_relative_sleep_ns(int64_t duration_ns); /************************************************************************** * Timespec manipulation helpers * **************************************************************************/ #ifndef NS_IN_SEC #define NS_IN_SEC 1000000000 #endif #ifndef US_IN_SEC #define US_IN_SEC 1000000 #endif #ifndef MS_IN_SEC #define MS_IN_SEC 1000 #endif /** * mm_timediff_ns() - compute time difference in nanoseconds * @ts: time point * @orig: time reference * * Return: the interval @ts - @orig in nanoseconds */ static inline int64_t mm_timediff_ns(const struct mm_timespec* ts, const struct mm_timespec* orig) { int64_t dt; dt = (ts->tv_sec - orig->tv_sec) * (int64_t)NS_IN_SEC; dt += ts->tv_nsec - orig->tv_nsec; return dt; } /** * mm_timediff_us() - compute time difference in microseconds * @ts: time point * @orig: time reference * * Return: the interval @ts - @orig in microseconds */ static inline int64_t mm_timediff_us(const struct mm_timespec* ts, const struct mm_timespec* orig) { int64_t dt; dt = (ts->tv_sec - orig->tv_sec) * (int64_t)US_IN_SEC; dt += (ts->tv_nsec - orig->tv_nsec) / (NS_IN_SEC/US_IN_SEC); return dt; } /** * mm_timediff_ms() - compute time difference in milliseconds * @ts: time point * @orig: time reference * * Return: the interval @ts - @orig in milliseconds */ static inline int64_t mm_timediff_ms(const struct mm_timespec* ts, const struct mm_timespec* orig) { int64_t dt; dt = (ts->tv_sec - orig->tv_sec) * (int64_t)MS_IN_SEC; dt += (ts->tv_nsec - orig->tv_nsec) / (NS_IN_SEC/MS_IN_SEC); return dt; } /** * mm_timeadd_ns() - apply nanosecond offset to timestamp * @ts: time point * @dt: offset in nanoseconds to apply to @ts */ static inline void mm_timeadd_ns(struct mm_timespec* ts, int64_t dt) { ts->tv_sec += dt / NS_IN_SEC; ts->tv_nsec += dt % NS_IN_SEC; if (ts->tv_nsec >= NS_IN_SEC) { ts->tv_nsec -= NS_IN_SEC; ts->tv_sec++; } else if (ts->tv_nsec < 0) { ts->tv_nsec += NS_IN_SEC; ts->tv_sec--; } } /** * mm_timeadd_us() - apply microsecond offset to timestamp * @ts: time point * @dt: offset in microseconds to apply to @ts */ static inline void mm_timeadd_us(struct mm_timespec* ts, int64_t dt) { ts->tv_sec += dt / US_IN_SEC; ts->tv_nsec += (dt % US_IN_SEC) * (NS_IN_SEC / US_IN_SEC); if (ts->tv_nsec >= NS_IN_SEC) { ts->tv_nsec -= NS_IN_SEC; ts->tv_sec++; } else if (ts->tv_nsec < 0) { ts->tv_nsec += NS_IN_SEC; ts->tv_sec--; } } /** * mm_timeadd_ms() - apply millisecond offset to timestamp * @ts: time point * @dt: offset in milliseconds to apply to @ts */ static inline void mm_timeadd_ms(struct mm_timespec* ts, int64_t dt) { ts->tv_sec += dt / MS_IN_SEC; ts->tv_nsec += (dt % MS_IN_SEC) * (NS_IN_SEC / MS_IN_SEC); if (ts->tv_nsec >= NS_IN_SEC) { ts->tv_nsec -= NS_IN_SEC; ts->tv_sec++; } else if (ts->tv_nsec < 0) { ts->tv_nsec += NS_IN_SEC; ts->tv_sec--; } } #ifdef __cplusplus } #endif #endif /* ifndef MMTIME_H */ mmlib-1.4.2/src/mutex-lockval.h000066400000000000000000000115501435717460000163620ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef MUTEX_LOCK_H #define MUTEX_LOCK_H #include #define MTX_OWNER_TID_MASK 0x00000000FFFFFFFFLL #define MTX_WAITER_TID_MASK 0xFFFFFE0000000000LL #define MTX_WAITER_TID_SHIFTLEN 41 #define MTX_NEED_RECOVER_MASK 0x0000010000000000LL #define MTX_NWAITER_MASK 0x000000FF00000000LL #define MTX_NWAITER_SHIFTLEN 32 #define MTX_NWAITER_INCREMENT (1LL << MTX_NWAITER_SHIFTLEN) #define MTX_UNRECOVERABLE_MASK (MTX_NEED_RECOVER_MASK | \ MTX_OWNER_TID_MASK) /** * is_mtx_waited() - test thread waiting mutex to be unlocked * @lockval: the value in the lock associated with mutex * * Return: true if there is at least one thread waiting for the mutex, false * otherwise. */ static inline bool is_mtx_waited(int64_t lockval) { return (lockval & MTX_NWAITER_MASK); } /** * is_mtx_locked() - test any thread has locked the mutex * @lockval: the value in the lock associated with mutex * * Return: true if there is a thread that has locked the mutex, false * otherwise. */ static inline bool is_mtx_locked(int64_t lockval) { return (lockval & MTX_OWNER_TID_MASK); } /** * is_mtx_waiterlist_full() - test a mutex has its waiter count full * @lockval: the value in the lock associated with mutex * * This helper indicates whether the mutex can register more waiter. If the * waiter count is maximal, a new thread that wait for the mutex will have * to revert to spin with scheduling yielding. * * Return: true if the waiter count of the mutex is maximal, false * otherwise. */ static inline bool is_mtx_waiterlist_full(int64_t lockval) { return ((lockval & MTX_NWAITER_MASK) == MTX_NWAITER_MASK); } /** * mtx_lockval() - generate lock value * @owner_tid: ID of the thread owning the mutex * @waiter_tid: ID of the thread that is updating the wait count * @nwaiter: number of thread waiting for the mutex being unlocked * * Return: the lock value */ static inline int64_t mtx_lockval(DWORD owner_tid, DWORD waiter_tid, int nwaiter) { return (owner_tid & MTX_OWNER_TID_MASK) | ((int64_t)nwaiter << MTX_NWAITER_SHIFTLEN) | ((int64_t)waiter_tid << MTX_WAITER_TID_SHIFTLEN); } /** * is_thread_mtx_owner() - test whether a thread own a mutex * @lockval: the value in the lock associated with mutex * @tid: ID of the thread to test * * Return: true is the thread whose ID is @tid appears to own the mutex lock * given the value in @lockval, false otherwise. */ static inline bool is_thread_mtx_owner(int64_t lockval, DWORD tid) { lockval &= MTX_OWNER_TID_MASK; return (mtx_lockval(tid, 0, 0) == lockval); } /** * is_thread_mtx_waiterlist_owner() - test a thread locking the wait list * @lockval: the value in the lock associated with mutex * @tid: ID of the thread to test * * Return: true is the thread whose ID is @tid appears to own the change on * wait list count of the mutex given the value in @lockval, false otherwise. */ static inline bool is_thread_mtx_waiterlist_owner(int64_t lockval, DWORD tid) { lockval &= MTX_WAITER_TID_MASK; return (mtx_lockval(0, tid, 0) == lockval); } /** * is_mtx_ownerdead() - test whether robust mutex is in inconsistent state * @lockval: the value in the lock associated with mutex * * If an owner of a robust mutex terminates while holding the mutex, the * mutex becomes inconsistent and the next thread that acquires the mutex * lock shall be notified of it. This helper allows one to identify this from * its lock value @lockval. * * Return: true if previous owner has died with the mutex locked, or in * other word, the mutex is in inconsistent state. false otherwise. */ static inline bool is_mtx_ownerdead(int64_t lockval) { return (lockval & MTX_NEED_RECOVER_MASK) ? true : false; } /** * is_mtx_unrecoverable() - test whether robust mutex is permanently unusable * @lockval: the value in the lock associated with mutex * * If a robust mutex is in inconsistent and the owning thead unlock it * without a recover it, ie calling to mmthr_mtx_consistent() before * mm_thr_mutex_unlock(), the mutex is marked permanently unusable. This helper * allows one to identify this state from the mutex lock value @lockval. * * Return: true if mutex was inconsistent and previous owner did not recover * it before unlocking. false otherwise. */ static inline bool is_mtx_unrecoverable(int64_t lockval) { return ((lockval & MTX_UNRECOVERABLE_MASK) == MTX_UNRECOVERABLE_MASK); } /** * mtx_num_waiter() - get number of registered waiters of a mutex * @lockval: the value in the lock associated with mutex * * Return: the number of thread that are registered waiting for the lock. */ static inline int mtx_num_waiter(int64_t lockval) { return (int)((lockval & MTX_NWAITER_MASK) >> MTX_NWAITER_SHIFTLEN); } #endif /* ifndef MUTEX_LOCK_H */ mmlib-1.4.2/src/nls-internals.h000066400000000000000000000006211435717460000163550ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef NLS_INTERNALS_H #define NLS_INTERNALS_H #if ENABLE_NLS #include #define N_(msg) msg #define _(msg) dgettext(PACKAGE_NAME, msg) #define _domaindir(podir) bindtextdomain(PACKAGE_NAME, podir) #else // ENABLE_NLS #define N_(msg) msg #define _(msg) msg #define _domaindir(podir) (void)(0) #endif // ENABLE_NLS #endif /* NLS_INTERNALS_H */ mmlib-1.4.2/src/process-posix.c000066400000000000000000000475541435717460000164150ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #define _GNU_SOURCE // for pipe2() #include "mmsysio.h" #include "mmerrno.h" #include "mmlog.h" #include #include #include #include #include #include #include #include #include #include /** * struct startproc_opts - holder for argument passed to mm_spawn() * @file: path or basename of the executable file * @num_map: number of element in the @fd_map array * @fd_map: array of file descriptor remapping to pass into the child * @flags: spawn flags * @argv: null-terminated array of string containing the command * arguments (starting with command). * @envp: null-terminated array of strings specifying the environment * of the executed program. can be NULL * * This structure is meant to hold the argument passed in the call to * mm_spawn() and forward them to the deeper layer of the mm_spawn() * implementation. */ struct startproc_opts { const char* file; int flags; int num_map; const struct mm_remap_fd* fd_map; char* const* argv; char* const* envp; }; /** * set_fd_cloexec() - Set/unset FD_CLOEXEC flag to file descriptor * @fd: file descriptor to modify * @cloexec: flag indicating whether FD_CLOEXEC must set or unset * * Return: 0 in case of success, -1 otherwise with errno set. */ static int set_fd_cloexec(int fd, int cloexec) { int curr_flags; curr_flags = fcntl(fd, F_GETFD); if (curr_flags == -1) return -1; if (cloexec) curr_flags |= FD_CLOEXEC; else curr_flags &= ~FD_CLOEXEC; return fcntl(fd, F_SETFD, curr_flags); } /** * set_cloexec_all_fds() - Add FD_CLOEXEC to all FDs excepting a list * @min_fd: minimal file descriptor to close (all lower fds will be kept * untouched) * * This function will set the FD_CLOEXEC flag to all file descriptor in the * caller process excepting below @min_fd. If a file descriptor has * the FD_CLOEXEC flag, it will be closed across a call to exec*(). * * This is implemented by scanning the /proc/self/fd or /dev/fd directories * which contains information about all open file descriptor in the current * process. * * NOTE: There is no portable way to know all open file descriptors in a * process. This function will function only on Linux, Solaris, AIX, Cygwin, * NetBSD (/proc/self/fd) and FreeBSD, Darwin, OS X (/dev/fd) */ static void set_cloexec_all_fds(int min_fd) { int fd; DIR* dir; struct dirent* entry; if (!(dir = opendir("/proc/self/fd")) && !(dir = opendir("/dev/fd"))) { mm_log_warn("Cannot find list of open file descriptors. " "Leaving maybe some fd opened in the child..."); return; } // Loop over entries in the directory while ((entry = readdir(dir))) { // Convert name into file descriptor value if (!sscanf(entry->d_name, "%i", &fd)) continue; if (fd >= min_fd) set_fd_cloexec(fd, 1); } closedir(dir); } /** * remap_file_descriptors() - setup the open files descriptor in the child * @num_map: number of element in @fd_map * @fd_map: array of file descriptor remapping to pass into the child * * Meant to be called in the child process, between the fork() and exec(), * this function will move (actually duplicate) the inherited file * descriptor to the specified child fd, and mark as cloexec those that are * specifically noted for closing, ie, those associated with * &mm_remap_fd.parent_fd == -1. * * Return: 0 in case of success, -1 otherwise with error state set */ static int remap_file_descriptors(int num_map, const struct mm_remap_fd* fd_map) { int i, child_fd, parent_fd; // Tag all fd beyond STDERR for closing at exec set_cloexec_all_fds(3); for (i = 0; i < num_map; i++) { child_fd = fd_map[i].child_fd; parent_fd = fd_map[i].parent_fd; if (parent_fd == -1) set_fd_cloexec(child_fd, 1); // If parend and child have same fd, we need to unset // CLOEXEC directly because dup2() will leave fd untouch in // such a case. if (parent_fd == child_fd) { set_fd_cloexec(child_fd, 0); continue; } // Duplicate parent_fd to child_fd. We use dup2, so if parent_fd // has CLOEXEC flag, it will be removed in the duplicated fd if (dup2(parent_fd, child_fd) < 0) { mm_raise_from_errno("dup2(%i, %i) failed", parent_fd, child_fd); return -1; } } return 0; } /** * report_to_parent_and_exit() - report error from child and terminate * @report_pipe: pipe to use to write error info * * This function is used when an error is detected in the child process * preventing the child to execute the specified executable with the * specified argument. @report_pipe is meant to be the write end of a pipe * connected to the parent that will be watched there after the fork. */ static noreturn void report_to_parent_and_exit(int report_pipe) { struct mm_error_state errstate; char* cbuf; size_t len; ssize_t rsz; mm_save_errorstate(&errstate); // Do full write to report_pipe cbuf = (char*)&errstate; len = sizeof(errstate); while (len > 0) { rsz = write(report_pipe, cbuf, len); if (rsz < 0) { // Retry if call has simply been interrupted by // signal delivery if (errno == EINTR) continue; // If anything else happens, there is nothing we can // do. However, given we write on a clean pipe, the // only possible error should be EPIPE due to parent // dead. In such a case, we don't care (reporting a // death of a child to a dead parent?!? o_O) break; } len -= rsz; cbuf += rsz; } exit(EXIT_FAILURE); } /** * load_new_proc_img() - configure current process for new binary * @opts: data holding argument passed to mm_spawn() * @report_pipe: pipe end to use if an error is detected * * Meant to be called in the new forked child process, this set it up with * the right file descriptors opened and replace the current executable * image with the one specified in @opts. If any of the step fails, the * error will be reported through @report_pipe. */ static noreturn void load_new_proc_img(const struct startproc_opts* opts, int report_pipe) { // Perform remapping if MM_SPAWN_KEEP_FDS is not set in flags if (!(opts->flags & MM_SPAWN_KEEP_FDS) && remap_file_descriptors(opts->num_map, opts->fd_map)) goto failure; if (strchr(opts->file, '/')) execve(opts->file, opts->argv, opts->envp); else execvpe(opts->file, opts->argv, opts->envp); // If we read here, execve has failed mm_raise_from_errno("Cannot run \"%s\"", opts->file); failure: report_to_parent_and_exit(report_pipe); } /** * wait_for_load_process_result() - wait child failure or success * @watch_fd: pipe end to watch * * This function is meant to be called in the parent process. @watch_fd is * supposed to be the read end of a pipe whose the write end is accessible * to the child, ie, after fork(). If any error occurs before or during * child process call exec(), it will be reported by the through this pipe * with report_to_parent_and_exit(). Both end of the pipe are supposed to * have the CLOEXEC flag set. If the child end of the pipe is closed before * any data can be read from @watch_fd, this means that the write end has * been closed by the successful call to exec(). * * Return: 0 if the exec call is successful in child process, -1 otherwise * and the error state will be the one reported by child. */ static int wait_for_load_process_result(int watch_fd) { ssize_t rsz; int ret; struct mm_error_state errstate; ret = -1; rsz = read(watch_fd, &errstate, sizeof(errstate)); if (rsz < 0) { mm_raise_from_errno("Cannot read from result pipe"); goto exit; } // If read return and nothing is read, it means the write end of the // pipe is closed before any data has been written. This means that // exec was successful if (rsz == 0) { ret = 0; goto exit; } // If something has been read, the exec has failed. The error state // must have been passed in the pipe if (rsz < (ssize_t)sizeof(errstate)) { mm_raise_error(EIO, "Incomplete error state from child"); goto exit; } mm_set_errorstate(&errstate); exit: close(watch_fd); return ret; } /** * spawn_child() - spawn a direct child of the calling process * @child_pid: pointer to a variable that will receive process ID of child * @opts: data holding argument passed to mm_spawn() * * Return: 0 in case success, -1 otherwise with error state set accordingly */ static int spawn_child(mm_pid_t* child_pid, const struct startproc_opts* opts) { int rv; pid_t pid; int pipefd[2], watch_fd, report_fd; // Create pipe connecting child to parent if (pipe2(pipefd, O_CLOEXEC)) return -1; watch_fd = pipefd[0]; report_fd = pipefd[1]; pid = fork(); if (pid == -1) { mm_raise_from_errno("unable to fork"); close(watch_fd); close(report_fd); return -1; } // if pid is 0, this means that we are in the child process of the fork if (pid == 0) { close(watch_fd); load_new_proc_img(opts, report_fd); } // we are in the parent part of the fork close(report_fd); // watch_fd is going to be closed here rv = wait_for_load_process_result(watch_fd); if (rv == 0) { if (child_pid) *child_pid = pid; } else { waitpid(pid, NULL, 0); } return rv; } /** * spawn_daemon() - spawn a background process * @opts: data holding argument passed to mm_spawn() * * Return: 0 in case success, -1 otherwise with error state set accordingly */ static int spawn_daemon(const struct startproc_opts* opts) { pid_t pid; int status; int pipefd[2], watch_fd, report_fd; struct startproc_opts child_opts; // Create pipe connecting child to parent if (pipe2(pipefd, O_CLOEXEC)) return -1; watch_fd = pipefd[0]; report_fd = pipefd[1]; pid = fork(); if (pid == -1) { mm_raise_from_errno("unable to do first fork"); close(watch_fd); close(report_fd); return -1; } // if pid > 0, this means that we are in the parent process of the first // fork if (pid > 0) { close(report_fd); waitpid(pid, &status, 0); // watch_fd is going to be closed here return wait_for_load_process_result(watch_fd); } // We are in the child process of the first fork so setup for daemon close(watch_fd); // Turn possibly relative path to absolute executable path before // changing curr dir to / (otherwise it won't be found). However // apply this only if it refers to a path, not a file to be search // in PATH env var (ie if path DOES NOT contains any '/') child_opts = *opts; if (strchr(opts->file, '/')) { child_opts.file = realpath(opts->file, NULL); if (!child_opts.file) { mm_raise_from_errno(""); report_to_parent_and_exit(report_fd); } } if (chdir("/")) { mm_raise_from_errno("Unable to chdir(\"/\")"); report_to_parent_and_exit(report_fd); } umask(0); setsid(); // Do second fork pid = fork(); if (pid == -1) { mm_raise_from_errno("unable to do second fork"); report_to_parent_and_exit(report_fd); } // Parent side of the second fork) if (pid > 0) { exit(EXIT_SUCCESS); } load_new_proc_img(&child_opts, report_fd); } /** * mm_spawn() - spawn a new process * @child_pid: pointer receiving the child process pid * @file: path or basename to the executable file * @num_map: number of element in the @fd_map array * @fd_map: array of file descriptor remapping to pass into the child * @flags: spawn flags * @argv: null-terminated array of string containing the command * arguments (starting with command). Can be NULL. * @envp: null-terminated array of strings specifying the environment * of the executed program. If it is NULL, it inherit its * environment from the calling process * * This function creates a new process executing the file specified by * @file. The pid the of created child is set in the variable pointed by * @child_pid. * * The argument @file is used to construct a pathname that identifies the * child process image file. If the @file argument contains a directory * separator character, it will be used as the pathname for this file. * Otherwise, the path prefix for this file is obtained by a search of the * list of directories passed as the environment variable PATH. The list is * searched from beginning to end, applying the filename to each prefix, * until an executable file with the specified name and appropriate * execution permissions is found. On Windows platform, the filename will * be tried first as is and then with .exe extension. * * The child process will inherit only the open file descriptors specified * in the @fd_map array whose length is indicated by @num_map. For each * element in @fd_map, a file descriptor numbered as specified in * &mm_remap_fd.child_fd is available at child startup referencing the * same file description as the corresponding &mm_remap_fd.parent_fd * in the calling process. This means that after successful execution of * mm_spawn() two reference of the same file will exist and the underlying * file will be actually closed when all file descriptor referencing it * will be closed. The @fd_map array is processed sequentially so a mapping * in the first element can be overridden in the next elements. If an element * in @fd_map has a &mm_remap_fd.parent_fd field set to -1, it means that * the corresponding @fd_map has a &mm_remap_fd.child_fd must not opened in * the child process. * * For convenience, the standard input, output and error are inherited by * default in the child process. If any of those file are meant to be closed * or redirected in the child, this can simply be done by adding element in * @fd_map that redirect a standard file descriptor in the parent, or close * them (by setting &mm_remap_fd.parent_fd to -1. * * @flags must contains a OR-combination or 0 or any number of the following * flags : * * MM_SPAWN_DAEMONIZE * the created process will be detached from calling process and will * survive to its parent death (a daemon in the UNIX terminology). * MM_SPAWN_KEEP_FDS * All open file descriptors in the calling process that are inherintable are * kept in the child with the same index. All the other file descriptor are * closed. Unless specified otherwise, all file descriptor created in mmlib * API are not inheritable. If this flag is specified, @num_map and * @fd_map argument are ignored. * * The argument @argv, if not null, is an array of character pointers to * null-terminated strings. The application shall ensure that the last * member of this array is a null pointer. These strings constitutes * the argument list available to the new process image. The value in * argv[0] should point to a filename that is associated with the process * being started. If @argv is NULL, the behavior is as if mm_spawn() were * called with a two array argument, @argv[0] = @file, @argv[1] = NULL. * * The argument @envp is an array of character pointers to null-terminated * strings. These strings constitutes the environment for the new * process image. The @envp array is terminated by a null pointer. If @envp * is NULL, the new process use the same environment of the calling process * at the time of the mm_spawn() call. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_spawn(mm_pid_t* child_pid, const char* file, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp) { char* default_argv[] = {(char*)file, NULL}; int ret; struct startproc_opts proc_opts = { .file = file, .flags = flags, .num_map = num_map, .fd_map = fd_map, .argv = argv, .envp = envp, }; if (!file) return mm_raise_error(EINVAL, "file must not be NULL"); if (flags & ~(MM_SPAWN_KEEP_FDS | MM_SPAWN_DAEMONIZE)) return mm_raise_error(EINVAL, "Invalid flags (%08x)", flags); if (!argv) proc_opts.argv = default_argv; if (!envp) proc_opts.envp = environ; if (flags & MM_SPAWN_DAEMONIZE) { ret = spawn_daemon(&proc_opts); } else { ret = spawn_child(child_pid, &proc_opts); } return ret; } /** * mm_execv() - replace executable image of the calling process * @file: path or basename to the executable file * @num_map: number of element in the @fd_map array * @fd_map: array of file descriptor remapping to pass in the new image * @flags: spawn flags * @argv: null-terminated array of string containing the command * arguments (starting with command). Can be NULL. * @envp: null-terminated array of strings specifying the environment * of the executed program. If it is NULL, it inherit its * environment from the calling process * * This function is the same as mm_spawn() excepting that, instead of * creating a new child process, it replaces the current process image with * a new process image. There shall be no return from a successful call, * because the calling process image is overlaid by the new process image. * Hence the PID number of calling process can still be used with * mm_wait_process() to wait for the new process image to finish. * * As a consequence, MM_SPAWN_DAEMONIZE flag is meaningless in the context of * mm_execv(). Hence, this is not an accepted value for @flags. * * NOTE: Beware, on some platform, you are not ensured that the PID of the * process image will remains the exactly same. However on those, the old * process, when it calls mm_execv(), will free as much resources it can * (terminates all threads and close all fds) and spawn the new process * image. It will then remain available during the lifetime of the new * image process and usable with mm_wait_process() if the old process image * was created with mm_spawn(). * * Return: the function will NOT return in case of success. Otherwise, -1 * is returned and error state is set accordingly. */ API_EXPORTED int mm_execv(const char* file, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp) { char* default_argv[] = {(char*)file, NULL}; if (!file) return mm_raise_error(EINVAL, "file must not be NULL"); if (flags & ~MM_SPAWN_KEEP_FDS) return mm_raise_error(EINVAL, "Invalid flags (%08x)", flags); // Perform remapping if MM_SPAWN_KEEP_FDS is not set in flags if (!(flags & MM_SPAWN_KEEP_FDS) && remap_file_descriptors(num_map, fd_map)) return -1; if (!argv) argv = default_argv; if (!envp) envp = environ; if (strchr(file, '/')) execve(file, argv, envp); else execvpe(file, argv, envp); // If we read here, execve has failed mm_raise_from_errno("Cannot run \"%s\"", file); return -1; } /** * mm_wait_process() - wait for a child process to terminate * @pid: PID of child process * @status: location where to put status of the child process * * This function get the status of a the child process whose PID is @pid. If * the child process is not terminated yet, the function will block until * the child is terminated. * * If @status is not NULL, it refers to a location that will receive the * status information of the terminated process. The information is a mask * of MM_WSTATUS_* indicating whether the child has terminated because of * normal termination or abnormal one and the exit code (or signal number). * To be accessible in the status information, the return code of a child * program must be between 0 and 255. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_wait_process(mm_pid_t pid, int* status) { int posix_status, mm_status; if (waitpid(pid, &posix_status, 0) < 0) { mm_raise_from_errno("waitpid(%lli) failed", pid); return -1; } if (WIFEXITED(posix_status)) { mm_status = MM_WSTATUS_EXITED | WEXITSTATUS(posix_status); } else if (WIFSIGNALED(posix_status)) { mm_status = MM_WSTATUS_SIGNALED | WTERMSIG(posix_status); } else { mm_crash("waitpid() must return exited or signaled status"); } if (status) *status = mm_status; return 0; } mmlib-1.4.2/src/process-win32.c000066400000000000000000001206361435717460000162060ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "file-internal.h" #include "mmsysio.h" #include "mmerrno.h" #include "mmlog.h" #include "mmlib.h" #include "utils-win32.h" #include #include #include #include #include #include #include #include #define DEFAULT_PATHSEARCH "C:\\Windows\\system32;C:\\Windows;" /* __osfile flag values for DOS file handles */ #define FOPEN 0x01 /* file handle open */ #define FEOFLAG 0x02 /* end of file has been encountered */ #define FCRLF 0x04 /* CR-LF across read buffer (in text mode) */ #define FPIPE 0x08 /* file handle refers to a pipe */ #define FNOINHERIT 0x10 /* file handle opened _O_NOINHERIT */ #define FAPPEND 0x20 /* file handle opened O_APPEND */ #define FDEV 0x40 /* file handle refers to device */ #define FTEXT 0x80 /* file handle is in text mode */ #define CRT_BUFFER_SIZE(nh) ( sizeof(int) \ + (nh) * sizeof(unsigned char) \ + (nh) * sizeof(HANDLE) \ + (nh) * sizeof(unsigned char)) static const struct mm_remap_fd std_fd_mappings[] = {{0, 0}, {1, 1}, {2, 2}}; /** * startup_config - configuration of child startup info * @num_hnd: number of WIN32 handle inherited in child process * @num_fd: maximum number of inherited file descriptor * @num_crt_fd: length of @crt_fd_hnds and @crt_fd_flags * @inherited_hnds: array of @num_hnd WIN32 handle that child must inherit * @crt_buff: buffer holding the data to pass to &STARTUPINFO.cbReserved2 * @crt_fd_hnds: array of handle of each child fd (each element may * INVALID_HANDLE_VALUE if not inherited). Points in @crt_buff * @crt_fd_flags: array of flags indicating the type of CRT fd inherited * in the child process. Points in @crt_buff * @crt_fd_infos: mmlib fd type array passed to child, points in @crt_buff * @info: WIN32 structure configured for call of CreateProcess() * @attr_list: helper data holder for call of CreateProcess(). This is * internally used for setting @info up. * @is_attr_init: true if @attr_list is initialized, false otherwise * * This structure is meant to keep track and generate the WIN32 structures * to create the child process inheriting the right file descriptors and * WIN32 handles. */ struct startup_config { int num_hnd; int num_fd; int num_crt_fd; HANDLE* inherited_hnds; BYTE* crt_buff; unsigned char* crt_fd_flags; HANDLE* crt_fd_hnds; unsigned char* crt_fd_infos; STARTUPINFOEXW info; LPPROC_THREAD_ATTRIBUTE_LIST attr_list; bool is_attr_init; }; /************************************************************************** * * * child processes tracking * * * **************************************************************************/ /** * DOC: Rationale of child process tracking * * When designing the Win32 size of the API of process creation and * termination wait, there were 2 possibilities regarding how a child * process must be referenced (what mm_pid_t should represent) : * * - either expose a win32 handle * - either expose the process ID (as POSIX part does) * * Since a process can only be manipulated through handle, for the sake of * simplicity the initial versions of the API used to expose an handle. * However it quickly become obvious that the handle which is meaningful * only in the context of the parent process, was not lacking interesting * property of identification (either for reporting/logging or IPC). Then * rised the need for a Win32 specific API of handle->pid. This was the * proof that exposing handle was not the right approach and show the need * PID based manipulation. * * However if we only expose PID (ie, we forget the handle that are granted * at process creation), we run into other issues: the PID can be reused if * the process finishes, and it is not sure that can obtain an handle (with * sufficient rights) from the PID when we will ask later. To solve those * issues, we can simply keep the handle that the process creation provides * as internal data (along with PID) and expose the PID. As long as the * handle is not closed, we are ensured that the PID is not reused, and * whenever we need to wait for the process to finish, we find the handle of * process and perform the wait with it. * * This means that we can wait only for process that are direct child, but * the good news is that it is exactly the behavior of POSIX (wait*() can be * used only on direct children). */ /** * struct child_entry - entry for the children list * @pid: Process ID of the child * @hnd: WIN32 handle to the child process */ struct child_entry { mm_pid_t pid; HANDLE hnd; }; /** * struct children_list - children list * @num_child: number of direct child process * @num_max: length of allocated @entries array * @lock: lock protecting the list update * @entries: array of @num_child child entries */ struct children_list { int num_child; int num_max; SRWLOCK lock; struct child_entry* entries; }; // Global list of children that have been created with mmlib static struct children_list children_list = {.lock = SRWLOCK_INIT}; /** * add_to_children_list() - add a new child in the children list * @pid: process ID of the child to add * @hnd: handle to process to add to list * * Return: 0 in case of success, -1 otherwise with error state set * accordingly */ static int add_to_children_list(mm_pid_t pid, HANDLE hnd) { struct children_list* list = &children_list; struct child_entry * entries, * entry; int nmax, retval; AcquireSRWLockExclusive(&list->lock); // Check that allocated array of entries is large enough to // accommodate a new element. Resize it if necessary. retval = 0; if (list->num_child >= list->num_max) { nmax = (list->num_max != 0) ? (list->num_max * 2) : 16; entries = realloc(list->entries, nmax*sizeof(*entries)); if (!entries) { retval = mm_raise_from_errno("Cannot alloc child"); goto exit; } list->entries = entries; list->num_max = nmax; } // Register the new child (ie keep link between pid and handle) entry = &list->entries[list->num_child++]; entry->pid = pid; entry->hnd = hnd; exit: ReleaseSRWLockExclusive(&children_list.lock); return retval; } /** * get_handle_from_children_list() - get handle of a child * @pid: process ID of a direct child * * Return: handle of the child if found in the children list, NULL * if not found. */ static HANDLE get_handle_from_children_list(mm_pid_t pid) { struct children_list* list = &children_list; int i; HANDLE hnd; AcquireSRWLockExclusive(&list->lock); // Search for index of child with matching pid hnd = NULL; for (i = 0; i < list->num_child; i++) { if (list->entries[i].pid == pid) { hnd = list->entries[i].hnd; break; } } ReleaseSRWLockExclusive(&children_list.lock); return hnd; } /** * drop_child_from_children_list() - remove child for children list * @pid: process ID of child to drop */ static void drop_child_from_children_list(mm_pid_t pid) { struct child_entry* entries; int i, num_child; AcquireSRWLockExclusive(&children_list.lock); num_child = children_list.num_child; entries = children_list.entries; // Search for index of child with matching pid for (i = 0; i < num_child; i++) { if (entries[i].pid == pid) { // Remove matching entry memmove(entries + i, entries + i+1, (num_child-i-1)*sizeof(*entries)); children_list.num_child--; break; } } ReleaseSRWLockExclusive(&children_list.lock); // if i is equal or bigger to num_child, no entries has been found. // This should never happen mm_check(i < num_child); } /************************************************************************** * * * Startup info manipulation * * * **************************************************************************/ /** * DOC: file descriptors inherited in a new Win32 process * * FD passing on Windows * --------------------- * * Like in POSIX, file descriptor on Windows can be inherited from parent * into a child at its creation. However file descriptor on Win32 are not a * object of the OS, but a construct provided by the CRT (msvcrt of each * compiler version or more recently the ucrt). Those are simply a * combination of a Win32 handle combined to some metadata (file open, * append mode, is it a device...) store in a global list indexed by the * file descriptor. * * While the handle backing the file descriptor inheritance is done by the * OS, the actual file descriptor can be inherited in child process only * with the CRT and OS cooperating. Enter the &cbReserved2 and &lpReserved2 * fields of the &STARTUPINFO structure used in CreateProcess() function. * In MSDN those members are noted as "Reserved for use by the C Run-time", * and lack of documentation. But this is completely documented by the * source code of the CRT (available either in Visual Studio or in Windows * SDK, see UCRT source code in lowio/ioinit.cpp and exec/spawnv.cpp). The * &STARTUPINFO.lpReserved2 field has the follow layout : * * +--------+---------------------+---------------------------+ * | num_fd | Array of CRT flags | Array of win32 handle | * +--------+---------------------+---------------------------+ * | int | num_fd * uchar | num_fd * HANDLE | * +--------+---------------------+---------------------------+ * * The layout is packed without any padding. The arrays correspond to the * length whole file descriptor array from 0 to the highest fd inherited (or * beyond if the tail of arrays correspond to fd not meant to be opened in * child). For each pair of flag and handle indexed at %i in both arrays, if * flags has FOPEN (0x01) set and not FNOINHERIT (0x10) and the handle is * valid and inheritable in the calling process, a file descriptor %i will * be available at startup in the child. &STARTUPINFO.cbReserved2 must * correspond to the size of this buffer, ie * sizeof(int)+num_fd*(sizeof(char)+sizeof(HANDLE)) * * Please note that this mechanism, although it is not documented in MSDN, * cannot be changed by Microsoft without breaking compatibility with * existing software: the inheritance of file descriptor is documented in * MSDN and must coop with the different CRT used in different compiler * version. Consequently this mechanism has not changed since at least * Windows 95. (MS has just added layer of validation of the data passed * over the versions of Windows). * * FD passing with mmlib * --------------------- * * Even if a software cannot access to internal FD flags of a CRT, if it knows * the type of fd it has to pass, it can simply setup &STARTUPINFO.lpReserved2 * and call CreateProcess(), the CRT (whichever it is) of the child will accept * the data and setup the file descriptors the same way the parent would have * called spawnv(). * * mmlib uses this to keep metadata regarding fd opened by itself while keeping * use of CRT to handle fd allocation and closing (with _open_osfhandle(), * _get_osfhandle(), _close()...). It is done by adding an extra array in the * data passed in &STARTUPINFO.lpReserved2. This extra array will be * interpreted by mmlib at startup. This mechanism also allows one to cooperate * well with piece of code in the same process that do not use mmlib to create * its file descriptors. The number of FD to be passed is indeed specified by * the first field of &STARTUPINFO.lpReserved2: any data after the array of * win32 handle will be ignored by process not using mmlib since the offset * and length of this array is solely dependent on number of FD passed. */ /** * mm_fd_init() - Initializer of fd info array * * This function is called at startup (before main or during dllmain if * dynamically loaded) and initialize the fd information array according to * what the parent may have pass to the current process */ MM_CONSTRUCTOR(mm_fd_init) { STARTUPINFOW si; int fd, num_fd; const BYTE* crtbuff; const unsigned char* fd_infos; // Get startup parameters GetStartupInfoW(&si); crtbuff = si.lpReserved2; if (!crtbuff) return; // Get num_fd and handle from startup info. memcpy is // used because crtbuff is not guaranteed to be aligned memcpy(&num_fd, crtbuff, sizeof(num_fd)); fd_infos = crtbuff + sizeof(int) + num_fd * sizeof(unsigned char) + num_fd * sizeof(HANDLE); // Check from buffer size that lpReserved2 has been set by mmlib if (si.cbReserved2 != CRT_BUFFER_SIZE(num_fd)) return; // Initialize the fd info array with the data passed in startup info for (fd = 0; fd < num_fd; fd++) set_fd_info(fd, fd_infos[fd]); } /** * convert_fdinfo_to_crtflags() - convert mmlib fd info into CRT flags * @fdinfo: mmlib file descriptor info * * Return: a flag value to be used in the field &STARTUPINFO.cbReserved2 (to * be consumed by msvcrt or ucrt) */ static unsigned char convert_fdinfo_to_crtflags(int fdinfo) { unsigned char crtflags; int fd_type = fdinfo & FD_TYPE_MASK; crtflags = FOPEN; switch (fd_type) { case FD_TYPE_PIPE: crtflags |= FPIPE; break; case FD_TYPE_CONSOLE: crtflags |= FDEV; break; default: break; } if (fdinfo & FD_FLAG_APPEND) crtflags |= FAPPEND; if (fdinfo & FD_FLAG_TEXT) crtflags |= FTEXT; return crtflags; } /** * get_highest_child_fd() - get the child fd which the highest index * @num_map: number of mapping element in @fd_map * @fd_map: array of remapping file descriptor between child and parent * * Return: the value of the highest &mm_remap_fd.child_fd member in the * array @fd_map. */ static int get_highest_child_fd(int num_map, const struct mm_remap_fd* fd_map) { int i, fd_max, fd; fd_max = -1; for (i = 0; i < num_map; i++) { fd = fd_map[i].child_fd; if (fd > fd_max) fd_max = fd; } return fd_max; } /** * round_up() - round up a value to the next multiple of a divider * @value: value to round up * @divider: multiple of which @value must be round up * * Return: the first number bigger or equal to @value that is dividable by * @divider. */ static int round_up(int value, int divider) { return ((value + divider-1)/divider)*divider; } /** * startup_config_get_startup_info() - return a configured STARTUPINFO pointer * @cfg: initialized startup config * * Use this function after having called startup_config_init() to return a * pointer to a configured STARTUPINFO structure, meant to be used in * Win32 CreateProcess() API. The pointer returns internal data of @cfg. * Also it actually returns a STARTUPINFOEX structure, so it can be used * with the EXTENDED_STARTUPINFO_PRESENT flag passed to CreateProcess(). * * Return: pointer to STARTUPINFO configured according the configured * startup config. */ static STARTUPINFOW* startup_config_get_startup_info(struct startup_config* cfg) { cfg->info = (STARTUPINFOEXW) { .StartupInfo = { .cb = sizeof(cfg->info), .cbReserved2 = CRT_BUFFER_SIZE(cfg->num_crt_fd), .lpReserved2 = cfg->crt_buff, .dwFlags = STARTF_USESTDHANDLES, .hStdInput = cfg->crt_fd_hnds[0], .hStdOutput = cfg->crt_fd_hnds[1], .hStdError = cfg->crt_fd_hnds[2], }, .lpAttributeList = cfg->attr_list, }; return &cfg->info.StartupInfo; } /** * startup_config_alloc_crt_buffs() - allocated and setup CRT buffers * @cfg: being initialized startup config * * This internal function is meant to allocate the CRT buffer (to be used in * &STARTUPINFO.cbReserved2) and setup properly the fields * @cfg->crt_fd_flags and @cfg->crt_fd_hnds which will point into the * allocated @cfg->crt_buf. It will find an appropriate value of * @cfg->num_crt_fd so that @cfg->crt_fd_hnds has an alignment suitable for * its data type (HANDLE). * * Return: 0 in case of success, -1 otherwise with error state set. */ static int startup_config_alloc_crt_buffs(struct startup_config* cfg) { int num_crt_fd; int offset; // Adjust num_fd so that cfg->crt_fd_hnds is aligned. This is the // case if sizeof(int)+num_fd is a multiple of sizeof(HANDLE) num_crt_fd = round_up(sizeof(int)+cfg->num_fd, sizeof(HANDLE)) - sizeof(int); // Allocate a CRT buffer with the adjusted number of file descriptor cfg->num_crt_fd = num_crt_fd; cfg->crt_buff = calloc(1, CRT_BUFFER_SIZE(cfg->num_crt_fd)); if (!cfg->crt_buff) return mm_raise_error(ENOMEM, "Failed to CRT buffers"); // By adjustment of num_crt_fd, we are ensured that // cfg->crt_fd_hnds pointer is properly aligned on HANDLE. offset = 0; *(int*)cfg->crt_buff = num_crt_fd; offset += sizeof(int); cfg->crt_fd_flags = cfg->crt_buff + offset; offset += num_crt_fd * sizeof(unsigned char); cfg->crt_fd_hnds = (HANDLE*)(cfg->crt_buff + offset); offset += num_crt_fd * sizeof(HANDLE); cfg->crt_fd_infos = cfg->crt_buff + offset; return 0; } /** * startup_config_allocate_internals() - alloc internal data of startup config * @cfg: being initialized startup config * * Allocate the internal buffers and create the extra handle and memory * mapping. This function is meant to be called in startup_config_init(). * * Return: 0 in case of success, -1 otherwise with error state set. */ static int startup_config_allocate_internals(struct startup_config* cfg) { int max_num_hnd; SIZE_T attrlist_bufsize; SECURITY_ATTRIBUTES sa = { .nLength = sizeof(sa), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE, }; max_num_hnd = cfg->num_fd + 1; InitializeProcThreadAttributeList(NULL, 1, 0, &attrlist_bufsize); if (startup_config_alloc_crt_buffs(cfg)) return -1; // Allocate auxiliary buffers cfg->inherited_hnds = malloc(max_num_hnd * sizeof(*cfg->inherited_hnds)); cfg->attr_list = malloc(attrlist_bufsize); if (!cfg->inherited_hnds || !cfg->attr_list) return mm_raise_error(ENOMEM, "Cannot alloc info buffers"); // Allocate a process attribute list if (!InitializeProcThreadAttributeList(cfg->attr_list, 1, 0, &attrlist_bufsize)) return mm_raise_from_w32err("cannot init attribute list"); cfg->is_attr_init = true; return 0; } /** * startup_config_allocate_internals() - setup CRT buffer according to remapping * @cfg: being initialized startup config * @num_map: number of element in @fd_map * @fd_map: array of file descriptor mapping elements * * Configures @cfg->crt_fd_flags, @cfg->crt_fd_hnds and cfg->fd_infos * according to the remapping defined in @fd_map * * Return: 0 in case of success, -1 otherwise with error set accordingly. */ static int startup_config_setup_mappings(struct startup_config* cfg, int num_map, const struct mm_remap_fd* fd_map) { int i, fd_info, child_fd, parent_fd; HANDLE hnd; for (i = 0; i < num_map; i++) { child_fd = fd_map[i].child_fd; parent_fd = fd_map[i].parent_fd; if (child_fd < 0) { mm_raise_error(EBADF, "fd_map[%i].child_fd=%i is invalid", i, child_fd); return -1; } // If parent_fd is -1, this means that the child_fd must not // be inherited in child if (parent_fd == -1) { cfg->crt_fd_hnds[child_fd] = INVALID_HANDLE_VALUE; cfg->crt_fd_flags[child_fd] = 0; continue; } // Get handle of fd in parent hnd = (HANDLE)_get_osfhandle(parent_fd); if (hnd == INVALID_HANDLE_VALUE) { mm_raise_error(EBADF, "fd_map[%i].parent_fd=%i is invalid", i, parent_fd); return -1; } // setup child_fd mapping fd_info = get_fd_info(parent_fd); cfg->crt_fd_hnds[child_fd] = hnd; cfg->crt_fd_flags[child_fd] = convert_fdinfo_to_crtflags(fd_info); cfg->crt_fd_infos[child_fd] = fd_info; } return 0; } /** * startup_config_dup_inherited_hnds() - duplicate inherited handles * @cfg: being initialized startup config * * This function, meant to be called in startup_config_init(), will populate * the array of inherited handle. Since this list must contains only * inheritable handle and we cannot know (for sure) if it is the case, we * duplicate them unconditionally. * * Return: 0 in case of success, -1 otherwise with error set accordingly. */ static int startup_config_dup_inherited_hnds(struct startup_config* cfg) { int i; BOOL res; HANDLE hnd, dup_hnd, proc_hnd; proc_hnd = GetCurrentProcess(); for (i = 0; i < cfg->num_fd; i++) { hnd = cfg->crt_fd_hnds[i]; // Skip if not inherited in child if ((hnd == INVALID_HANDLE_VALUE) || !(cfg->crt_fd_flags[i] & FOPEN)) continue; // Duplicate handle: all handle in the attr_list must be // inheritable. Since we cannot know it the case or not, we // duplicate the handle unconditionally and set // inheritability in the duplicated handle res = DuplicateHandle(proc_hnd, hnd, proc_hnd, &dup_hnd, 0, TRUE, DUPLICATE_SAME_ACCESS); if (res == FALSE) return mm_raise_from_w32err("cannot duplicate handle"); // Add the handle in the inherited list and replace the one // in CRT handle list by the duplicated one cfg->inherited_hnds[cfg->num_hnd++] = dup_hnd; cfg->crt_fd_hnds[i] = dup_hnd; } // Add the inherited handle list in attribute list UpdateProcThreadAttribute(cfg->attr_list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, cfg->inherited_hnds, cfg->num_hnd*sizeof(*cfg->inherited_hnds), NULL, NULL); return 0; } /** * startup_config_deinit() - cleanup a startup config * @cfg: startup config * * This function cleanup and release all resources used to setup the child * process startup config. */ static void startup_config_deinit(struct startup_config* cfg) { int i; if (cfg->is_attr_init) DeleteProcThreadAttributeList(cfg->attr_list); for (i = 0; i < cfg->num_hnd; i++) CloseHandle(cfg->inherited_hnds[i]); free(cfg->crt_buff); free(cfg->inherited_hnds); free(cfg->attr_list); *cfg = (struct startup_config) {.num_fd = 0}; } /** * startup_config_init() - Initial a child process startup config * @cfg: startup config to init * @num_map number of element in the @fd_map array * @fd_map: array of file descriptor remapping to pass into the child * * This function allocate and configure all data necessary to setup a * STARTUPINFOEX structure meant to be used in a call to CreateProcess(). * * Return: 0 in case of success, -1 otherwise with error state set. */ static int startup_config_init(struct startup_config* cfg, int num_map, const struct mm_remap_fd* fd_map) { int num_fd; num_fd = get_highest_child_fd(num_map, fd_map)+1; // We need to take consider at least std file descriptors if (num_fd < 3) num_fd = 3; *cfg = (struct startup_config) { .num_fd = num_fd, }; if (startup_config_allocate_internals(cfg) || startup_config_setup_mappings(cfg, MM_NELEM(std_fd_mappings), std_fd_mappings) || startup_config_setup_mappings(cfg, num_map, fd_map) || startup_config_dup_inherited_hnds(cfg)) { startup_config_deinit(cfg); return -1; } return 0; } /** * concat_strv() - Concatenate an array of null-terminated strings * @strv: NULL-terminated array of null-terminated strings (may be NULL) * @sep: separator to include between the strings * * This function transform into UTF-16 and concatenate the strings found in * the @strv arrayThe character @sep will be put between each concatenated * strings. * * Return: NULL if @strv is NULL, otherwise the concatenated string encoded * in UTF-16 */ static char16_t* concat_strv(char* const* strv, char16_t sep) { int i, len, tot_len, rem_len; char16_t * concatstr, * ptr; // This is a legit possibility (for example envp can be NULL and this // is the value that must then be returned) if (!strv) return NULL; // Compute the total length for allocating the concatanated string // (including null termination) tot_len = 1; for (i = 0; strv[i]; i++) { len = get_utf16_buffer_len_from_utf8(strv[i]); if (len < 0) return NULL; tot_len += len; } concatstr = malloc(tot_len*sizeof(*concatstr)); if (!concatstr) return NULL; // Do actual concatenation. Only the first element will not be prefixed // by the separator ptr = concatstr; for (i = 0; strv[i]; i++) { if (i != 0) *(ptr++) = sep; rem_len = tot_len - (ptr - concatstr); len = conv_utf8_to_utf16(ptr, rem_len, strv[i]); ptr += len-1; } // Null terminate the concatanated string *ptr = L'\0'; return concatstr; } /** * set_char() - write char in a buffer offset if buffer is not NULL * @str: string buffer (can be NULL) * @offset: offset in @str at which the character must be written * @c: byte to write * * If @str is NULL, this function does nothing. This simple function is * actually an helper to escape_cmd_str() to handle the case when the * destination string is NULL */ static void set_char(char* str, int offset, char c) { if (str) str[offset] = c; } /** * escape_cmd_str() - espace a command line argument string * @dst: buffer where to write escaped string (can be NULL) * @src: NULL terminated string that must be escaped. * * This function takes the string @src and transform it into an escaped * string suitable for consumption in CreateProcess() command line argument * such a way that the string will be recognized as a unique argument * following the interpretation detailed at * https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw * * Please note that the terminal byte will be written in the escaped string * and is taken into account in the returned number of bytes written. * * If @dst is NULL, nothing will be written @dst but the number of bytes * required to generate the escaped string will be returned. * * Return: The number of byte written or needed by @dst. */ static int escape_cmd_str(char* restrict dst, const char* restrict src) { int i = 0, nbackslash = 0; char c; // Write initial quotation mark set_char(dst, i++, '"'); while (1) { c = *src++; if (c == '\0') break; switch (c) { case '\\': nbackslash++; break; case '"': while (nbackslash) { set_char(dst, i++, '\\'); set_char(dst, i++, '\\'); nbackslash--; } set_char(dst, i++, '\\'); set_char(dst, i++, '"'); break; default: while (nbackslash) { set_char(dst, i++, '\\'); nbackslash--; } set_char(dst, i++, c); break; } } // Write final quotation mark while (nbackslash) { set_char(dst, i++, '\\'); set_char(dst, i++, '\\'); nbackslash--; } set_char(dst, i++, '"'); set_char(dst, i++, '\0'); return i; } /** * escape_argv() - clone argument array into an escaped argument array * @argv: NULL terminated array of NULL terminated argument strings * * Generate an argument array identical to @argv excepting that the string * will be escaped (with quotation mark) so that when concatenated and * separated by space, the resulting commandline (after UTF-16 * transformation), when supplied to CommandLineToArgvW(), the generate * argument will be the same as @argv (modulo the UTF-8/UTF-16 * transformation). * * This transformation is necessary because without, an argument containing * one or more spaces will be parse/split into 2 or more argument in the * child process. * * The resulting array in allocated in one block of memory which contains * both the array of string pointer and the different strings. * * Return: the escaped argument array in case of success. This must be * cleanup by a sole call to free() with the returned pointer. NULL in case * of failure with error state set accordingly. */ static char** escape_argv(char* const* argv) { char** esc_argv; char * ptr, * newptr; intptr_t* index_strv; int i, len, index, maxlen, argc; // Count number of argument in the array for (argc = 0; argv[argc]; argc++) ; // Do an initial allocation maxlen = 256; ptr = malloc(maxlen); if (!ptr) goto failure; index_strv = (intptr_t*)ptr; index = (argc+1)*sizeof(char*); for (i = 0; i < argc; i++) { // Save offset of the i-th argument string index_strv[i] = index; // Compute the space needed for the escaped string len = escape_cmd_str(NULL, argv[i]); // Check allocated block suffices and realloc if necessary if (index + len > maxlen) { // Readjust maxlen to ensure new block fits for (; maxlen < index + len; maxlen *= 2) ; // Realloc memory block newptr = realloc(ptr, maxlen); if (!newptr) goto failure; ptr = newptr; index_strv = (intptr_t*)ptr; } // Write the actual escaped string index += escape_cmd_str(ptr+index, argv[i]); } // Transform offset of allocated memory wrt base pointer of the // allocated memory into actual string pointer array esc_argv = (char**)index_strv; for (i = 0; i < argc; i++) esc_argv[i] = ptr + index_strv[i]; // Null terminate the array of pointer. esc_argv[argc] = NULL; return esc_argv; failure: free(ptr); mm_raise_from_errno("failed to escape argv"); return NULL; } /** * translate_exitcode_in_status() - translate Win32 exit code into mmlib one * @exitcode: Win32 exitcode * * This function will identify the error case from @exitcode and translate * them into a signal value if applicable, or return the reported exitcode * in case of normal exit. * * Return: a status code meant to be returned by mm_wait_process() */ static int translate_exitcode_in_status(DWORD exitcode) { int status = 0; // Normal exit case if (exitcode < 0xC0000000) { status = exitcode & MM_WSTATUS_CODEMASK; status |= MM_WSTATUS_EXITED; return status; } // Error cases switch (exitcode) { case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: case EXCEPTION_GUARD_PAGE: case EXCEPTION_STACK_OVERFLOW: case EXCEPTION_DATATYPE_MISALIGNMENT: status = SIGSEGV; break; case EXCEPTION_FLT_DENORMAL_OPERAND: case EXCEPTION_FLT_DIVIDE_BY_ZERO: case EXCEPTION_FLT_INEXACT_RESULT: case EXCEPTION_FLT_INVALID_OPERATION: case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_FLT_STACK_CHECK: case EXCEPTION_FLT_UNDERFLOW: case EXCEPTION_INT_DIVIDE_BY_ZERO: case EXCEPTION_INT_OVERFLOW: status = SIGFPE; break; case EXCEPTION_ILLEGAL_INSTRUCTION: status = SIGILL; break; case STATUS_CONTROL_C_EXIT: status = SIGINT; break; default: status = SIGABRT; break; } status |= MM_WSTATUS_SIGNALED; return status; } /** * contains_dirsep() - indicate whether argument has a directory separator * @path: string to test * * Return: 1 if @path has at least one \ or / character */ static int contains_dirsep(const char* path) { char c; for (; *path != '\0'; path++) { c = *path; if (is_path_separator(c)) return 1; } return 0; } /** * search_bin_in_path() - search executable in folders listed in PATH * @base: basename of the executable * * Return: the path of the executable found on an allocated block of memory * in case of success. In such case the path returned must be cleanup using * free() when no longer needed. In case of failure, NULL is returned with * error state set accordingly. */ static char* search_bin_in_path(const char* base) { const char * dir, * eos; char * path, * new_path, * ext; int baselen, dirlen, len, maxlen, error, rv, i; const char* search_exts[] = {"", ".exe"}; baselen = strlen(base); path = NULL; maxlen = 0; // Get the PATH environment variable. dir = mm_getenv("PATH", DEFAULT_PATHSEARCH); error = ENOENT; while (dir[0] != '\0') { // Compute length of the dir component (ie until next ;) eos = strchr(dir, ';'); dirlen = eos ? (eos - dir) : (int)strlen(dir); // Realloc path if too small (take into account the extension) len = dirlen + baselen + 6; if (len > maxlen) { new_path = realloc(path, len); if (!new_path) { mm_raise_from_errno("Cannot allocate path"); free(path); return NULL; } path = new_path; maxlen = len; } // Form the path base on the dir component memcpy(path, dir, dirlen); path[dirlen] = '\\'; memcpy(path + dirlen + 1, base, baselen+1); // Get pointer to extension substring in path ext = path + dirlen + 1 + baselen; // Search a candidate by trying all the possible extension for (i = 0; i < MM_NELEM(search_exts); i++) { strcpy(ext, search_exts[i]); // Check the candidate path exist and can be run rv = mm_check_access(path, X_OK); if (rv == 0) return path; if (rv == EACCES) error = EACCES; } // Move to next dir component (maybe end of list) dir += dirlen; if (dir[0] == ';') dir++; } mm_raise_error(error, "Cannot find %s executable in PATH", base); free(path); return NULL; } /************************************************************************** * * * Spawn functions and wait * * * **************************************************************************/ /** * spawn_process() - spawn a new process * @pid: pointer to variable that will receive PID of new process * @path: path to the executable file * @num_map number of element in the @fd_map array * @fd_map: array of file descriptor remapping to pass into the child * @flags: spawn flags * @argv: null-terminated array of string containing the command * arguments (starting with command). Can be NULL. * @envp: null-terminated array of strings specifying the environment * of the executed program. If it is NULL, it inherit its * environment from the calling process * * Backend to mm_spawn(), this will have the same behavior as mm_spawn() but * return an handle to the created process. * * Return: handle to the created process in case of success, * INVALID_HANDLE_VALUE in case of failure with error state set accordingly. */ static HANDLE spawn_process(DWORD* pid, const char* path, int num_map, const struct mm_remap_fd* fd_map, char* const* argv, char* const* envp) { PROCESS_INFORMATION proc_info; struct startup_config cfg; BOOL res; int path_u16_len; char16_t* path_u16; char16_t* cmdline = NULL; char16_t* concat_envp = NULL; HANDLE proc_hnd = INVALID_HANDLE_VALUE; path_u16_len = get_utf16_buffer_len_from_utf8(path); if (path_u16_len < 0) { mm_raise_from_w32err("Invalid UTF-8 path"); goto exit; } // Transform the provided argument and environment arrays into their // concatanated form: argument must be separated by spaces and // environment variables by '\0' (See doc of CreateProcess()). // envp is allowed to be null (meaning keep same env for child), and // NULL must then be passed to CreateProcees() cmdline = concat_strv(argv, L' '); concat_envp = concat_strv(envp, L'\0'); if (!cmdline || (envp && !concat_envp)) { mm_raise_from_w32err("Failed to format cmdline or environment"); goto exit; } // Fill the STARTUPINFO struct. The list of inherited handle will be // written there. if (startup_config_init(&cfg, num_map, fd_map)) goto exit; // Create process with a temporary UTF-16 version of path path_u16 = mm_malloca(path_u16_len * sizeof(*path_u16)); conv_utf8_to_utf16(path_u16, path_u16_len, path); res = CreateProcessW(path_u16, cmdline, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, concat_envp, NULL, startup_config_get_startup_info(&cfg), &proc_info); mm_freea(path_u16); // We no longer need the configured STARTUPINFO startup_config_deinit(&cfg); if (res == FALSE) { mm_raise_from_w32err("Cannot exec \"%s\"", path); goto exit; } // We use only the handle of new process, no its main thread CloseHandle(proc_info.hThread); proc_hnd = proc_info.hProcess; *pid = proc_info.dwProcessId; exit: free(cmdline); free(concat_envp); return proc_hnd; } /* doc in posix implementation */ API_EXPORTED int mm_spawn(mm_pid_t* child_pid, const char* path, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp) { char* default_argv[] = {(char*)path, NULL}; char** esc_argv; HANDLE hnd; DWORD pid; int retval = -1; char* actual_path = NULL; if (!path) return mm_raise_error(EINVAL, "path must not be NULL"); if (flags & ~(MM_SPAWN_KEEP_FDS | MM_SPAWN_DAEMONIZE)) return mm_raise_error(EINVAL, "Invalid flags (%08x)", flags); if (flags & MM_SPAWN_KEEP_FDS) return mm_raise_error(ENOTSUP, "MM_SPAWN_KEEP_FDS " "not supported yet"); if (!argv) argv = default_argv; esc_argv = escape_argv(argv); if (!esc_argv) return -1; // Search executable in PATH if path does not contains dirsep if (!contains_dirsep(path)) { path = actual_path = search_bin_in_path(path); if (!path) goto exit; } hnd = spawn_process(&pid, path, num_map, fd_map, esc_argv, envp); if (hnd == INVALID_HANDLE_VALUE) goto exit; if (child_pid && !(flags & MM_SPAWN_DAEMONIZE)) { add_to_children_list(pid, hnd); *child_pid = pid; } else { CloseHandle(hnd); } retval = 0; exit: free(actual_path); free(esc_argv); return retval; } /* doc in posix implementation */ API_EXPORTED int mm_wait_process(mm_pid_t pid, int* status) { DWORD exitcode; HANDLE hnd; hnd = get_handle_from_children_list(pid); if (hnd == NULL) { mm_raise_error(ECHILD, "Cannot find process %lli in the list " "of children", pid); return -1; } // Wait for the process to be signaled (ie to stop) WaitForSingleObject(hnd, INFINITE); GetExitCodeProcess(hnd, &exitcode); drop_child_from_children_list(pid); CloseHandle(hnd); if (status) *status = translate_exitcode_in_status(exitcode); return 0; } /************************************************************************** * * * mm_execv() implementation * * * **************************************************************************/ static HANDLE child_hnd_to_exit = INVALID_HANDLE_VALUE; MM_DESTRUCTOR(waited_child) { union { LPTHREAD_START_ROUTINE thr_start; FARPROC farproc; } cast_fn; HMODULE hmod; if (child_hnd_to_exit == INVALID_HANDLE_VALUE) return; // Get pointer of ExitProcess in this process. Given how windows load // dll, we are ensured that the address will be the same in the child // process. union is used to cast function type to avoid compiler // warnings (no compiler should warn this). hmod = GetModuleHandle("kernel32.dll"); cast_fn.farproc = GetProcAddress(hmod, "ExitProcess"); // Create remotely a thread in child process that will call // ExitProcess(STATUS_CONTROL_C_EXIT); CreateRemoteThread(child_hnd_to_exit, NULL, 0, cast_fn.thr_start, (LPVOID)STATUS_CONTROL_C_EXIT, 0, NULL); CloseHandle(child_hnd_to_exit); child_hnd_to_exit = NULL; } /* doc in posix implementation */ API_EXPORTED int mm_execv(const char* path, int num_map, const struct mm_remap_fd* fd_map, int flags, char* const* argv, char* const* envp) { DWORD exitcode; mm_pid_t pid; HANDLE hnd; if (!path) return mm_raise_error(EINVAL, "path must not be NULL"); if (flags & ~MM_SPAWN_KEEP_FDS) return mm_raise_error(EINVAL, "Invalid flags (%08x)", flags); if (mm_spawn(&pid, path, num_map, fd_map, flags, argv, envp)) return -1; // Get handle of child process (and remove it from list) and mark // it as the process to exit if the current process receive an // early exit hnd = child_hnd_to_exit = get_handle_from_children_list(pid); drop_child_from_children_list(pid); close_all_known_fds(); // Wait for the process to be signaled (ie to stop) WaitForSingleObject(hnd, INFINITE); child_hnd_to_exit = INVALID_HANDLE_VALUE; // Transfer child process exit code untouched GetExitCodeProcess(hnd, &exitcode); CloseHandle(hnd); ExitProcess(exitcode); } mmlib-1.4.2/src/profile.c000066400000000000000000000507561435717460000152350ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include "mmprofile.h" #include "mmpredefs.h" #include "mmtime.h" #include "mmsysio.h" #define SEC_IN_NSEC 1000000000 #define NUM_TS_MAX 16 #define MAX_LABEL_LEN 64 #define VALUESTR_LEN 8 #define UNITSTR_LEN 2 #define UNIT_MASK \ (PROF_FORCE_NSEC|PROF_FORCE_USEC|PROF_FORCE_MSEC|PROF_FORCE_SEC) #define NUM_COL_MAX 5 #define MIN(a, b) ((a) <= (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define ABS(a) ((a) > 0 ? (a) : (-a)) /** * struct unit - time unit definition * @scale: scale at which the unit must be preferred * @name: name which must appear in the textual results * @forcemask: mask which force the unit to be used. */ struct unit { int64_t scale; char name[8]; int forcemask; }; static const struct unit unit_list[] = { {1L, "ns", PROF_FORCE_NSEC}, {1000L, "us", PROF_FORCE_USEC}, {1000000L, "ms", PROF_FORCE_MSEC}, {1000000000L, "s", PROF_FORCE_SEC}, }; #define NUM_UNIT ((int)(sizeof(unit_list)/sizeof(unit_list[0]))) /************************************************************************** * * * Approximate median estimate * * * **************************************************************************/ /** * DOC: * The approximate median algorithm use the FAME algorithm. This is a streaming * estimation of the median which converge to the actual median. Details can be * found at: * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.7376&rep=rep1&type=pdf */ #define STEP_NEED_INIT INT64_MAX struct median_estimator { int64_t median; int64_t step; }; static void median_estimator_init(struct median_estimator* me) { // Defer initialization to when the first data will be seen me->step = STEP_NEED_INIT; } static void median_estimator_update(struct median_estimator* me, int64_t data) { int64_t diff; int64_t step = me->step; int64_t median = me->median; // Perform actual initialization if (UNLIKELY(step == STEP_NEED_INIT)) { me->median = data; me->step = MAX(ABS(data/2), SEC_IN_NSEC); return; } diff = data - median; if (diff > 0) { median += step; } else if (diff < 0) { median -= step; } if (ABS(diff) < me->step) { step /= 2; } else { // Increase step by a epsilon (epsilon = step / 16) step += MAX((step >> 4), 1); } me->step = step; me->median = median; } static int64_t median_estimator_getvalue(struct median_estimator* me) { return me->median; } /************************************************************************** * * * Profile data * * * **************************************************************************/ static int clock_id; // Clock type to use to measure time static int num_ts; // Maximum number of points of measure used so far static int next_ts; // Index of the next point of measure slot. 0 is for // the measure done by mm_tic() static int num_iter; // Number of iteration recorded so far static int64_t toc_overhead; // Overhead of a mm_tic()/mm_toc() call static struct mm_timespec timestamps[NUM_TS_MAX]; // current iteration measure static int64_t max_diff_ts[NUM_TS_MAX]; // max time difference static int64_t min_diff_ts[NUM_TS_MAX]; // min time difference static int64_t sum_diff_ts[NUM_TS_MAX]; // sum of time difference overall static struct median_estimator median_diff_ts[NUM_TS_MAX]; // approximate // median of time // diff static char* labels[NUM_TS_MAX]; static char label_storage[MAX_LABEL_LEN*NUM_TS_MAX]; /************************************************************************** * * * Internal implementation * * * **************************************************************************/ /** * get_diff_ts() - Estimate the time difference between 2 consecutive points * @i: Index of the point. The difference will be computed between the * (i-1)-th and the (i)-th timestamp. (index 0 correspond to mm_tic()) * * Compute the time difference between 2 consecutive points at indices (i-1) * and (i). If it is not called at the initialization, the computed * difference takes into account the overhead of mm_tic() and mm_toc(). This * means that if the difference correspond to 2 consecutive call to mm_toc(), * the estimated difference (in absence of cold cache) should be close to 0 * ns (depends on the clock used). * * There is no argument validation, so 0 must NOT be passed. * * Returns: the time differences in nanoseconds */ static int64_t get_diff_ts(int i) { int64_t diff; diff = (timestamps[i].tv_sec - timestamps[i-1].tv_sec)*SEC_IN_NSEC; diff += timestamps[i].tv_nsec - timestamps[i-1].tv_nsec; diff -= toc_overhead; return diff; } /** * update_diffs() - Update the statistics of timestamp difference * * This function is meant to be called at the end of all tic/toc iteration. * It updates the min, max and sum (for mean) of the time difference based * on the previous iteration. */ static void update_diffs(void) { int i; int64_t diff; for (i = 1; i < next_ts; i++) { diff = get_diff_ts(i); min_diff_ts[i] = MIN(diff, min_diff_ts[i]); max_diff_ts[i] = MAX(diff, max_diff_ts[i]); sum_diff_ts[i] += diff; median_estimator_update(&median_diff_ts[i], diff); } } /** * reset_diffs() - reset the statistics of timestamp difference * * Reset the min, max, sum of the time differences. Also the maximum number * of timestamps that have been used so far. */ static void reset_diffs(void) { int i; next_ts = 0; num_ts = 0; num_iter = 0; for (i = 0; i < NUM_TS_MAX; i++) { min_diff_ts[i] = INT64_MAX; max_diff_ts[i] = 0L; sum_diff_ts[i] = 0L; median_estimator_init(&median_diff_ts[i]); } } /** * estimate_toc_overhead() - Estimate the overhead of call to mm_tic/mm_toc * * The estimation is done by several call to mm_tic mm_toc after resetting the * toc overhead to 0. Only the min value provide insight of the actual * overhead. * * NOTE: This approach of measuring call overhead can work only if the calls * to mm_toc() and mm_tic() are not optimized, ie, the prologues are not * skipped because the functions are in the same dynamic shared object. This * is ensured by setting a default visibility (ie API_EXPORTED_RELOCATABLE) * to the mm_tic() and mm_toc() functions. */ static void estimate_toc_overhead(void) { int i; reset_diffs(); toc_overhead = 0; for (i = 0; i < 1000; i++) { mm_tic(); mm_toc(); mm_toc(); mm_tic(); mm_toc_label(""); mm_toc_label(""); // Remove the first measure to avoid cold cache effect if (i == 0) reset_diffs(); } toc_overhead = MIN(min_diff_ts[1], min_diff_ts[2]); } /** * local_toc() - Measure the current timestamp * * Measures the current time into the next timestamp and advances it. If * applicable, increase the maximum number of timestamps that have been * measured within a same iteration. */ static inline void local_toc(void) { struct mm_timespec ts; mm_gettime(clock_id, &ts); if (next_ts == NUM_TS_MAX-1) return; timestamps[next_ts] = ts; if (next_ts >= num_ts) num_ts = next_ts+1; next_ts++; } /************************************************************************** * * * result display helpers * * * **************************************************************************/ /** * max_label_len() - Get the maximum length of registered labels * * Returns: the maximum length */ static int max_label_len(void) { int i, max, len; max = 0; for (i = 1; i < num_ts; i++) { len = 2; if (labels[i]) len = strlen(labels[i]); max = MAX(max, len); } // Ensure that return length will not overflow (even by mistake). BTW, // this helps the compiler to know that sprintf("%*s"...) will not // overflow if (max > MAX_LABEL_LEN-1) max = MAX_LABEL_LEN-1; return max; } /** * compute_requested_timings() - Compute and store result in an array * @mask: mask of the requested timings computation * @num_points: number of time measure (ie number of call to mm_toc()) * @data: array (num_col x @num_points) receiving the results * * Returns: the number of different timing computation requested, i.e. the * number of columns in @data array. */ static int compute_requested_timings(int mask, int num_points, int64_t data[]) { int i, icol = 0; double mean; int64_t median; if (mask & PROF_CURR) { for (i = 0; i < num_points; i++) { data[i + icol*num_points] = get_diff_ts(i+1); } icol++; } if (mask & PROF_MEAN) { for (i = 0; i < num_points; i++) { mean = (double)sum_diff_ts[i+1] / num_iter; data[i + icol*num_points] = mean; } icol++; } if (mask & PROF_MIN) { for (i = 0; i < num_points; i++) { data[i + icol*num_points] = min_diff_ts[i+1]; } icol++; } if (mask & PROF_MAX) { for (i = 0; i < num_points; i++) { data[i + icol*num_points] = max_diff_ts[i+1]; } icol++; } if (mask & PROF_MEDIAN) { for (i = 0; i < num_points; i++) { median = median_estimator_getvalue(&median_diff_ts[i+1]); data[i + icol*num_points] = median; } icol++; } return icol; } /** * get_display_unit() - get the index of suitable unit * @num_points: number of rows in @data (number of call to mm_toc()) * @num_cols: number of columns in @data * @data: array (num_col x @num_points) holding the results * @mask: mask supplied by user to possibly force use of a unit * * Returns: index of the suitable unit in unit_list array */ static int get_display_unit(int num_points, int num_cols, int64_t data[], int mask) { int i; int64_t minval, maxval, scale; // Use the specified unit if one has been forced by the mask for (i = 0; i < NUM_UNIT; i++) { if (unit_list[i].forcemask == (mask & UNIT_MASK)) return i; } minval = INT64_MAX; maxval = 0; for (i = 0; i < num_cols*num_points; i++) { minval = MIN(minval, data[i]); maxval = MAX(maxval, data[i]); } // select the most suitable unit based on the min and max value for (i = 0; i < NUM_UNIT-1; i++) { scale = unit_list[i].scale; if ((minval < scale*100 && maxval < scale*10000) || (maxval - minval) < scale) break; } return i; } /** * format_header_line() - print the result table header in string * @mask: the requested timing computations * @label_width: maximum length of a registered label * @str: output string * * Returns: number of bytes written in the output string */ static int format_header_line(int mask, int label_width, char str[]) { int len; len = sprintf(str, "%*s |", label_width, ""); if (mask & PROF_CURR) { len += sprintf(str+len, "%*s %*s |", VALUESTR_LEN, "current", UNITSTR_LEN, ""); } if (mask & PROF_MEAN) { len += sprintf(str+len, "%*s %*s |", VALUESTR_LEN, "mean", UNITSTR_LEN, ""); } if (mask & PROF_MIN) { len += sprintf(str+len, "%*s %*s |", VALUESTR_LEN, "min", UNITSTR_LEN, ""); } if (mask & PROF_MAX) { len += sprintf(str+len, "%*s %*s |", VALUESTR_LEN, "max", UNITSTR_LEN, ""); } if (mask & PROF_MEDIAN) { len += sprintf(str+len, "%*s %*s |", VALUESTR_LEN, "median", UNITSTR_LEN, ""); } str[len++] = '\n'; memset(str+len, '-', len-1); len += len-1; str[len++] = '\n'; return len; } /** * format_result_line() - print a line of the result table * @ncol: number of columns in @data * @num_points: number of rows in @data (number of call to mm_toc()) * @v: index of the desired line in the table (first is 0) * @unit_index: index of the unit to use to display the result * @label_width: maximum length of a registered label * @data: array (num_col x @num_points) containing the results * @str: output string * * Returns: number of bytes written in the output string */ static int format_result_line(int ncol, int num_points, int v, int unit_index, int label_width, const int64_t data[], char str[]) { int i, len; double value, scale = unit_list[unit_index].scale; const char* unitname = unit_list[unit_index].name; if (labels[v+1]) len = sprintf(str, "%*s |", label_width, labels[v+1]); else len = sprintf(str, "%*i |", label_width, v+1); for (i = 0; i < ncol; i++) { value = data[i*num_points+v]/scale; len += sprintf(str+len, "%*.2f %*s |", VALUESTR_LEN, value, UNITSTR_LEN, unitname); } str[len++] = '\n'; return len; } /** * full_mm_write() - full write of buffer succeed or error is reported * @fd: file descriptor to write to * @buf: buffer to transfer * @len: size of @buf * * Return: 0 if full buffer has been written to @fd, -1 otherwise with error * state set accordingly */ static int full_mm_write(int fd, const void* buf, size_t len) { const char* cbuf = buf; ssize_t rsz; while (len) { rsz = mm_write(fd, cbuf, len); if (rsz < 0) return -1; len -= rsz; cbuf += rsz; } return 0; } /************************************************************************** * * * API implementation * * * **************************************************************************/ /** * mm_tic() - Start a iteration of profiling * * Update the timing statistics with the previous data if applicable and * reset the metadata for a new timing iteration. Finally measure the * timestamp of the iteration start. * * NOTE: Contrary to the usual API functions, mm_tic() uses the attribute * API_EXPORTED_RELOCATABLE. This is done on purpose. See NOTE of * estimate_toc_overhead(). */ API_EXPORTED_RELOCATABLE void mm_tic(void) { update_diffs(); next_ts = 0; num_iter++; local_toc(); } /** * mm_toc() - Add a new point of measure to the current timing iteration * * NOTE: Contrary to the usual API functions, mm_toc() uses the attribute * API_EXPORTED_RELOCATABLE. This is done on purpose. See NOTE of * estimate_toc_overhead(). */ API_EXPORTED_RELOCATABLE void mm_toc(void) { local_toc(); } /** * mm_toc_label() - Add a new point of measure associated with a label * @label: string to appear in front of measure point at result display * * This function is the same as mm_toc() excepting it provides a way to label * the meansure point. Beware than only the first occurrence of a label * associated with a measure point will be retained. Any subsequent call to * mm_toc_label() at the same measure point index will be the same as calling * mm_toc(). * * NOTE: Contrary to the usual API functions, mm_toc_label() uses the * attribute API_EXPORTED_RELOCATABLE. This is done on purpose. See NOTE of * estimate_toc_overhead(). */ API_EXPORTED_RELOCATABLE void mm_toc_label(const char* label) { // Copy label if it the first time to appear if (!labels[next_ts]) { labels[next_ts] = &label_storage[next_ts*MAX_LABEL_LEN]; strncpy(labels[next_ts], label, MAX_LABEL_LEN-1); } local_toc(); } /** * mm_profile_print() - Print the timing statistics gathered so far * @mask: combination of flags indicating statistics must be printed * @fd: file descriptor to which the statistics must be printed * * Print the timing statistics on the file descriptor specified by fd. The * printed statistics between each consecutive point of measure is * controlled by the mask parameter which will a bitwise-or'd combination of * the following flags : * * - PROF_CURR: display the value of the current iteration * - PROF_MIN: display the min value since the last reset * - PROF_MAX: display the max value since the last reset * - PROF_MEDIAN: display the median value since the last reset * - PROF_FORCE_NSEC: force result display in nanoseconds * - PROF_FORCE_USEC: force result display in microseconds * - PROF_FORCE_MSEC: force result display in milliseconds * - PROF_FORCE_SEC: force result display in seconds * * Returns: 0 in case of success, -1 otherwise with errno set accordingly * * See: mm_profile_reset(), mm_tic(), write() */ API_EXPORTED int mm_profile_print(int mask, int fd) { int i, ncol, num_points, label_width, unit_index; char str[512]; size_t len; int64_t data[NUM_COL_MAX*NUM_TS_MAX]; update_diffs(); label_width = max_label_len(); num_points = num_ts-1; ncol = compute_requested_timings(mask, num_points, data); unit_index = get_display_unit(ncol, num_points, data, mask); for (i = 0; i < num_ts; i++) { if (i == 0) len = format_header_line(mask, label_width, str); else len = format_result_line(ncol, num_points, i-1, unit_index, label_width, data, str); // Write line to file if (full_mm_write(fd, str, len)) return -1; } sprintf(str, "toc overhead = %li ns\n", (long)toc_overhead); return full_mm_write(fd, str, strlen(str)); } /** * mm_profile_get_data - Retrieve profile result programmatically * @measure_point: measure point whose statistic must be get * @type: type of statistic (PROF_[CURR|MIN|MEAN|MAX|MEDIAN]) * * Return: statistic value in nanosecond */ API_EXPORTED int64_t mm_profile_get_data(int measure_point, int type) { int64_t data[NUM_TS_MAX]; int num_points, mask; if (measure_point >= num_ts-1) return -1; // Validate input type (can be only one measure type, not // combination of multiple flags) switch (type) { case PROF_CURR: case PROF_MIN: case PROF_MEAN: case PROF_MAX: case PROF_MEDIAN: break; default: return -1; } num_points = num_ts-1; mask = type|PROF_FORCE_NSEC; compute_requested_timings(mask, num_points, data); return data[measure_point]; } /** * mm_profile_reset() - Reset the statistics and change the timer * @flags: bit-OR combination of flags influencing the reset behavior. * * Reset the timing statistics, ie, reset the min, max, mean values as well * as the number of point used in one iteration and the associated labels. * Additionally it provides a ways to change the type of timer used for * measure. * * The @flags arguments allows one to change the behavior of the reset. If the * PROF_RESET_CPUCLOCK flag is set, it will use a timer based on CPU's * instruction counter. This timer has a very fine granularity (of the order of * very few nanoseconds) but it does not take measure time spent on sleeping * while waiting certain event (disk io, mutex/cond, etc...). This timer * indicates the processing power spent on tasks. * * Alternatively if PROF_RESET_CPUCLOCK is not set, it will use a timer * based on wall clock. This timer has bigger granularity (order of hundred * nanoseconds) and report time spent at sleeping. The indicates the * realtime update will performing certain task. * * If the PROF_RESET_KEEPLABEL flag is set in the @flags argument, the * labels associated with each measure point will be kept over the reset. * In practice, this provides a way to avoid the overhead of of label copy * when using mm_toc_label(): an initial iteration will copy the label and * the measurements are reset after this first iteration while keeping the * label. Then the subsequent call to mm_toc_label() will not be affected by * the string copy overhead. * * At startup, the function are configured to use CPU based timer. * * See: mm_profile_print(), mm_tic(), mm_toc_label() */ API_EXPORTED void mm_profile_reset(int flags) { unsigned int i; if (flags & PROF_RESET_CPUCLOCK) clock_id = MM_CLK_CPU_PROCESS; else clock_id = MM_CLK_MONOTONIC; estimate_toc_overhead(); reset_diffs(); if (!(flags & PROF_RESET_KEEPLABEL)) { labels[0] = label_storage; for (i = 1; i < sizeof(labels)/sizeof(labels[0]); i++) labels[i] = NULL; } } mmlib-1.4.2/src/pshared-lock.c000066400000000000000000000325341435717460000161430ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "pshared-lock.h" #include "lock-referee-proto.h" #include "mmsysio.h" #include "mmpredefs.h" #include "utils-win32.h" #include "atomic-win32.h" #include "mutex-lockval.h" #include "mmlog.h" #include "mmlib.h" #include #define LOCK_REFEREE_SERVER_BIN LIBEXECDIR "/lock-referee.exe" #define PIPE_WAIT_MS 50 #ifdef LOCKSERVER_IN_MMLIB_DLL static void spawn_lockserver_thread(void) { mm_thread_t thid; mm_thr_create(&thid, lockserver_thread_routine, NULL); mm_thr_detach(thid); } static void try_spawn_lockserver(void) { static mm_once_t lockserver_once = MM_THR_ONCE_INIT; mm_once(&lockserver_once, spawn_lockserver_thread); } #else //!LOCKSERVER_IN_MMLIB_DLL static void spawn_lock_server(HANDLE pipe) { STARTUPINFOEX info = {.StartupInfo = {.cb = sizeof(info)}}; LPPROC_THREAD_ATTRIBUTE_LIST attrlist = NULL; SIZE_T attrlist_bufsize; const char* binpath; char* cmdline = NULL; PROCESS_INFORMATION proc_info; // Determine the path of the referee server binpath = mm_getenv("MMLIB_LOCKREF_BIN", LOCK_REFEREE_SERVER_BIN); // cmdline is formatted with "binpath 0x" cmdline = mm_malloca(strlen(binpath) + 1 + 2 + 16 + 1); if (!cmdline) goto exit; sprintf(cmdline, "%s 0x%016llx", binpath, (unsigned long long)pipe); // Allocate attribute list InitializeProcThreadAttributeList(NULL, 1, 0, &attrlist_bufsize); attrlist = mm_malloca(attrlist_bufsize); if (!attrlist) goto exit; // Setup attribute list with the pipe handle to inherit InitializeProcThreadAttributeList(attrlist, 1, 0, &attrlist_bufsize); UpdateProcThreadAttribute(attrlist, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &pipe, sizeof(pipe), NULL, NULL); // Spawn detached process (ie, close child process handles) info.lpAttributeList = attrlist; if (!CreateProcess(binpath, cmdline, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &info.StartupInfo, &proc_info)) { mm_raise_from_w32err("Unable to spawn \"%s\"", cmdline); } else { // Success case: close handle to child CloseHandle(proc_info.hThread); CloseHandle(proc_info.hProcess); } DeleteProcThreadAttributeList(attrlist); exit: mm_freea(attrlist); mm_freea(cmdline); } static void try_spawn_lockserver(void) { HANDLE pipe, inheritable_pipe, proc_hnd; // It is normal if the pipe creation failed. This happens if another // lockserver has been openned concurrently. Hence we should be quiet. pipe = create_srv_first_pipe(); if (pipe == INVALID_HANDLE_VALUE) return; // Make pipe inheritable proc_hnd = GetCurrentProcess(); if (!DuplicateHandle(proc_hnd, pipe, proc_hnd, &inheritable_pipe, 0, TRUE, DUPLICATE_SAME_ACCESS)) { mm_raise_from_w32err("Unable to duplicate pipe handle"); return; } CloseHandle(pipe); spawn_lock_server(inheritable_pipe); CloseHandle(inheritable_pipe); } #endif //!LOCKSERVER_IN_MMLIB_DLL /** * connect_to_lockref_server() - establish connection to lock referee server * * This function will try and retry to establish the connection to the lock * referee server. If it fails at the first attempt, it will try to spawn * the server process here. It is safe to try creating the server * concurrently in the different threads/processes because only one instance * can get the server endpoint of the lock referee named pipe. * * Return: handle of connection to the server */ static HANDLE connect_to_lockref_server(void) { HANDLE pipe = INVALID_HANDLE_VALUE; BOOL ret; DWORD open_mode = GENERIC_READ|GENERIC_WRITE; DWORD pipe_mode = PIPE_READMODE_MESSAGE; while (pipe == INVALID_HANDLE_VALUE) { // Wait until a server instance of named pipe is available. // If failed, try to spawn a server instance and retry ret = WaitNamedPipe(referee_pipename, PIPE_WAIT_MS); if (!ret) { try_spawn_lockserver(); continue; } // Try Connect named pipe to server. If failed, it will // restart over. open_mode = GENERIC_READ|GENERIC_WRITE; pipe = CreateFile(referee_pipename, open_mode, 0, NULL, OPEN_EXISTING, 0, NULL); } SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL); return pipe; } /** * get_lockval_change() - get change to undo the effect of one dead thread * @lockval: current lock value * @is_waiter: non zero if the dead thread was reported as waiter of mutex * @tid: ID of the dead thread * * Return: the different to add to the lock value to undo the effect of the * dead thread. */ static int64_t get_lockval_change(int64_t lockval, int is_waiter, DWORD tid) { if (is_thread_mtx_owner(lockval, tid)) { return MTX_NEED_RECOVER_MASK - mtx_lockval(tid, 0, 0); } if (is_thread_mtx_waiterlist_owner(lockval, tid)) return -mtx_lockval(0, tid, 1); if (is_waiter) return -mtx_lockval(0, 0, 1); return 0; } /** * cleanup_mutex_lock() - perform mutex cleanup job * @hmap: handle of file mapping containing the cleanup job * @shlock: description of shared lock (contains pointer of lock value) * @ack_msg: data to use to reply to server. * * Perform a cleanup job sent by the lock referee server. This function * inspect the state of the mutex lock value and check it against the list * of dead thread provided by lock referee server. For each dead thread, it * will compute the change to apply to the lock value to undo the effect on * it introduced by the dead thread. At the end, it prepares the response to * sent back to server (need of waiter wakeup will be indicated here). */ static NOINLINE void cleanup_mutex_lock(HANDLE hmap, struct shared_lock shlock, struct lockref_req_msg* ack_msg) { struct mutex_cleanup_job* job; int i; int64_t lockval, cleanup_val; DWORD tid; int is_waiter; // Map the cleanup job send by the lock referee job = MapViewOfFile(hmap, FILE_MAP_ALL_ACCESS, 0, 0, 0); CloseHandle(hmap); // Inspect the current value of the shared lock lockval = atomic_load(shlock.ptr); // Find the modification to apply for each dead onto the lock value cleanup_val = 0; for (i = 0; i < job->num_dead; i++) { tid = job->deadlist[i].tid; is_waiter = job->deadlist[i].is_waiter; cleanup_val += get_lockval_change(lockval, is_waiter, tid); } // Apply the combined cleanup and compute the resulting value // (atomic_fetch_add() returns the previous value before the add) lockval = atomic_fetch_add(shlock.ptr, cleanup_val); lockval += cleanup_val; // Notify the cleanup job is done job->num_dead = 0; // Prepare reply to sent to server ack_msg->opcode = LOCKREF_OP_CLEANUP_DONE; ack_msg->cleanup.key = shlock.key; ack_msg->cleanup.num_wakeup = 0; if (is_mtx_waited(lockval) && !is_mtx_locked(lockval)) ack_msg->cleanup.num_wakeup = 1; } /** * create_robust_data() - request robust data to lock referee server * @pipe: handle to connection pipe to the server * * Return: pointer to the robust data (memory shared with the server) */ static struct robust_data* create_robust_data(HANDLE pipe) { DWORD rsz; BOOL ret; struct lockref_resp_msg response; struct lockref_req_msg request; void* ptr; struct robust_data* robust_data; // Setup the request to the server request.opcode = LOCKREF_OP_GETROBUST; request.getrobust.num_keys = 0; // Send the request and get reply ret = TransactNamedPipe(pipe, &request, sizeof(request), &response, sizeof(response), &rsz, NULL); mm_check(ret && (rsz == sizeof(response))); // map the handle retuned by server ptr = MapViewOfFile(response.hmap, FILE_MAP_ALL_ACCESS, 0, 0, 0); mm_check(ptr != NULL); CloseHandle(response.hmap); robust_data = ptr; // Setup the robust data with thread info that the server could not // have. robust_data->thread_id = get_tid(); return robust_data; } /** * init_lock_referee_connection() - Initialize lock referee connection * @conn: connection data to initialize * * This function establish the connection with the lock referee server * (spawning it on the way if necessary) and get the robust data to use. This * function is meant to be called once for each thread that need interact * with process shared mutex or conditions, ie, @conn is assumed to point * to thread local data. */ static NOINLINE void init_lock_referee_connection(struct lockref_connection* conn) { if (conn->is_init) return; conn->pipe = connect_to_lockref_server(); conn->robust_data = create_robust_data(conn->pipe); conn->is_init = 1; } /** * deinit_lock_referee_connection() - cleanup a connection to lock server * @conn: connection data to cleanup * * Finish the connection pointed in @conn. This function is meant to be * called at the termination of a thread. Terminating the connection will * indicate the lock server that the thread is terminated and will inspect * in the robust data of the terminated thread (the server can access it * since it is memory mapped there as well) and launch cleanup if necessary * (like a mutex kept locked at thread termination). */ LOCAL_SYMBOL void deinit_lock_referee_connection(struct lockref_connection* conn) { if (!conn->is_init) return; // Close connection to the lock server CloseHandle(conn->pipe); conn->pipe = INVALID_HANDLE_VALUE; // Unmap robust data UnmapViewOfFile(conn->robust_data); conn->robust_data = NULL; conn->is_init = 0; } /** * pshared_get_robust_data() - get the robust data * @conn: data of connection to the lock server */ LOCAL_SYMBOL struct robust_data* pshared_get_robust_data(struct lockref_connection* conn) { // Init lock server connection if not done yet if (UNLIKELY(!conn->is_init)) init_lock_referee_connection(conn); return conn->robust_data; } /** * pshared_init_lock() - generates a key for a new lock * @conn: data of connection to the lock server * * Return: key of a new lock */ LOCAL_SYMBOL int64_t pshared_init_lock(struct lockref_connection* conn) { DWORD rsz; BOOL ret; struct lockref_resp_msg response; struct lockref_req_msg request; // Init lock server connection if not done yet if (UNLIKELY(!conn->is_init)) init_lock_referee_connection(conn); // Prepare INITLOCK request, send it and wait for reply request.opcode = LOCKREF_OP_INITLOCK; ret = TransactNamedPipe(conn->pipe, &request, sizeof(request), &response, sizeof(response), &rsz, NULL); mm_check(ret && (rsz == sizeof(response))); return response.key; } /** * pshared_wait_on_lock() - make calling thread wait on specified lock * @conn: data of connection to the lock server * @lock: lock description * @wakeup_val: threshold for waking the thread * @timeout: pointer to lock timeout (NULL if infinite wait) * * This sets the calling thread asleep until the deadline provided in * @timeout is reached or another thread has waken up the lock referenced by * the key @lock.key with a wakeup value equal or bigger than @wakeup_val. * * Return: 0 if the thread has been waken up due to normal wakeup, ETIMEDOUT * the wait has timed out. */ LOCAL_SYMBOL int pshared_wait_on_lock(struct lockref_connection* conn, struct shared_lock lock, int64_t wakeup_val, const struct lock_timeout* timeout) { DWORD rsz; BOOL ret; struct lockref_resp_msg response; struct lockref_req_msg request; // Prepare wait request request.opcode = LOCKREF_OP_WAIT; request.wait.key = lock.key; request.wait.val = wakeup_val; if (timeout) { request.wait.timeout = timeout->ts; request.wait.clk_flags = timeout->clk_flags; } else { request.wait.clk_flags = 0; } // Send wait request and wait for server reply. Server might // (occasionally) reply with a request to cleanup the lock. If this // happens, do the job and acknowedge it. Redo this until the server // reply without a cleanup job request. while (1) { ret = TransactNamedPipe(conn->pipe, &request, sizeof(request), &response, sizeof(response), &rsz, NULL); mm_check(ret && (rsz == sizeof(response))); if (UNLIKELY(response.respcode == LOCKREF_OP_CLEANUP)) cleanup_mutex_lock(response.hmap, lock, &request); else break; } return response.timedout ? ETIMEDOUT : 0; } /** * pshared_wake_lock() - wake a process shared lock * @conn: connection of the thread to the lock referee server * @shlock: shared lock * @val: wakeup value * @num_wakeup: minimum number of thread to wakeup * * This function indicates the lock referee server to wakeup at least * @num_wakeup thread that are waiting for the lock described by @shlock and * whose wakeup value are lower or equal to @val. */ LOCAL_SYMBOL void pshared_wake_lock(struct lockref_connection* conn, struct shared_lock shlock, int64_t val, int num_wakeup) { DWORD rsz; BOOL ret; struct lockref_resp_msg response; struct lockref_req_msg request; // Prepare wake request request.opcode = LOCKREF_OP_WAKE; request.wake.key = shlock.key; request.wake.val = val; request.wake.num_wakeup = num_wakeup; // Send request to lock server and wait for reply ret = TransactNamedPipe(conn->pipe, &request, sizeof(request), &response, sizeof(response), &rsz, NULL); mm_check(ret && (rsz == sizeof(response))); } mmlib-1.4.2/src/pshared-lock.h000066400000000000000000000036451435717460000161510ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef PSHARED_LOCK_H #define PSHARED_LOCK_H #include #include #include "lock-referee-proto.h" /** * struct lockref_connection - data handling connection to lock referee server * @pipe: handle to a client end of the named pipe to lock server * @robust_data: pointer to the memory map of the robust data * @is_init: 1 if the structure is initialized, 0 otherwise. If it is not * initialized, the other fields in the structure hold * meaningless value. */ struct lockref_connection { HANDLE pipe; struct robust_data* robust_data; int is_init; }; /** * struct shared_lock - description of a process shared lock * @key: key of the lock known by lock server * @ptr: pointer to the lock value shared by all lock users (points * likely to memory mapped data if associated mutex or * condition is shared with other processes). */ struct shared_lock { int64_t key; int64_t* ptr; }; /** * struct lock_timeout - data indicating a timeout for a lock * @clk_flags: flag indicating the clock type to use for timeout. Must be * one of the WAITCLK_FLAG_* flag value * @ts: absolute time of the timeout, @clk_flags specifies clock base */ struct lock_timeout { int clk_flags; struct mm_timespec ts; }; void deinit_lock_referee_connection(struct lockref_connection* conn); struct robust_data* pshared_get_robust_data(struct lockref_connection* conn); int64_t pshared_init_lock(struct lockref_connection* conn); int pshared_wait_on_lock(struct lockref_connection* conn, struct shared_lock lock, int64_t wakeup_val, const struct lock_timeout* timeout); void pshared_wake_lock(struct lockref_connection* conn, struct shared_lock lock, int64_t val, int num_wakeup); #endif /* ifndef PSHARED_LOCK_H */ mmlib-1.4.2/src/shm-posix.c000066400000000000000000000275261435717460000155230ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmsysio.h" #include "mmerrno.h" #include #include #include #include #include #include #include #include /************************************************************************** * * * tracking of block of mapping * * * **************************************************************************/ #define MIN_NUM_ENTRIES 64 /** * struct map_entry - memory map entry * @ptr: starting address of mapping * @len: length of mapping */ struct map_entry { void* ptr; size_t len; }; /** * struct mapping_list - list keeping track of effective memory mapping * @entries: array of map entries * @num_entries: number of memory mapping * @num_max_entries: length of @entries array * @mtx: mutex protecting modification of list */ struct mapping_list { struct map_entry* entries; int num_entries; int num_max_entries; pthread_mutex_t mtx; }; /** * mapping_list_cleanup() - cleanup memory mapping list * @list: mapping list to cleanup * * Typically called when program exit (or library unload) to frees data used * to keep track of memory mapping. */ static void mapping_list_cleanup(struct mapping_list* list) { free(list->entries); list->num_entries = 0; list->num_max_entries = 0; } /** * mapping_list_add_entry() - register a new map entry in mapping list * @list: mapping list to modify * @ptr: starting address of mapping * @len: length of mapping * * Return: 0 in case of success, -1 otherwise with error state set */ static int mapping_list_add_entry(struct mapping_list* list, void* ptr, size_t len) { int retval = 0; int num_max; struct map_entry* entries; struct map_entry* entry; pthread_mutex_lock(&list->mtx); entries = list->entries; num_max = list->num_max_entries; // Resize entries array if too small if (num_max <= list->num_entries) { if (num_max < MIN_NUM_ENTRIES) num_max = MIN_NUM_ENTRIES; else num_max *= 2; entries = realloc(entries, num_max*sizeof(*entries)); if (!entries) { mm_raise_from_errno("Can't allocate mapping list"); retval = -1; goto exit; } list->entries = entries; list->num_max_entries = num_max; } // Add mapping block to the mapping list entry = &entries[list->num_entries++]; entry->ptr = ptr; entry->len = len; exit: pthread_mutex_unlock(&list->mtx); return retval; } /** * mapping_list_remove_entry() - unregister a map entry from mapping list * @list: mapping list to modify * @ptr: starting address of mapping to remove * * Return: A non negative integer corresponding to the size of the mapping * in case of success, -1 otherwise with error state set */ static ssize_t mapping_list_remove_entry(struct mapping_list* list, void* ptr) { int i; bool found; size_t mapping_size = -1; struct map_entry* entries; pthread_mutex_lock(&list->mtx); entries = list->entries; // Search the mapping in the entries found = false; for (i = 0; i < list->num_entries; i++) { if (ptr == entries[i].ptr) { found = true; break; } } // Check the mapping has been found if (!found) goto exit; // Remove mapping for list mapping_size = entries[i].len; memmove(entries + i, entries + i+1, (list->num_entries-i-1)*sizeof(*entries)); list->num_entries--; exit: pthread_mutex_unlock(&list->mtx); if (!found) { mm_raise_error(EFAULT, "Address do no refer to any mapping"); return -1; } return mapping_size; } static struct mapping_list file_mapping_list = { .mtx = PTHREAD_MUTEX_INITIALIZER }; MM_DESTRUCTOR(mapping_cleanup) { mapping_list_cleanup(&file_mapping_list); } /** * mapblock_add() - register a memory mapping in global mapping list * @ptr: starting address of mapping * @len: length of mapping * * Return: 0 in case of success, -1 otherwise with error state set */ static int mapblock_add(void* ptr, size_t len) { return mapping_list_add_entry(&file_mapping_list, ptr, len); } /** * mapblock_remove() - Remove a mapping from global mapping list * @ptr: starting address of mapping to remove * * Return: A non negative integer corresponding to the size of the mapping * in case of success, -1 otherwise with error state set */ static ssize_t mapblock_remove(void* ptr) { return mapping_list_remove_entry(&file_mapping_list, ptr); } /************************************************************************** * * * File mapping and shared memory object APIs * * * **************************************************************************/ /** * get_mmap_prot() - get the mmap memory protection of the mapping * @mflags: flags passed to mm_mapfile * * Return: the memory protection value suitable for mmap() */ static int get_mmap_prot(int mflags) { int prot = PROT_NONE; if (mflags & MM_MAP_READ) prot |= PROT_READ; if (mflags & MM_MAP_WRITE) prot |= PROT_WRITE; if (mflags & MM_MAP_EXEC) prot |= PROT_EXEC; return prot; } /** * get_mmap_flags() - get the mmap flags * @mflags: flags passed to mm_mapfile * * Return: visibility flags for mmap() */ static int get_mmap_flags(int mflags) { int flags = 0; if (mflags & MM_MAP_SHARED) flags |= MAP_SHARED; else flags |= MAP_PRIVATE; return flags; } /** * mm_mapfile() - map pages of memory * @fd: file descriptor of file to map in memory * @offset: offset within the file from which the mapping must start * @len: length of the mapping * @mflags: control how the mapping is done * * The mm_mapfile() function establishes a mapping between a process' * address space and a portion or the entirety of a file or shared memory * object represented by @fd. The portion of the object to map can be * controlled by the parameters @offset and @len. @offset must be a multiple * of page size. * * The flags in parameters @mflags determines whether read, write, execute, * or some combination of accesses are permitted to the data being mapped. * The requested access can of course cannot grant more permission than the * one associated with @fd. It must be one of the following flags : * * MM_MAP_SHARED * Modifications to the mapped data are propagated to the underlying object. * MM_MAP_PRIVATE * Modifications to the mapped data will be visible only to the calling * process and shall not change the underlying object. * * In addition to the previous flag, @mflags can contain a OR-combination * of the following flags : * * MM_MAP_READ * Data can be read * MM_MAP_WRITE * Data can be written * MM_MAP_EXEC * Data can be executed * MM_MAP_RDWR * alias to MM_MAP_READ|MM_MAP_WRITE * * The mm_mapfile() function adds an extra reference to the file associated * with the file descriptor @fd which is not removed by a subsequent * mm_close() on that file descriptor. This reference will be removed when * there are no more mappings to the file. * * On windows platforms only, when mapping a file, the requested @size MUST * be lesser than or equal to the size of the file. Raise EOVERFLOW otherwise. * * Return: The starting address of the mapping in case of success. * Otherwise NULL is returned and error state is set accordingly. */ API_EXPORTED void* mm_mapfile(int fd, mm_off_t offset, size_t len, int mflags) { int prot, flags; void* addr; prot = get_mmap_prot(mflags); flags = get_mmap_flags(mflags); addr = mmap(NULL, len, prot, flags, fd, offset); if (addr == MAP_FAILED) { mm_raise_from_errno("mmap failed"); return NULL; } // Register memory mapping if (mapblock_add(addr, len)) { munmap(addr, len); return NULL; } return addr; } /** * mm_unmap() - unmap pages of memory * @addr: starting address of memory block to unmap * * Remove a memory mapping previously established. @addr must be NULL or must * have been returned by a successful call to mm_mapfile(). If @addr is NULL, * mm_unmap() do nothing. * * Return: 0 in case of success, -1 otherwise with error state set. */ API_EXPORTED int mm_unmap(void* addr) { ssize_t len; if (!addr) return 0; // Get memory mapping, retrieve its size and forget it. len = mapblock_remove(addr); if (len < 0) return -1; if (munmap(addr, len)) { mm_raise_from_errno("munmap failed"); return -1; } return 0; } /** * mm_anon_shm() - Creates an anonymous memory object * * This function creates an anonymous shared memory object (ie nameless) and * establishes a connection between it and a file descriptor. If successful, * the file descriptor for the shared memory object is the lowest numbered * file descriptor not currently open for that process. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_anon_shm(void) { static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; int fd; char name[32]; sprintf(name, "/mmlib-shm-%u", getpid()); pthread_mutex_lock(&mtx); fd = shm_open(name, O_RDWR|O_CREAT|O_EXCL, 0); if (fd != -1) shm_unlink(name); pthread_mutex_unlock(&mtx); if (fd == -1) return mm_raise_from_errno("anon_shm() failed"); return fd; } /** * mm_shm_open() - open a shared memory object * @name: name of the shared memory object * @oflag: flags controlling the open operation * @mode: permission if object is created * * This function establishes a connection between a shared memory object and * a file descriptor. The @name argument points to a string naming a shared * memory object. If successful, the file descriptor for the shared memory * object is the lowest numbered file descriptor not currently open for that * process. * * The file status flags and file access modes of the open file description * are according to the value of @oflag. It must contains the exactly one of * the following: %O_RDONLY, %O_RDWR. It can contains any combination of the * remaining flags: %O_CREAT, %O_EXCL, %O_TRUNC. The meaning of these * constant is exactly the same as for mm_open(). * * When a shared memory object is created, the state of the shared memory * object, including all data associated with the shared memory object, * persists until the shared memory object is unlinked and all other * references are gone. It is unspecified whether the name and shared memory * object state remain valid after a system reboot. * * Once a new shared memory object has been created, it can be removed with * a call to mm_shm_unlink(). * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_shm_open(const char* name, int oflag, int mode) { int fd; fd = shm_open(name, oflag, mode); if (fd == -1) return mm_raise_from_errno("shm_open(%s, ...) failed", name); return fd; } /** * mm_shm_unlink() - Removes the name of the shared memory object named by the * string pointed to by @name. * @name: file name to unlink * * If one or more references to the shared memory object exist when the object * is unlinked, the name is removed before mm_shm_unlink() returns, but the * removal of the memory object contents is postponed until all open and * map references to the shared memory object have been removed. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_shm_unlink(const char* name) { if (shm_unlink(name)) return mm_raise_from_errno("shm_unlink(%s) failed", name); return 0; } mmlib-1.4.2/src/shm-win32.c000066400000000000000000000125401435717460000153110ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmsysio.h" #include "mmlib.h" #include "mmerrno.h" #include #include #include #include #include #include "utils-win32.h" /** * get_win32_page_protect() - Get page protection and access mask * @mflags: flags passed to mm_mapfile * @protect_mask: pointer to mask specifying the page protect for file * mapping creation (output variable) * @access_mask: location of mask that will specify the access during * the creation of the map view (output variable) * * This function translate the flags passed to mm_mapfile() into page * protection mask to be used by CreateFileMapping() and access mask to be * used by MapViewOfFile(). */ static void get_win32_page_protect(int mflags, DWORD* protect_mask, DWORD* access_mask) { DWORD protect = 0; DWORD access = 0; if (mflags & MM_MAP_READ) { protect = PAGE_READONLY; access = FILE_MAP_READ; } if (mflags & MM_MAP_WRITE) { protect = PAGE_READWRITE; access = FILE_MAP_ALL_ACCESS; } if (!(mflags & MM_MAP_SHARED)) access = FILE_MAP_COPY; if (mflags & MM_MAP_EXEC) { protect <<= 4; access |= FILE_MAP_EXECUTE; } *protect_mask = protect; *access_mask = access; } /* doc in posix implementation */ API_EXPORTED void* mm_mapfile(int fd, mm_off_t offset, size_t len, int mflags) { HANDLE hfile, hmap; DWORD protect, access, off_h, off_l; struct mm_stat stat; void* ptr; if (unwrap_handle_from_fd(&hfile, fd)) return NULL; get_win32_page_protect(mflags, &protect, &access); // Create the Filemapping object hmap = CreateFileMapping(hfile, NULL, protect, 0, 0, NULL); if (hmap == NULL) { mm_raise_from_w32err("CreateFileMapping failed (fd=%i)", fd); return NULL; } // Map into memory off_h = offset >> 32; off_l = offset & 0xffffffff; ptr = MapViewOfFile(hmap, access, off_h, off_l, len); if (!ptr) { /* when trying to map a file, if the request length is larger * than the size of the file itself, windows returns a generic * permission error. Transform it into EINVAL instead */ if (GetLastError() == ERROR_ACCESS_DENIED && get_stat_from_handle(hfile, &stat) == 0 && S_ISREG(stat.mode) && stat.size < (mm_off_t) len) { mm_raise_error(EOVERFLOW, "CreateFileMapping failed (fd=%i). " "requested len (%d) > file size (%d)", fd, stat.size, len); } else { mm_raise_from_w32err("MapViewOfFile failed (fd=%i)", fd); } } CloseHandle(hmap); return ptr; } /* doc in posix implementation */ API_EXPORTED int mm_unmap(void* addr) { if (!addr) return 0; if (!UnmapViewOfFile(addr)) return mm_raise_from_w32err("UnmapViewOfFile failed"); return 0; } /************************************************************************** * * * SHM access per se * * * **************************************************************************/ #define MAX_ATTEMPT 32 #define MAX_NAME 256 #define SHMROOT_PATH_MAXLEN 512 /** * get_shm_path() - return the path of directory to be stored SHM objects * * This function returns the path where the files backing the shared memory * objects are located. If this folder do not exist yet, it will create it. * Multiple executions of the functions will lead to the same return value. * * Return: path where to store SHM object. */ static const char* get_shm_path(void) { static char shm_root[SHMROOT_PATH_MAXLEN] = ""; const char* tmp_root; // Check the shm root path has not already be determined if (shm_root[0] != '\0') return shm_root; // Form shm root path tmp_root = mm_getenv("TMP", "C:\\WINDOWS\\TEMP"); snprintf(shm_root, sizeof(shm_root), "%s/shm", tmp_root); // Create folder _mkdir(shm_root); return shm_root; } /* doc in posix implementation */ API_EXPORTED int mm_anon_shm(void) { HANDLE hnd; int i, fd; LARGE_INTEGER count; unsigned int randval; char filename[SHMROOT_PATH_MAXLEN + MAX_NAME + 8]; for (i = 0; i < MAX_ATTEMPT; i++) { // Generate random data filename QueryPerformanceCounter(&count); randval = (unsigned int)count.LowPart; sprintf(filename, "%s/mmlib-shm-%u", get_shm_path(), randval); // Try open the file hnd = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL); if (hnd != INVALID_HANDLE_VALUE) break; } if (hnd == INVALID_HANDLE_VALUE) { mm_raise_from_w32err("CreateFile(%s) failed", filename); return -1; } if (wrap_handle_into_fd(hnd, &fd, FD_TYPE_NORMAL)) { CloseHandle(hnd); return -1; } return fd; } /* doc in posix implementation */ API_EXPORTED int mm_shm_open(const char* name, int oflag, int mode) { char filename[SHMROOT_PATH_MAXLEN + MAX_NAME + 8]; sprintf(filename, "%s/%s", get_shm_path(), name); return mm_open(filename, oflag, mode); } /* doc in posix implementation */ API_EXPORTED int mm_shm_unlink(const char* name) { char filename[SHMROOT_PATH_MAXLEN + MAX_NAME + 8]; sprintf(filename, "%s/%s", get_shm_path(), name); return mm_unlink(filename); } mmlib-1.4.2/src/socket-internal.h000066400000000000000000000010321435717460000166630ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef SOCKET_INTERNAL_H #define SOCKET_INTERNAL_H int internal_getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res, char* errmsg); int internal_getnameinfo(const struct sockaddr* addr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags, char* errmsg); #endif /* ifndef SOCKET_INTERNAL_H */ mmlib-1.4.2/src/socket-posix.c000066400000000000000000000701601435717460000162140ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include "mmsysio.h" #include "mmerrno.h" #include "socket-internal.h" #include union posix_sockopt { int ival; struct timeval timeout; }; /** * mm_socket() - create an endpoint for communication * @domain: communications domain in which a socket is to be created * @type: type of socket to be created * @protocol: particular protocol to be used with the socket. If 0, the * default protocol for the socket domain and type is used. * * The mm_socket() function creates an unbound socket in a communications * domain, and return a file descriptor that can be used in later function * calls that operate on sockets. The allocated file descriptor is the * lowest numbered file descriptor not currently open for that process. * * The @domain argument specifies the address family used in the * communications domain. The address families supported by the system are * system-defined. However you can expect that the following to be * available : * * %AF_UNSPEC * The address family is unspecified. * %AF_INET * The Internet Protocol version 4 (IPv4) address family. * %AF_INET6 * The Internet Protocol version 6 (IPv6) address family. * * The @type argument specifies the socket type, which determines the * semantics of communication over the socket. The following socket types * are defined; Some systems may specify additional types : * * SOCK_STREAM * Provides sequenced, reliable, bidirectional, connection-mode byte * streams * SOCK_DGRAM * Provides datagrams, which are connectionless-mode, unreliable messages * of fixed maximum length. * * The @protocol specifies a particular protocol to be used with the socket. * Normally only a single protocol exists to support a particular socket type * within a given protocol family, in which case protocol can be specified as * 0. However, it is possible that many protocols may exist, in which case a * particular protocol must be specified in this manner. The protocol number to * use is specific to the “communication domain” in which communication is to * take place. Refer to the documentation of your system to know the supported * protocols and their protocol number. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_socket(int domain, int type, int protocol) { int fd; fd = socket(domain, type, protocol); if (fd < 0) return mm_raise_from_errno("socket(%i, %i, %i) failed", domain, type, protocol); return fd; } /** * mm_bind() - bind a name to a socket * @sockfd: file descriptor of the socket to be bound * @addr: points to a &struct sockaddr containing the address bind * @addrlen: length of the &struct sockaddr pointed to by @addr * * This assigns a local socket address @addr to a socket identified by * descriptor @sockfd that has no local socket address assigned. Socket * created with mm_socket() are initially unnamed; they are identified only * by their address family. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen) { if (bind(sockfd, addr, addrlen) < 0) return mm_raise_from_errno("bind() failed"); return 0; } /** * mm_getsockname() - returns the current address to which sockfd is bound * @sockfd: file descriptor to which the socket is bound * @addr: points to a &struct sockaddr containing the bound address * @addrlen: length of the &struct sockaddr pointed to by @addr * * getsockname() returns the current address to which the socket sockfd is * bound, in the buffer pointed to by @addr. * The @addrlen argument should be initialized to indicate the amount of space * (in bytes) pointed to by addr. On return it contains the actual size of the * socket address. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_getsockname(int sockfd, struct sockaddr * addr, socklen_t * addrlen) { if (getsockname(sockfd, addr, addrlen) < 0) return mm_raise_from_errno("getsockname() failed"); return 0; } /** * mm_getpeername() - get name of connected peer socket * @sockfd: file descriptor to which the socket is bound * @addr: points to a &struct sockaddr containing the peer address * @addrlen: length of the &struct sockaddr pointed to by @addr * * This obtains the address of the peer connected to the socket @sockfd in * the buffer pointed to by @addr. The @addrlen argument should be * initialized to indicate the amount of space (in bytes) pointed to by * addr. On return it contains the actual size of the socket address. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_getpeername(int sockfd, struct sockaddr * addr, socklen_t * addrlen) { if (getpeername(sockfd, addr, addrlen) < 0) return mm_raise_from_errno("getpeername() failed"); return 0; } /** * mm_listen() - listen for socket connections * @sockfd: file descriptor of the socket that must listen * @backlog: hint for the queue limit * * This mark a connection-mode socket, specified by the @sockfd argument, as * accepting connections. The @backlog argument provides a hint to the * implementation which the implementation shall use to limit the number of * outstanding connections in the socket's listen queue. * * return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_listen(int sockfd, int backlog) { if (listen(sockfd, backlog) < 0) return mm_raise_from_errno("listen() failed"); return 0; } /** * mm_accept() - accept a new connection on a socket * @sockfd: file descriptor of a listening socket * @addr: NULL or pointer to &struct sockaddr containing the address * of accepted socket * @addrlen: a pointer to a &typedef socklen_t object which on input * specifies the length of the supplied &struct sockaddr * structure, and on output specifies the length of the stored * address. It can be NULL if @addr is NULL. * * This extracts the first connection on the queue of pending connections, * create a new socket with the same socket type protocol and address family * as the specified socket, and allocate a new file descriptor for that * socket. The allocated file descriptor is the lowest numbered file * descriptor not currently open for that process. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { int fd; fd = accept(sockfd, addr, addrlen); if (fd < 0) return mm_raise_from_errno("accept() failed"); return fd; } /** * mm_connect() - connect a socket to a peer * @sockfd: file descriptor of the socket * @addr: pointer to &struct sockaddr containing the peer address * @addrlen: length of the supplied &struct sockaddr pointed by @addr * * This attempt to make a connection on a connection-mode socket or to set * or reset the peer address of a connectionless-mode socket. If the socket * has not already been bound to a local address, mm_connect() shall bind it * to an address which is an unused local address. * * If the initiating socket is not connection-mode, then mm_connect() sets * the socket's peer address, and no connection is made. For %SOCK_DGRAM * sockets, the peer address identifies where all datagrams are sent on * subsequent mm_send() functions, and limits the remote sender for * subsequent mm_recv() functions. * * If the initiating socket is connection-mode, then mm_connect() attempts * to establish a connection to the address specified by the @addr argument. * If the connection cannot be established immediately and the call will * block for up to an unspecified timeout interval until the connection is * established. * * return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen) { if (connect(sockfd, addr, addrlen) < 0) return mm_raise_from_errno("connect() failed"); return 0; } /** * mm_setsockopt() - set the socket options * @sockfd: file descriptor of the socket * @level: protocol level at which the option resides * @optname: option name * @optval: pointer to option value * @optlen: size of the option value * * This sets the option specified by the @optname argument, at the protocol * level specified by the @level argument, to the value pointed to by the * @optval argument for the socket associated with the file descriptor * specified by the @sockfd argument. * * The level argument specifies the protocol level at which the option * resides. To set options at the socket level, specify the level argument * as %SOL_SOCKET. To set options at other levels, supply the appropriate * level identifier for the protocol controlling the option. For example, to * indicate that an option is interpreted by the TCP (Transport Control * Protocol), set level to %IPPROTO_TCP. * * return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_setsockopt(int sockfd, int level, int optname, const void * optval, socklen_t optlen) { struct timeval timeout; int delay_ms; // If SO_RCVTIMEO/SO_SNDTIMEO, Posix mandates timeval structure. // Since we accept delay in ms mapped to int, we do the conversion // now. if ((level == SOL_SOCKET) && (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) { if (optlen != sizeof(int)) return mm_raise_error(EINVAL, "bad option length, " "SO_RCVTIMEO/SO_SNDTIMEO " "accepts int (timeout in ms)"); delay_ms = *((int*)optval); timeout.tv_sec = delay_ms / 1000; timeout.tv_usec = (delay_ms % 1000) * 1000; optval = &timeout; optlen = sizeof(timeout); } if (setsockopt(sockfd, level, optname, optval, optlen) < 0) return mm_raise_from_errno("setsockopt() failed"); return 0; } /** * mm_getsockopt() - get the socket options * @sockfd: file descriptor of the socket * @level: protocol level at which the option resides * @optname: option name * @optval: pointer to option value * @optlen: pointer to size of the option value on input, actual length * of option value on output * * This function retrieves the value for the option specified by the * @optname argument for the socket specified by the socket argument. If * the size of the option value is greater than @optlen, the value stored * in the object pointed to by the @optval argument shall be silently * truncated. Otherwise, the object pointed to by the @optlen argument * shall be modified to indicate the actual length of the value. * * return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_getsockopt(int sockfd, int level, int optname, void * optval, socklen_t* optlen) { int ret, delay_ms; union posix_sockopt posix_opt; socklen_t posix_optlen; posix_optlen = sizeof(posix_opt); ret = getsockopt(sockfd, level, optname, &posix_opt, &posix_optlen); if (ret) return mm_raise_from_errno("getsockopt() failed"); // If SO_RCVTIMEO/SO_SNDTIMEO, Posix mandates timeval structure. // Since we accept delay in ms mapped to int, we do the conversion // now. if ((level == SOL_SOCKET) && (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) { delay_ms = posix_opt.timeout.tv_sec * 1000; delay_ms += posix_opt.timeout.tv_usec / 1000; posix_optlen = sizeof(int); posix_opt.ival = delay_ms; } if (*optlen > posix_optlen) *optlen = posix_optlen; memcpy(optval, &posix_opt, *optlen); return ret; } /** * mm_shutdown() - shut down socket send and receive operations * @sockfd: file descriptor of the socket * @how: type of shutdown * * This causes all or part of a full-duplex connection on the socket associated * with the file descriptor socket to be shut down. The type of shutdown is * controlled by @how which can be one of the following values : * * SHUT_RD * Disables further receive operations. * SHUT_WR * Disables further send operations. * SHUT_RDWR * Disables further send and receive operations. * * return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_shutdown(int sockfd, int how) { int ret; ret = shutdown(sockfd, how); if (ret) mm_raise_from_errno("shutdown() failed"); return ret; } /** * mm_send() - send a message on a connected socket * @sockfd: socket file descriptor. * @buffer: buffer containing the message to send. * @length: the length of the message in bytes * @flags: type of message transmission * * This initiates transmission of a message from the specified socket to its * peer. The mm_send() function sends a message only when the socket is * connected (including when the peer of a connectionless socket has been * set via mm_connect()). * * @flags specifies the type of message transmission. If flags contains * MSG_OOB, the call send out-of-band data on sockets that support * out-of-band communications. The significance and semantics of out-of-band * data are protocol-specific. * * The length of the message to be sent is specified by the length argument. * If the message is too long to pass through the underlying protocol, * mm_send() will fail and no data shall be transmitted (This is typically * the case of datagram protocol, like UDP). If space is not * available at the sending socket to hold the message to be transmitted, * mm_send() will block until space is available. In the case of a stream * protocol (like TCP), there are possibility that the sent data is actually * smaller than requested (for example due to early interruption because of * signal delivery). * * Return: the number of bytes actually sent in case of success, -1 * otherwise with error state set accordingly. */ API_EXPORTED ssize_t mm_send(int sockfd, const void * buffer, size_t length, int flags) { ssize_t ret_sz; ret_sz = send(sockfd, buffer, length, flags); if (ret_sz < 0) return mm_raise_from_errno("send() failed"); return ret_sz; } /** * mm_recv() - receive a message from a socket * @sockfd: socket file descriptor. * @buffer: buffer containing the message to receive. * @length: the size of buffer pointed by @buffer * @flags: type of message reception * * This receives a message from a connection-mode or connectionless-mode * socket. It is normally used with connected sockets because it does not * permit the application to retrieve the source address of received data. * * @flags specifies the type of message reception. Values of this argument * are formed by logically OR'ing zero or more of the following values : * * %MSG_PEEK * Peeks at an incoming message. The data is treated as unread and the * next mm_recv() or similar function shall still return this data. * %MSG_OOB * Requests out-of-band data. The significance and semantics of * out-of-band data are protocol-specific. * %MSG_WAITALL * On SOCK_STREAM sockets this requests that the function block until the * full amount of data can be returned. The function may return the * smaller amount of data if the socket is a message-based socket, if a * signal is caught, if the connection is terminated, if MSG_PEEK was * specified, or if an error is pending for the socket. * * The mm_recv() function return the length of the message written to the * buffer pointed to by the buffer argument. For message-based sockets, such * as SOCK_DGRAM and SOCK_SEQPACKET, the entire message will be read in a * single operation. If a message is too long to fit in the supplied buffer, * and %MSG_PEEK is not set in the @flags argument, the excess bytes will be * discarded. For stream-based sockets, such as SOCK_STREAM, message * boundaries will be ignored. In this case, data is returned to the * user as soon as it becomes available, and no data will be discarded. * * If the MSG_WAITALL flag is not set, data will be returned only up to the * end of the first message. * * If no messages are available at the socket, mm_recv() will block until a * message arrives. * * Return: the number of bytes actually received in case of success, -1 * otherwise with error state set accordingly. */ API_EXPORTED ssize_t mm_recv(int sockfd, void * buffer, size_t length, int flags) { ssize_t ret_sz; ret_sz = recv(sockfd, buffer, length, flags); if (ret_sz < 0) return mm_raise_from_errno("recv() failed"); return ret_sz; } /** * mm_sendmsg() - send a message on a socket using a message structure * @sockfd: socket file descriptor. * @msg: message structure containing both the destination address * (if any) and the buffers for the outgoing message. * @flags: type of message transmission * * This functions send a message through a connection-mode or * connectionless-mode socket. If the socket is connectionless-mode, the * message will be sent to the address specified by @msg. If the socket * is connection-mode, the destination address in @msg is ignored. * * The @msg->msg_iov and @msg->msg_iovlen fields of message specify zero or * more buffers containing the data to be sent. @msg->msg_iov points to an * array of iovec structures; @msg->msg_iovlen is set to the dimension of * this array. In each &struct iovec structure, the &iovec.iov_base field * specifies a storage area and the &iovec.iov_len field gives its size in * bytes. Some of these sizes can be zero. The data from each storage area * indicated by @msg.msg_iov is sent in turn. * * Excepting for the specification of the message buffers and destination * address, the behavior of mm_sendmsg() is the same as mm_send(). * * Return: the number of bytes actually sent in case of success, -1 * otherwise with error state set accordingly. */ API_EXPORTED ssize_t mm_sendmsg(int sockfd, const struct msghdr * msg, int flags) { ssize_t ret_sz; ret_sz = sendmsg(sockfd, msg, flags); if (ret_sz < 0) return mm_raise_from_errno("sendmsg() failed"); return ret_sz; } /** * mm_recvmsg() - receive a message from a socket using a message structure * @sockfd: socket file descriptor. * @msg: message structure containing both the source address (if * set) and the buffers for the inbound message. * @flags: type of message reception * * This function receives a message from a connection-mode or * connectionless-mode socket. It is normally used with connectionless-mode * sockets because it permits the application to retrieve the source address * of received data. * * In the &struct mm_sock_msg structure, the &msghdr.msg_name and * &msghdr.msg_namelen members specify the source address if the socket is * unconnected. If the socket is connected, those members are ignored. The * @msg->msg_name may be a null pointer if no names are desired or required. * The @msg->msg_iov and @msg->msg_iovlen fields are used to specify where * the received data will be stored. @msg->msg_iov points to an array of * &struct iovec structures; @msg->msg_iovlen is set to the dimension of * this array. In each &struct iovec structure, the &iovec.iov_base field * specifies a storage area and the &iovec.iov_len field gives its size in * bytes. Each storage area indicated by @msg.msg_iov is filled with * received data in turn until all of the received data is stored or all of * the areas have been filled. * * The recvmsg() function returns the total length of the message. For * message-based sockets, such as SOCK_DGRAM and SOCK_SEQPACKET, the entire * message is read in a single operation. If a message is too long to fit in * the supplied buffers, and %MSG_PEEK is not set in the flags argument, the * excess bytes will be discarded, and %MSG_TRUNC will be set in * @msg->flags. For stream-based sockets, such as SOCK_STREAM, message * boundaries are ignored. In this case, data will be returned to the user * as soon as it becomes available, and no data will be discarded. * * Excepting for the specification of message buffers and source address, * the behavior of mm_recvmsg() is the same as mm_rec(). * * Return: the number of bytes actually received in case of success, -1 * otherwise with error state set accordingly. */ API_EXPORTED ssize_t mm_recvmsg(int sockfd, struct msghdr* msg, int flags) { ssize_t ret_sz; ret_sz = recvmsg(sockfd, msg, flags); if (ret_sz < 0) return mm_raise_from_errno("recvmsg failed"); return ret_sz; } /** * mm_send_multimsg() - send multiple messages on a socket * @sockfd: socket file descriptor. * @vlen: size of @msgvec array * @msgvec: pointer to an array of &struct mm_sock_multimsg structures * @flags: type of message transmission * * This function is an extension of mm_sendmsg that allows the * caller to send multiple messages to a socket using a single * call. This is equivalent to call mm_sendmsg() in a loop for each element * in @msgvec. * * On return from mm_sendmmsg(), the &struct mm_sock_multimsg.data_len fields * of successive elements of @msgvec are updated to contain the number of * bytes transmitted from the corresponding &struct mm_sock_multimsg.msg. * * Return: On success, it returns the number of messages sent from @msgvec; * if this is less than @vlen, the caller can retry with a further * mm_sendmmsg() call to send the remaining messages. On error, -1 is * returned and the error state is set accordingly. */ API_EXPORTED int mm_send_multimsg(int sockfd, int vlen, struct mm_sock_multimsg * msgvec, int flags) { int ret; struct mmsghdr* hdrvec = (struct mmsghdr*)msgvec; ret = sendmmsg(sockfd, hdrvec, vlen, flags); if (ret < 0) return mm_raise_from_errno("sendmmsg failed"); return ret; } /** * mm_recv_multimsg() - receive multiple messages from a socket * @sockfd: socket file descriptor. * @vlen: size of @msgvec array * @msgvec: pointer to an array of &struct mm_sock_multimsg structures * @flags: type of message reception * @timeout: timeout for receive operation. If NULL, the operation blocks * indefinitely * * This function is an extension of mm_sendmsg that allows the * caller to receive multiple messages from a socket using a single * call. This is equivalent to call mm_recvmsg() in a loop for each element * in @msgvec with loop break if @timeout has been reached. * * On return from mm_recvmmsg(), the &struct mm_sock_multimsg.data_len fields * of successive elements of @msgvec are updated to contain the number of * bytes received from the corresponding &struct mm_sock_multimsg.msg. * * Return: On success, it returns the number of messages received from @msgvec; * if this is less than @vlen, the caller can retry with a further * mm_recvmmsg() call to receive the remaining messages. On error, -1 is * returned and the error state is set accordingly. */ API_EXPORTED int mm_recv_multimsg(int sockfd, int vlen, struct mm_sock_multimsg * msgvec, int flags, struct mm_timespec * timeout) { int ret; struct mmsghdr* hdrvec = (struct mmsghdr*)msgvec; ret = recvmmsg(sockfd, hdrvec, vlen, flags, (struct timespec*)timeout); if (ret < 0) return mm_raise_from_errno("recvmmsg failed"); return ret; } /** * mm_getaddrinfo() - get address information * @node: descriptive name or address string (can be NULL) * @service: string identifying the requested service (can be NULL) * @hints: input values that may direct the operation by providing * options and by limiting the returned information (can be * NULL) * @res: return value that will contain the resulting linked list of * &struct addrinfo structures. * * Same as getaddrinfo() from POSIX excepting that mmlib error state will be * set in case of error. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. * * Errors: * In case of failure, the error number reported in the error state * indicates the origin of failure : * * %MM_ENONAME * The @node is not known. * %MM_ENOTFOUND * The service is not known or not available for the requested socket * type. * %EDDRNOTAVAILABLE * Host is found but does not have any network address or in the requested * family. * %EAGAIN * The name server returned a temporary failure indication. Try again * later. * %EAFNOSUPPORT * Address family is not supported or address length was invalid for * specified family * %EINVAL * Invalid salue in flags. Both @node and @service are NULL. %AI_CANONNAME * set in flags but @node is NULL. %AI_NUMERICSERV set in flags but * @service is not numeric port-number string. * %EPROTOTYPE * Requested socket type is not supported or inconsistent with protocol. * * Other error can be reported by the platform is other case not listed * above. */ API_EXPORTED int mm_getaddrinfo(const char * node, const char * service, const struct addrinfo * hints, struct addrinfo ** res) { int errnum; char errmsg[256]; errnum = internal_getaddrinfo(node, service, hints, res, errmsg); if (errnum == 0) return 0; // Handle platform specific error if (errnum == -1) { errnum = errno; mm_strerror_r(errnum, errmsg, sizeof(errmsg)); } mm_raise_error(errnum, "getaddrinfo(%s, %s) failed: %s", node, service, errmsg); return -1; } /** * mm_getnameinfo() - get name information * @addr: socket address * @addrlen: size of @addr * @host: buffer receiving the host name * @hostlen: size of buffer in @host * @serv: buffer receiving the service name * @servlen: size of buffer in @serv * @flags: control of processing of mm_getnameinfo() * * Same as getnameinfo() from POSIX excepting that mmlib error state will * be set in case of error. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_getnameinfo(const struct sockaddr * addr, socklen_t addrlen, char * host, socklen_t hostlen, char * serv, socklen_t servlen, int flags) { int errnum; char errmsg[256]; errnum = internal_getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags, errmsg); if (errnum == 0) return 0; // Handle platform specific error if (errnum == -1) { errnum = errno; mm_strerror_r(errnum, errmsg, sizeof(errmsg)); } mm_raise_error(errnum, "getnameinfo() failed: %s", errmsg); return -1; } /** * mm_freeaddrinfo() - free linked list of address * @res: linked list of addresses returned by @mm_getaddrinfo() * * Deallocate linked list of address allocated by a successful call to * mm_getaddrinfo(). If @res is NULL, mm_getnameinfo() do nothing. */ API_EXPORTED void mm_freeaddrinfo(struct addrinfo * res) { freeaddrinfo(res); } /** * mm_poll() - waits for one of a set of fd to become ready to perform I/O. * @fds: array of struct pollfd. See below. * @nfds: number of @fds passed in argument * @timeout_ms: number of milliseconds that poll() should block waiting * * In each element of @fds array, &mm_pollfd.fd should be a *socket* file * descriptor. * * If @timeout_ms is set to 0, the call will return immediately even if no file * descriptors are ready. if @timeout_ms is negative, the call will block * indefinitely. * * Negative file descriptors are ignored, with their .revent set to 0. * mm_poll() will wait the full @timeout_ms even if all the file descriptors * are negatives * * Return: * (>0) On success, the number of fds on which an event was raised * (=0) zero if poll() returned because the timeout was reached * (<0) a negative value on error */ API_EXPORTED int mm_poll(struct mm_pollfd * fds, int nfds, int timeout_ms) { int rv; rv = poll(fds, nfds, timeout_ms); if (rv < 0) return mm_raise_from_errno("poll() failed"); return rv; } mmlib-1.4.2/src/socket-win32.c000066400000000000000000000421071435717460000160140ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmlib.h" #include "mmsysio.h" #include "mmerrno.h" #include "mmpredefs.h" #include "mmthread.h" #include "socket-internal.h" #include "socket-win32.h" #include "utils-win32.h" #include #include #include // Work around missing definition is Mingw #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif #define wrap_socket_into_fd(hnd, p_fd) \ wrap_handle_into_fd((HANDLE)(hnd), p_fd, FD_TYPE_SOCKET) #define unwrap_socket_from_fd(p_hnd, fd) \ unwrap_handle_from_fd((HANDLE*)(p_hnd), fd) /************************************************************************** * * * Winsock initialization * * * **************************************************************************/ static void winsock_deinit(void) { WSACleanup(); } static void winsock_init(void) { WSADATA wsa; if (WSAStartup(MAKEWORD(2, 2), &wsa)) mm_raise_from_w32err("WSAStartup() failed"); atexit(winsock_deinit); } static int check_wsa_init(void) { static mm_thr_once_t wsa_ini_once = MM_THR_ONCE_INIT; mm_thr_once(&wsa_ini_once, winsock_init); return 0; } /************************************************************************** * * * Overlapped operation helpers * * * **************************************************************************/ /** * struct xfer_data - overlapped transfer data * @overlapped: data needed by Winsock to handle the overlapped operation * @data_len: reported transferred length when operation complete * @status: indicator reporting when the operation is still in progress * (XFER_PENDING) or completed by success (XFER_SUCCESS) or * failure (XFER_ERROR) * @flags: flags reported by the operation. Meaningful only when * a reception complete */ struct xfer_data { WSAOVERLAPPED overlapped; size_t data_len; enum {XFER_PENDING, XFER_SUCCESS, XFER_ERROR} status; DWORD flags; }; #define GET_XFER_DATA_FROM_LPO(lpo) ((struct xfer_data*)(((char*)lpo)- \ offsetof(struct \ xfer_data, \ overlapped))) /** * xfer_completion() - transfer completion callback * @error: error reported by the complete operation (0 if success) * @xfer_sz: size of the transferred data * @lpo: WSAOVERLAPPED data used during the transfer * @flags: flags set on return of received operation. Can be ignored * for send operations */ static void CALLBACK xfer_completion(DWORD error, DWORD xfer_sz, LPWSAOVERLAPPED lpo, DWORD flags) { struct xfer_data* xfer = GET_XFER_DATA_FROM_LPO(lpo); xfer->status = XFER_SUCCESS; xfer->flags = flags; xfer->data_len = xfer_sz; if (error) { xfer->status = XFER_ERROR; WSASetLastError(error); } } /** * wait_operation_xfer() - wait for a initiated transfer to complete * @xfer: pointer to a transfer submitted previously * * This function waits until the transfer specified by @xfer complete, be it * by a success of the submitted operation or failure. The transfer can have * been submitted by submit_send_xfer() or submit_recv_xfer(). * * Return: 0 the submitted operation completed successfully, -1 otherwise. */ static int wait_operation_xfer(const struct xfer_data* xfer) { while (xfer->status == XFER_PENDING) { SleepEx(INFINITE, TRUE); } return (xfer->status == XFER_SUCCESS) ? 0 : -1; } /** * submit_send_xfer() - initiate send based overlapped IO * @xfer: pointer xfer_data that will keep track of the operation * @s: WIN32 socket handle to use for send operation * @msg: message structure to holding data to send * @flags: type of message transmission * * Return: 0 in case of success, -1 otherwise (error state not set here!) */ static int submit_send_xfer(struct xfer_data* xfer, SOCKET s, const struct msghdr* msg, int flags) { int ret; WSABUF* buffs; DWORD dwflags = flags; // Safe to do: struct iovec has been defined with the exact same // memory layout as WSABUF buffs = (WSABUF*)msg->msg_iov; xfer->status = XFER_PENDING; if (msg->msg_name == NULL) { ret = WSASend(s, buffs, msg->msg_iovlen, NULL, dwflags, &xfer->overlapped, xfer_completion); } else { ret = WSASendTo(s, buffs, msg->msg_iovlen, NULL, dwflags, msg->msg_name, msg->msg_namelen, &xfer->overlapped, xfer_completion); } // Check overlapped IO submission has not failed if ((ret == SOCKET_ERROR) && (WSAGetLastError() != WSA_IO_PENDING)) return -1; return 0; } /** * submit_recv_xfer() - initiate recv based overlapped IO * @xfer: pointer xfer_data that will keep track of the operation * @s: WIN32 socket handle to use for recv operation * @msg: message structure to holding received data * @flags: type of message transmission * * Return: 0 in case of success, -1 otherwise (error state not set here!) */ static int submit_recv_xfer(struct xfer_data* xfer, SOCKET s, struct msghdr* msg, int flags) { int ret; WSABUF* buffs; DWORD dwflags = flags; // Safe to do: struct iovec has been defined with the exact same // memory layout as WSABUF buffs = (WSABUF*)msg->msg_iov; // Submit async recv. Oddly WSARecvFrom failed with UDP socket // when addr is NULL (while it works with TCP socket) proving MSDN // documentation of WSARecvFrom() wrong xfer->status = XFER_PENDING; if (msg->msg_name == NULL) { ret = WSARecv(s, buffs, msg->msg_iovlen, NULL, &dwflags, &xfer->overlapped, xfer_completion); } else { ret = WSARecvFrom(s, buffs, msg->msg_iovlen, NULL, &dwflags, msg->msg_name, &msg->msg_namelen, &xfer->overlapped, xfer_completion); } // Check overlapped IO submission has not failed if ((ret == SOCKET_ERROR) && (WSAGetLastError() != WSA_IO_PENDING)) return -1; return 0; } /************************************************************************** * * * Socket API * * * **************************************************************************/ /* doc in posix implementation */ API_EXPORTED int mm_socket(int domain, int type, int protocol) { SOCKET s; int sockfd; if (check_wsa_init()) return -1; s = WSASocketW(domain, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED|WSA_FLAG_NO_HANDLE_INHERIT); if (s == INVALID_SOCKET) return mm_raise_from_w32err("WSAsocket failed"); if (wrap_socket_into_fd(s, &sockfd)) { closesocket(s); return -1; } return sockfd; } /* doc in posix implementation */ API_EXPORTED int mm_bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (bind(s, addr, addrlen) < 0) return mm_raise_from_w32err("bind() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_getsockname(int sockfd, struct sockaddr * addr, socklen_t * addrlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (getsockname(s, addr, addrlen) < 0) return mm_raise_from_w32err("getsockname() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_getpeername(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (getpeername(s, addr, addrlen) < 0) return mm_raise_from_w32err("getpeername() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_listen(int sockfd, int backlog) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (listen(s, backlog) < 0) return mm_raise_from_w32err("listen() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) { SOCKET listening_sock, s; int conn_fd; if (check_wsa_init() || unwrap_socket_from_fd(&listening_sock, sockfd)) return -1; s = accept(listening_sock, addr, addrlen); if (s == INVALID_SOCKET) return mm_raise_from_w32err("accept() failed"); if (wrap_socket_into_fd(s, &conn_fd)) { closesocket(s); return -1; } return conn_fd; } /* doc in posix implementation */ API_EXPORTED int mm_connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (connect(s, addr, addrlen) < 0) return mm_raise_from_w32err("connect() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (setsockopt(s, level, optname, optval, optlen) < 0) return mm_raise_from_w32err("setsockopt() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_getsockopt(int sockfd, int level, int optname, void* optval, socklen_t* optlen) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (getsockopt(s, level, optname, optval, optlen) < 0) return mm_raise_from_w32err("getsockopt() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_shutdown(int sockfd, int how) { SOCKET s; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (shutdown(s, how) < 0) mm_raise_from_w32err("shutdown() failed"); return 0; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_send(int sockfd, const void* buffer, size_t length, int flags) { SOCKET s; ssize_t ret_sz; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; ret_sz = send(s, buffer, length, flags); if (ret_sz < 0) return mm_raise_from_w32err("send() failed"); return ret_sz; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_recv(int sockfd, void* buffer, size_t length, int flags) { SOCKET s; ssize_t ret_sz; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; ret_sz = recv(s, buffer, length, flags); if (ret_sz < 0) return mm_raise_from_w32err("recv() failed"); return ret_sz; } LOCAL_SYMBOL ssize_t sock_hnd_write(HANDLE hnd, const void* buffer, size_t length) { if (check_wsa_init()) return -1; return send((SOCKET)hnd, buffer, length, 0); } LOCAL_SYMBOL ssize_t sock_hnd_read(HANDLE hnd, void* buffer, size_t length) { if (check_wsa_init()) return -1; return recv((SOCKET)hnd, buffer, length, 0); } /* doc in posix implementation */ API_EXPORTED ssize_t mm_sendmsg(int sockfd, const struct msghdr* msg, int flags) { SOCKET s; struct xfer_data xfer; const char* errmsg; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (submit_send_xfer(&xfer, s, msg, flags) || wait_operation_xfer(&xfer)) { errmsg = msg->msg_name ? "WsaSendTo() failed" : "WsaSend() failed"; return mm_raise_from_w32err(errmsg); } return xfer.data_len; } /* doc in posix implementation */ API_EXPORTED ssize_t mm_recvmsg(int sockfd, struct msghdr* msg, int flags) { SOCKET s; struct xfer_data xfer; const char* errmsg; if (check_wsa_init() || unwrap_socket_from_fd(&s, sockfd)) return -1; if (submit_recv_xfer(&xfer, s, msg, flags) || wait_operation_xfer(&xfer)) { errmsg = msg->msg_name ? "WsaRecvFrom() failed" : "WsaRecv() failed"; return mm_raise_from_w32err(errmsg); } msg->msg_flags = xfer.flags; return xfer.data_len; } /* doc in posix implementation */ API_EXPORTED int mm_send_multimsg(int sockfd, int vlen, struct mm_sock_multimsg* msgvec, int flags) { int i; ssize_t ret_sz; // TODO implement mm_sendmmsg with multiple WSASendTo for (i = 0; i < vlen; i++) { ret_sz = mm_sendmsg(sockfd, &msgvec[i].msg, flags); if (ret_sz < 0) return (i == 0) ? -1 : i; msgvec[i].datalen = (unsigned int)ret_sz; } return vlen; } /* doc in posix implementation */ API_EXPORTED int mm_recv_multimsg(int sockfd, int vlen, struct mm_sock_multimsg* msgvec, int flags, struct mm_timespec* timeout) { int i; ssize_t ret_sz; (void)timeout; // TODO implement mm_recvmmsg with multiple WSARecvFrom for (i = 0; i < vlen; i++) { ret_sz = mm_recvmsg(sockfd, &msgvec[i].msg, flags); if (ret_sz < 0) return (i == 0) ? -1 : i; msgvec[i].datalen = (unsigned int)ret_sz; } return vlen; } /** * validate_hints() - validate protocol with other fields in hints * @hints: pointer to hints * @errmsg: pointer to buffer where to write message in case of error * * Return: 0 if hints are valid, the error number in case of error. */ static int validate_hints(const struct addrinfo* hints, char* errmsg) { int valid, socktype, family; if (!hints || !hints->ai_protocol) return 0; socktype = hints->ai_socktype; family = hints->ai_family; switch (hints->ai_protocol) { case IPPROTO_UDP: valid = (!socktype || socktype == SOCK_DGRAM) && (!family || family == AF_INET || family == AF_INET6); break; case IPPROTO_TCP: valid = (!socktype || socktype == SOCK_STREAM) && (!family || family == AF_INET || family == AF_INET6); break; case IPPROTO_ICMP: valid = (!socktype || socktype == SOCK_DGRAM) && (!family || family == AF_INET); break; case IPPROTO_ICMPV6: valid = (!socktype || socktype == SOCK_DGRAM) && (!family || family == AF_INET6); break; default: // If we cannot prove there is an issue, better let it pass valid = 1; break; } if (!valid) { strcpy(errmsg, "requested protocol inconsistent with " "requested family or socket type"); return EPROTOTYPE; } return 0; } /* doc in posix implementation */ API_EXPORTED int mm_getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res) { int errnum; DWORD w32err; char errmsg[256]; if (check_wsa_init()) return -1; errnum = validate_hints(hints, errmsg); if (errnum != 0) goto error_exit; errnum = internal_getaddrinfo(node, service, hints, res, errmsg); if (errnum == 0) return 0; // Handle platform specific error if (errnum == -1) { w32err = GetLastError(); errnum = get_errcode_from_w32err(w32err); write_w32err_msg(w32err, sizeof(errmsg), errmsg); } error_exit: mm_raise_error(errnum, "getaddrinfo(%s, %s) failed: %s", node, service, errmsg); return -1; } /* doc in posix implementation */ API_EXPORTED int mm_getnameinfo(const struct sockaddr* addr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags) { int errnum; DWORD w32err; char errmsg[256]; if (check_wsa_init()) return -1; errnum = internal_getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags, errmsg); if (errnum == 0) return 0; // Handle platform specific error if (errnum == -1) { w32err = GetLastError(); errnum = get_errcode_from_w32err(w32err); write_w32err_msg(w32err, sizeof(errmsg), errmsg); } mm_raise_error(errnum, "getnameinfo() failed: %s", errmsg); return -1; } /* doc in posix implementation */ API_EXPORTED void mm_freeaddrinfo(struct addrinfo* res) { freeaddrinfo(res); } /* doc in posix implementation */ API_EXPORTED int mm_poll(struct mm_pollfd* fds, int nfds, int timeout_ms) { int i, rv, flags; int all_negative; SOCKET s; struct pollfd * wfds; if (check_wsa_init()) return -1; wfds = mm_malloca(nfds * sizeof(*wfds)); if (wfds == NULL) return -1; /* ignore log errors raised when unwrapping socket from fd: * poll tolerates invalid sockets */ rv = 0; all_negative = 1; flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); for (i = 0; i < nfds; i++) { s = INVALID_SOCKET; if (fds[i].fd >= 0) { unwrap_socket_from_fd(&s, fds[i].fd); all_negative = 0; } wfds[i] = (struct pollfd) {.fd = s, .events = fds[i].events}; } mm_error_set_flags(flags, MM_ERROR_IGNORE); /* WSAPoll() does not wait on invalid sockets. * Let's sleep instead, then return 0 (as if timeout) */ if (all_negative) { mm_relative_sleep_ms(timeout_ms); goto exit; } rv = WSAPoll(wfds, nfds, timeout_ms); if (rv < 0) { rv = mm_raise_from_w32err("poll() failed"); goto exit; } for (i = 0; i < nfds; i++) { if (fds[i].fd < 0) fds[i].revents = 0; else fds[i].revents = wfds[i].revents; } exit: mm_freea(wfds); return rv; } mmlib-1.4.2/src/socket-win32.h000066400000000000000000000004041435717460000160130ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef SOCKET_WIN32_H #define SOCKET_WIN32_H #include ssize_t sock_hnd_read(HANDLE hpipe, void* buf, size_t nbyte); ssize_t sock_hnd_write(HANDLE hpipe, const void* buf, size_t nbyte); #endif /* SOCKET_WIN32_H */ mmlib-1.4.2/src/socket.c000066400000000000000000000240611435717460000150530ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif // Necessary for using some non standard error code returned by getaddrinfo() // on GNU/Linux platforms #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "mmsysio.h" #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "socket-internal.h" #include #include #ifdef _WIN32 # ifndef EAI_OVERFLOW # define EAI_OVERFLOW ERROR_BUFFER_OVERFLOW # endif # ifndef EAI_NODATA # define EAI_NODATA WSANO_DATA # endif #endif /** * is_numeric_string() - test if a string contains unsigned numeric value * @str: string to test (may be NULL) * * @str pointing to NULL or an empty string will not be considered as a * numeric value. * * Return: 1 if @str contains an unsigned numeric value, 0 otherwise. */ static int is_numeric_string(const char* str) { if (!str || *str == '\0') return 0; while (*str) { if (*str < '0' || *str > '9') return 0; str++; } return 1; } /************************************************************************** * * * Common socket internals * * * **************************************************************************/ static const struct { int eai; int errnum; const char* msg; } eai_error_cases[] = { #ifdef EAI_ADDRFAMILY {EAI_ADDRFAMILY, EADDRNOTAVAIL, "host does not have network address in requested family"}, #endif {EAI_AGAIN, EAGAIN, "The name server returned a temporary failure. Try again later."}, {EAI_FAMILY, EAFNOSUPPORT, "Address family was not recognized or address length was invalid " "for the specified family"}, {EAI_SERVICE, MM_ENOTFOUND, "Requested service not available for the requested socket type"}, {EAI_BADFLAGS, EINVAL, "invalid value in flags"}, {EAI_FAIL, EIO, "A non recoverable error occurred"}, {EAI_MEMORY, ENOMEM, "Out of memory"}, #ifdef EAI_NODATA /* RFC 2553 had both EAI_NODATA and EAI_NONAME, while RFC 3493 has only * EAI_NONAME. Some implementations define EAI_NODATA and EAI_NONAME to * the same value, others don't. This also means that the returned error * depends on the platform AND on the contacted DNS server as well as on * his upstream. * * Let's handle EAI_NODATA and EAI_NONAME the same for * more robustness of the error path. */ {EAI_NODATA, MM_ENONAME, "Node is not known"}, #endif {EAI_NONAME, MM_ENONAME, "Node is not known"}, {EAI_OVERFLOW, EOVERFLOW, "host or serv buffer is too small"}, {EAI_SOCKTYPE, EPROTOTYPE, "requested socket type not supported or inconsistent with protocol"}, }; /** * translate_eai_to_errnum() - translate EAI_* return code into error code * @eai: return value of getaddrinfo() or getnameinfo() * @errmsg: pointer to buffer that will receive the error message * * Return: the translated error code if an error case has been matched, -1 * otherwise. */ static int translate_eai_to_errnum(int eai, char* errmsg) { int i; // Try to find an eai return value that match an know error case for (i = 0; i < MM_NELEM(eai_error_cases); i++) { if (eai_error_cases[i].eai == eai) { strcpy(errmsg, eai_error_cases[i].msg); return eai_error_cases[i].errnum; } } errmsg[0] = '\0'; return -1; } /** * internal_getaddrinfo() - get address information * @node: descriptive name or address string (can be NULL) * @service: string identifying the requested service (can be NULL) * @hints: input values that may direct the operation by providing * options and by limiting the returned information (can be * NULL) * @res: return value that will contain the resulting linked list of * &struct addrinfo structures. * @errmsg: buffer receiving the error message if translated from EAI * * Same as mm_getaddrinfo() excepting that error code is returned in return * value and associated error message is written in @errmsg if the error is * not platform specific. * * Return: 0 in case of success, > 0 if a error translated from EAI_* * returned value has been found, -1 if the error must be translated from * the system (maybe platform specific). */ LOCAL_SYMBOL int internal_getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res, char* errmsg) { int rv; rv = getaddrinfo(node, service, hints, res); if (rv == 0) return 0; if (!node && !service) { strcpy(errmsg, "Both node and service are NULL"); return EINVAL; } if ((hints != NULL) && (hints->ai_flags & AI_CANONNAME) && !node) { strcpy(errmsg, "FQDN requested but node is NULL"); return EINVAL; } if ((hints != NULL) && (hints->ai_flags & AI_NUMERICSERV) && !is_numeric_string(service)) { strcpy(errmsg, "while requested, service is not " "numeric port-number string"); return EINVAL; } return translate_eai_to_errnum(rv, errmsg); } /** * internal_getnameinfo() - get name information * @addr: socket address * @addrlen: size of @addr * @host: buffer receiving the host name * @hostlen: size of buffer in @host * @serv: buffer receiving the service name * @servlen: size of buffer in @serv * @flags: control of processing of mm_getnameinfo() * @errmsg: buffer receiving the error message if translated from EAI * * Same as mm_getnameinfo() excepting that error code is returned in return * value and associated error message is written in @errmsg if the error is * not platform specific. * * Return: 0 in case of success, > 0 if a error translated from EAI_* * returned value has been found, -1 if the error must be translated from * the system (maybe platform specific). */ LOCAL_SYMBOL int internal_getnameinfo(const struct sockaddr* addr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags, char* errmsg) { int rv; rv = getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); if (rv == 0) return 0; return translate_eai_to_errnum(rv, errmsg); } /************************************************************************** * * * Exported socket helpers * * * **************************************************************************/ static struct { char name[8]; int socktype; } protocol_services[] = { {"tcp", SOCK_STREAM}, {"udp", SOCK_DGRAM}, }; static int get_socktype_from_protocol_services(const char* service) { int i; for (i = 0; i < MM_NELEM(protocol_services); i++) { if (!strcmp(protocol_services[i].name, service)) return protocol_services[i].socktype; } // No match, so return unspecified socket type return 0; } static int create_connected_socket(const char* service, const char * host, int port, const struct addrinfo* hints) { struct addrinfo * ai, * res; int fd, family, socktype; struct sockaddr_in6* addrin6; struct sockaddr_in* addrin; struct mm_error_state errstate; fd = -1; // Name resolution if (mm_getaddrinfo(host, service, hints, &res)) return -1; // Create and connect socket (loop over all possible addresses) mm_save_errorstate(&errstate); for (ai = res; ai != NULL; ai = ai->ai_next) { family = ai->ai_family; socktype = ai->ai_socktype; // Override port is specified if (port > 0) { if (family == AF_INET6) { addrin6 = (struct sockaddr_in6*)ai->ai_addr; addrin6->sin6_port = port; } else if (family == AF_INET) { addrin = (struct sockaddr_in*)ai->ai_addr; addrin->sin_port = port; } } // Try create the socket and connect if ((fd = mm_socket(family, socktype, 0)) < 0 || mm_connect(fd, ai->ai_addr, ai->ai_addrlen)) { mm_close(fd); fd = -1; } else { // We succeed, so we need to pop the last error mm_set_errorstate(&errstate); break; } } mm_freeaddrinfo(res); return fd; } /** * mm_create_sockclient() - Create a client socket and connect it to server * @uri: URI indicating the resource to connect to * * This functions resolves URI resource, create a socket and try to connect * to resource. The service, protocol, port, hostname will be parsed from * @uri and the resulting socket will be configured and connected to the * resource. * * In addition to the normal services registered in the system, the function * supports tcp and udp as scheme in the URI. In such a case, the port number * must be specified in the URI, otherwise the function will fail. * * Return: a non-negative integer representing the file descriptor in case * of success. Otherwise -1 is returned with error state set accordingly. */ API_EXPORTED int mm_create_sockclient(const char* uri) { size_t len; char service[16]; char* host; int port = -1; int num_field, retval; struct addrinfo hints = { .ai_family = AF_UNSPEC, }; if (!uri) return mm_raise_error(EINVAL, "uri cannot be NULL"); len = strlen(uri); host = mm_malloca(len+1); if (!host) return -1; num_field = sscanf(uri, "%[a-z]://%[^/:]:%i", service, host, &port); if (num_field < 2) { mm_raise_error(EINVAL, "uri \"%s\" does not follow " "service://host or service://host:port " "format", uri); return -1; } // Force socket type from service name if tcp:// or udp://. // Otherwise the socket type will be 0 (ie unspecified) and will be // left to mm_getaddrinfo() to propose proper matches. hints.ai_socktype = get_socktype_from_protocol_services(service); if (hints.ai_socktype != 0) { if (port < 0) { mm_raise_error(EINVAL, "port must be specified " "with %s", service); return -1; } sprintf(service, "%i", port); hints.ai_flags |= AI_NUMERICSERV; port = -1; } retval = create_connected_socket(service, host, port, &hints); mm_freea(host); return retval; } mmlib-1.4.2/src/startup-win32.c000066400000000000000000000323011435717460000162210ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include "mmlib.h" #include "utils-win32.h" /************************************************************************** * * * Command line conversion in UTF-8 argument array * * * **************************************************************************/ /** * alloc_argv_block() - allocate a memory block suitable for argv * @p_args: pointer to pointer variable that will receive the buffer * used to hold all the argument strings * @cmdline: commandline in UTF-8 * * This function scan roughly the commandline passed in @cmdline argument * and estimate the maximum number of arguments that command line contains * and the maximum size needed to hold all the argument strings. Based on * this, it allocates a memory block that can hold the argument array * pointer and a buffer for the argument strings. In case of success, the * buffer of argument string is set in *@p_args. * * Return: a memory block large enough to the array of pointer and a buffer * holding the string of the arguments. Both element are contiguous but the * memory starts with the array of pointer hence the type returned. In case * of failure (allocation problem), NULL is returned. */ static char** alloc_argv_block(char** p_args, const char* cmdline) { int maxnarg, i; size_t maxsize; void* ptr; char* args; // First argument (executable path) count for one maxnarg = 1; // Search for length of commandline and count number of space // character. There are at most as number of argument as the number of // spaces for (i = 0; cmdline[i]; i++) { if (cmdline[i] == ' ') maxnarg++; } // Allocate the memory block (both pointer array and string pool // contiguous) maxsize = (i+1) + (maxnarg+1)*sizeof(char*); ptr = malloc(maxsize); if (!ptr) return NULL; // String pool starts right after the array of pointer args = ptr; args += (maxnarg+1)*sizeof(char*); *p_args = args; return ptr; } /** * write_nbackslash() - write a specified number of backslash in string * @str: buffer to which the backslash must be written * @nbackslash: number of backslash character that must be written * * Return: the char pointer immediately after the @nbackslash have been * written, ie, @str + @nbackslash. */ static char* write_nbackslash(char* str, int nbackslash) { for (; nbackslash; nbackslash--) *str++ = '\\'; return str; } /** * parse_cmdline() - convert command line into array of argument string * @p_argv: pointer to an argument array variable that will receive the * allocated null terminated array of argument strings * cmdline: string of command line * * This function convert the command line into a NULL terminated array of * argument string. The argument will be transformed following the * convention in: * https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx * * Return: in case of success, the number of argument in the array of * argument strings. In such a case, *@p_argv receive the array. In case of * failure, -1 is returned. */ static int parse_cmdline(char*** p_argv, const char* cmdline) { char c, * str, ** argv; int argc = 0, nbackslash = 0; bool in_quotes = false, prev_is_whitespace = false; argv = alloc_argv_block(&str, cmdline); if (!argv) return -1; argv[0] = str; // Loop over all character in cmdline (until null termination) while ( (c = *cmdline++) != '\0') { switch (c) { case '\\': nbackslash++; prev_is_whitespace = false; break; case '"': if (nbackslash % 2 == 0) { str = write_nbackslash(str, nbackslash/2); in_quotes = !in_quotes; } else { str = write_nbackslash(str, (nbackslash-1)/2); *str++ = '"'; } nbackslash = 0; prev_is_whitespace = false; break; case '\t': case '\r': case '\n': case '\v': case '\f': case ' ': // If not in quotes, terminate argument string, // otherwise normal processing if (!in_quotes) { if (prev_is_whitespace) break; *str++ = '\0'; argv[++argc] = str; prev_is_whitespace = true; break; } /* fall through */ default: str = write_nbackslash(str, nbackslash); nbackslash = 0; *str++ = c; prev_is_whitespace = false; break; } } // Terminate current argument string and the argv pointer array if (!prev_is_whitespace) { *str++ = '\0'; argc++; } argv[argc] = NULL; *p_argv = argv; return argc; } /** * setup_argv_utf8() - allocate and set argument array in UTF-8 * @p_argv: pointer to a variable receiving the resulting argument array * * This function allocate and set a NULL terminated array of arguments in UTF-8. * * Return: an non negative number of argument in *@p_argv in case of success, * -1 otherwise. */ static int setup_argv_utf8(char*** p_argv) { int cmdline_u8_len, retval; char* cmdline_u8; char16_t* cmdline_u16; // Get UTF-16 command line from WIN32 API cmdline_u16 = GetCommandLineW(); // Estimate the size of the command line converted in UTF-8 cmdline_u8_len = get_utf8_buffer_len_from_utf16(cmdline_u16); if (cmdline_u8_len < 0) return -1; // Allocate UTF-8 string on stack (or heap if too big) cmdline_u8 = mm_malloca(cmdline_u8_len * sizeof(*cmdline_u8)); if (!cmdline_u8) return -1; // Convert the UTF-16 command line into a stack allocated UTF-8 string conv_utf16_to_utf8(cmdline_u8, cmdline_u8_len, cmdline_u16); // Do the actual split command line -> argument array retval = parse_cmdline(p_argv, cmdline_u8); mm_freea(cmdline_u8); return retval; } /************************************************************************** * * * Override of UCRT/MSVCRT argv setup * * * **************************************************************************/ int __getmainargs(int * p_argc, char *** p_argv, char *** p_env, int do_wildcard, void* startinfo); int _get_startup_argv_mode(void); /** * DOC: Supporting UTF-8 argument in main() in Windows * * Rationale: * Windows support unicode natively, however only through UTF-16 which is the * native format of string in NT kernel. When a *A() variant of an WINAPI * function is called, the Win32 subsystem transforms the char* string into * UTF-16 (with the WHAR type) using the active codepage (accessible through * GetACP()) and makes a kernel syscall. Now while it exists a UTF-8 codepage * (CP_UTF8, 65001), it cannot be set as active codepage. If attempted, this * provokes various crashes making the system unusable (I've heard someone had * to revert back to a previous restore point). This codepage can only be used * as argument of MultiBytetoWideChar() and WideCharToMultiByte() functions. * Since this issue has been known for more than 10-15 years and Microsoft has * never shown any will to fix it, we must assume this will remain for the * lifetime of Windows. * * For this reason, if we want to reasonably support UTF-8 as argument array * when spawning a new process or in a new executable (ie, in main, not the * unportable wmain()), we have to do the conversion ourself. For the * record, most (if not all) other OS support UTF-8 in their syscall (and it is * no wonder, given the advantages it brings over UTF-16). * * How it works: * As you may already know, main() is not real the entry function of an * executable. Usually the OS loader setup the dynamic libraries in the * userland address space and call the real entry point which will initialize * the C runtime (CRT) and do the actual call to main after that. The CRT * initialization bit usually appears as static library or directly as object * files (or both) linked with the actual executable. Those init bits are of * course CRT and compiler dependent, however the CRT init bits calls function * of the CRT which are a dynamic library with known exported symbol and which * are stable over versions. We can use this fact to override certain behavior * of the CRT initialization. * * On Windows, the argument array is initialized just before the C++ * initializers are called (but after the C initializers) and the way how * depends on which version of CRT is used. * * msvcrt.dll like (mingw and clang and MSVC until VS2013): * Those initialize the argument array to pass to main by a call to a function * called __getmainargs() exported by the CRT (see crtexe.c from mingw64-crt). * This is the actual function that does argument split. This function is * invoked from the CRT init bits. Since the symbol is included in the CRT * import library which is linked automatically by the compiler after all * object files and the direct dependencies of the project being compiled, any * symbol of this name in any object file or libraries will take precedence and * thus override it. * * ucrt.dll (MSVC from VS2015): * The argument array is initialized in _configure_narrow_argv() whose behavior * depends on the function _get_startup_argv_mode(). (See exe_main.cpp, * exe_common.inl and vcstartup_internal.h from VC source code of CRT * initialization). The _get_startup_argv_mode() function has its default * implementation in the CRT init bits but can be overridden by any function of * this name that would appear first in the linking order, ie, any function in * object file or linked libraries. Please note that new version on mingw64-crt * (used by mingw and clang) allows one to use ucrt instead of msvcrt, however * it is done in a way that the wrapper around urtbase.dll define * _getmainargs() so that the CRT init bits remain the same, so the previous * case is still applicable for them. * * NOTE: Do not use it directly in user code. This function is exported for the * sole purpose of overriding the function used in mingw64 CRT init code * (exported by msvcrt.dll or by their wrapper for ucrtbase.dll). * * In conclusion, by ensuring that mmlib export __getmainargs() and * _get_startup_argv_mode(), which will both initialize the actual UTF-8 * argument array, we can ensure that any executable linked *directly* with * mmlib will have argv in utf-8. If not linked directly, argv with string use * the actve codepage will be used. */ /** * __getmainargs() - Generate the argument array, count and envp for main() * @_argc: pointer to variable receiving the argument count * @_argv: pointer to variable receiving the argument array * @_env: pointer to variable receiving the environment array * @_do_wildcard: ignored (it is not the place to do expansion) * @_Startinfo: ignored * * This function perform the setup of the argument array and environment * suitable to be passed to main(). Contrary to what is done in msvcrt.dll, * the argument are converted from GetCommandLineW() (ie in UTF-16) so that * the returned argument strings holds unicode string in UTF-8. * * Return: 0 in case of success, -1 otherwise */ API_EXPORTED int __getmainargs(int * p_argc, char *** p_argv, char *** p_env, int do_wildcard, void* startinfo) { (void)do_wildcard; (void)startinfo; int argc; char** argv; argc = setup_argv_utf8(&argv); if (argc < 0) return -1; // The first call to getenv will initialize the environ array _wgetenv(L"PATH"); getenv("PATH"); // Besides output pointers, set __argc and __argv (exported by CRT) in // case they would be used in user code (this is not portable but it // has existed with CRT on windows for a long time) *p_argc = __argc = argc; *p_argv = __argv = argv; *p_env = environ; return 0; } // Value equivalent as from vcruntime-startup.h (_crt_argv_mode) enum { ucrt_argv_no_arguments, ucrt_argv_unexpanded_arguments, ucrt_argv_expanded_arguments, }; /** * _get_startup_argv_mode() - get argv mode, used during executable startup * * This function is meant to override the default implementation in CRT init * bits. It ensures that command line is split in array of UTF-8 argument * strings and if successful, __argc and __argv (from CRT dll) are initialized * and prevent by its return value ucrt to overwrite __argc and __argv. * * NOTE: Do not use it directly in user code. This function is exported for the * sole purpose of overriding the function used in MSVC CRT init code (exported * by ucrt.lib or by any setargv*.obj). * * Return: ucrt_argv_no_arguments (0) if no argument parsing must be done, * ucrt_argv_unexpanded_arguments(1) if argument parsing must be done but * without wildcard expansion. */ API_EXPORTED int _get_startup_argv_mode(void) { int argc; char** argv; argc = setup_argv_utf8(&argv); // If something fails, lets give normal processing in ucrt a try if (argc < 0) return ucrt_argv_unexpanded_arguments; // It is necessary to set __argc and __argv (exported from CRT) here: // this is what CRT init use when calling main() if compiled by MSVC __argc = argc; __argv = argv; // The argument array setup is already done, so we pretend that the CRT // do not has to do it return ucrt_argv_no_arguments; } mmlib-1.4.2/src/thread-posix.c000066400000000000000000000462571435717460000162050ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmthread.h" #include "mmerrno.h" #include "mmlog.h" #include #include /** * mm_thr_mutex_init() - Initialize a mutex * @mutex: mutex to initialize * @flags: OR-combination of flags indicating the type of mutex * * Use this function to initialize @mutex. The type of mutex is controlled * by @flags which must contains one or several of the following : * * MM_THR_PSHARED: init a mutex shareable by other processes. When a mutex * is process shared, it is also a robust mutex. * * If no flags is provided, the type of initialized mutex just a normal * mutex and a call to this function could be avoided if the data pointed by * @mutex has been statically initialized with MM_MTX_INITIALIZER. * * Currently, a robust mutex can only be initialized if is a process shared * mutex. * * It is undefined behavior if a mutex is reinitialized before getting * destroyed first. * * Return: * * 0 * The mutex has been initialized * * EINVAL * @flags set the robust mutex attribute without the process-shared * attribute. */ API_EXPORTED int mm_thr_mutex_init(mm_thr_mutex_t* mutex, int flags) { int ret; pthread_mutexattr_t attr; if (flags) { pthread_mutexattr_init(&attr); if (flags & MM_THR_PSHARED) { pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); #if HAVE_PTHREAD_MUTEX_CONSISTENT pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); #else mm_log_warn("Process shared mutex are supposed to be " "robust as well. But I do not how to have " "a robust mutex on this platform"); #endif } } ret = pthread_mutex_init(mutex, flags ? &attr : NULL); if (ret) mm_raise_error(ret, "Failed initializing mutex: %s", strerror(ret)); if (flags) pthread_mutexattr_destroy(&attr); return ret; } /** * mm_thr_mutex_lock() - lock a mutex * @mutex: initialized mutex * * The mutex object referenced by @mutex is locked by a successful * call to mm_thr_mutex_lock(). If the mutex is already locked by another * thread, the calling thread blocks until the mutex becomes available. If * the mutex is already locked by the calling thread, the function will * never return. * * If @mutex is a robust mutex, and the previous owner has died while * holding the lock, the return value EOWNERDEAD will indicate the calling * thread of this situation. In this case, the mutex is locked by the * calling thread but the state it protects is marked as inconsistent. The * application should ensure that the state is made consistent for reuse and * when that is complete call mm_thr_mutex_consistent(). If the application is * unable to recover the state, it should unlock the mutex without a prior * call to mm_thr_mutex_consistent(), after which the mutex is marked * permanently unusable. * * NOTE: If @mutex is a robust mutex and actually used across different * processes, the return value must not be ignored. * * Return: * * 0 * The mutex has been successfully locked by the calling thread * * EOWNERDEAD * The mutex is a robust mutex and the previous owning thread terminated * while holding the mutex lock. The mutex lock is acquired by the calling * thread and it is up to the new owner to make the state consistent (see * mm_thr_mutex_consistent()). * * ENOTRECOVERABLE * The mutex is a robust mutex and the state protected by it is not * recoverable. */ API_EXPORTED int mm_thr_mutex_lock(mm_thr_mutex_t* mutex) { return pthread_mutex_lock(mutex); } /** * mm_thr_mutex_trylock() - try to lock a mutex * @mutex: initialized mutex * * This function is equivalent to mm_thr_mutex_lock(), except that if the * mutex object referenced by @mutex is currently locked (by any thread, * including the current thread), the call returns immediately. * * If @mutex is a robust mutex, and the previous owner has died while * holding the lock, the return value EOWNERDEAD will indicate the calling * thread of this situation. In this case, the mutex is locked by the * calling thread but the state it protects is marked as inconsistent. The * application should ensure that the state is made consistent for reuse and * when that is complete call mm_thr_mutex_consistent(). If the application is * unable to recover the state, it should unlock the mutex without a prior * call to mm_thr_mutex_consistent(), after which the mutex is marked * permanently unusable. * * NOTE: If @mutex is a robust mutex and actually used across different * processes, the return value must not be ignored. * * Return: * * 0 * The mutex has been locked by the calling thread * * EOWNERDEAD * The mutex is a robust mutex and the previous owning thread terminated * while holding the mutex lock. The mutex lock is acquired by the calling * thread and it is up to the new owner to make the state consistent (see * mm_thr_mutex_consistent()). * * ENOTRECOVERABLE * The mutex is a robust mutex and the state protected by it is not * recoverable. * * EBUSY * The mutex could not be acquired because it has already been locked by a * thread. */ API_EXPORTED int mm_thr_mutex_trylock(mm_thr_mutex_t* mutex) { return pthread_mutex_trylock(mutex); } /** * mm_thr_mutex_consistent() - mark state protected by mutex as consistent * @mutex: initialized robust mutex * * If mutex is a robust mutex in an inconsistent state, this function can be * used to mark the state protected by the mutex referenced by @mutex as * consistent again. * * If an owner of a robust mutex terminates while holding the mutex, the * mutex becomes inconsistent and the next thread that acquires the mutex * lock is notified of the state by the return value EOWNERDEAD. In this * case, the mutex does not become normally usable again until the state is * marked consistent. * * If the new owner is not able to make the state consistent, do not call * mm_thr_mutex_consistent() for the mutex, but simply unlock the mutex. All * waiters will then be woken up and all subsequent calls to * mm_thr_mutex_lock() will fail to acquire the mutex by returning * ENOTRECOVERABLE error code. * * If the thread which acquired the mutex lock with the return value * EOWNERDEAD terminates before calling either mm_thr_mutex_consistent() or * mm_thr_mutex_unlock(), the next thread that acquires the mutex lock shall be * notified about the state of the mutex by the return value EOWNERDEAD. * * Return: * * 0 * in case of success * * EINVAL * the mutex object is not robust or does not protect an inconsistent * state * * EPERM * the mutex is a robust mutex, and the current thread does not own the * mutex. */ API_EXPORTED int mm_thr_mutex_consistent(mm_thr_mutex_t* mutex) { #if HAVE_PTHREAD_MUTEX_CONSISTENT return pthread_mutex_consistent(mutex); #else (void) mutex; mm_raise_error(ENOTSUP, "Robust mutex not supported on this platform"); return ENOTSUP; #endif } /** * mm_thr_mutex_unlock() - Unlock a mutex * @mutex: mutex owned by the calling thread * * This releases the mutex object referenced by @mutex. If there are threads * blocked on the mutex object referenced by @mutex when mm_thr_mutex_unlock() * is called, one of these thread will be unblocked. * * Unlocking a mutex not owned by the calling thread, or not initialized * will result in undefined behavior. * * Return: * * 0 * in case of success * * EPERM * the mutex is a robust mutex, and the current thread does not own the * mutex. */ API_EXPORTED int mm_thr_mutex_unlock(mm_thr_mutex_t* mutex) { return pthread_mutex_unlock(mutex); } /** * mm_thr_mutex_deinit() - cleanup an initialized mutex * @mutex: initialized mutex to destroy * * This destroys the mutex object referenced by @mutex; the mutex object * becomes, in effect, uninitialized. A destroyed mutex object can be * reinitialized using mm_thr_mutex_init(); the results of otherwise * referencing the object after it has been destroyed are undefined. * * It is safe to destroy an initialized mutex that is unlocked. Attempting * to destroy a locked mutex, or a mutex that another thread is attempting * to lock, or a mutex that is being used in a mm_thr_cond_timedwait() or * mm_thr_cond_wait() call by another thread, results in undefined behavior. * * Return: 0 */ API_EXPORTED int mm_thr_mutex_deinit(mm_thr_mutex_t* mutex) { return pthread_mutex_destroy(mutex); } /** * mm_thr_cond_init() - Initialize a condition variable * @cond: condition variable to initialize * @flags: OR-combination of flags indicating the type of @cond * * Use this function to initialize @cond. The type of condition is * controlled by @flags which must contains one or several of the following: * * - MM_THR_PSHARED: init a condition shareable by other processes * - MM_THR_WAIT_MONOTONIC: the clock base used in mm_thr_cond_timedwait() is * MM_CLK_MONOTONIC instead of the default MM_CLK_REALTIME. * * If 0 is passed, a call to this function could have be avoided if the data * pointed by @cond had been statically initialized with * MM_COND_INITIALIZER. * * It is undefined behavior if a condition variable is reinitialized before * getting destroyed first. * * Return: 0 */ API_EXPORTED int mm_thr_cond_init(mm_thr_cond_t* cond, int flags) { int ret; pthread_condattr_t attr; if (flags) { pthread_condattr_init(&attr); if (flags & MM_THR_PSHARED) pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); if (flags & MM_THR_WAIT_MONOTONIC) pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); } ret = pthread_cond_init(cond, flags ? &attr : NULL); if (ret) mm_raise_error(ret, "Failed initializing cond: %s", strerror(ret)); if (flags) pthread_condattr_destroy(&attr); return ret; } /** * mm_thr_cond_wait() - wait on a condition * @cond: Condition to wait * @mutex: mutex protecting the condition wait update * * This function blocks on a condition variable. The application shall * ensure that this function is called with @mutex locked by the calling * thread; otherwise, undefined behavior results. * * It atomically releases @mutex and cause the calling thread to block on * the condition variable @cond. Upon successful return, the mutex shall * have been locked and shall be owned by the calling thread. If mutex is a * robust mutex where an owner terminated while holding the lock and the * state is recoverable, the mutex shall be acquired even though the * function returns an error code. * * When a thread waits on a condition variable, having specified a * particular mutex to either the mm_thr_cond_wait(), a dynamic binding is * formed between that mutex and condition variable that remains in effect * as long as at least one thread is blocked on the condition variable. * During this time, the effect of an attempt by any thread to wait on that * condition variable using a different mutex is undefined. * * The behavior is undefined if the value specified by the @cond or @mutex * argument to these functions does not refer to an initialized condition * variable or an initialized mutex object, respectively. * * NOTE: If @mutex is a robust mutex and actually used across different * processes, the return value must not be ignored. * * Return: * * 0 * in case success * * Other errors * Any error that mm_thr_mutex_unlock() and mm_thr_mutex_lock() can return. */ API_EXPORTED int mm_thr_cond_wait(mm_thr_cond_t* cond, mm_thr_mutex_t* mutex) { return pthread_cond_wait(cond, mutex); } /** * mm_thr_cond_timedwait() - wait on a condition with timeout * @cond: Condition to wait * @mutex: mutex protecting the condition wait update * @abstime: absolute time indicating the timeout * * This function is the equivalent to mm_thr_cond_wait(), except that an * error is returned if the absolute time specified by @time passes (that * is, clock time equals or exceeds @time) before the condition cond is * signaled or broadcasted, or if the absolute time specified by @time has * already been passed at the time of the call. When such timeouts occur, * mm_thr_cond_timedwait() will nonetheless release and re-acquire the mutex * referenced by mutex, and may consume a condition signal directed * concurrently at the condition variable. * * The clock ID to measure timeout is determined at the initialization of * the condition with pthread_cond_init(). If @cond has been initialized * statically with MM_MTX_INITIALIZER, the clock used is MM_CLK_REALTIME. * * NOTE: If @mutex is a robust mutex and actually used across different * processes, the return value must not be ignored. * * Return: * * 0 * in case success * * ETIMEDOUT * The time specified by @time has passed * * EINVAL * @time argument specifies a nanosecond value less than zero or greater * than or equal to 1000 million. * * Other errors * Any error that mm_thr_mutex_unlock() and mm_thr_mutex_lock() can return. */ API_EXPORTED int mm_thr_cond_timedwait(mm_thr_cond_t* cond, mm_thr_mutex_t* mutex, const struct mm_timespec* abstime) { return pthread_cond_timedwait(cond, mutex, (const struct timespec*)abstime); } /** * mm_thr_cond_signal() - signal a condition * @cond: condition variable to signal * * This function unblocks at least one of the threads that are blocked on * the specified condition variable @cond (if any threads are blocked on * @cond). * * mm_thr_cond_signal() functions may be called by a thread whether or not it * currently owns the mutex associated to the condition variable * (association made by threads calling mth_cond_wait() or * mm_thr_cond_timedwait() with the condition variable); however, if * predictable scheduling behavior is required, then that mutex shall be * locked by the thread calling mm_thr_cond_signal(). * * The behavior is undefined if the value specified by the @cond argument * does not refer to an initialized condition variable. * * Return: 0 */ API_EXPORTED int mm_thr_cond_signal(mm_thr_cond_t* cond) { return pthread_cond_signal(cond); } /** * mm_thr_cond_broadcast() - broadcast a condition * @cond: condition variable to broadcast * * This function unblocks all threads currently blocked on the specified * condition variable @cond (if any threads are blocked on @cond). * * mm_thr_cond_broadcast() functions may be called by a thread whether or not * it currently owns the mutex associated to the condition variable * (association made by threads calling mth_cond_wait() or * mm_thr_cond_timedwait() with the condition variable); however, if * predictable scheduling behavior is required, then that mutex shall be * locked by the thread calling mm_thr_cond_broadcast(). * * The behavior is undefined if the value specified by the @cond argument * does not refer to an initialized condition variable. * * Return: 0 */ API_EXPORTED int mm_thr_cond_broadcast(mm_thr_cond_t* cond) { return pthread_cond_broadcast(cond); } /** * mm_thr_cond_deinit() - cleanup an initialized condition variable * @cond: initialized condition variable to destroy * * This destroys the condition variable object referenced by @cond which * becomes, in effect, uninitialized. A destroyed condition variable object * can be reinitialized using mm_thr_cond_init(); the results of otherwise * referencing the object after it has been destroyed are undefined. * * It is safe to destroy an initialized condition variable that is not * waited. Attempting to destroy a condition variable waited by another * thread undefined behavior. * * Return: 0 */ API_EXPORTED int mm_thr_cond_deinit(mm_thr_cond_t* cond) { return pthread_cond_destroy(cond); } /** * mm_thr_once() - One-time initialization * @once: control data of the one-time call * @once_routine: routine to call only once * * The first call to mm_thr_once() by any thread in a process, with a given * once_control, shall call @once_routine with no arguments. Subsequent * calls of mm_thr_once() with the same once_control shall not call * @once_routine. On return from mm_thr_once(), init_routine shall have * completed. The once_control parameter shall determine whether the * associated initialization routine has been called. * * Return: 0 */ API_EXPORTED int mm_thr_once(mm_thr_once_t* once, void (* once_routine)(void)) { return pthread_once(once, once_routine); } /** * mm_thr_create() - thread creation * @thread: location to store the ID of the new thread * @proc: routine to execute in the thread * @arg: argument passed to @proc * * This functions create a new thread. The thread is created executing * start_routine with arg as its sole argument. Upon successful creation, * mm_thr_create() shall store the ID of the created thread in the location * referenced by thread. * * Once a thread has been successfully created, its resources will have * eventually to be reclaimed. This is achieved by calling * mm_thr_join() or mm_thr_detach() later. * * Return: 0 in case of success, otherwise the associated error code with * error state set accordingly. */ API_EXPORTED int mm_thr_create(mm_thread_t* thread, void* (*proc)(void*), void* arg) { int ret; ret = pthread_create(thread, NULL, proc, arg); if (ret) mm_raise_error(ret, "Failed creating thread: %s", strerror(ret)); return ret; } /** * mm_thr_join() - wait for thread termination * @thread: ID of the thread to wait * @value_ptr: location receiving the return value * * The mm_thr_join() function suspends execution of the calling thread until * the target thread terminates, unless the target thread has already * terminated. On return from a successful mm_thr_join() call with a * non-NULL @value_ptr argument, the value returned by the terminating * thread shall be made available in the location referenced by @value_ptr. * * The behavior is undefined if the value specified by the thread argument * to mm_thr_join() does not refer to a joinable thread as well if the * @thread argument refers to the calling thread. * * Return: 0 */ API_EXPORTED int mm_thr_join(mm_thread_t thread, void** value_ptr) { int ret; ret = pthread_join(thread, value_ptr); if (ret) mm_raise_error(ret, "Failed to join thread: %s", strerror(ret)); return ret; } /** * mm_thr_detach() - Detach a thread * @thread: ID of the thread to detach * * This function indicates that thread storage for the thread @thread can be * reclaimed when that thread terminates. In other words, this makes @thread * detached or not joinable. * * The behavior is undefined if the value specified by the thread argument * to mm_thr_detach() does not refer to a joinable thread. * * Return: 0 in case of success, otherwise the associated error code with * error state set accordingly. */ API_EXPORTED int mm_thr_detach(mm_thread_t thread) { int ret; ret = pthread_detach(thread); if (ret) mm_raise_error(ret, "Failed to detach thread: %s", strerror(ret)); return ret; } /** * mm_thr_self() - get the calling thread ID * * Return: thread ID of the calling thread. */ API_EXPORTED mm_thread_t mm_thr_self(void) { return pthread_self(); } mmlib-1.4.2/src/thread-win32.c000066400000000000000000001021171435717460000157710ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmthread.h" #include "mmerrno.h" #include "mmtime.h" #include "mmlog.h" #include "pshared-lock.h" #include "atomic-win32.h" #include "error-internal.h" #include "mutex-lockval.h" #include "utils-win32.h" #ifdef _MSC_VER #define restrict __restrict #endif #define STATE_STOPPED 0x01 #define STATE_DETACHED 0x02 #define NS_IN_MS (NS_IN_SEC / MS_IN_SEC) /** * struct mm_thread - data structure to manipulate thread * @hnd: WIN32 thread object handle * @routine: pointer to routine to execute in the thread (NULL if thread not * created by mmlib) * @arg: argument passed to @routine, * NULL if thread not created by mmlib. * @retval: value returned by the thread when it terminates * @state: state of the thread indicating if it is stopped or detached * * This structure represents the data necessary to manipulate thread through * the API of mmlib. The &typedef mm_thread_t type corresponds to a pointer to * this structure layout. * * This structure should be freed when: * - the thread terminates in the case of a detached thread * - mm_thr_join() is called in case of joinable thread */ struct mm_thread { HANDLE hnd; void* (* routine)(void*); void* arg; void* retval; int64_t state; }; /** * struct thread_local_data - thread local data handling threading in mmlib * @lockref: data maintaining the communication with the lock referee. * @last_error: error state of the thread * @thread: pointer to the thread manipulation structure. Can be NULL if * thread has been created externally and mm_thr_self() has not * been called for the thread. * * This structure represents the actual thread local data used in mmlib with * respect with thread manipulation and thread synchronization. * * The lifetime of this structure must not be confused with the one of struct * mm_thread. This structure is destroyed (if allocated) always when the thread * terminates. */ struct thread_local_data { struct lockref_connection lockref; struct error_info last_error; struct mm_thread* thread; }; /************************************************************************** * * * Provide thread local data * * * **************************************************************************/ static DWORD threaddata_tls_index; /** * safe_alloc() - memory allocation callable from DllMain * @len: size of the memory block to allocate. * * Allocate memory of size @len and initialize it to 0. This function is in * essence equivalent to calloc() but can safely be called from DllMain(). * To free the allocated memory use safe_free(). The typical use is for * allocating thread local data. * * Return: the pointer to allocated memory in case of success, NULL * otherwise. */ static void* safe_alloc(size_t len) { return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len); } /** * safe_free() - memory deallocation * @ptr: pointer to data to be freed * * Free memory block @ptr that has been allocated by safe_alloc(). This * function can be called from DllMain(). This is typically where it is * used in DLL_THREAD_DETACH case, for example deallocating thread local * data. */ static void safe_free(void* ptr) { if (!ptr) return; HeapFree(GetProcessHeap(), 0, ptr); } static struct mm_thread* create_mm_thread_data(void) { struct mm_thread* th; th = safe_alloc(sizeof(*th)); if (!th) { mm_raise_from_w32err("Cannot allocate data for thread"); return NULL; } th->state = STATE_DETACHED; th->hnd = NULL; return th; } static void destroy_mm_thread_data(struct mm_thread* th) { if (!th) return; if (th->hnd) CloseHandle(th->hnd); safe_free(th); } static NOINLINE struct thread_local_data* allocate_thread_local_data(void) { struct thread_local_data* data; data = safe_alloc(sizeof(*data)); if (!data) { mm_log_fatal("Cannot allocate thread private data"); abort(); } // Set allocated data as thread local data TlsSetValue(threaddata_tls_index, data); return data; } static void thread_local_data_on_exit(void) { struct thread_local_data* data; struct mm_thread* self; int64_t prev_state; // Retrieve currently currently set thread local data data = tls_get_value(threaddata_tls_index); if (!data) return; // If thread structure is initialized and is detached, it must be // cleaned up now self = data->thread; if (self) { prev_state = atomic_fetch_add(&self->state, STATE_STOPPED); if (prev_state & STATE_DETACHED) destroy_mm_thread_data(self); } // Close connection of the thread with lock server if any. deinit_lock_referee_connection(&data->lockref); safe_free(data); } /* ... to silence some dumb warning about DllMain being undefined */ BOOL WINAPI DllMain(HINSTANCE hdll, DWORD reason, LPVOID reserved); LOCAL_SYMBOL BOOL WINAPI DllMain(HINSTANCE hdll, DWORD reason, LPVOID reserved) { (void)hdll; (void)reserved; switch (reason) { case DLL_PROCESS_ATTACH: threaddata_tls_index = TlsAlloc(); if (threaddata_tls_index == TLS_OUT_OF_INDEXES) { return FALSE; } break; case DLL_PROCESS_DETACH: TlsFree(threaddata_tls_index); break; case DLL_THREAD_DETACH: thread_local_data_on_exit(); break; } return TRUE; } static struct thread_local_data* get_thread_local_data(void) { struct thread_local_data* data; data = tls_get_value(threaddata_tls_index); if (LIKELY(data)) return data; return allocate_thread_local_data(); } static struct lockref_connection* get_thread_lockref_data(void) { return &(get_thread_local_data()->lockref); } LOCAL_SYMBOL struct error_info* get_thread_last_error(void) { return &(get_thread_local_data()->last_error); } /************************************************************************** * * * Process shared mutex * * * **************************************************************************/ /** * DOC: process shared mutex implementation * * The process shared mutex is based on a 64bit lock val which is shared * (memory mapped if over different process) and whose change are all done * atomically. This 64bits lock value is divided in 2 parts of 32bits: - the * lower one indicates the owning thread id (32bit on windows) - the higher one * encodes the number of thread waiting for the lock * * By manipulating the lock value only through atomic addition/subtraction, * the lock value is updated only we avoid any problem of lost wake-up * * When the lock is tried to be obtained, a thread attempts to update * atomically the lock value with its thread ID combined with the number of * thread it believes that are waiting for the lock. This defines a new lock * value that the thread will try to store atomically in the address of the * lock. If the number of waiting thread is not the expected one or another * thread has already obtained the lock the expected lock value will mismatch * and the store will fail. With this a thread can know if it has obtained the * lock or not. If not it will increase atomically the number of waiting thread * and will go sleeping using pshared_wait_on_lock(). * * When unlocking, a thread just has to subtract its thread ID from the lock * value: a null value in the owner part of the lock value indicates the lock * in unused (but some other threads may still wait for it). The unlocking * thread just has to examine the number of thread waiting to know if it must * wakeup any thread (using pshared_wake_lock()) */ /** * start_mtx_operation() - Initiate a lock/unlock operation on a mutex * @mtx_key: key of the pshared lock of the mutex * @robust_data: robust data of the calling thread * * Indicates the beginning of a mutex lock or mutex unlock and is the * symmetric of finish_mtx_lock() or finish_mtx_unlock(). This will register * the mutex lock key into the robust data of the thread. */ static void start_mtx_operation(int64_t mtx_key, struct robust_data* robust_data) { robust_data->attempt_key = mtx_key; } /** * register_waiter_in_mtx() - register a thread trying to lock a mutex. * @lock: pointer to the shared lock * @poldval: pointer to a variable that old the previous value of the lock. * The pointed variable will be updated, this acts as input and * output of the function. * @robust_data: robust data of the calling thread * * This function is meant to be called when the calling fails to lock the * mutex. It update the lock of a mutex referenced in @lock, as well as the * robust data of the calling thread. This function must be called to be called * between start_mtx_operation() and finish_mtx_lock(). * * There are 2 reasons why a wait registration fails: * - the mutex has reached its maximal count of registered waiter in the mutex * - the mutex is permanently unusable. * If the registration fails due to waiter list full, it can be attempted later * if still needed. * * Return: true if the thread is now registered as waiter of mutex, false * otherwise. */ static bool register_waiter_in_mtx(int64_t* restrict lock, int64_t* restrict poldval, struct robust_data* robust_data) { int64_t newval, incval; DWORD tid = 0; tid = robust_data->thread_id; incval = mtx_lockval(0, tid, 1); // Register as waiter: update waiter count and get its lock while (1) { // Check waiter count has not reached the max. If so, give // other thread the chance to run and retry when the thread // is rescheduled if (UNLIKELY(is_mtx_waiterlist_full(*poldval))) { Sleep(1); *poldval = atomic_load(lock); return false; } // try to update the waiter count newval = (*poldval & ~MTX_WAITER_TID_MASK) + incval; if (atomic_cmp_exchange(lock, poldval, newval)) break; if (is_mtx_unrecoverable(*poldval)) return false; } // Notify thread's robust data that it is a waiter of the key robust_data->is_waiter = 1; // Now that robust data has been updated, we can release the waiter // count lock *poldval = ~MTX_WAITER_TID_MASK & atomic_fetch_sub(lock, mtx_lockval(0, tid, 0)); return true; } /** * finish_mtx_lock() - finish the mutex lock operation * @robust_data: robust data of the calling thread * @locked: true if calling thread owns the mutex lock * @oldval: last lock value of the mutex observed * * This function is meant to be called at the end of mutex lock attempt * after start_mtx_operation(). It will update the robust data of the mutex. * It will also return the return code that must be returned to the lock * function. * * Return: the return value to report to the calling lock function. */ static int finish_mtx_lock(struct robust_data* robust_data, bool locked, int64_t oldval) { int64_t mtx_key; int retval; retval = locked ? 0 : EBUSY; if (locked) { mtx_key = robust_data->attempt_key; robust_data->locked_keys[robust_data->num_locked] = mtx_key; robust_data->num_locked++; if (is_mtx_ownerdead(oldval)) retval = EOWNERDEAD; } robust_data->is_waiter = 0; robust_data->attempt_key = 0; return retval; } /** * finish_mtx_unlock() - finish the mutex unlock operation * @robust_data: robust data of the calling thread * * This function is meant to be called at the end of mutex unlock after * start_mtx_operation(). It will update the robust data of the thread. */ static void finish_mtx_unlock(struct robust_data* robust_data) { robust_data->num_locked--; robust_data->attempt_key = 0; } /** * pshared_mtx_init() - Mutex init in case of MM_THR_PSHARED * @mutex: mutex to initialize * * Implementation of mm_thr_mutex_init() in the case of process shared mutex. * * Return: always 0 */ static int pshared_mtx_init(struct mm_thr_mutex_pshared * mutex) { struct lockref_connection* lockref = get_thread_lockref_data(); mutex->lock = 0; mutex->pshared_key = pshared_init_lock(lockref); return 0; } /** * pshared_mtx_lock() - Mutex lock in case of MM_THR_PSHARED * @mutex: mutex to lock * * Implementation of mm_thr_mutex_lock() in the case of process shared mutex. * * Return: Always 0 excepting if @mutex refers to a robust mutex in * inconsistent state (EOWNERDEAD) or a robust mutex marked permanently * unusable (ENOTRECOVERABLE). */ static int pshared_mtx_lock(struct mm_thr_mutex_pshared* mutex) { struct lockref_connection* lockref; struct robust_data* robust_data; int64_t oldval, newval, incval; int64_t* lockptr = &mutex->lock; int is_waiting_notified; struct shared_lock shlock = { .key = mutex->pshared_key, .ptr = &mutex->lock, }; lockref = get_thread_lockref_data(); robust_data = pshared_get_robust_data(lockref); start_mtx_operation(mutex->pshared_key, robust_data); // Assume initially that no other thread are waiting. If it is not the // case, we will discover this the next time the lock value will be // read incval = mtx_lockval(get_tid(), 0, 0); oldval = 0; is_waiting_notified = 0; while (1) { // Try to get the lock. New value is the current number of // waiter observed + the thread ID of this thread newval = oldval + incval; if (LIKELY(atomic_cmp_exchange(lockptr, &oldval, newval))) { break; // we got the lock } if (UNLIKELY(is_mtx_unrecoverable(oldval))) { finish_mtx_lock(robust_data, false, oldval); return ENOTRECOVERABLE; } // If this is the first time we failed to get the lock, we need // to indicate in the lock value that we are waiting for it, // ie, increasing the number of waiters part of the lock if (!is_waiting_notified) { if (!register_waiter_in_mtx(lockptr, &oldval, robust_data)) continue; oldval &= ~MTX_OWNER_TID_MASK; incval -= mtx_lockval(0, 0, 1); is_waiting_notified = 1; continue; } // We are ready to wait pshared_wait_on_lock(lockref, shlock, 1, NULL); oldval = atomic_load(lockptr) & ~MTX_OWNER_TID_MASK; } return finish_mtx_lock(robust_data, true, oldval); } /** * pshared_mtx_trylock() - Mutex try lock in case of MM_THR_PSHARED * @mutex: mutex to lock * * Implementation of mm_thr_mutex_trylock() in the case of process shared mutex. * * Return: Always 0 excepting if @mutex is in inconsistent state * (EOWNERDEAD) or marked permanently unusable (ENOTRECOVERABLE). */ static int pshared_mtx_trylock(struct mm_thr_mutex_pshared * mutex) { struct robust_data* robust_data = NULL; int64_t oldval, newval; int i; bool locked; robust_data = pshared_get_robust_data(get_thread_lockref_data()); start_mtx_operation(mutex->pshared_key, robust_data); oldval = 0; newval = mtx_lockval(get_tid(), 0, 0); locked = false; // try lock twice. The first time, it assumes the lock value is totally // clean. The second time, it will try to base the new value with what // has been seen during the first attempt. The second time is // particularly necessary for robust mutex that need recovery (the need // recover bit is then set) for (i = 0; i < 2; i++) { if (atomic_cmp_exchange(&mutex->lock, &oldval, newval)) { locked = 1; break; } if (UNLIKELY(is_mtx_unrecoverable(oldval))) { finish_mtx_lock(robust_data, 0, oldval); return ENOTRECOVERABLE; } oldval &= ~MTX_OWNER_TID_MASK; newval += oldval; } return finish_mtx_lock(robust_data, locked, oldval); } /** * pshared_mtx_unlock() - Mutex try unlock in case of MM_THR_PSHARED * @mutex: mutex to unlock * * Implementation of mm_thr_mutex_unlock() in the case of process shared mutex. * * Return: Always 0. */ static int pshared_mtx_unlock(struct mm_thr_mutex_pshared * mutex) { struct lockref_connection* lockref = NULL; struct robust_data* robust_data = NULL; int num_unlock, unrecoverable = 0; int64_t oldval, unlock_val; struct shared_lock shlock = { .key = mutex->pshared_key, .ptr = &mutex->lock, }; unlock_val = mtx_lockval(get_tid(), 0, 0); num_unlock = 1; lockref = get_thread_lockref_data(); robust_data = pshared_get_robust_data(lockref); // Check that inconsistent state has been removed by now if (is_mtx_ownerdead(atomic_load(&mutex->lock))) { unlock_val -= MTX_OWNER_TID_MASK; unrecoverable = 1; } start_mtx_operation(mutex->pshared_key, robust_data); // Do actual unlock (This is performed by subtracting the Thread ID // from the lock value. After this one, the owner part of the lock shall // be null, thus indicating that no one hold the lock oldval = atomic_fetch_sub(&mutex->lock, unlock_val); // Wake up a waiter if there is any if (is_mtx_waited(oldval)) { if (unrecoverable) num_unlock = mtx_num_waiter(oldval); pshared_wake_lock(lockref, shlock, 1, num_unlock); } finish_mtx_unlock(robust_data); return 0; } /** * pshared_mtx_consistent() - recover mutex from inconsistent state * @mutex: robust mutex to recover * * Implementation of mm_thr_mutex_consistent() in the case of process shared * robust mutex. * * Return: 0 in case of success. EINVAL if @mutex does not protect an * inconsistent state. */ static int pshared_mtx_consistent(struct mm_thr_mutex_pshared * mutex) { int64_t lockval; lockval = atomic_load(&mutex->lock); if (!is_mtx_ownerdead(lockval)) return EINVAL; atomic_sub(&mutex->lock, MTX_NEED_RECOVER_MASK); return 0; } /************************************************************************** * * * Process shared condition variable * * * **************************************************************************/ /** * pshared_cond_wait() - process shared condition variable wait * @cond: condition variable initialized with MM_THR_PSHARED * @mutex: mutex protecting the condition wait update * @abstime: absolute time indicating the timeout or NULL in case of * infinite wait. * * Implementation of mm_thr_cond_wait() and mm_thr_cond_timedwait() in the case * of process shared condition. * * Return: 0 in case of success, any error that mm_thr_mutex_lock() can return, * or ETIMEDOUT if @abstime is not NULL and the wait has timedout. */ static int pshared_cond_wait(struct mm_thr_cond_pshared * cond, mm_thr_mutex_t * mutex, const struct mm_timespec* abstime) { struct lockref_connection* lockref = get_thread_lockref_data(); int64_t wakeup_val; int wait_ret, ret; struct shared_lock shlock = {.key = cond->pshared_key}; struct lock_timeout timeout, * timeout_ptr; timeout_ptr = NULL; if (abstime) { timeout.clk_flags = WAITCLK_FLAG_REALTIME; timeout.ts = *abstime; timeout_ptr = &timeout; } wakeup_val = atomic_fetch_add(&cond->waiter_seq, 1); mm_thr_mutex_unlock(mutex); wait_ret = pshared_wait_on_lock(lockref, shlock, wakeup_val, timeout_ptr); ret = mm_thr_mutex_lock(mutex); // Report return value of timed wait operation only if there is nothing // to report from mutex lock. This way, we cannot miss EOWNERDEAD or // ENOTRECOVERABLE that might be returned by mm_thr_mutex_lock(). if (abstime && !ret) ret = wait_ret; return ret; } /** * pshared_cond_signal() - signal process shared condition variable * @cond: condition variable initialized with MM_THR_PSHARED * * Implementation of mm_thr_cond_signal() in the case of process shared * condition. * * Return: always 0 */ static int pshared_cond_signal(struct mm_thr_cond_pshared * cond) { struct lockref_connection* lockref; int64_t wakeup_val, waiter_val, num_waiter; struct shared_lock shlock = {.key = cond->pshared_key}; waiter_val = atomic_load(&cond->waiter_seq); wakeup_val = atomic_load(&cond->wakeup_seq); num_waiter = waiter_val - wakeup_val; if (num_waiter <= 0) return 0; lockref = get_thread_lockref_data(); wakeup_val = atomic_fetch_add(&cond->wakeup_seq, 1); pshared_wake_lock(lockref, shlock, wakeup_val, 1); return 0; } /** * pshared_cond_broadcast() - broadcast process shared condition variable * @cond: condition variable initialized with MM_THR_PSHARED * * Implementation of mm_thr_cond_broadcast() in the case of process shared * condition. * * Return: always 0 */ static int pshared_cond_broadcast(struct mm_thr_cond_pshared * cond) { struct lockref_connection* lockref; int64_t wakeup_val, waiter_val, num_waiter; struct shared_lock shlock = {.key = cond->pshared_key}; waiter_val = atomic_load(&cond->waiter_seq); wakeup_val = atomic_load(&cond->wakeup_seq); num_waiter = waiter_val - wakeup_val; if (num_waiter <= 0) return 0; lockref = get_thread_lockref_data(); wakeup_val = atomic_fetch_add(&cond->wakeup_seq, num_waiter); wakeup_val += num_waiter - 1; pshared_wake_lock(lockref, shlock, wakeup_val, num_waiter); return 0; } /** * pshared_cond_init() - initialize process shared condition variable * @cond: condition variable to init * * Implementation of mm_thr_cond_init() in the case of process shared condition. * * Return: always 0 */ static int pshared_cond_init(struct mm_thr_cond_pshared * cond) { struct lockref_connection* lockref; lockref = get_thread_lockref_data(); cond->pshared_key = pshared_init_lock(lockref); return 0; } /************************************************************************** * * * Exported API of synchronization primitives * * * **************************************************************************/ /** * struct mm_thr_mutex - mutex structure behind &typedef mm_thr_mutex_t * @type: flags indicating the type of mutex (0 or MM_THR_PSHARED) * @srw_lock: data aliasing with SRWLOCK (used if @type is 0) * @lock: variable whose update indicate the owner and * contended state. Must be updated only through atomic * operation. This field is used if @flag is MM_THR_PSHARED. * @pshared_key: Identifier of the process-shared lock as known by the lock * referee service process. used if @flag MM_THR_PSHARED. * * This structure is the container of &typedef mm_thr_mutex_t on Win32. * Depending on @flag at mutex initialization, static or with * mm_thr_mutex_init(), @srw_lock or @lock/@pshared_key will be used. * * If @flag is 0, the mutex is a normal one, ie, not shared across process * nor robust. In such a case @srw_lock field is used with the * AcquireSRWLockExclusive() and ReleaseSRWLockExclusive(). Performance will * be similar to using them directly. * * Please note that @srw_lock is defined as void* and not as SRWLOCK to * avoid the need of including synchapi.h or winbase.h (depends on the * Windows version)... The Windows header have many side effect that could * lead to a lot of trouble in the user code depending which headers are * included there and in which order. Declaring as void* is completely safe * (same size and alignment) since a SRWLOCK is a simple structure * containing only one pointer. * * If @flag has MM_THR_PSHARED set, the mutex will use @lock and @pshared_key * fields. See the "process shared mutex implementation" doc for more * details. */ static int mm_thr_mutex_is_pshared(mm_thr_mutex_t * mutex) { /* pshared and srw flag fields are aliased */ return ((mutex->pshared.flag & MM_THR_PSHARED) == MM_THR_PSHARED); } static int mm_thr_cond_is_pshared(mm_thr_cond_t * cond) { /* pshared and srw flag fields are aliased */ return ((cond->pshared.flag & MM_THR_PSHARED) == MM_THR_PSHARED); } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_lock(mm_thr_mutex_t* mutex) { if (mm_thr_mutex_is_pshared(mutex)) return pshared_mtx_lock(&mutex->pshared); AcquireSRWLockExclusive((SRWLOCK*)(&mutex->srw.srw_lock)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_trylock(mm_thr_mutex_t* mutex) { if (mm_thr_mutex_is_pshared(mutex)) return pshared_mtx_trylock(&mutex->pshared); TryAcquireSRWLockExclusive((SRWLOCK*)(&mutex->srw.srw_lock)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_consistent(mm_thr_mutex_t* mutex) { if (mm_thr_mutex_is_pshared(mutex)) return pshared_mtx_consistent(&mutex->pshared); mm_raise_error(EINVAL, "The mutex type is not process shared"); return EINVAL; } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_unlock(mm_thr_mutex_t* mutex) { if (mm_thr_mutex_is_pshared(mutex)) return pshared_mtx_unlock(&mutex->pshared); ReleaseSRWLockExclusive((SRWLOCK*)(&mutex->srw.srw_lock)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_deinit(mm_thr_mutex_t* mutex) { (void)mutex; return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_mutex_init(mm_thr_mutex_t* mutex, int flags) { /* pshared and srw flag fields are aliased */ mutex->pshared.flag = flags; if (mm_thr_mutex_is_pshared(mutex)) return pshared_mtx_init(&mutex->pshared); InitializeSRWLock((SRWLOCK*)(&mutex->srw.srw_lock)); return 0; } /** * struct mm_thr_cond - structure on Win32 behind &typedef mm_thr_cond_t * @flag: flags indicating behavior (any combination of MM_THR_PSHARED * and MM_THR_WAIT_MONOTONIC) * @cv: data aliased to CONDITION_VARIABLE (used if @flag has * MM_THR_PSHARED flag set) * @pshared_key: Identifier of the process-shared lock as known by the lock * referee service process. Used MM_THR_PSHARED if set in @flag. * @waiter_seq: wakeup value of the last waiter queued. Used MM_THR_PSHARED * if set in @flag. * @wakeup_seq: wakeup value of the last wakeup operation that has been * signaled. Used MM_THR_PSHARED if set in @flag. * * This structure is the container of &typedef mm_thr_cond_t on Win32. * Depending on MM_THR_PSHARED flag is set in @flag, the implementation will * differ radically. * * If MM_THR_PSHARED is not set in @type, the implementation of wait, signal, * broadcast will use the Win32 SleepConditionVariableSRW(), * WakeConditionVariable() and WakeAllConditionVariable() and @cv field will * be used. For the same reason as for &mm_thr_mutex.srw, @cv is declared as * void* and not CONDITION_VARIABLE. * * If MM_THR_PSHARED is set in @flag, the condition will be shareable across * processes. The wait, signal, broadcast operations will use the lock * referee process with the @pshared_key, @waiter_seq and @wakeup_seq * fields. * * The MM_THR_WAIT_MONOTONIC flag in @flag will indicate whether the timeout * in mm_thr_cond_timedwait() will be based on MM_CLK_REALTIME or * MM_CLK_MONOTONIC clock. */ static int sleep_win32cv(CONDITION_VARIABLE* cv, SRWLOCK* srwlock, DWORD timeout_ms) { BOOL res; res = SleepConditionVariableSRW(cv, srwlock, timeout_ms, 0); if (res == TRUE) return 0; return (GetLastError() != ERROR_TIMEOUT) ? EINVAL : ETIMEDOUT; } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_wait(mm_thr_cond_t* cond, mm_thr_mutex_t* mutex) { if (mm_thr_cond_is_pshared(cond)) return pshared_cond_wait(&cond->pshared, mutex, NULL); CONDITION_VARIABLE* cv = (CONDITION_VARIABLE*)(&cond->srw.cv); SRWLOCK* srwlock = (SRWLOCK*)(&mutex->srw.srw_lock); return sleep_win32cv(cv, srwlock, INFINITE); } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_timedwait(mm_thr_cond_t* _cond, mm_thr_mutex_t* mutex, const struct mm_timespec* abstime) { struct mm_timespec now; int ret; int64_t delta_ns; DWORD timeout_ms; clockid_t clk_id; CONDITION_VARIABLE* cv; SRWLOCK* srwlock; struct mm_thr_cond_swr * cond; if (mm_thr_cond_is_pshared(_cond)) return pshared_cond_wait(&_cond->pshared, mutex, abstime); cond = &_cond->srw; cv = (CONDITION_VARIABLE*)(&cond->cv); srwlock = (SRWLOCK*)(&mutex->srw.srw_lock); // Find the type of clock to use for timeout clk_id = CLOCK_REALTIME; if (cond->flag & WAITCLK_FLAG_MONOTONIC) clk_id = CLOCK_MONOTONIC; do { // Compute the relative delay to reach timeout mm_gettime(clk_id, &now); delta_ns = mm_timediff_ns(abstime, &now); if (delta_ns < 0) return ETIMEDOUT; // Do actual wait timeout_ms = delta_ns/NS_IN_MS; ret = sleep_win32cv(cv, srwlock, timeout_ms); } while (ret == ETIMEDOUT); return ret; } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_signal(mm_thr_cond_t* cond) { if (mm_thr_cond_is_pshared(cond)) return pshared_cond_signal(&cond->pshared); WakeConditionVariable((CONDITION_VARIABLE*)(&cond->srw.cv)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_broadcast(mm_thr_cond_t* cond) { if (mm_thr_cond_is_pshared(cond)) return pshared_cond_broadcast(&cond->pshared); WakeAllConditionVariable((CONDITION_VARIABLE*)(&cond->srw.cv)); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_deinit(mm_thr_cond_t* cond) { (void) cond; return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_cond_init(mm_thr_cond_t * _cond, int flags) { struct mm_thr_cond_swr * cond = &_cond->srw; /* pshared and srw flag fields are aliased */ cond->flag = flags & ~WAITCLK_MASK; if (flags & MM_THR_WAIT_MONOTONIC) cond->flag |= WAITCLK_FLAG_MONOTONIC; else cond->flag |= WAITCLK_FLAG_REALTIME; if (flags & MM_THR_PSHARED) return pshared_cond_init(&_cond->pshared); InitializeConditionVariable((CONDITION_VARIABLE*)(&cond->cv)); return 0; } static NOINLINE int once_run_init(mm_thr_once_t* once, void (* once_routine)(void)) { static SRWLOCK once_global_lock = SRWLOCK_INIT; static int global_lock_recursion_level = 0; static DWORD global_lock_owner = 0; DWORD tid; // Acquire lock allowing recursion tid = get_tid(); if (global_lock_owner != tid) { AcquireSRWLockExclusive(&once_global_lock); global_lock_owner = tid; } global_lock_recursion_level++; // Execute once routine if (*once == MM_THR_ONCE_INIT) { *once = !MM_THR_ONCE_INIT; once_routine(); } // Unlock recursive lock if (--global_lock_recursion_level == 0) { global_lock_owner = 0; ReleaseSRWLockExclusive(&once_global_lock); } return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_once(mm_thr_once_t* once, void (* once_routine)(void)) { if (UNLIKELY(*once == MM_THR_ONCE_INIT)) once_run_init(once, once_routine); return 0; } /************************************************************************** * * * thread manipulation * * * **************************************************************************/ static unsigned __stdcall thread_proc_wrapper(void* param) { struct mm_thread* thread = param; // Set mm_thread passed as self get_thread_local_data()->thread = thread; thread->retval = thread->routine(thread->arg); _endthreadex(0); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_create(mm_thread_t* thread, void* (*proc)(void*), void* arg) { struct mm_thread* th; th = create_mm_thread_data(); if (!th) return errno; // Mark thread as joinable and register the thread routine th->state &= ~STATE_DETACHED; th->routine = proc; th->arg = arg; th->hnd = (HANDLE)_beginthreadex(NULL, 0, thread_proc_wrapper, th, 0, NULL); if (!th->hnd) { mm_raise_from_errno("Failed to begin thread"); destroy_mm_thread_data(th); return errno; } *thread = th; return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_join(mm_thread_t thread, void** value_ptr) { if (atomic_load(&thread->state) & STATE_DETACHED) { mm_raise_error(EINVAL, "The thread is detached"); return EINVAL; } WaitForSingleObject(thread->hnd, INFINITE); if (value_ptr) *value_ptr = thread->retval; destroy_mm_thread_data(thread); return 0; } /* doc in posix implementation */ API_EXPORTED int mm_thr_detach(mm_thread_t thread) { int64_t prev_state; // Add detached state prev_state = atomic_fetch_add(&thread->state, STATE_DETACHED); if (prev_state & STATE_DETACHED) { mm_raise_error(EINVAL, "The thread is already detached"); return EINVAL; } // Cleanup resources since thread is terminated: resources were not // cleaned at thread termination because it was then known as // joinable if (prev_state & STATE_STOPPED) destroy_mm_thread_data(thread); return 0; } /* doc in posix implementation */ API_EXPORTED mm_thread_t mm_thr_self(void) { struct mm_thread* self; self = get_thread_local_data()->thread; if (LIKELY(self)) return self; // If we reach here, the mm_thread structure of current thread is not // set because it has not been created by mm_thread_create(). So we // must create a mm_thread structure usable by other thread. It is // not necessary to create a usable thread handle because this // thread is not joinable self = create_mm_thread_data(); get_thread_local_data()->thread = self; return self; } mmlib-1.4.2/src/time-posix.c000066400000000000000000000073251435717460000156650ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmtime.h" #include "mmerrno.h" // Ensure that struct mm_timespec if binary compatible with struct timespec // STYLE-EXCEPTION-BEGIN static_assert(sizeof(struct mm_timespec) == sizeof(struct timespec) && alignof(struct mm_timespec) == alignof(struct timespec) && offsetof(struct mm_timespec, tv_sec) == \ offsetof(struct timespec, tv_sec) && sizeof(((struct mm_timespec*)0)->tv_sec) == \ sizeof(((struct timespec*)0)->tv_sec) && offsetof(struct mm_timespec, tv_nsec) == \ offsetof(struct timespec, tv_nsec) && sizeof(((struct mm_timespec*)0)->tv_nsec) == \ sizeof(((struct timespec*)0)->tv_nsec), "mm_timespec structure is not binary compatible with timespec"); // STYLE-EXCEPTION-END /** * mm_gettime() - Get clock value * @clock_id: ID clock type (one of the MM_CLK_* value) * @ts: location that must receive the clock value * * This function get the current value @ts for the specified clock @clock_id. * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. */ API_EXPORTED int mm_gettime(clockid_t clock_id, struct mm_timespec * ts) { int ret; ret = clock_gettime(clock_id, (struct timespec*)ts); if (ret) { mm_raise_from_errno("clock_gettime failed"); return -1; } return 0; } /** * mm_getres() - Get clock resolution * @clock_id: ID clock type (one of the MM_CLK_* value) * @res: location that must receive the clock resolution * * This function get the resolution of the specified clock @clock_id. The * resolution of the specified clock shall be stored in the location pointed * to by @res. * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. * * NOTE: * The resolution of a clock is the minimal time difference that a clock can * observe. If two measure points are taken closer than the resolution step, * the difference between the 2 reported values will be either 0 or the * resolution step. The resolution must not be confused with the accuracy * which refers to how much the system deviates from the truth. The accuracy * of a system can never exceed its resolution! However it is possible to a * accuracy much worse than its resolution. */ API_EXPORTED int mm_getres(clockid_t clock_id, struct mm_timespec * res) { int ret; ret = clock_getres(clock_id, (struct timespec*)res); if (ret) { mm_raise_from_errno("clock_getres failed"); return -1; } return 0; } /** * mm_nanosleep() - Absolute time sleep with specifiable clock * @clock_id: ID clock type (one of the MM_CLK_* value) * @ts: absolute time when execution must resume * * This function cause the current thread to be suspended from execution * until either the time value of the clock specified by @clock_id reaches * the absolute time specified by the @ts argument. If, at the time of the * call, the time value specified by @ts is less than or equal to the time * value of the specified clock, then mm_nanosleep() shall return * immediately and the calling process shall not be suspended. * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. */ API_EXPORTED int mm_nanosleep(clockid_t clock_id, const struct mm_timespec * ts) { int ret; ret = clock_nanosleep(clock_id, TIMER_ABSTIME, (struct timespec*)ts, NULL); if (ret) { mm_raise_error(ret, "clock_nanosleep failed: %s", strerror(ret)); return -1; } return 0; } mmlib-1.4.2/src/time-win32.c000066400000000000000000000065421435717460000154650ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmtime.h" #include "mmerrno.h" #include "clock-win32.h" #include #include #include #include /************************************************************************** * * * Clock gettime implementation * * * **************************************************************************/ /* doc in posix implementation */ API_EXPORTED int mm_gettime(clockid_t clock_id, struct mm_timespec* ts) { switch (clock_id) { case MM_CLK_REALTIME: gettimespec_wallclock_w32(ts); break; case MM_CLK_MONOTONIC: case MM_CLK_MONOTONIC_RAW: gettimespec_monotonic_w32(ts); break; case MM_CLK_CPU_THREAD: gettimespec_thread_w32(ts); break; case MM_CLK_CPU_PROCESS: gettimespec_process_w32(ts); break; default: mm_raise_error(EINVAL, "Invalid clock id: %i", clock_id); return -1; } return 0; } /* doc in posix implementation */ API_EXPORTED int mm_getres(clockid_t clock_id, struct mm_timespec* res) { switch (clock_id) { case MM_CLK_REALTIME: getres_wallclock_w32(res); break; case MM_CLK_MONOTONIC: case MM_CLK_MONOTONIC_RAW: getres_monotonic_w32(res); break; case MM_CLK_CPU_THREAD: getres_thread_w32(res); break; case MM_CLK_CPU_PROCESS: getres_process_w32(res); break; default: mm_raise_error(EINVAL, "Invalid clock id: %i", clock_id); return -1; } return 0; } /************************************************************************** * * * Nanosleep implementation * * * **************************************************************************/ static HANDLE timer_hnd; MM_CONSTRUCTOR(timer) { timer_hnd = CreateWaitableTimer(NULL, TRUE, NULL); } MM_DESTRUCTOR(timer) { CloseHandle(timer_hnd); } static CALLBACK void timer_apc_completion(void* data, DWORD timer_low, DWORD timer_high) { (void)timer_low; (void)timer_high; int* done = data; *done = 1; } static void relative_microsleep(int64_t delta_ns) { int64_t ft_i64; int done; // ft_i64 must be negative to request a relative wait in // SetWaitableTimer() ft_i64 = -((delta_ns + 99) / 100); // Set deadline of waitable timer done = 0; SetWaitableTimer(timer_hnd, (LARGE_INTEGER*)&ft_i64, 0, timer_apc_completion, &done, FALSE); // Do sleep until timer APC completion is executed do { SleepEx(INFINITE, TRUE); } while (done == 0); } /* doc in posix implementation */ API_EXPORTED int mm_nanosleep(clockid_t clock_id, const struct mm_timespec* target) { struct mm_timespec now; int64_t delta_ns; if (clock_id != MM_CLK_REALTIME && clock_id != MM_CLK_MONOTONIC && clock_id != MM_CLK_MONOTONIC_RAW) return mm_raise_error(EINVAL, "Invalid clock (%i)", clock_id); // Wait until the target timestamp is reached while (1) { // Compute the delta in nanosecond to reach the request mm_gettime(clock_id, &now); delta_ns = mm_timediff_ns(target, &now); if (delta_ns <= 0) break; relative_microsleep(delta_ns); } return 0; } mmlib-1.4.2/src/time.c000066400000000000000000000025641435717460000145250ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmtime.h" #include "mmerrno.h" /** * mm_relative_sleep_ns() - relative sleep in nanoseconds * @duration_ns: duration of sleep in nanoseconds * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. */ API_EXPORTED int mm_relative_sleep_ns(int64_t duration_ns) { struct mm_timespec ts; mm_gettime(MM_CLK_MONOTONIC, &ts); mm_timeadd_ns(&ts, duration_ns); return mm_nanosleep(MM_CLK_MONOTONIC, &ts); } /** * mm_relative_sleep_us() - relative sleep in microseconds * @duration_us: duration of sleep in microseconds * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. */ API_EXPORTED int mm_relative_sleep_us(int64_t duration_us) { struct mm_timespec ts; mm_gettime(MM_CLK_MONOTONIC, &ts); mm_timeadd_us(&ts, duration_us); return mm_nanosleep(MM_CLK_MONOTONIC, &ts); } /** * mm_relative_sleep_ms() - relative sleep in milliseconds * @duration_ms: duration of sleep in milliseconds * * Return: 0 in case of success, -1 otherwise with error state set to * indicate the error. */ API_EXPORTED int mm_relative_sleep_ms(int64_t duration_ms) { struct mm_timespec ts; mm_gettime(MM_CLK_MONOTONIC, &ts); mm_timeadd_ms(&ts, duration_ms); return mm_nanosleep(MM_CLK_MONOTONIC, &ts); } mmlib-1.4.2/src/utils-win32.c000066400000000000000000000664261435717460000156760ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "utils-win32.h" #include #include #include #include #include #include "mmerrno.h" #include "mmlog.h" #include "mmlib.h" #include "mmthread.h" // https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-offlineuseraccounts-offlinedomainaccounts-offlinedomainaccount-sid #define SID_STRING_MAXLEN 256 static int set_access_mode(struct w32_create_file_options* opts, int oflags) { switch (oflags & (_O_RDONLY|_O_WRONLY| _O_RDWR)) { case _O_RDONLY: opts->access_mode = GENERIC_READ; break; case _O_WRONLY: opts->access_mode = GENERIC_WRITE; break; case _O_RDWR: opts->access_mode = (GENERIC_READ | GENERIC_WRITE); break; default: mm_raise_error(EINVAL, "Invalid combination of file access mode"); return -1; } return 0; } static int set_creation_mode(struct w32_create_file_options* opts, int oflags) { switch (oflags & (_O_TRUNC|_O_CREAT|_O_EXCL)) { case 0: case _O_EXCL: opts->creation_mode = OPEN_EXISTING; break; case _O_CREAT: opts->creation_mode = OPEN_ALWAYS; break; case _O_TRUNC: case _O_TRUNC|_O_EXCL: opts->creation_mode = TRUNCATE_EXISTING; break; case _O_CREAT|_O_EXCL: case _O_CREAT|_O_TRUNC|_O_EXCL: opts->creation_mode = CREATE_NEW; break; case _O_CREAT|_O_TRUNC: opts->creation_mode = CREATE_ALWAYS; break; default: mm_crash("Previous cases should have covered all possibilities"); } return 0; } LOCAL_SYMBOL int set_w32_create_file_options(struct w32_create_file_options* opts, int oflags) { if (set_access_mode(opts, oflags) || set_creation_mode(opts, oflags)) return -1; opts->file_attribute = FILE_ATTRIBUTE_NORMAL; return 0; } /** * open_handle() - Helper to open file from UTF-8 path * @path: UTF-8 path fo the file to open * @access: desired access to file (dwDesiredAccess in CreateFile()) * @creat: action to take on a file or device that exists or does not * exist. (dwCreationDisposition argument in CreateFile()) * @sec: Security descriptor to use if the file is created. If NULL, * the file associated with the returned handle is assigned a * default security descriptor. * @flags: Flags and attributes. (dwFlagsAndAttributes in CreateFile()) * * Return: in case of success, the handle of the file opened or created. * Otherwise INVALID_HANDLE_VALUE. Please note that this function is meant to * be helper for implement public operation and as such does not set error * state for the sake of more informative error reporting (The caller has * indeed the context of the call. It may retrieve error with GetLastError()) */ LOCAL_SYMBOL HANDLE open_handle(const char* path, DWORD access, DWORD creat, SECURITY_DESCRIPTOR* sec, DWORD flags) { HANDLE hnd = INVALID_HANDLE_VALUE; char16_t* path_u16 = NULL; int path_u16_len; SECURITY_ATTRIBUTES sec_attrs = { .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = sec, .bInheritHandle = FALSE, }; // Get size for converted path into UTF-16 path_u16_len = get_utf16_buffer_len_from_utf8(path); if (path_u16_len < 0) return INVALID_HANDLE_VALUE; // Allocate temporary UTF-16 path path_u16 = mm_malloca(path_u16_len*sizeof(*path_u16)); if (!path_u16) return INVALID_HANDLE_VALUE; // Convert to UTF-16 and open/create file conv_utf8_to_utf16(path_u16, path_u16_len, path); hnd = CreateFileW(path_u16, access, FILE_SHARE_ALL, &sec_attrs, creat, flags, NULL); mm_freea(path_u16); return hnd; } /** * get_file_id_info_from_handle() - get file and volume ids * @hnd: handle of an opened file * @info: pointer to FILE_ID_INFO struct to fill * * This function is similar to GetFileInformationByHandleEx() called with * FileIdInfo with fallback in case of buggy volume type: * GetFileInformationByHandleEx() returns error when called on file on FAT32. * * Returns: 0 in case of success, -1 otherwise. */ LOCAL_SYMBOL int get_file_id_info_from_handle(HANDLE hnd, FILE_ID_INFO* id_info) { BY_HANDLE_FILE_INFORMATION file_info; DWORD id[4]; // First try with normal call if (GetFileInformationByHandleEx(hnd, FileIdInfo, id_info, sizeof(*id_info))) return 0; // GetFileInformationByHandleEx() usually fail with file handle in // FAT32. Let's fall back on GetFileInformationByHandle() if (!GetFileInformationByHandle(hnd, &file_info)) return mm_raise_from_w32err("failed to get file info"); // Convert 2 DWORDs file ID into FILE_ID_128 id[0] = file_info.nFileIndexLow; id[1] = file_info.nFileIndexHigh; id[2] = 0; id[3] = 0; memcpy(&id_info->FileId, id, sizeof(id)); id_info->VolumeSerialNumber = file_info.dwVolumeSerialNumber; return 0; } /************************************************************************** * * * Access control setup * * * **************************************************************************/ #define MIN_ACCESS_FLAGS \ (READ_CONTROL | SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_EA) #define MIN_OWNER_ACCESS_FLAGS \ (DELETE | WRITE_DAC | WRITE_OWNER | FILE_WRITE_EA | \ FILE_WRITE_ATTRIBUTES) #define IS_DACL_EMPTY(acl, dacl_present) \ (((acl) == NULL) && ((dacl_present) == TRUE)) #define IS_DACL_NULL(acl, dacl_present) \ (((acl) == NULL) && ((dacl_present) == FALSE)) /** * union local_sid - type to preallocate buffer for SID on stack * @sid: SID structure (not complete to hold a SID) * @buffer: buffer large enough to hold any SID */ union local_sid { SID sid; char buffer[SECURITY_MAX_SID_SIZE]; }; static union local_sid everyone; MM_CONSTRUCTOR(wellknown_sid) { DWORD len = sizeof(everyone); CreateWellKnownSid(WinWorldSid, NULL, &everyone.sid, &len); } static mode_t access_to_mode(DWORD access) { mode_t mode = 0; mode |= (access & FILE_READ_DATA) ? S_IREAD : 0; mode |= (access & FILE_WRITE_DATA) ? S_IWRITE : 0; mode |= (access & FILE_EXECUTE) ? S_IEXEC : 0; return mode; } static DWORD get_access_from_perm_bits(mode_t mode) { DWORD access = MIN_ACCESS_FLAGS; access |= (mode & S_IREAD) ? FILE_GENERIC_READ : 0; access |= (mode & S_IWRITE) ? FILE_GENERIC_WRITE : 0; access |= (mode & S_IEXEC) ? FILE_GENERIC_EXECUTE : 0; return access; } /** * get_caller_sids() - retrieve owner and primary group SIDs of caller * @owner: pointer to buffer that will receive the owner SID. * Ignored if NULL. * @primary_group: pointer to buffer that will receive the primary group SID. * Ignored if NULL * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error. */ static int get_caller_sids(SID* owner, SID* primary_group) { HANDLE htoken = INVALID_HANDLE_VALUE; char tmp[128]; TOKEN_OWNER* owner_info; TOKEN_PRIMARY_GROUP* group_info; DWORD len; int rv = -1; // Open access token of the caller if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htoken)) goto exit; // Get Owner SID if (owner) { len = sizeof(tmp); owner_info = (TOKEN_OWNER*)tmp; if (!GetTokenInformation(htoken, TokenOwner, owner_info, len, &len)) goto exit; CopySid(SECURITY_MAX_SID_SIZE, owner, owner_info->Owner); } // Get Primary group SID if (primary_group) { len = sizeof(tmp); group_info = (TOKEN_PRIMARY_GROUP*)tmp; if (!GetTokenInformation(htoken, TokenPrimaryGroup, group_info, len, &len)) goto exit; CopySid(SECURITY_MAX_SID_SIZE, primary_group, group_info->PrimaryGroup); } rv = 0; exit: safe_closehandle(htoken); return rv; } static int owner_sid_strlen = -1; static char16_t owner_sid_str[SID_STRING_MAXLEN]; static void init_owner_sid_str(void) { union local_sid owner; char16_t* sidstr = NULL; if (get_caller_sids(&owner.sid, NULL) || !ConvertSidToStringSidW(&owner.sid, &sidstr)) { mm_raise_from_w32err("could not initialize owner SID string"); return; } owner_sid_strlen = wcslen(sidstr); memcpy(owner_sid_str, sidstr, (owner_sid_strlen+1)*sizeof(*sidstr)); LocalFree(sidstr); } LOCAL_SYMBOL const char16_t* get_caller_string_sid_u16(void) { static mm_thr_once_t sid_init_once = MM_THR_ONCE_INIT; // Initialize owner SID string only once: the owner SID of a process // cannot be changed mm_thr_once(&sid_init_once, init_owner_sid_str); if (owner_sid_strlen < 0) return NULL; return owner_sid_str; } /** * local_secdesc_resize() - accommodate local secdesc for a new size * @lsd: pointer to local_secdesc structure to initialize * @size: needed size for SECURITY_DESCRIPTOR * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error. */ static int local_secdesc_resize(struct local_secdesc* lsd, size_t size) { local_secdesc_deinit(lsd); if (size <= sizeof(lsd->buffer)) { lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer; return 0; } lsd->sd = malloc(size); return lsd->sd ? 0 : -1; } /** * local_secdesc_init_from_mode() - init from permission bits * @lsd: pointer to local_secdesc structure to initialize * @mode: permission bits * * This function initialize a security descriptor in self-relative format in * @lsd->sd from the @mode argument and the access token of the caller. This * function is mostly called directly or indirectly when creating a new * object on filesystem (file, directory, named pipe, ...). * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error. */ LOCAL_SYMBOL int local_secdesc_init_from_mode(struct local_secdesc* lsd, mode_t mode) { PACL acl = NULL; SECURITY_DESCRIPTOR abs_sd; DWORD acl_size, access; union local_sid owner, group; DWORD len; int rv = -1; // Initial local secdesc functional field lsd->sd = NULL; // Get SID values if (get_caller_sids(&owner.sid, &group.sid)) goto exit; // Initialize tmp_sd in absolute format InitializeSecurityDescriptor(&abs_sd, SECURITY_DESCRIPTOR_REVISION); acl_size = sizeof(ACL) + 3*sizeof(ACCESS_ALLOWED_ACE); acl_size += GetLengthSid(&owner.sid) - sizeof(DWORD); acl_size += GetLengthSid(&group.sid) - sizeof(DWORD); acl_size += GetLengthSid(&everyone.sid) - sizeof(DWORD); acl = mm_malloca(acl_size); InitializeAcl(acl, acl_size, ACL_REVISION); // Set owner permissions access = get_access_from_perm_bits(mode)|MIN_OWNER_ACCESS_FLAGS; if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &owner.sid)) goto exit; // Set group member permission access = get_access_from_perm_bits(mode << 3); if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &group.sid)) goto exit; // Set everyone permission access = get_access_from_perm_bits(mode << 6); if (!AddAccessAllowedAce(acl, ACL_REVISION, access, &everyone.sid)) goto exit; // Add ACL into security descriptor and return a self-relative version if (!SetSecurityDescriptorDacl(&abs_sd, TRUE, acl, FALSE)) goto exit; // Transform the security descriptor in absolute format into a // self-relative format and put it in lsd buffer len = sizeof(lsd->buffer); lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer; while (!MakeSelfRelativeSD(&abs_sd, lsd->sd, &len)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || local_secdesc_resize(lsd, len)) goto exit; } rv = 0; exit: if (rv) local_secdesc_deinit(lsd); mm_freea(acl); return rv; } /** * local_secdesc_init_from_handle() - init from securable object handle * @lsd: pointer to local_secdesc structure to initialize * @hnd: handle to the securable object (most of time file handle) * * This function initializes a copy of security descriptor of @hnd in * self-relative format in @lsd->sd. * * Return: 0 in case of success, -1 otherwise. Please note that this * function does not set error state. Use GetLastError() to retrieve the * origin of error. */ LOCAL_SYMBOL int local_secdesc_init_from_handle(struct local_secdesc* lsd, HANDLE hnd) { int rv; DWORD len; SECURITY_INFORMATION requested_info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; // Start with using lsd->buffer as backing buffer of secuity descriptor lsd->sd = (SECURITY_DESCRIPTOR*)lsd->buffer; len = sizeof(lsd->buffer); rv = 0; while (!GetKernelObjectSecurity(hnd, requested_info, lsd->sd, len, &len)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || local_secdesc_resize(lsd, len)) { rv = -1; local_secdesc_deinit(lsd); break; } } return rv; } /** * local_secdesc_deinit() - Cleanup a initialized local secdesc. * @lsd: pointer to an initialized local secdesc */ LOCAL_SYMBOL void local_secdesc_deinit(struct local_secdesc* lsd) { if ((char*)lsd->sd != lsd->buffer) free(lsd->sd); lsd->sd = NULL; } /** * local_secdesc_get_mode() - Get permission bits from secdesc * @lsd: pointer to an initialized local secdesc * * Return: permission bits inferred from the security descriptor. */ LOCAL_SYMBOL mode_t local_secdesc_get_mode(struct local_secdesc* lsd) { PSID owner_sid = NULL, group_sid = NULL, sid = NULL; PACL acl = NULL; ACCESS_ALLOWED_ACE* ace = NULL; BOOL defaulted, dacl_present; mode_t mode; int i; GetSecurityDescriptorOwner(lsd->sd, &owner_sid, &defaulted); GetSecurityDescriptorGroup(lsd->sd, &group_sid, &defaulted); GetSecurityDescriptorDacl(lsd->sd, &dacl_present, &acl, &defaulted); // null DACL means all access while empty dacl means no access. See // https://msdn.microsoft.com/en-us/library/windows/desktop/aa446648(v=vs.85).aspx if (IS_DACL_NULL(acl, dacl_present)) return 0777; if (IS_DACL_EMPTY(acl, dacl_present)) return 0; // Scan all "Access Allowed" entries and interpret them as owner, // group or other permission depending on SID in the ACE mode = 0; for (i = 0; i < acl->AceCount; i++) { GetAce(acl, i, (LPVOID*)&ace); if (ace->Header.AceType != ACCESS_ALLOWED_ACE_TYPE) continue; sid = (PSID)(&ace->SidStart); if (owner_sid && EqualSid(sid, owner_sid)) { mode |= access_to_mode(ace->Mask); } else if (group_sid && EqualSid(sid, group_sid)) { mode |= (access_to_mode(ace->Mask) >> 3); } else if (EqualSid(sid, &everyone.sid)) { mode |= (access_to_mode(ace->Mask) >> 6); } } return mode; } /************************************************************************** * * * Win32 Error reporting * * * **************************************************************************/ /** * get_errcode_from_w32err() - translate Win32 error into error code * @w32err: error value returned by GetLastError() * * Return: translated error code usable in mm_raise_error() */ LOCAL_SYMBOL int get_errcode_from_w32err(DWORD w32err) { switch (w32err) { case ERROR_TOO_MANY_OPEN_FILES: case WSAEMFILE: return EMFILE; case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS: return EEXIST; case ERROR_INVALID_NAME: case ERROR_PATH_NOT_FOUND: case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_INVALID_HANDLE: return EBADF; case ERROR_OUTOFMEMORY: return ENOMEM; case ERROR_ACCESS_DENIED: case WSAEACCES: return EACCES; case ERROR_INVALID_PARAMETER: case ERROR_INVALID_ACCESS: case ERROR_INVALID_DATA: case WSAEINVAL: return EINVAL; case ERROR_BAD_EXE_FORMAT: return ENOEXEC; case WSAESHUTDOWN: case ERROR_NO_DATA: case ERROR_BROKEN_PIPE: return EPIPE; case ERROR_NOT_SAME_DEVICE: return EXDEV; case WSASYSNOTREADY: case ERROR_DIRECTORY_NOT_SUPPORTED: return EISDIR; case ERROR_NOT_SUPPORTED: return ENOTSUP; case WSAENETDOWN: return ENETDOWN; case WSAENETUNREACH: return ENETUNREACH; case WSAVERNOTSUPPORTED: return ENOSYS; case WSAEINPROGRESS: return EINPROGRESS; case WSAECONNRESET: return ECONNRESET; case WSAEFAULT: return EFAULT; case WSAEMSGSIZE: return EMSGSIZE; case WSAENOBUFS: return ENOBUFS; case WSAEISCONN: return EISCONN; case WSAENOTCONN: return ENOTCONN; case WSAENOTSOCK: return ENOTSOCK; case WSAECONNREFUSED: return ECONNREFUSED; case WSAEDESTADDRREQ: return EDESTADDRREQ; case WSAEADDRINUSE: return EADDRINUSE; case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; case WSAEOPNOTSUPP: return EOPNOTSUPP; case WSAEINTR: return EINTR; case WSAENOPROTOOPT: return ENOPROTOOPT; case WSAEPFNOSUPPORT: case WSAEAFNOSUPPORT: return EAFNOSUPPORT; case WSAESOCKTNOSUPPORT: case WSAEPROTOTYPE: return EPROTOTYPE; case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; case WAIT_TIMEOUT: case WSAETIMEDOUT: return ETIMEDOUT; case ERROR_NO_UNICODE_TRANSLATION: return EILSEQ; default: return EIO; } } /** * write_w32err_msg() - write error message associated to Win32 error * @w32err: Win32 error code (for example from GetLastError()) * @len: length of buffer in @errmsg * @buff: buffer that must receive the error message * * This writes in @buff the error message associated with Win32 error code * specified by @w32err. If @len is too short, the error message will be * truncated. The resulting string will be null-terminated (if @len is not 0). */ LOCAL_SYMBOL void write_w32err_msg(DWORD w32err, size_t len, char* buff) { if (len == 0) return; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, w32err, 0, buff, len-1, NULL); // Replace '%' character in win32 message to avoid messing with // error message formatting in mm_raise_error_vfull() for (; *buff != '\0'; buff++) { if (*buff == '%') *buff = ' '; } } LOCAL_SYMBOL int mm_raise_from_w32err_full(const char* module, const char* func, const char* srcfile, int srcline, const char* desc, ...) { DWORD w32err = GetLastError(); int errcode, ret; size_t len; va_list args; char errmsg[512]; len = snprintf(errmsg, sizeof(errmsg), "%s : ", desc); // Append Win32 error message if space is remaining on string write_w32err_msg(w32err, sizeof(errmsg)-len, errmsg+len); // Translate win32 error into mmlib error code errcode = get_errcode_from_w32err(w32err); va_start(args, desc); ret = mm_raise_error_vfull(errcode, module, func, srcfile, srcline, NULL, errmsg, args); va_end(args); return ret; } /************************************************************************** * * * File descriptor mmlib metadata * * * **************************************************************************/ /** * is_cygpty_pipe() - Test a pipe handle is a cygwin PTY * @hnd: a handle to a pipe * * cygwin/msys often use mintty to emulate terminal. When this happens, the * handle associated with the terminal is not of the type Console but a * named pipe connected to a local server which implements the xterm * protocol. The name of the pipe is very specific to it and can be * recognized easily. Example of read handle of PTY1 when using msys: * \msys-dd50a72ab4668b33-pty1-from-master * * Return: 1 if @hnd has been recognized to be cygwin/msys PTY, 0 otherwise */ static int is_cygpty_pipe(HANDLE hnd) { char buff[256]; FILE_NAME_INFO* info = (FILE_NAME_INFO*)buff; char* name_u8; char orig[8] = ""; char dir[16] = ""; int r, len; // Get name associated with the handle... Advertised buffer size is // smaller to ensure we can write null terminator at the end // of filename. Also we don't bother to get the name if buffer is // not sufficient, because in this case, the name definitively does // not fit the PTY name pattern if (!GetFileInformationByHandleEx(hnd, FileNameInfo, info, sizeof(buff)-sizeof(WCHAR))) return 0; // Add null terminator to filename len = info->FileNameLength/sizeof(*info->FileName); info->FileName[len] = L'\0'; // Convert filename in utf8 name_u8 = alloca(len + 1); if (conv_utf16_to_utf8(name_u8, len+1, info->FileName) < 0) return 0; // PTY from cygwin/msys have a name of the form: // \(cygwin|msys)-[number_in_hexa]-ptyN-(from-master|to-master) // (the actual \Device\NamedPipe part is striped by // GetFileInformationByHandleEx(FileNameInfo) r = sscanf(name_u8, "\\%7[a-z]-%*[0-9a-f]-pty%*u-%15[a-z]-master", orig, dir); if ((r == 2) && (!strcmp(orig, "msys") || !strcmp(orig, "cygwin")) && (!strcmp(dir, "from") || !strcmp(dir, "to"))) return 1; return 0; } /** * guess_fd_info() - inspect fd and associated handle and guess type info * @fd: file descriptor to inspect * * Once the type is guessed, it is stored in mmlib fd metadata so that it will * not be guessed the next time @fd is encountered... Of course, until * mm_close() is called on @fd. * * Return: a FD_TYPE_* constant different from FD_TYPE_UNKNOWN in case of * success, -1 in case of failure. */ static NOINLINE int guess_fd_info(int fd) { int info, tmode; HANDLE hnd; DWORD mode, type; hnd = (HANDLE)_get_osfhandle(fd); if (hnd == INVALID_HANDLE_VALUE) return -1; type = GetFileType(hnd); info = FD_TYPE_NORMAL; if ((type == FILE_TYPE_CHAR) && GetConsoleMode(hnd, &mode)) { info = FD_TYPE_CONSOLE | FD_FLAG_TEXT | FD_FLAG_ISATTY; goto exit; } else if (type == FILE_TYPE_PIPE) { info = FD_TYPE_PIPE; if (is_cygpty_pipe(hnd)) { info |= FD_FLAG_ISATTY; goto exit; } } // Detect whether the file is in text mode tmode = _setmode(fd, _O_BINARY); if (tmode != -1) { _setmode(fd, tmode); if (tmode != _O_BINARY) info |= FD_FLAG_TEXT; } exit: set_fd_info(fd, info); return info; } // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio #define MAX_FD 2048 static unsigned char fd_infos[MAX_FD] = {0}; /** * get_fd_info_checked() - validate file descriptor and retrieve its info * @fd: file descriptor whose info has to be retrieved * * Return: If successful, a non-negative file descriptor info. If @fd cannot be * a valid file descriptor -1 is returned (please note that error state is NOT * set in such a case). */ LOCAL_SYMBOL int get_fd_info_checked(int fd) { int info; if ((fd < 0) || (fd >= MAX_FD)) return -1; info = fd_infos[fd]; if (UNLIKELY(info == FD_TYPE_UNKNOWN)) info = guess_fd_info(fd); return info; } /** * get_fd_info() - get file descriptor info (no validation check) * @fd: file descriptor whose info has to be retrieved * * Return: file descriptor info */ LOCAL_SYMBOL int get_fd_info(int fd) { return fd_infos[fd]; } /** * set_fd_info() - set file descriptor info (no validation check) * @fd: file descriptor whose info has to be set */ LOCAL_SYMBOL void set_fd_info(int fd, int info) { fd_infos[fd] = info; } /** * close_all_known_fds() - forcingly close all open file descriptors * * Use this function only in case of unusual cleanup process. * Beware: this is not thread safe. Use only this function if the other * threads cannot oipen new fds or if it does not matter */ LOCAL_SYMBOL void close_all_known_fds(void) { int fd; for (fd = 0; fd < MAX_FD; fd++) { if (fd_infos[fd] == FD_TYPE_UNKNOWN) continue; _close(fd); fd_infos[fd] = FD_TYPE_UNKNOWN; } } /************************************************************************** * * * UTF-8/UTF-16 conversion * * * **************************************************************************/ /** * get_utf16_buffer_len_from_utf8() - get size needed for the UTF-16 string * @utf8_str: null terminated UTF-8 encoded string * * Return: number of UTF-16 code unit (ie char16_t) needed to hold the UTF-16 * encoded string that would be equivalent to @utf8_str (this includes the * NUL termination). */ LOCAL_SYMBOL int get_utf16_buffer_len_from_utf8(const char* utf8_str) { int len; len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_str, -1, NULL, 0); return (len == 0) ? -1 : len; } /** * conv_utf8_to_utf16() - convert UTF-8 string into a UTF-16 string * @utf16_str: buffer receiving the converted UTF-16 string * @utf16_len: length of @utf16_len in code unit (char16_t) * @utf8_str: null terminated UTF-8 encoded string * * This function convert the string @utf8_str encoded in UTF-8 into UTF-16 * and store the result in @utf16_str. The length @utf16_len of this buffer * must be large enough to hold the whole transformed string including NUL * termination. Use get_utf16_buffer_size_from_utf8() to allocate the * necessary size. * * Return: O in case of success, -1 otherwise with error state set */ LOCAL_SYMBOL int conv_utf8_to_utf16(char16_t* utf16_str, int utf16_len, const char* utf8_str) { int len; len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_str, -1, utf16_str, utf16_len); return (len == 0) ? -1 : len; } /** * get_utf8_buffer_size_from_utf16() - get size needed for the UTF-8 string * @utf16_str: null terminated UTF-16 encoded string * * Return: number of UTF-8 code unit (ie char) needed to hold the UTF-8 * encoded string that would be equivalent to @utf16_str (this includes the * NUL termination). */ LOCAL_SYMBOL int get_utf8_buffer_len_from_utf16(const char16_t* utf16_str) { int len; len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_str, -1, NULL, 0, NULL, NULL); return (len == 0) ? -1 : len; } /** * conv_utf16_to_utf8() - convert UTF-16 string into a UTF-8 string * @utf8_str: buffer receiving the converted UTF-8 string * @utf8_len: length of @utf8_len in code unit (char16_t) * @utf16_str: null terminated UTF-16 encoded string * * This function convert the string @utf16_str encoded in UTF-16 into UTF-8 * and store the result in @utf8_str. The length @utf8_len of this buffer * must be large enough to hold the whole transformed string including NUL * termination. Use get_utf8_buffer_size_from_utf16() to allocate the * necessary size. * * Return: O in case of success, -1 otherwise with error state set */ LOCAL_SYMBOL int conv_utf16_to_utf8(char* utf8_str, int utf8_len, const char16_t* utf16_str) { int len; len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_str, -1, utf8_str, utf8_len, NULL, NULL); return (len == 0) ? -1 : len; } mmlib-1.4.2/src/utils-win32.h000066400000000000000000000273521435717460000156760ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef UTILS_WIN32_H #define UTILS_WIN32_H #include "mmerrno.h" #include "mmpredefs.h" #include "mmtime.h" #include "mmsysio.h" #include #include #include #include #include #include #define FILE_SHARE_ALL FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE // Not defined in mingw's system header #ifndef ERROR_DIRECTORY_NOT_SUPPORTED # define ERROR_DIRECTORY_NOT_SUPPORTED 0x150 #endif /* ERROR_DIRECTORY_NOT_SUPPORTED */ struct w32_create_file_options { DWORD access_mode; DWORD creation_mode; DWORD file_attribute; }; int set_w32_create_file_options(struct w32_create_file_options* opts, int oflags); int get_errcode_from_w32err(DWORD w32err); void write_w32err_msg(DWORD w32err, size_t len, char* buff); int mm_raise_from_w32err_full(const char* module, const char* func, const char* srcfile, int srcline, const char* desc, ...); #define mm_raise_from_w32err(...) \ mm_raise_from_w32err_full(MM_LOG_MODULE_NAME, \ __func__, \ __FILE__, \ __LINE__, \ __VA_ARGS__) /************************************************************************** * * * Access control setup * * * **************************************************************************/ #define SELF_RELATIVE_SD_MINSIZE 512 /** * struct local_secdesc - type to use SECURITY_DESCRIPTOR on stacl * @sd: pointer to SECURITY_DESCRIPTOR to use * @desc: SECURITY_DESCRIPTOR field to force proper alignment * @buffer: reserved data for stack allocation * * This structure is meant to provide a way to create and manipulate * SECURITY_DESCRIPTOR on stack while ensuring it can coop with arbitrarily * large SECURITY_DESCRIPTOR. * * Once initialized, @sd points either to @buffer (static allocation) * either to a malloc'ed buffer (if the size needed is larger than * sizeof(buffer). */ struct local_secdesc { SECURITY_DESCRIPTOR* sd; union { SECURITY_DESCRIPTOR desc; char buffer[SELF_RELATIVE_SD_MINSIZE]; }; }; int local_secdesc_init_from_handle(struct local_secdesc* lsd, HANDLE hnd); int local_secdesc_init_from_mode(struct local_secdesc* lsd, mode_t mode); void local_secdesc_deinit(struct local_secdesc* lsd); mode_t local_secdesc_get_mode(struct local_secdesc* lsd); const char16_t* get_caller_string_sid_u16(void); /************************************************************************** * * * Win32 handle / file descriptor wrapping * * * **************************************************************************/ enum { FD_TYPE_UNKNOWN = 0, FD_TYPE_NORMAL, FD_TYPE_PIPE, FD_TYPE_CONSOLE, FD_TYPE_IPCDGRAM, FD_TYPE_SOCKET, FD_TYPE_MASK = 0x07 }; #define FD_FIRST_FLAG (FD_TYPE_MASK+1) #define FD_FLAG_APPEND (FD_FIRST_FLAG << 0) #define FD_FLAG_ISATTY (FD_FIRST_FLAG << 1) #define FD_FLAG_TEXT (FD_FIRST_FLAG << 2) #define FD_FLAG_CARRY (FD_FIRST_FLAG << 3) #define FD_CARRY_POS 24 #define FD_CARRY_MASK (0xFF << FD_CARRY_POS) int get_fd_info_checked(int fd); int get_fd_info(int fd); void set_fd_info(int fd, int info); void close_all_known_fds(void); static inline int fd_info_set_carry_char(int info, char ch) { info &= ~FD_CARRY_MASK; info |= ((int)ch) << FD_CARRY_POS; info |= FD_FLAG_CARRY; return info; } static inline char fd_info_get_carry_char(int info) { return (info >> FD_CARRY_POS) & 0xFF; } static inline int fd_info_drop_carry(int info) { return info & ~FD_FLAG_CARRY; } static inline int wrap_handle_into_fd_with_logctx(HANDLE hnd, int* p_fd, int info, const char* func, const char* srcfile, int srcline) { int fd, osf_flags, errnum; osf_flags = _O_NOINHERIT | _O_BINARY; if (info & FD_FLAG_APPEND) osf_flags |= _O_APPEND; fd = _open_osfhandle((intptr_t)hnd, osf_flags); if (UNLIKELY(fd == -1)) goto error; set_fd_info(fd, info); *p_fd = fd; return 0; error: errnum = errno; mm_raise_error_full(errnum, MM_LOG_MODULE_NAME, func, srcfile, srcline, NULL, "Failed to wrap windows handle into file " "descriptor: %s", strerror(errnum)); return -1; } static inline int unwrap_handle_from_fd_with_logctx(HANDLE* p_hnd, int fd, const char* func, const char* srcfile, int srcline) { HANDLE hnd; int errnum; hnd = (HANDLE)_get_osfhandle(fd); if (UNLIKELY(hnd == INVALID_HANDLE_VALUE)) goto error; *p_hnd = hnd; return 0; error: errnum = errno; mm_raise_error_full(errnum, MM_LOG_MODULE_NAME, func, srcfile, srcline, NULL, "Failed to unwrap windows handle from file " "descriptor: %s", strerror(errnum)); return -1; } #define wrap_handle_into_fd(hnd, p_fd, type) \ wrap_handle_into_fd_with_logctx(hnd, \ p_fd, \ type, \ __func__, \ __FILE__, \ __LINE__) #define unwrap_handle_from_fd(p_hnd, fd) \ unwrap_handle_from_fd_with_logctx(p_hnd, \ fd, \ __func__, \ __FILE__, \ __LINE__) HANDLE open_handle(const char* path, DWORD access, DWORD create, SECURITY_DESCRIPTOR* sec, DWORD flags); /** * open_handle_for_metadata() - open file only for reading attributes * @path: UTF-8 path of file * @nofollow: non zero if symlink must not be followed (ie the actual * symlink file, not the target, must be opened) * * Return: in case of success, the handle of the file opened or created. * Otherwise INVALID_HANDLE_VALUE. Please note that this function is meant to * be helper for implement public operation and as such does not set error * state for the sake of more informative error reporting (The caller has * indeed the context of the call. It may retrieve error with GetLastError()) */ #define open_handle_for_metadata(path, nofollow) \ open_handle((path), READ_CONTROL, OPEN_EXISTING, NULL, \ FILE_FLAG_BACKUP_SEMANTICS \ | ((nofollow) ? FILE_FLAG_OPEN_REPARSE_POINT : 0)) static inline void safe_closehandle(HANDLE hnd) { if (hnd == INVALID_HANDLE_VALUE) return; CloseHandle(hnd); } int get_stat_from_handle(HANDLE hnd, struct mm_stat* buf); int get_file_id_info_from_handle(HANDLE hnd, FILE_ID_INFO* info); /************************************************************************** * * * UTF-8/UTF-16 conversion * * * **************************************************************************/ int get_utf16_buffer_len_from_utf8(const char* utf8_str); int conv_utf8_to_utf16(char16_t* utf16_str, int utf16_len, const char* utf8_str); int get_utf8_buffer_len_from_utf16(const char16_t* utf16_str); int conv_utf16_to_utf8(char* utf8_str, int utf8_len, const char16_t* utf16_str); char** get_environ_utf8(void); char* getenv_utf8(const char* name); int setenv_utf8(const char* name, const char* value, int overwrite); int unsetenv_utf8(const char* name); /************************************************************************** * * * Time conversion * * * **************************************************************************/ /* timespec time are expressed since Epoch i.e. since January, 1, 1970 * whereas windows FILETIME since January 1, 1601 (UTC)*/ #define FT_EPOCH (((LONGLONG)27111902 << 32) + (LONGLONG)3577643008) static inline void filetime_to_timespec(FILETIME ft, struct mm_timespec* ts) { ULARGE_INTEGER time_int; time_int.LowPart = ft.dwLowDateTime; time_int.HighPart = ft.dwHighDateTime; time_int.QuadPart -= FT_EPOCH; ts->tv_sec = time_int.QuadPart / 10000000; ts->tv_nsec = (time_int.QuadPart % 10000000)*100; } static inline time_t filetime_to_time(FILETIME ft) { struct mm_timespec ts; filetime_to_timespec(ft, &ts); return ts.tv_sec; } static inline FILETIME timespec_to_filetime(struct mm_timespec ts) { ULARGE_INTEGER time_int; time_int.QuadPart = ((LONGLONG)ts.tv_sec) * 10000000; time_int.QuadPart += (ts.tv_nsec / 100); time_int.QuadPart += FT_EPOCH; return (FILETIME) { .dwLowDateTime = time_int.LowPart, .dwHighDateTime = time_int.HighPart, }; } /************************************************************************** * * * thread related utils * * * **************************************************************************/ /** * get_tid() - fast inline replacement for GetCurrentThreadId() * * This function is a replacement of GetCurrentThreadId() without involving any * library call. This is just a lookup in the thread information block provided * by the Windows NT kernel which is held in the GS segment register (offset * 0x48) in case of 64 bits or FS segment register (offset 0x24) in case of 32 * bits. * * Return: the ID of the current thread */ static inline DWORD get_tid(void) { DWORD tid; #if defined (__x86_64) || defined (_M_X64) tid = __readgsdword(0x48); #elif defined (__i386__) || defined (_M_IX86) tid = __readfsdword(0x24); #else tid = GetCurrentThreadId(); #endif return tid; } static inline void* read_teb_address(unsigned long offset) { #if defined (__x86_64) || defined (_M_X64) return (void*)__readgsqword(offset); #elif defined (__i386__) || defined (_M_IX86) return (void*)__readfsdword(offset); #else return (((char*)NtCurrentTeb())+offset); #endif } #define TEB_OFFSET(member) (offsetof(struct _TEB, member)) #define NUM_TLS_SLOTS (MM_NELEM(((struct _TEB*)0)->TlsSlots)) /** * tls_get_value() - fast inline replacement for TlsGetValue() * @index: The TLS index allocated by the TlsAlloc() function * * This function is a replacement of TlsGetValue() without involving any * library call. It does the same but by directly interrogating the thread * information block. * * Return: If the function succeeds, the return value is the value stored in * the calling thread's TLS slot associated with the specified index. NULL * is returned if no data has been previously associated for this thread. * * NOTE: The function does not perform any parameter validation, it is * important that @index argument is a really a value returned by * TlsAlloc(). */ static inline void* tls_get_value(DWORD index) { unsigned long offset; void** tls_exp_slots; if (index < NUM_TLS_SLOTS) { offset = TEB_OFFSET(TlsSlots) + index*sizeof(PVOID); return read_teb_address(offset); } tls_exp_slots = read_teb_address(TEB_OFFSET(TlsExpansionSlots)); if (!tls_exp_slots) return NULL; return tls_exp_slots[index - NUM_TLS_SLOTS]; } #endif /* ifndef UTILS_WIN32_H */ mmlib-1.4.2/src/utils.c000066400000000000000000000321161435717460000147230ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "file-internal.h" #include "mmerrno.h" #include "mmlib.h" #include "mmlog.h" #include "mmpredefs.h" #include "mmthread.h" #include #include #include #ifdef _WIN32 # include "utils-win32.h" # define getenv getenv_utf8 # define setenv setenv_utf8 # define unsetenv unsetenv_utf8 #else //!_WIN32 extern char** environ; #endif /* ifdef _WIN32 */ /** * mm_getenv() - Return environment variable or default value * @name: name of the environment variable * @default_value: default value * * Return: the value set in the environment if the variable @name is * set. Otherwise @default_value is returned. */ API_EXPORTED const char* mm_getenv(const char* name, const char* default_value) { const char* value = getenv(name); return (value) ? value : default_value; } #ifdef _WIN32 #define MM_ENV_DELIM ";" #else #define MM_ENV_DELIM ":" #endif /** * mm_setenv() - Add or change environment variable * @name: name of the environment variable * @value: value to set the environment variable called @name * @action: set to 0 if only add is permitted * * This updates or adds a variable in the environment of the calling * process. The @name argument points to a string containing the name of an * environment variable to be added or altered. The environment variable * will be set to the value to which @value points. If the environment * variable named by @name already exists, behavior is determined by the value * of the @action variable. * * If the value of @action is 0, the function shall return success and the * environment shall remain unchanged. * If the value of @action is 1/MM_ENV_OVERWRITE, the function overwrites the * environment and returns. * If the value of @action is 2/MM_ENV_PREPEND, the function prepends the * @value to the environment. * If the value of @action is 3/MM_ENV_APPEND, the function appends the @value * to the environment. * * Note: the PREPEND and APPEND actions only make sense when used with PATH-like * environment variables * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_setenv(const char* name, const char* value, int action) { int rv; const char * old_value; char * new_value = NULL; if (action < 0 || action >= MM_ENV_MAX) return mm_raise_error(EINVAL, "invalid action value"); if (action == MM_ENV_PREPEND || action == MM_ENV_APPEND) { old_value = mm_getenv(name, NULL); if (old_value != NULL) { new_value = mm_malloca(strlen(value) + 1 + strlen(old_value)); if (action == MM_ENV_PREPEND) sprintf(new_value, "%s%s%s", value, MM_ENV_DELIM, old_value); else /* action == MM_ENV_APPEND */ sprintf(new_value, "%s%s%s", old_value, MM_ENV_DELIM, value); value = new_value; } } rv = setenv(name, value, action); mm_freea(new_value); if (rv != 0) return mm_raise_from_errno("setenv(%s, %s) failed", name, value); else return 0; } /** * mm_unsetenv() - remove an environment variable * @name: name of environment variable to remove * * This function removes an environment variable from the environment of the * calling process. The @name argument points to a string, which is the name * of the variable to be removed. If the named variable does not exist in * the current environment, the environment shall be unchanged and the * function is considered to have completed successfully. * * Return: 0 in case of success, -1 otherwise with error state set * accordingly. */ API_EXPORTED int mm_unsetenv(const char* name) { if (unsetenv(name)) mm_raise_from_errno("unsetenv(%s) failed", name); return 0; } /** * mm_get_environ() - get array of environment variables * * This function gets an NULL-terminated array of strings corresponding to * the environment of the current process. Each string has the format * "key=val" where key is the name of the environment variable and val its * value. * * NOTE: The array and its content is valid until the environment is * modified, ie until any environment variable is added, modified or * removed. * * Return: a NULL terminated array of "key=val" environment strings. */ API_EXPORTED char const* const* mm_get_environ(void) { char** envp; #if _WIN32 envp = get_environ_utf8(); #else envp = environ; #endif return (char const* const*) envp; } /************************************************************************** * * * Base directories utils * * * **************************************************************************/ static char* basedirs[MM_NUM_DIRTYPE]; MM_DESTRUCTOR(basedir) { int i; for (i = 0; i < MM_NELEM(basedirs); i++) { free(basedirs[i]); basedirs[i] = NULL; } } /** * set_basedir() - assign a value for a known folder * @dirtype: base folder type to assign * @value: value to copy and assign to specified base folder */ static void set_basedir(enum mm_known_dir dirtype, const char* value) { size_t len = strlen(value)+1; char* dir; dir = malloc(len); mm_check(dir != NULL); memcpy(dir, value, len); basedirs[dirtype] = dir; } /** * set_dir_or_home_relative() - set a known folder or use default value * @dirtype: base folder type to assign * @dir: value to copy and assign to base folder if not NULL * @parent: parent folder of the default value (if @dir is NULL) * @suffix: suffix to add to @parent to form default if @dir is NULL * * This function sets basedirs[@dirtype] to a copy of @dir if @dir is not * NULL, otherwise basedirs[@dirtype] is set to a path formed by * @parent/@suffix */ static void set_dir_or_home_relative(enum mm_known_dir dirtype, const char* dir, const char* parent, const char* suffix) { size_t len; char* value; if (dir) { set_basedir(dirtype, dir); return; } len = strlen(parent) + strlen(suffix) + 2; value = mm_malloca(len); mm_check(value); sprintf(value, "%s/%s", parent, suffix); set_basedir(dirtype, value); mm_freea(value); } static void init_basedirs(void) { const char * home, * dir; home = mm_getenv("HOME", mm_getenv("USERPROFILE", NULL)); mm_check(home != NULL); set_basedir(MM_HOME, home); dir = mm_getenv("XDG_CONFIG_HOME", NULL); set_dir_or_home_relative(MM_CONFIG_HOME, dir, home, ".config"); dir = mm_getenv("XDG_CACHE_HOME", NULL); set_dir_or_home_relative(MM_CACHE_HOME, dir, home, ".cache"); dir = mm_getenv("XDG_DATA_HOME", NULL); set_dir_or_home_relative(MM_DATA_HOME, dir, home, ".local/share"); dir = mm_getenv("XDG_RUNTIME_DIR", NULL); set_dir_or_home_relative(MM_RUNTIME_DIR, dir, mm_getenv("TEMP", "/tmp"), mm_getenv("USERNAME", "self")); } /** * mm_get_basedir() - get location of standard base folder * @dirtype: &enum mm_known_dir value specifying the base folder to get * * The base folder provided follow the XDG base directory specification * https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html. * As such, the reported folder can be controlled through environment variables. * * Return: pointer to the corresponding string in case of success, NULL * otherwise with error state set accordingly. If @dirtype is valid, the * function cannot fail. */ API_EXPORTED const char* mm_get_basedir(enum mm_known_dir dirtype) { static mm_thr_once_t basedirs_once = MM_THR_ONCE_INIT; if (dirtype < 0 || dirtype >= MM_NUM_DIRTYPE) { mm_raise_error(EINVAL, "Unknown dir type (%i)", dirtype); return NULL; } // Init base directory the first time we reach here mm_thr_once(&basedirs_once, init_basedirs); return basedirs[dirtype]; } /** * mm_path_from_basedir() - form a path using a standard base folder * @dirtype: &enum mm_known_dir value specifying the base folder * @suffix: path to append to base folder * * Return: pointer to allocated string containing the specified path in case of * success, NULL with error state set accordingly otherwise. The allocated * string must be freed by a call to free() when it is no longer needed. */ API_EXPORTED char* mm_path_from_basedir(enum mm_known_dir dirtype, const char* suffix) { const char* base; char* dir; if (!suffix) { mm_raise_error(EINVAL, "suffix cannot be null"); return NULL; } base = mm_get_basedir(dirtype); if (!base) return NULL; dir = malloc(strlen(base) + strlen(suffix) + 2); if (!dir) { mm_raise_from_errno("Alloc failed"); return NULL; } sprintf(dir, "%s/%s", base, suffix); return dir; } /************************************************************************** * * * Path manipulation utils * * * **************************************************************************/ static const char* get_last_nonsep_ptr(const char* path) { const char* c = path + strlen(path) - 1; // skip trailing path separators while (c > path && is_path_separator(*c)) c--; return c; } static const char* get_basename_ptr(const char* path) { const char * c, * lastptr; lastptr = get_last_nonsep_ptr(path); for (c = lastptr-1; c >= path; c--) { if (is_path_separator(*c)) return (c == lastptr) ? c : c + 1; } return path; } /** * mm_basename() - get basename of a path * @base: string buffer receiving the result (may be NULL) * @path: path whose basename must be computed (may be NULL) * * This function takes the pathname pointed to by @path and write in * @base (if not NULL) the final component of the pathname, deleting any * trailing path separator characters ('/' on POSIX, '/' and '\\' on * Windows). If @base is NULL, the result is not written, only the number * of character needed by the resulting string is returned. * * If path is NULL or "", the resulting basename will be "."; * * Thr pointer @base and @path need not be different: it is legal to use * the same buffer for both (thus realizing an inplace transformation of * the path). More generally, @base and @path may overlap. Also contrary to * its POSIX equivalent, this function is guaranteed to be reentrant. * * NOTE: If allocated length for @base is greater or equal to length of * @path (including null terminator) with a minimum of 2, then it is * guaranteed that the result will not overflow @base. * * Return: the number of character (excluding null termination) that are * written to @base (or would be written if @base is NULL) */ API_EXPORTED int mm_basename(char* base, const char* path) { const char* baseptr; const char* lastptr; int len; if (!path) path = ""; baseptr = get_basename_ptr(path); lastptr = get_last_nonsep_ptr(path)+1; len = lastptr - baseptr; // if len == 0, path was "" or "/" (or "//" or "///" or ...) if (len <= 0) { len = 1; if (path[0] == '\0') baseptr = "."; } if (base) { memmove(base, baseptr, len); base[len] = '\0'; } return len; } /** * mm_dirname() - get directory name of a path * @dir: string buffer receiving the result (may be NULL) * @path: path whose dirname must be computed (may be NULL) * * This takes a pointer to a character string @path that contains a * pathname, and write the pathname of the parent directory of that file in * the buffer pointed to by @dir (if not NULL). This does not perform * pathname resolution; the result will not be affected by whether or not * path exists or by its file type. Trailing path separator ('/' on POSIX, * '/' and '\\' on Windows) not also leading characters are not counted as * part of the path. If @dir is NULL, the result is not written, only the * number of character needed by the resulting string is returned. * * If path is NULL or "", the resulting dirname will be "."; * * The pointers @dir and @path need not be different: it is legal to use * the same buffer for both (thus realizing an inplace transformation of * the path). More generally, @dir and @path may overlap. Also contrary to * its POSIX equivalent, this function is guaranteed to be reentrant. * * NOTE: If allocated length for @dir is greater or equal to length of * @path (including null terminator) with a minimum of 2, then it is * guaranteed that the result will not overflow @dir. * * Return: the number of character (excluding null termination) that are * written to @dir (or would be written if @dir is NULL) */ API_EXPORTED int mm_dirname(char* dir, const char* path) { const char* baseptr; const char* lastptr; int len; if (!path) path = ""; baseptr = get_basename_ptr(path); if (baseptr == path) { len = 1; if (!is_path_separator(*baseptr)) path = "."; goto exit; } lastptr = baseptr-1; // Remove trailing separator while (lastptr > path && is_path_separator(*lastptr)) lastptr--; len = lastptr - path + 1; exit: if (dir) { memmove(dir, path, len); dir[len] = '\0'; } return len; } mmlib-1.4.2/src/volume-win32.c000066400000000000000000000110601435717460000160250ustar00rootroot00000000000000/* * @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include "mmerrno.h" #include "mmpredefs.h" #include "mmsysio.h" #include "utils-win32.h" #include "volume-win32.h" #define INVALID_VOLUME_DEV (~((mm_dev_t)0)) static SRWLOCK volume_lock = SRWLOCK_INIT; static int num_cached_volumes = 0; static struct volume cached_volumes[32]; /* * This descructor cleans up at exit, the volumes information cached lazily by * the function get_volume(). */ MM_DESTRUCTOR(volume_cache) { int i; for (i = 0; i < num_cached_volumes; i++) free(cached_volumes[i].guid_path); } static HANDLE open_volume_path(char16_t* volume_path) { DWORD access = READ_CONTROL; DWORD create = OPEN_EXISTING; DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; return CreateFileW(volume_path, access, FILE_SHARE_ALL, NULL, create, flags, NULL); } static mm_dev_t get_volume_dev(HANDLE hnd) { FILE_ID_INFO id_info = {.VolumeSerialNumber = INVALID_VOLUME_DEV}; get_file_id_info_from_handle(hnd, &id_info); return id_info.VolumeSerialNumber; } static int get_volume_fs_type(HANDLE hnd) { char16_t fs_name[32] = {0}; if (!GetVolumeInformationByHandleW(hnd, NULL, 0, NULL, NULL, NULL, fs_name, sizeof(fs_name)-1)) return FSTYPE_UNKNOWN; if (!wcscmp(fs_name, L"NTFS")) return FSTYPE_NTFS; if (!wcscmp(fs_name, L"FAT32")) return FSTYPE_FAT32; if (!wcscmp(fs_name, L"EXFAT")) return FSTYPE_EXFAT; return FSTYPE_UNKNOWN; } static void volume_init(struct volume* vol, const char16_t* volume_path, mm_dev_t dev, HANDLE hnd) { vol->dev = dev; vol->guid_path = wcsdup(volume_path); vol->fs_type = get_volume_fs_type(hnd); } static struct volume* search_in_cached_volumes(mm_dev_t dev) { int i; for (i = 0; i < num_cached_volumes; i++) { if (dev == cached_volumes[i].dev) return &cached_volumes[i]; } return NULL; } static const struct volume* add_volume(mm_dev_t dev) { HANDLE hnd, find_vol_hnd; char16_t root[MAX_PATH]; struct volume* vol = NULL; if (num_cached_volumes == MM_NELEM(cached_volumes)) { mm_raise_error(EOVERFLOW, "Too many volumes in cache"); return NULL; } root[0] = L'\0'; find_vol_hnd = FindFirstVolumeW(root, MAX_PATH); if (find_vol_hnd == INVALID_HANDLE_VALUE) { mm_raise_from_w32err("FindFirstVolume failed"); return NULL; } // Loop over volume on system until one has a matching serial do { hnd = open_volume_path(root); if (hnd == INVALID_HANDLE_VALUE) continue; if (dev == get_volume_dev(hnd)) { // We have a match vol = &cached_volumes[num_cached_volumes++]; volume_init(vol, root, dev, hnd); } CloseHandle(hnd); } while (!vol && FindNextVolumeW(find_vol_hnd, root, MAX_PATH)); FindVolumeClose(find_vol_hnd); if (vol == NULL) mm_raise_error(ENODEV, "Could not find volume %016llx", dev); return vol; } LOCAL_SYMBOL const struct volume* get_volume_from_dev(mm_dev_t dev) { const struct volume* vol; AcquireSRWLockExclusive(&volume_lock); vol = search_in_cached_volumes(dev); if (!vol) vol = add_volume(dev); ReleaseSRWLockExclusive(&volume_lock); return vol; } /** * volume_get_trash_prefix_u16() - Get folder of user recycle bin or alike * @vol: volume in which the trash must be found * @path: output buffer (must be at least MAX_PATH char16_t wide) * * Return: In case of success, the length of prefix written in @path in term of * number of char16_t (excluding string terminator). Otherwise, -1 is returned. * Please note that this function does not set error state. Use GetLastError() * to retrieve the origin of error. */ LOCAL_SYMBOL int volume_get_trash_prefix_u16(const struct volume* vol, char16_t* path) { const char16_t* sid; // On FAT (FAT32 and exFAT), there aren't any recycle bin folder (at // least in many Windows version) but those filesystem do not have // permission per file, hence we can always write in the root folder of // the volume (if writable volume). if (vol->fs_type == FSTYPE_FAT32 || vol->fs_type == FSTYPE_EXFAT) { return swprintf(path, MAX_PATH, L"%ls\\", vol->guid_path); } sid = get_caller_string_sid_u16(); if (!sid) return -1; // We use recycle bin of the same volume because this is a writable // folder available on each NTFS volume. Now for the FS not NTFS and // not FAT we don't have workaround. Hence we prefer hoping there is a // $Recycle.Bin... We will fallback to usual windows behavior if not. return swprintf(path, MAX_PATH, L"%ls\\$Recycle.bin\\%ls\\", vol->guid_path, sid); } mmlib-1.4.2/src/volume-win32.h000066400000000000000000000006631435717460000160410ustar00rootroot00000000000000/* * @mindmaze_header@ */ #ifndef VOLUME_WIN32_H #define VOLUME_WIN32_H #include #include "mmsysio.h" enum { FSTYPE_UNKNOWN, FSTYPE_NTFS, FSTYPE_FAT32, FSTYPE_EXFAT, }; struct volume { mm_dev_t dev; char16_t* guid_path; int fs_type; }; const struct volume* get_volume_from_dev(mm_dev_t dev); int volume_get_trash_prefix_u16(const struct volume* vol, char16_t* path); #endif /* ifndef VOLUME_WIN32_H */ mmlib-1.4.2/tests/000077500000000000000000000000001435717460000137675ustar00rootroot00000000000000mmlib-1.4.2/tests/.gitignore000066400000000000000000000002501435717460000157540ustar00rootroot00000000000000# ignore test binaries if compiled in-place child-proc perflock testapi.tap testdlfcn testerrno testfile testinternals testlog testprocess testprofile tests-child-proc mmlib-1.4.2/tests/Makefile.am000066400000000000000000000056631435717460000160350ustar00rootroot00000000000000eol= SUBDIRS = handmaid clean-local: $(RM) *.gcno *.gcda AM_CPPFLAGS = \ -I$(top_srcdir)/src \ -DLOCALEDIR=\"$(localedir)\" \ -DBUILDDIR=\"$(abs_builddir)\" \ -DEXEEXT=\"$(EXEEXT)\" \ -DTOP_BUILDDIR=\"$(abs_top_builddir)\" \ -DSRCDIR=\"$(abs_srcdir)\" \ $(CHECK_CPPFLAGS) \ $(eol) AM_CFLAGS = $(CHECK_CFLAGS) $(MM_WARNFLAGS) MMLIB = $(top_builddir)/src/libmmlib.la TEST_EXTENSIONS = .test .tap #if check support TAP output, make use of it if TAP_SUPPORT_IN_CHECK AM_CPPFLAGS += -DCHECK_SUPPORT_TAP TAP_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \ $(top_srcdir)/build-aux/tap-driver.sh else TAP_LOG_DRIVER = $(LOG_DRIVER) endif #TAP_SUPPORT_IN_CHECK TESTS = \ testlog \ testerrno \ testprofile \ $(eol) if BUILD_CHECK_TESTS TESTS += \ testinternals.tap \ testapi.tap \ $(eol) endif check_LTLIBRARIES = dynlib-test.la check_PROGRAMS = \ $(TESTS) \ child-proc \ perflock \ tests-child-proc \ $(eol) testlog_SOURCES = testlog.c testlog_LDADD = $(MMLIB) testerrno_SOURCES = testerrno.c testerrno_LDADD = $(MMLIB) testprofile_SOURCES = testprofile.c testprofile_LDADD = $(MMLIB) child_proc_SOURCES = child-proc.c child_proc_LDADD = $(MMLIB) perflock_SOURCES = perflock.c perflock_LDADD = $(MMLIB) dynlib_test_la_SOURCES = \ dynlib-api.h \ dynlib-test.c \ $(eol) dynlib_test_la_LDFLAGS= \ -module \ -avoid-version \ -no-undefined \ -rpath /nowhere \ $(eol) testinternals_tap_SOURCES = \ internals-testcases.h \ testinternals.c \ log-internals.c \ $(eol) testinternals_tap_LDADD = \ $(CHECK_LIBS) \ @LTLIBINTL@ \ ../src/libmmlib-internal-wrapper.la \ $(eol) testinternals_tap_CPPFLAGS = $(AM_CPPFLAGS) if OS_TYPE_WIN32 testinternals_tap_SOURCES += \ startup-win32-internals.c \ $(eol) testinternals_tap_CPPFLAGS += \ -DMMLIB_API=API_EXPORTED \ -DWIN32_LEAN_AND_MEAN \ -D_WIN32_WINNT=_WIN32_WINNT_WIN8 \ $(eol) endif testapi_tap_SOURCES = \ testapi.c \ api-testcases.h \ tests-child-proc.h \ tests-run-func.c \ alloc-api-tests.c \ time-api-tests.c \ thread-api-tests.c \ threaddata-manipulation.h \ threaddata-manipulation.c \ file-api-tests.c \ socket-api-tests.c \ socket-testlib.h \ socket-testlib.c \ ipc-api-tests.c \ ipc-api-tests-exported.c \ ipc-api-tests-exported.h \ shm-api-tests.c \ dirtests.c \ dlfcn-api-tests.c \ process-api-tests.c \ process-testlib.c \ process-testlib.h \ argparse-api-tests.c \ utils-api-tests.c \ file_advanced_tests.c \ $(eol) testapi_tap_LDADD = \ $(CHECK_LIBS) \ $(MMLIB) \ $(eol) tests_child_proc_SOURCES = \ tests-child-proc.c \ tests-child-proc.h \ process-testlib.c \ process-testlib.h \ threaddata-manipulation.h \ threaddata-manipulation.c \ socket-testlib.h \ socket-testlib.c \ ipc-api-tests-exported.c \ ipc-api-tests-exported.h \ $(eol) tests_child_proc_LDADD = $(MMLIB) tests_child_proc_LDFLAGS = \ -export-dynamic \ $(eol) tests_child_proc_CPPFLAGS = \ $(AM_CPPFLAGS) \ -DMMLOG_MODULE_NAME=\"tests_child_proc\" \ $(eol) mmlib-1.4.2/tests/alloc-api-tests.c000066400000000000000000000075571435717460000171520ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include "api-testcases.h" #include "mmlib.h" #include "mmlog.h" #include "mmerrno.h" #include "mmpredefs.h" #define NUM_ALLOC 30 static int prev_max_loglvl; static void setup(void) { prev_max_loglvl = mm_log_set_maxlvl(MM_LOG_NONE); } static void teardown(void) { mm_log_set_maxlvl(prev_max_loglvl); } START_TEST(aligned_heap_allocation) { void* ptr; size_t align, size; int i; for (i = 1; i < NUM_ALLOC; i++) { for (align = sizeof(void*); align <= MM_PAGESZ; align *= 2) { size = i * align; ptr = mm_aligned_alloc(align, size); ck_assert(ptr != NULL); ck_assert_int_eq((uintptr_t)ptr & (align-1), 0); memset(ptr, 'x', size); mm_aligned_free(ptr); } } } END_TEST START_TEST(aligned_heap_allocation_error) { #if !defined(__SANITIZE_ADDRESS__) void* ptr; size_t align; struct mm_error_state errstate; mm_save_errorstate(&errstate); // Test error if alignment is not power of 2 of pointer size for (align = 0; align < MM_PAGESZ; align++) { if (align >= sizeof(void*) && MM_IS_POW2(align)) continue; ptr = mm_aligned_alloc(align, 4*MM_PAGESZ); ck_assert(ptr == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); } // Test error is size is too big ptr = mm_aligned_alloc(sizeof(void*), SIZE_MAX); ck_assert(ptr == NULL); ck_assert(mm_get_lasterror_number() == ENOMEM); mm_set_errorstate(&errstate); #endif /* !__SANITIZE_ADDRESS__ */ } END_TEST static size_t stack_alloc_sizes[] = { 1, 3, sizeof(double), 64, 57, 256, 950, 2044, 2048, 2056, 4032, }; START_TEST(aligned_stack_allocation) { void* ptr; size_t align, size; size = stack_alloc_sizes[_i]; for (align = sizeof(void*); align <= 2048; align *= 2) { ptr = mm_aligned_alloca(align, size); ck_assert(ptr != NULL); ck_assert_int_eq((uintptr_t)ptr & (align-1), 0); memset(ptr, 'x', size); } } END_TEST static size_t malloca_sizes[] = { 1, 3, sizeof(double), 64, 57, 256, 950, 2044, 2048, 2056, 4032, 4096, 6*4091, 6*4096, 10000000, 100000000 }; START_TEST(safe_stack_allocation) { void* ptr; size_t size = malloca_sizes[_i]; ptr = mm_malloca(size); ck_assert(ptr != NULL); memset(ptr, 'x', size); ck_assert_int_eq((uintptr_t)ptr & (sizeof(char)-1), 0); ck_assert_int_eq((uintptr_t)ptr & (sizeof(short)-1), 0); ck_assert_int_eq((uintptr_t)ptr & (sizeof(int)-1), 0); ck_assert_int_eq((uintptr_t)ptr & (sizeof(long)-1), 0); ck_assert_int_eq((uintptr_t)ptr & (sizeof(float)-1), 0); ck_assert_int_eq((uintptr_t)ptr & (sizeof(double)-1), 0); mm_freea(ptr); } END_TEST START_TEST(safe_stack_allocation_error) { #if !defined(__SANITIZE_ADDRESS__) void* ptr; size_t rem_sz; struct mm_error_state errstate; mm_save_errorstate(&errstate); for (rem_sz = 0; rem_sz < 4*MM_STK_ALIGN; rem_sz++) { ptr = mm_malloca(SIZE_MAX - rem_sz); ck_assert(ptr == NULL); ck_assert(mm_get_lasterror_number() == ENOMEM); } mm_set_errorstate(&errstate); #endif /* !__SANITIZE_ADDRESS__ */ } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_allocation_tcase(void) { TCase *tc = tcase_create("allocation"); tcase_add_checked_fixture(tc, setup, teardown); tcase_add_test(tc, aligned_heap_allocation); tcase_add_test(tc, aligned_heap_allocation_error); tcase_add_loop_test(tc, aligned_stack_allocation, 0, MM_NELEM(stack_alloc_sizes)); tcase_add_loop_test(tc, safe_stack_allocation, 0, MM_NELEM(malloca_sizes)); tcase_add_test(tc, safe_stack_allocation_error); return tc; } mmlib-1.4.2/tests/api-testcases.h000066400000000000000000000010411435717460000167010ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef API_TESTCASES_H #define API_TESTCASES_H #include TCase* create_allocation_tcase(void); TCase* create_time_tcase(void); TCase* create_thread_tcase(void); TCase* create_file_tcase(void); TCase* create_socket_tcase(void); TCase* create_ipc_tcase(void); TCase* create_dir_tcase(void); TCase* create_shm_tcase(void); TCase* create_dlfcn_tcase(void); TCase* create_process_tcase(void); TCase* create_argparse_tcase(void); TCase* create_utils_tcase(void); TCase* create_advanced_file_tcase(void); #endif mmlib-1.4.2/tests/argparse-api-tests.c000066400000000000000000000630001435717460000176450ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "api-testcases.h" #include "mmargparse.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmsysio.h" #include #include #include #define LOREM_IPSUM "Lorem ipsum dolor sit amet, consectetur adipiscing" \ "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." \ "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi " \ "ut aliquip ex ea commodo consequat..." #define NARGV_MAX 10 #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #define COMP_TREE_TESTDIR BUILDDIR"/completion_testdir" struct argv_case { char* argv[NARGV_MAX]; const char* expval; }; /** * argv_len() - return the number of argument in a NULL terminated array * @argv: NULL terminated array of argument strings * * Return: the number of element in @argv, excluding NULL termination */ static int argv_len(char* argv[]) { int len = 0; while (argv[len] != NULL) len++; return len; } /** * fork_enabled() - return 1 if tests are executing through fork() calls * and 0 otherwise * * Return: 1 or 0 depending on the activation of fork() for tests */ static int fork_enabled(void) { const char *s; #ifdef _WIN32 return 0; #endif s = mm_getenv("CK_FORK", NULL); if (s) return 1; return 0; } /************************************************************************** * * * setup/teardown of tests * * * **************************************************************************/ static int prev_stdout_fd; static int rw_out_fd; static FILE* read_out_fp; static char* prev_cwd; static const char* completion_tree[] = { "adir/yet_another_file", "afile", "anotherdir/bar", "anotherdir/foo", "emptydir/", "file_1", "file_2", }; /** * create_completion_test_tree() - create a completion test tree if folder * @testdir: folder where to create the completion tree (will be wiped * if it already exist). */ static void create_completion_test_tree(const char* testdir) { char path[128], dirpath[128]; int i, len, fd; mm_remove(testdir, MM_RECURSIVE|MM_DT_ANY); for (i = 0; i < MM_NELEM(completion_tree); i++) { sprintf(path, "%s/%s", testdir, completion_tree[i]); len = strlen(path); // If this is folder name, create only a folder if (path[len-1] == '/') mm_mkdir(path, 0777, MM_RECURSIVE); // Create parent dir mm_dirname(dirpath, path); mm_mkdir(dirpath, 0777, MM_RECURSIVE); // Create file fd = mm_open(path, O_CREAT|O_WRONLY, 0666); mm_close(fd); } } /** * reset_output_fd() - reset file of redirected output * * This function cleanup the content of file receiving the redirected * standard output and reset its file pointer. */ static void reset_output_fd(void) { fseek(read_out_fp, 0, SEEK_SET); mm_ftruncate(rw_out_fd, 0); } /** * argparse_case_setup() - global setup of argparse tests * * This backups the current standard output object and create a file that * will receive the redirected standard output during the tests */ static void argparse_case_setup(void) { // Save standard output object prev_stdout_fd = mm_dup(STDOUT_FILENO); // Create file that will receive the standard output of test rw_out_fd = mm_open(BUILDDIR"/argparse.out", O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0666); read_out_fp = fdopen(rw_out_fd, "r"); // Create completion test dir and change current directory for it create_completion_test_tree(COMP_TREE_TESTDIR); prev_cwd = mm_getcwd(NULL, 0); mm_chdir(COMP_TREE_TESTDIR); } /** * argparse_case_teardown() - global cleanup of argparse tests * * Restore the standard output as it was before running the argparse test * cases and close the file receiving the redirected standard output */ static void argparse_case_teardown(void) { // Close standard output object backup mm_close(prev_stdout_fd); // This will close rw_out_fd as well fclose(read_out_fp); // Remove completion testdir and restore current directory mm_remove(COMP_TREE_TESTDIR, MM_RECURSIVE|MM_DT_ANY); mm_chdir(prev_cwd); free(prev_cwd); } /** * each_test_setup() - setup run before each argparse test * * This test setup reset (empty) the content of the redirected standard * output. Also it restablish the redirection standard output to file. This * redirection is necessary to catch and inspect the standard output produced * during the test. */ static void each_test_setup(void) { // Since we are going to change the standard output object, we must // flush any pending data in stdout file stream (the thing behind // printf()), otherwise previous write might occurs on redirected // object. In particular, without flush, TAP report may appear in // the wrong place. fflush(stdout); reset_output_fd(); // Connect standard output to a file we can read mm_dup2(rw_out_fd, STDOUT_FILENO); } /** * each_test_teardown() - cleanup run after each argparse test * * This test setup restore the standard output to its initial object. This * is necessary to communicate the test result to the upper layers. */ static void each_test_teardown(void) { // Same as setup counterpart, we change object behind STDOUT_FILENO // file descriptor, so we need to flush stdout file stream // beforehand. fflush(stdout); // Restore previous standard output mm_dup2(prev_stdout_fd, STDOUT_FILENO); } /************************************************************************** * * * argument parsing tests * * * **************************************************************************/ #define STRVAL_UNSET u8"unset_value" #define STRVAL_DEFAULT u8"default_value" #define STRVAL1 u8"skljfhls" #define STRVAL2 u8"é(è-d--(è" #define STRVAL3 u8"!:;mm" #define STRVAL4 u8"µ%POPIP" static struct mm_arg_opt cmdline_optv[] = { {"d|distractor", MM_OPT_OPTSTR, "default_distractor", {NULL}, NULL}, {"s|set", MM_OPT_OPTSTR, STRVAL_DEFAULT, {NULL}, NULL}, }; static const struct argv_case parsing_order_cases[] = { {{"prg_name", "-s", STRVAL1, NULL}, STRVAL1}, {{"prg_name", "-s", STRVAL1, "-s", STRVAL2, NULL}, STRVAL2}, {{"prg_name", "-s", STRVAL1, "-s", NULL}, STRVAL_DEFAULT}, {{"prg_name", "--set="STRVAL3, NULL}, STRVAL3}, {{"prg_name", "--set", STRVAL3, NULL}, STRVAL_DEFAULT}, {{"prg_name", "-s", STRVAL1, "--set="STRVAL3, NULL}, STRVAL3}, {{"prg_name", "-s", STRVAL1, "an_argument", "--set="STRVAL3, NULL}, STRVAL1}, {{"prg_name", "-s", STRVAL1, "--", "--set="STRVAL3, NULL}, STRVAL1}, {{"prg_name", "--set="STRVAL3, "-s", STRVAL4, NULL}, STRVAL4}, {{"prg_name", "-d", STRVAL1, "-s", STRVAL2, NULL}, STRVAL2}, {{"prg_name", "-s", STRVAL1, "-d", STRVAL2, NULL}, STRVAL1}, {{"prg_name", "-s", STRVAL1, "--set="STRVAL4, NULL}, STRVAL4}, {{"prg_name", NULL}, STRVAL_UNSET}, }; static int parse_option_cb(const struct mm_arg_opt* opt, union mm_arg_val value, void* data, int state) { (void)state; const char** strval = data; if (mm_arg_opt_get_key(opt) == 's') *strval = value.str; return 0; } START_TEST(parsing_order_cb) { const char* str_value = STRVAL_UNSET; struct mm_arg_parser parser = { .optv = cmdline_optv, .num_opt = MM_NELEM(cmdline_optv), .cb = parse_option_cb, .cb_data = &str_value, }; int arg_index, argc; const struct argv_case* case_data = &parsing_order_cases[_i]; char** argv = (char**)case_data->argv; argc = argv_len(argv); arg_index = mm_arg_parse(&parser, argc, argv); ck_assert_int_ge(arg_index, 0); ck_assert_str_eq(str_value, case_data->expval); } END_TEST /************************************************************************** * * * argument validation tests * * * **************************************************************************/ static struct { long long ll; unsigned long long ull; int i; unsigned int ui; const char* str; } optdata = {0}; static struct mm_arg_opt argval_valid_tests_optv[] = { {"set-ll", MM_OPT_NEEDLLONG, NULL, {.llptr = &optdata.ll}, NULL}, {"set-ull", MM_OPT_NEEDULLONG, NULL, {.ullptr = &optdata.ull}, "Use this option to ull to @VAL_ULL. Recall value is @VAL_ULL."}, {"i|set-i", MM_OPT_NEEDINT, NULL, {.iptr = &optdata.i}, NULL}, {"set-ui", MM_OPT_NEEDUINT, NULL, {.uiptr = &optdata.ui}, NULL}, {"set-str", MM_OPT_NEEDSTR, NULL, {.sptr = &optdata.str}, NULL}, }; static const struct argv_case help_argv_cases[] = { {.argv = {"prg_name", "-h"}}, {.argv = {"prg_name", "--help"}}, {.argv = {"prg_name", "--help", "hello"}}, {.argv = {"prg_name", "--set-ll=-1", "--help", "hello"}}, {.argv = {"prg_name", "-h", "hello"}}, {.argv = {"prg_name", "--set-ll=-1", "-h", "hello"}}, }; START_TEST(print_help) { struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT, .doc = LOREM_IPSUM, .optv = argval_valid_tests_optv, .num_opt = MM_NELEM(argval_valid_tests_optv), }; int argc, rv; char** argv = (char**)help_argv_cases[_i].argv; argc = argv_len(argv); rv = mm_arg_parse(&parser, argc, argv); ck_assert_int_eq(rv, MM_ARGPARSE_STOP); } END_TEST #define INT_TOOBIG "2147483648" // INT32_MAX+1 #define INT_TOOSMALL "-2147483649" // INT32_MIN-1 #define UINT_TOOBIG "4294967296" // UINT32_MAX+1 #define LLONG_TOOBIG "9223372036854775808" // INT64_MAX+1 #define LLONG_TOOSMALL "-9223372036854775809" // INT64_MIN-1 #define ULLONG_TOOBIG "18446744073709551616" // UINT64_MAX+1 static const struct argv_case error_argv_cases[] = { {.argv = {"prg_name", "-k"}}, {.argv = {"prg_name", "-i"}}, {.argv = {"prg_name", "-i", "-o"}}, {.argv = {"prg_name", "---set-ll=-1"}}, {.argv = {"prg_name", "--unknown-opt"}}, {.argv = {"prg_name", "--set-ll=-1", "--unknown-opt"}}, {.argv = {"prg_name", "-i", "42", "--unknown-opt"}}, {.argv = {"prg_name", "--set-i=not_a_number"}}, {.argv = {"prg_name", "--set-i=21_noise"}}, {.argv = {"prg_name", "--set-i="INT_TOOBIG}}, {.argv = {"prg_name", "--set-i="INT_TOOSMALL}}, {.argv = {"prg_name", "--set-ui="UINT_TOOBIG}}, {.argv = {"prg_name", "--set-ui=-1"}}, {.argv = {"prg_name", "--set-ll="LLONG_TOOBIG}}, {.argv = {"prg_name", "--set-ll="LLONG_TOOSMALL}}, {.argv = {"prg_name", "--set-ull="ULLONG_TOOBIG}}, {.argv = {"prg_name", "--set-ull=-1"}}, }; START_TEST(parsing_error) { struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT, .optv = argval_valid_tests_optv, .num_opt = MM_NELEM(argval_valid_tests_optv), }; int argc, rv; char** argv = (char**)error_argv_cases[_i].argv; argc = argv_len(argv); rv = mm_arg_parse(&parser, argc, argv); ck_assert_int_eq(rv, MM_ARGPARSE_ERROR); } END_TEST START_TEST(optv_parsing_error) { int argc; char** argv = (char**)error_argv_cases[_i].argv; argc = argv_len(argv); mm_arg_optv_parse(MM_NELEM(argval_valid_tests_optv), argval_valid_tests_optv, argc, argv); } END_TEST static const struct argv_case success_argv_cases[] = { {.argv = {"prg_name", "an_arg", "--unknown-opt"}, "an_arg"}, {.argv = {"prg_name", "--", "--unknown-opt"}, "--unknown-opt"}, {.argv = {"prg_name", "-", "--unknown-opt"}, "-"}, {.argv = {"prg_name", "-i", "42", "--", "--unknown-opt"}, "--unknown-opt"}, {.argv = {"prg_name", "-i", "42", "another --arg"}, "another --arg"}, }; START_TEST(parsing_success) { struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT, .optv = argval_valid_tests_optv, .num_opt = MM_NELEM(argval_valid_tests_optv), }; int argc, rv; char** argv = (char**)success_argv_cases[_i].argv; argc = argv_len(argv); rv = mm_arg_parse(&parser, argc, argv); ck_assert_int_ge(rv, 0); ck_assert_str_eq(argv[rv], success_argv_cases[_i].expval); } END_TEST START_TEST(optv_parsing_success) { int argc, rv; char** argv = (char**)success_argv_cases[_i].argv; argc = argv_len(argv); rv = mm_arg_optv_parse(MM_NELEM(argval_valid_tests_optv), argval_valid_tests_optv, argc, argv); ck_assert_int_ge(rv, 0); ck_assert_str_eq(argv[rv], success_argv_cases[_i].expval); } END_TEST /************************************************************************** * * * option name and shortkey parsing tests * * * **************************************************************************/ struct { struct mm_arg_opt opt; char key; char* name; } parse_optname_cases [] = { {{.name = "d"}, .key = 'd', .name = NULL}, {{.name = "choice"}, .key = 0, .name = "choice"}, {{.name = "d|choice"}, .key = 'd', .name = "choice"}, {{.name = "a-choice"}, .key = 0, .name = "a-choice"}, {{.name = "a|a-choice"}, .key = 'a', .name = "a-choice"}, }; START_TEST(get_key) { int key; const struct mm_arg_opt* opt = &parse_optname_cases[_i].opt; key = mm_arg_opt_get_key(opt); ck_assert_int_eq(key, parse_optname_cases[_i].key); } END_TEST START_TEST(get_name) { const char *name, *ref_name; const struct mm_arg_opt* opt = &parse_optname_cases[_i].opt; name = mm_arg_opt_get_name(opt); ref_name = parse_optname_cases[_i].name; if (ref_name) ck_assert_str_eq(name, ref_name); else ck_assert(name == NULL); } END_TEST /************************************************************************** * * * completion tests * * * **************************************************************************/ // Given an number of option, the number N of completion proposal can be up // to 2*N + 2 (short option and long option) and -h and --help. Also We need // to count an additional one for the NULL terminator. #define NMAX_PROPS (2*MM_NELEM(argval_valid_tests_optv) + 2 + 1) struct { char* arg; char* props[NMAX_PROPS]; } comp_cases[] = { {"-", {"-h", "--help", "--set-ll=", "--set-ull=", "-i", "--set-i=", "--set-ui=", "--set-str="}}, {"--", {"--help", "--set-ll=", "--set-ull=", "--set-i=", "--set-ui=", "--set-str="}}, {"--set", {"--set-ll=", "--set-ull=", "--set-i=", "--set-ui=", "--set-str="}}, {"--set-u", {"--set-ull=", "--set-ui="}}, {"--set-ul", {"--set-ull="}}, {"--set-ull=", {""}}, {"--set-ull=123", {"123"}}, }; /** * check_props_from_output_file() - verify completion proposal are present * @expected_props: array of expected completion proposal (NULL term) * * This function verifies that the lines sent to standard output (which has * been redirected to a file) correspond to the expected completion proposal * listed in @expected_props. The completion proposal in the standard output * do not need to appear in the same order as in @expected_props. */ static void check_props_from_output_file(char* expected_props[]) { char prop[128]; int expected_prop_seen[NMAX_PROPS] = {0}; int n_expected, n_seen, i, len; n_expected = argv_len(expected_props); n_seen = 0; // Flush standard output to ensure that content accessible through // read_out_fp is complete fflush(stdout); fseek(read_out_fp, 0, SEEK_SET); // loop over of the line of the file while (fgets(prop, sizeof(prop), read_out_fp)) { // Remove final end of line (if any) len = strlen(prop); if (prop[len-1] == '\n') prop[len-1] = '\0'; // Search the line in the expected props for (i = 0; i < n_expected; i++) { if (!strcmp(prop, expected_props[i])) break; } // Fail if line could have not been found ck_assert_msg(i != n_expected, "\"%s\" not found", prop); n_seen++; // Check we have not already seen the expected prop ck_assert(expected_prop_seen[i]++ == 0); } // verify that we have not missed any expected prop ck_assert_int_eq(n_seen, n_expected); } START_TEST(complete_empty_arg) { struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT | MM_ARG_PARSER_COMPLETION, .optv = argval_valid_tests_optv, .num_opt = MM_NELEM(argval_valid_tests_optv), }; char* only_argv[] = {"bin", ""}; char* few_argv[] = {"bin", "--set-ll=42", "-i", "-23", ""}; int rv; rv = mm_arg_parse(&parser, MM_NELEM(only_argv), only_argv); ck_assert_int_eq(rv, MM_NELEM(only_argv)-1); rv = mm_arg_parse(&parser, MM_NELEM(few_argv), few_argv); ck_assert_int_eq(rv, MM_NELEM(few_argv)-1); } END_TEST START_TEST(complete_opt) { struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT | MM_ARG_PARSER_COMPLETION, .optv = argval_valid_tests_optv, .num_opt = MM_NELEM(argval_valid_tests_optv), }; char** expected_props = comp_cases[_i].props; char* only_argv[] = {"bin", comp_cases[_i].arg}; char* few_argv[] = {"bin", "--set-ll=42", comp_cases[_i].arg}; int rv; rv = mm_arg_parse(&parser, MM_NELEM(only_argv), only_argv); ck_assert_int_eq(rv, MM_ARGPARSE_COMPLETE); check_props_from_output_file(expected_props); reset_output_fd(); rv = mm_arg_parse(&parser, MM_NELEM(few_argv), few_argv); ck_assert_int_eq(rv, MM_ARGPARSE_COMPLETE); check_props_from_output_file(expected_props); } END_TEST struct { char* arg; char* props[MM_NELEM(completion_tree)]; } comp_path_cases[] = { {"", {"adir/", "afile", "anotherdir/", "emptydir/", "file_1", "file_2"}}, {"a", {"adir/", "afile", "anotherdir/"}}, {"f", {"file_1", "file_2"}}, {"an", {"anotherdir/"}}, {"anotherdir", {"anotherdir/"}}, {"anotherdir/", {"anotherdir/bar", "anotherdir/foo"}}, {"emptydir/", {0}}, }; /** * filter_props_type() - copy elements of array that match typemask * @dst_props: destination array * @src_props: source array * @typemask: mask of type flags (MM_DT_*) */ static void filter_props_type(char** dst_props, char** src_props, int typemask) { struct mm_stat st; int i, j; j = 0; for (i = 0; src_props[i]; i++) { // Add to filtered proposal only it type match if (mm_stat(src_props[i], &st, MM_NOFOLLOW)) continue; if ((S_ISREG(st.mode) && (typemask & MM_DT_REG)) || (S_ISDIR(st.mode) && (typemask & MM_DT_DIR)) || (S_ISLNK(st.mode) && (typemask & MM_DT_LNK))) dst_props[j++] = src_props[i]; } dst_props[j] = NULL; } /** * filt_name_path_cb() - test whether name start string passed in data * @name: basename of completion proposal * @dir: dirname of completion proposal * @type: type of completion proposal * @data: pointer passed to mm_arg_complete_path()... must hold the * string beginning to check * * function used to test the callback mechanism of mm_arg_complete_path(). * It checks that completion candidate has its basename (@name) that start * with a string specified by @data. */ static int filt_name_path_cb(const char* name, const char* dir, int type, void* data) { const char* start = data; (void) type; (void) dir; return (strncmp(start, name, strlen(start)) == 0); } /** * filter_props_type() - filter array whose basename has the expected start * @dst_props: destination array * @src_props: source array * @start: string matching the beginning of basename of elements */ static void filter_props_name(char** dst_props, char** src_props, const char* start) { char base[64]; int i, j, len; len = strlen(start); j = 0; for (i = 0; src_props[i]; i++) { mm_basename(base, src_props[i]); if (strncmp(start, base, len) == 0) dst_props[j++] = src_props[i]; } dst_props[j] = NULL; } START_TEST(complete_path) { char* expected_props[MM_NELEM(comp_path_cases[0].props)]; char** expfull_props = comp_path_cases[_i].props; char* arg = comp_path_cases[_i].arg; int typemasks[] = {MM_DT_ANY, MM_DT_REG, MM_DT_DIR}; int i, mask, rv; for (i = 0; i < MM_NELEM(typemasks); i++) { // Consider only proposal matching typemask mask = typemasks[i]; filter_props_type(expected_props, expfull_props, mask); // Complete path from arg and check its expected proposals rv = mm_arg_complete_path(arg, mask, NULL, NULL); ck_assert(rv == 0); check_props_from_output_file(expected_props); reset_output_fd(); // limit further proposal whose basename start by "f" filter_props_name(expected_props, expected_props, "f"); rv = mm_arg_complete_path(arg, mask, filt_name_path_cb, "f"); ck_assert(rv == 0); check_props_from_output_file(expected_props); reset_output_fd(); } } END_TEST struct { char* argv[4]; int opttype; char* props[MM_NELEM(completion_tree)]; } comp_argval_path_cases[] = { { {"prog", "--set-path="}, MM_OPT_NEEDFILE, {"adir/", "afile", "anotherdir/", "emptydir/", "file_1", "file_2"} }, { {"prog", "-p", ""}, MM_OPT_NEEDFILE, {"adir/", "afile", "anotherdir/", "emptydir/", "file_1", "file_2"} }, { {"prog", "--set-path="}, MM_OPT_NEEDDIR, {"adir/", "anotherdir/", "emptydir/"} }, { {"prog", "-p", ""}, MM_OPT_NEEDDIR, {"adir/", "anotherdir/", "emptydir/"} }, { {"prog", "--set-path=a"}, MM_OPT_NEEDFILE, {"adir/", "afile", "anotherdir/"} }, { {"prog", "-p", "a"}, MM_OPT_NEEDFILE, {"adir/", "afile", "anotherdir/"} }, { {"prog", "--set-path=a"}, MM_OPT_NEEDDIR, {"adir/", "anotherdir/"} }, { {"prog", "--set-path=foobar"}, MM_OPT_NEEDFILE, {0} }, }; START_TEST(complete_argval_path) { char** expected_props = comp_argval_path_cases[_i].props; char** argv = comp_argval_path_cases[_i].argv; const char* str = NULL; struct mm_arg_opt optv[] = { {"p|set-path", comp_argval_path_cases[_i].opttype, NULL, {.sptr = &str}, NULL}, }; struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT | MM_ARG_PARSER_COMPLETION, .optv = optv, .num_opt = MM_NELEM(optv), }; int rv; rv = mm_arg_parse(&parser, argv_len(argv), argv); ck_assert(rv == MM_ARGPARSE_COMPLETE); check_props_from_output_file(expected_props); // Ensure that str has NOT been set (completion must not set val) ck_assert(str == NULL); } END_TEST #define INIT_IVAL 9999 static struct mm_arg_opt completion_in_cb_optv[] = { {"d|distractor", MM_OPT_NEEDSTR, "default_distractor", {NULL}, NULL}, {"i|set-i", MM_OPT_OPTINT, "-1", {NULL}, NULL}, }; static struct { char* argv[10]; int exp_val; char* props[2*MM_NELEM(completion_in_cb_optv)+3]; } comp_in_cb_cases[] = { {{"prg_name", "--set-i="}, INIT_IVAL, {"512", "1024"}}, {{"prg_name", "--set-i=42"}, INIT_IVAL, {"512", "1024"}}, {{"prg_name", "--set-i=42", "--set-i=24"}, 42, {"512", "1024"}}, {{"prg_name", "--set-i=42", "--"}, 42, {"--help", "--set-i", "--set-i=", "--distractor="}}, {{"prg_name", "-i"}, INIT_IVAL, {"-i"}}, {{"prg_name", "-i", "42"}, INIT_IVAL, {"512", "1024"}}, {{"prg_name", "-i", "42", "-i", "24"}, 42, {"512", "1024"}}, {{"prg_name", "-i", "42", "-i"}, 42, {"-i"}}, {{"prg_name", "-i", "42"}, INIT_IVAL, {"512", "1024"}}, {{"prg_name", "--set-i=42", "--distractor=24"}, 42, {"24"}}, {{"prg_name", "-i", "42", "-d", "24"}, 42, {"24"}}, }; static int comp_option_parse_cb(const struct mm_arg_opt* opt, union mm_arg_val value, void* data, int state) { int* ival = data; if (mm_arg_opt_get_key(opt) == 'i') { if (state & MM_ARG_OPT_COMPLETION) { printf("512\n1024\n"); return MM_ARGPARSE_COMPLETE; } *ival = value.i; } return 0; } START_TEST(completion_in_cb) { int i_value = INIT_IVAL; struct mm_arg_parser parser = { .flags = MM_ARG_PARSER_NOEXIT | MM_ARG_PARSER_COMPLETION, .optv = completion_in_cb_optv, .num_opt = MM_NELEM(completion_in_cb_optv), .cb = comp_option_parse_cb, .cb_data = &i_value, }; int arg_index, argc; char** argv = comp_in_cb_cases[_i].argv; char** expected_props = comp_in_cb_cases[_i].props; argc = argv_len(argv); arg_index = mm_arg_parse(&parser, argc, argv); ck_assert_int_eq(arg_index, MM_ARGPARSE_COMPLETE); ck_assert_int_eq(i_value, comp_in_cb_cases[_i].exp_val); check_props_from_output_file(expected_props); } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_argparse_tcase(void) { TCase *tc = tcase_create("argparse"); tcase_add_unchecked_fixture(tc, argparse_case_setup, argparse_case_teardown); tcase_add_checked_fixture(tc, each_test_setup, each_test_teardown); tcase_add_loop_test(tc, get_key, 0, MM_NELEM(parse_optname_cases)); tcase_add_loop_test(tc, get_name, 0, MM_NELEM(parse_optname_cases)); tcase_add_loop_test(tc, parsing_order_cb, 0, MM_NELEM(parsing_order_cases)); tcase_add_loop_test(tc, print_help, 0, MM_NELEM(help_argv_cases)); tcase_add_loop_test(tc, parsing_error, 0, MM_NELEM(error_argv_cases)); tcase_add_loop_test(tc, parsing_success, 0, MM_NELEM(success_argv_cases)); if (fork_enabled()) // needed as optv_parsing_error calls exit() tcase_add_loop_exit_test(tc, optv_parsing_error, EXIT_FAILURE, 0, MM_NELEM(error_argv_cases)); tcase_add_loop_test(tc, optv_parsing_success, 0, MM_NELEM(success_argv_cases)); tcase_add_test(tc, complete_empty_arg); tcase_add_loop_test(tc, complete_opt, 0, MM_NELEM(comp_cases)); tcase_add_loop_test(tc, complete_path, 0, MM_NELEM(comp_path_cases)); tcase_add_loop_test(tc, complete_argval_path, 0, MM_NELEM(comp_argval_path_cases)); tcase_add_loop_test(tc, completion_in_cb, 0, MM_NELEM(comp_in_cb_cases)); return tc; } mmlib-1.4.2/tests/child-proc.c000066400000000000000000000032001435717460000161520ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef _WIN32 #include #endif #if defined(_MSC_VER) # define raise_sigill __ud2 #else # define raise_sigill __builtin_trap #endif static void raise_sigfpe(void) { #ifdef _WIN32 RaiseException(EXCEPTION_FLT_DIVIDE_BY_ZERO, 0, 0, NULL); #else raise(SIGFPE); #endif } static int check_open_files(int argc, char* argv[]) { int i; int num_fd; char line[64]; for (i = 0; i < argc; i++) fprintf(stderr, "%s\n", argv[i]); num_fd = atoi(argv[argc-1]); fprintf(stderr, "num_fd = %i\n", num_fd); for (i = 3; i < 2*num_fd+3; i++) { sprintf(line, "fd = %i", i); if (mm_write(i, line, strlen(line)) < 0) fprintf(stderr, "failed write for fd=%i\n", i); } return 0; } static int check_signal(int signum) { union {int* iptr; intptr_t v;} val = {.v = 0}; switch (signum) { case SIGABRT: abort(); case SIGSEGV: printf("%i", *val.iptr); break; // Must raise segfault case SIGFPE: raise_sigfpe(); break; case SIGILL: raise_sigill(); break; default: raise(signum); } return EXIT_FAILURE; } int main(int argc, char* argv[]) { if (argc < 2) { fprintf(stderr, "child-proc: Missing argument\n"); return EXIT_FAILURE; } if (strcmp(argv[1], "check-open-files") == 0) return check_open_files(argc, argv); if (strcmp(argv[1], "check-exit") == 0) return atoi(argv[2]); if (strcmp(argv[1], "check-signal") == 0) return check_signal(atoi(argv[2])); fprintf(stderr, "child-proc: invalid argument: %s\n", argv[1]); return EXIT_FAILURE; } mmlib-1.4.2/tests/dirtests.c000066400000000000000000000263201435717460000157770ustar00rootroot00000000000000/* @mindmaze_header@ */ #if defined (HAVE_CONFIG_H) # include #endif #include #include #include #include #include #include #include #include #include "api-testcases.h" #include "mmerrno.h" #include "mmlib.h" #include "mmlog.h" #include "mmpredefs.h" #include "mmsysio.h" #define TMP_DIR_ROOT BUILDDIR "/mmlib-test-dir" #define TEST_DIR "testdir" #define TEST_FILE "testfile.dat" #define TEST_LINK "testfile.lnk" static void test_teardown(void) { int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); mm_chdir(BUILDDIR); mm_remove(TMP_DIR_ROOT, MM_DT_ANY|MM_RECURSIVE); mm_error_set_flags(flags, MM_ERROR_IGNORE); } static void test_init(void) { mm_mkdir(TMP_DIR_ROOT, 0777, 0); /* ignore error if already present */ mm_chdir(TMP_DIR_ROOT); } START_TEST(test_dir_open_close) { int rv; MM_DIR * d; rv = mm_mkdir("1/2/3", 0777, MM_RECURSIVE); ck_assert(rv == 0); d = mm_opendir("1/2"); ck_assert(d != NULL); mm_closedir(d); } END_TEST START_TEST(test_dir_create_twice) { ck_assert(mm_mkdir(TEST_DIR, 0777, 0) == 0); ck_assert(mm_check_access(TEST_DIR, F_OK) == 0); ck_assert(mm_mkdir(TEST_DIR, 0777, 0) != 0); ck_assert(mm_get_lasterror_number() == EEXIST); ck_assert(mm_mkdir(TEST_DIR, 0777, MM_RECURSIVE) == 0); ck_assert(mm_remove(TEST_DIR, MM_DT_DIR) == 0); } END_TEST START_TEST(test_dir_create_rec) { int rv; rv = mm_mkdir( "1/dir2/dir_3/dir 4/dir_with_a_longer_name_than_the_others", 0777, MM_RECURSIVE); ck_assert(rv == 0); ck_assert(mm_chdir("does_not_exist") != 0); ck_assert(mm_chdir("1/dir2/dir_3/dir 4") == 0); ck_assert(mm_chdir("dir_with_a_longer_name_than_the_others") == 0); } END_TEST static char const* filtype_str(char const type) { switch (type) { case MM_DT_FIFO: return "fifo"; case MM_DT_CHR: return "chr"; case MM_DT_BLK: return "blk"; case MM_DT_DIR: return "dir"; case MM_DT_REG: return "reg"; case MM_DT_LNK: return "link"; default: return "unknown"; } } START_TEST(test_dir_read) { MM_DIR * dir; struct mm_dirent const * dp; int fd; bool found_file, found_dir; int status; mm_mkdir("folder", 0777, 0); fd = mm_open(TEST_FILE, O_CREAT|O_TRUNC|O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd); ck_assert((dir = mm_opendir(".")) != NULL); found_file = found_dir = false; while ((dp = mm_readdir(dir, &status)) != NULL) { ck_assert(status == 0); /* should see the file and the folder we just created */ found_dir |= (dp->type == MM_DT_DIR); found_file |= (dp->type == MM_DT_REG); } while (mm_readdir(dir, &status) != NULL) { ck_assert(status == 0); ck_abort_msg( "readdir should not find anything without rewind()"); } mm_closedir(dir); } END_TEST START_TEST(test_remove) { int fd, rv; fd = mm_open(TEST_FILE, O_CREAT|O_TRUNC|O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd); mm_close(fd); rv = mm_remove(TEST_FILE, MM_DT_ANY); ck_assert(rv == 0); fd = mm_open(TEST_FILE, O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd == -1); } END_TEST START_TEST(test_remove_dir) { ck_assert(mm_mkdir(TEST_DIR, 0777, 0) == 0); ck_assert(mm_remove(TEST_DIR, MM_DT_ANY) == 0); ck_assert(mm_check_access(TEST_DIR, F_OK) == ENOENT); /* same with the recursive flag */ ck_assert(mm_mkdir(TEST_DIR, 0777, 0) == 0); ck_assert(mm_remove(TEST_DIR, MM_DT_ANY|MM_RECURSIVE) == 0); ck_assert(mm_check_access(TEST_DIR, F_OK) == ENOENT); } END_TEST START_TEST(test_remove_type) { int fd, rv; fd = mm_open(TEST_FILE, O_CREAT|O_TRUNC|O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd); mm_close(fd); /* should not remove anything, and fail with permission error: * TEST_FILE is not a link*/ rv = mm_remove(TEST_FILE, MM_DT_DIR); ck_assert(rv != 0); fd = mm_open(TEST_FILE, O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd != -1); mm_close(fd); } END_TEST static void realloc_if_needed(char ** buf, size_t *buf_len, size_t required_len) { char * ptr; size_t new_len; if (required_len <= *buf_len) return; new_len = *buf_len; while (new_len < required_len) new_len *= 2; ptr = realloc(*buf, new_len); ck_assert(ptr != NULL); *buf = ptr; *buf_len = new_len; } /* return a buffer that should be free'd */ static char* tree_rec(const char * path, int lvl, char ** buffer, size_t * buffer_len) { int i, rv, status; MM_DIR * dir; struct mm_dirent const * dp; char tmp[256]; size_t len; ck_assert((dir = mm_opendir(path)) != NULL); while ((dp = mm_readdir(dir, &status)) != NULL) { ck_assert(status == 0); len = strlen(dp->name); if ((len == 1 && memcmp(dp->name, ".", sizeof(".") - 1) == 0) || (len == 2 && memcmp(dp->name, "..", sizeof("..") - 1) == 0)) continue; tmp[0] = '\0'; for (i = 0; i < lvl; i++) strcat(tmp, "\t"); strcat(tmp, "─"); rv = sprintf(tmp + lvl, "├─ %s (%s)\n", dp->name, filtype_str( dp->type)); realloc_if_needed(buffer, buffer_len, strlen(*buffer) + lvl + rv); strcat(*buffer, tmp); if (dp->type == MM_DT_DIR) { char *new_path; new_path = malloc(strlen(path) + strlen(dp->name) + 2); *new_path = '\0'; ck_assert(new_path != NULL); strcat(new_path, path); strcat(new_path, "/"); strcat(new_path, dp->name); *buffer = tree_rec(new_path, lvl + 1, buffer, buffer_len); free(new_path); } } mm_closedir(dir); return *buffer; } static char* tree(const char * path) { size_t buffer_len = 256; char * buffer = malloc(buffer_len); ck_assert(buffer != NULL); buffer[0] = 0; buffer = tree_rec(path, 0, &buffer, &buffer_len); return buffer; } /* * Note about the order dirent() returns: * * Windows display files in alphabetical order * * Linux MOSTLY display files in time order * * Linux actually returns the entries depending on their order creation ... * AND depending on the inode recycling at the moment of the folder creation. * Most of the time it will seem like it's chronological, but it might not be */ #define TREE_REF_ALPHA\ "├─ 1 (dir)\n\t├─ 2 (dir)\n\t\t├─ 3 (dir)\n\t\t\t├─ file1.dat (reg)\n├─ 4 (dir)\n\t├─ 5 (dir)\n\t\t├─ 6 (dir)\n\t\t\t├─ file1.lnk (link)\n" #define TREE_REF_INODE\ "├─ 4 (dir)\n\t├─ 5 (dir)\n\t\t├─ 6 (dir)\n\t\t\t├─ file1.lnk (link)\n├─ 1 (dir)\n\t├─ 2 (dir)\n\t\t├─ 3 (dir)\n\t\t\t├─ file1.dat (reg)\n" #define TREE_1\ "├─ 4 (dir)\n\t├─ 5 (dir)\n\t\t├─ 6 (dir)\n\t\t\t├─ file1.lnk (link)\n" START_TEST(test_remove_rec) { int file1_fd; ssize_t rsz; char * tree_ref, * tree1, * tree2; /* * Create the following tree: * (leave file2.dat open during the call to mm_remove) * . * ├── 1 * │   └── 2 * │   └── 3 * │   └── file1.dat * ├── 4 * │   └── 5 * │   └── 6 * │   └── file1.lnk -> ../../../1/2/3/file1.dat */ ck_assert(mm_mkdir("1/2/3/", 0777, MM_RECURSIVE) == 0); ck_assert(mm_mkdir("4/5/6/", 0777, MM_RECURSIVE) == 0); ck_assert(mm_chdir("1/2/3/") == 0); file1_fd = mm_open("file1.dat", O_CREAT|O_TRUNC|O_RDWR, S_IWUSR| S_IRUSR); ck_assert(file1_fd > 0); /* do not close file1_fd */ ck_assert(mm_chdir("../../../") == 0); ck_assert(mm_symlink("1/2/3/file1.dat", "4/5/6/file1.lnk") == 0); /* tests steps: * - record initial tree * - 1 * - recursive remove all files * - check 1/2/3/file1.dat file and folders removal * - check 4/5/6/file1.lnk remains * - check file1.dat still open and usable * - 2 * - check file cannot be reopened * - close file1.dat * - recursive remove all files * - check tree unchanged */ tree_ref = tree("."); mm_log_debug("Initial tree:\n%s", tree_ref); ck_assert(strcmp(tree_ref, TREE_REF_ALPHA) == 0 || strcmp(tree_ref, TREE_REF_INODE) == 0); /* - 1 * - recursive remove all files * - check 1/2/3/file1.dat file and folders removal * - check 4/5/6/file1.lnk remains * - check file1.dat still open and usable */ ck_assert(mm_remove(".", MM_DT_REG|MM_DT_DIR|MM_RECURSIVE) == 0); tree1 = tree("."); mm_log_debug("Tree1:\n%s", tree1); ck_assert(strcmp(tree1, TREE_1) == 0); rsz = mm_write(file1_fd, "Hello world!", sizeof("Hello world!")); ck_assert(rsz == sizeof("Hello world!")); /* - 2 * - check file cannot be reopened * - close file1.dat * - recursive remove all files * - check tree unchanged */ ck_assert(mm_open("file1.dat", O_TRUNC|O_RDWR, S_IWUSR|S_IRUSR) == -1); mm_close(file1_fd); ck_assert(mm_remove(".", MM_DT_REG|MM_DT_DIR|MM_RECURSIVE) == 0); tree2 = tree("."); mm_log_debug("Tree2:\n%s", tree2); ck_assert(strcmp(tree1, tree2) == 0); /* cleaning */ free(tree2); free(tree1); free(tree_ref); } END_TEST static char* get_realpath(const char *path) { #if defined(_WIN32) return _fullpath(NULL, path, 0); #else return realpath(path, NULL); #endif } static int are_paths_equal(const char* p1, const char* p2) { int res; char *full_p1, *full_p2; full_p1 = get_realpath(p1); full_p2 = get_realpath(p2); res = (strcmp(full_p1, full_p2) == 0); free(full_p1); free(full_p2); return res; } START_TEST(get_currdir_wbuffer) { char buf[sizeof(TMP_DIR_ROOT)]; char* path; path = mm_getcwd(buf, sizeof(buf)); ck_assert(path != NULL); if (!are_paths_equal(path, TMP_DIR_ROOT)) ck_abort_msg("path mismatch: %s != %s", path, TMP_DIR_ROOT); } END_TEST START_TEST(get_currdir_tooshort) { char buf[sizeof(TMP_DIR_ROOT)-3]; ck_assert(mm_getcwd(buf, sizeof(buf)) == NULL); ck_assert_int_eq(mm_get_lasterror_number(), ERANGE); } END_TEST START_TEST(get_currdir_malloc) { char* path; path = mm_getcwd(NULL, 0); ck_assert(path != NULL); if (!are_paths_equal(path, TMP_DIR_ROOT)) ck_abort_msg("path mismatch: %s != %s", path, TMP_DIR_ROOT); free(path); } END_TEST static bool test_symlinks(void) { int fd; bool has_symlink_support = false; mm_remove("symlink-test-file", MM_DT_ANY); mm_remove("symlink-test-file.lnk", MM_DT_ANY); fd = mm_open("symlink-test-file", O_CREAT|O_TRUNC|O_RDWR, S_IWUSR| S_IRUSR); if (fd != -1) { has_symlink_support = (mm_symlink("symlink-test-file", "symlink-test-file.lnk") == 0); mm_close(fd); } mm_remove("symlink-test-file", MM_DT_ANY); mm_remove("symlink-test-file.lnk", MM_DT_ANY); if (!has_symlink_support && strcmp(mm_getenv("TC_DIR_SYMLINK", "no"), "yes") != 0) { fprintf(stderr, "Skipping symlink tests: " "unsupported on windows without special privileges\n" "\n" "Try runinng as administrator or with developer features enabled\n"); return false; } return true; } LOCAL_SYMBOL TCase* create_dir_tcase(void) { TCase * tc; bool has_unprivileged_symlinks = test_symlinks(); tc = tcase_create("dir"); tcase_add_checked_fixture(tc, test_init, test_teardown); tcase_add_test(tc, test_dir_open_close); tcase_add_test(tc, test_dir_create_twice); tcase_add_test(tc, test_dir_create_rec); tcase_add_test(tc, test_dir_read); tcase_add_test(tc, test_remove); tcase_add_test(tc, test_remove_dir); tcase_add_test(tc, test_remove_type); tcase_add_test(tc, get_currdir_wbuffer); tcase_add_test(tc, get_currdir_tooshort); tcase_add_test(tc, get_currdir_malloc); if (has_unprivileged_symlinks) tcase_add_test(tc, test_remove_rec); return tc; } mmlib-1.4.2/tests/dlfcn-api-tests.c000066400000000000000000000103401435717460000171260ustar00rootroot00000000000000/* @mindmaze_header@ */ #if defined (HAVE_CONFIG_H) # include #endif #include #include #include #include #include #include #include #include "api-testcases.h" #include "dynlib-api.h" #define TEST_VAL 0x1f2f3f4f START_TEST(dlopen_simple) { mm_dynlib_t * hndl = mm_dlopen(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, MM_LD_NOW); ck_assert(hndl != NULL); mm_dlclose(hndl); } END_TEST START_TEST(dlopen_invalid_flags) { mm_dynlib_t * hndl = mm_dlopen(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, MM_LD_NOW | MM_LD_LAZY); ck_assert(hndl == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); mm_dlclose(hndl); } END_TEST START_TEST(dlopen_invalid_path) { mm_dynlib_t * hndl = mm_dlopen("invalid-path-name", MM_LD_NOW | MM_LD_LAZY); ck_assert(hndl == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); mm_dlclose(hndl); } END_TEST START_TEST(dlsym_simple) { mm_dynlib_t * hndl = mm_dlopen(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, MM_LD_NOW); ck_assert(hndl != NULL); ck_assert(mm_dlsym(hndl, "api") != NULL); mm_dlclose(hndl); } END_TEST START_TEST(dlsym_invalid) { ck_assert(mm_dlsym(NULL, "invalid-symbol-name") == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); mm_dynlib_t * hndl = mm_dlopen(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, MM_LD_NOW); ck_assert(hndl != NULL); ck_assert(mm_dlsym(hndl, NULL) == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); mm_dlclose(hndl); } END_TEST START_TEST(dlsym_not_found) { mm_dynlib_t * hndl = mm_dlopen(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, MM_LD_NOW); ck_assert(hndl != NULL); ck_assert(mm_dlsym(hndl, "invalid-symbol-name") == NULL); mm_dlclose(hndl); } END_TEST START_TEST(dl_fileext_test) { #ifdef LT_MODULE_EXT ck_assert(strncmp(mm_dl_fileext(), LT_MODULE_EXT, sizeof(LT_MODULE_EXT)) == 0); #endif return; } END_TEST static bool check_dynlibdata_val(struct dynlib_data* data, int expected_ival, const char* expected_str) { if ((data->intval != expected_ival) || (strcmp(data->str, expected_str) != 0) ) return false; return true; } static int run_plugin_tests(const char* plugin, int flags) { mm_dynlib_t* libhnd; struct dynlib_data* data; struct dynlib_vtab* vtab; libhnd = mm_dlopen(plugin, flags); if (!libhnd) goto error; // Test symbol loading if (!(data = mm_dlsym(libhnd, "libdata")) || !(vtab = mm_dlsym(libhnd, "api")) ) goto error; // Test initial values if (!check_dynlibdata_val(data, INITIAL_INTVAL, INITIAL_STR) || (vtab->read_internal_code() != 0) ) { mm_raise_error(MM_EWRONGSTATE, "Wrong initial values"); goto error; } vtab->set_internal_code(TEST_VAL); if (vtab->read_internal_code() != TEST_VAL) { mm_raise_error(MM_EWRONGSTATE, "copied values do not match"); goto error; } vtab->set_data(-2, "test"); if (!check_dynlibdata_val(data, -2, "test")) { mm_raise_error(MM_EWRONGSTATE, "values not set in libdata"); goto error; } vtab->reset_data(); if (!check_dynlibdata_val(data, INITIAL_INTVAL, INITIAL_STR) ) { mm_raise_error(MM_EWRONGSTATE, "Wrong value reset"); goto error; } mm_dlclose(libhnd); return 0; error: fprintf(stderr, "run_plugin_tests(\"%s\", %08x) failed: %s", plugin, flags, mm_get_lasterror_desc()); if (libhnd) mm_dlclose(libhnd); return -1; } START_TEST(plugin_lt_module_ext) { ck_assert(run_plugin_tests(LT_OBJDIR "/dynlib-test" LT_MODULE_EXT, 0) == 0); } END_TEST START_TEST(plugin_ld_append_ext) { ck_assert(run_plugin_tests(LT_OBJDIR "/dynlib-test", MM_LD_APPEND_EXT) == 0); } END_TEST LOCAL_SYMBOL TCase* create_dlfcn_tcase(void) { TCase * tc; tc = tcase_create("dlfcn"); tcase_add_test(tc, dlopen_simple); tcase_add_test(tc, dlopen_invalid_flags); tcase_add_test(tc, dlopen_invalid_path); tcase_add_test(tc, dlsym_simple); tcase_add_test(tc, dlsym_invalid); tcase_add_test(tc, dlsym_not_found); tcase_add_test(tc, dl_fileext_test); tcase_add_test(tc, plugin_lt_module_ext); tcase_add_test(tc, plugin_ld_append_ext); return tc; } mmlib-1.4.2/tests/dynlib-api.h000066400000000000000000000006561435717460000161770ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef DYNLIB_API_H #define DYNLIB_API_H #ifdef __cplusplus extern "C" { #endif #define INITIAL_INTVAL 0xdeadbeef #define INITIAL_STR "str is init" struct dynlib_data { int intval; char str[32]; }; struct dynlib_vtab { void (*set_data)(int, const char*); void (*reset_data)(void); int (*read_internal_code)(void); void (*set_internal_code)(int); }; #ifdef __cplusplus } #endif #endif mmlib-1.4.2/tests/dynlib-test.c000066400000000000000000000015771435717460000164030ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H #include #endif #include "dynlib-api.h" #include #define DYNLIB_DATA_INITIALIZER {.intval = INITIAL_INTVAL, .str = INITIAL_STR} static int internal_code; API_EXPORTED_RELOCATABLE struct dynlib_data libdata = DYNLIB_DATA_INITIALIZER; static void dynlib_set_data(int ival, const char* str) { libdata.intval = ival; strncpy(libdata.str, str, sizeof(libdata.str)-1); } static void dynlib_reset_data(void) { libdata = (struct dynlib_data)DYNLIB_DATA_INITIALIZER; } static int dynlib_read_internal_code(void) { return internal_code; } static void dynlib_set_internal_code(int val) { internal_code = val; } API_EXPORTED struct dynlib_vtab api = { .set_data = dynlib_set_data, .reset_data = dynlib_reset_data, .read_internal_code = dynlib_read_internal_code, .set_internal_code = dynlib_set_internal_code, }; mmlib-1.4.2/tests/dynlib.h000066400000000000000000000001161435717460000154170ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef DYNLIB_API_H #define DYNLIB_API_H #endif mmlib-1.4.2/tests/file-api-tests.c000066400000000000000000000543631435717460000167740ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmsysio.h" #include "mmerrno.h" #include "mmtime.h" #include "api-testcases.h" #define TEST_DATA "string for data test" #define TEST_DIR BUILDDIR"/file-testdir" #define LINKNAME "newlink" #define TEST_FILE "test.dat" struct file_info { char path[64]; int mode; size_t sz; time_t ctime; }; static struct file_info init_setup_files[] = { {.path = "a_filename", .mode = S_IRUSR, .sz = 980650}, {.path = u8"gggg5ψ", .mode = S_IRUSR, .sz = 980650}, {.path = u8"f2éà", .mode = S_IWUSR, .sz = 541}, {.path = u8"afile㐘", .mode = S_IWUSR|S_IRUSR, .sz = 1024}, {.path = u8"dfg-gf", .mode = S_IRWXU, .sz = 8192}, {.path = u8"ozuç㐘rye", .mode = S_IRUSR|S_IXUSR, .sz = 6980}, {.path = u8"fdgrye", .mode = 0, .sz = 1065000}, }; #define NUM_FILE_CASE MM_NELEM(init_setup_files) static void gen_rand_data(char* buff, size_t len) { int data; while (len > 0) { data = rand(); if (len < sizeof(data)) { memcpy(buff, &data, len); break; } *(int*)buff = data; len -= sizeof(data); buff += sizeof(data); } } static int create_file(const char* path, int mode, size_t filelen) { int fd, rv; char buff[512]; ssize_t rsz; size_t len; fd = mm_open(path, O_CREAT|O_WRONLY, mode); if (fd < 0) return -1; rv = 0; while (filelen) { len = filelen > sizeof(buff) ? sizeof(buff) : filelen; gen_rand_data(buff, len); rsz = mm_write(fd, buff, len); if (rsz <= 0) { rv = -1; break; } filelen -= rsz; } mm_close(fd); return rv; } static int create_entry(struct file_info* info) { struct mm_timespec ts; char suffix[16]; // Append a random suffix to prevent file metadata reuse on windows mm_gettime(MM_CLK_REALTIME, &ts); sprintf(suffix, "-%li", (long)ts.tv_nsec); strcat(info->path, suffix); time(&info->ctime); if (create_file(info->path, info->mode, info->sz)) return -1; return 0; } static int fullread(int fd, void* buf, size_t len) { char* cbuf = buf; ssize_t rsz; while (len) { rsz = mm_read(fd, cbuf, len); if (rsz <= 0) return -1; len -= rsz; cbuf += rsz; } return 0; } static bool are_files_same(const char* path1, const char* path2) { int fd1, fd2; ssize_t rsz; char buff1[512], buff2[512]; bool result = false; fd1 = mm_open(path1, O_RDONLY, 0); fd2 = mm_open(path2, O_RDONLY, 0); if (fd1 < 0 || fd2 < 0) goto exit; while (1) { rsz = mm_read(fd1, buff1, sizeof(buff1)); if (rsz < 0) goto exit; // Check end of file1 has been reached if (rsz == 0) { // Check file2 is also at end of file rsz = mm_read(fd2, buff2, sizeof(buff2)); result = (rsz == 0) ? true : false; break; } // Read from file2 and compare data if (fullread(fd2, buff2, rsz) || memcmp(buff1, buff2, rsz) != 0 ) break; } exit: mm_close(fd1); mm_close(fd2); return result; } START_TEST(path_stat) { struct mm_stat st; const struct file_info* info = &init_setup_files[_i]; ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.size, info->sz); ck_assert_int_le(llabs(st.ctime - info->ctime), 1); ck_assert_int_eq(st.nlink, 1); ck_assert(S_ISREG(st.mode)); ck_assert_int_eq((st.mode & 0777), info->mode); } END_TEST START_TEST(fd_stat) { int fd; struct mm_stat st; const struct file_info* info = &init_setup_files[_i]; // Skip file that could not be open if (!(info->mode & S_IRUSR)) return; fd = mm_open(info->path, O_RDONLY, 0); ck_assert(fd >= 0); ck_assert(mm_fstat(fd, &st) == 0); mm_close(fd); ck_assert_int_eq(st.size, info->sz); ck_assert_int_le(llabs(st.ctime - info->ctime), 1); ck_assert_int_eq(st.nlink, 1); ck_assert(S_ISREG(st.mode)); } END_TEST START_TEST(hard_link) { struct mm_stat st, st_ln; const struct file_info* info = &init_setup_files[_i]; // Check number of link is initially 1 ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.nlink, 1); // Make link and check content is identical ck_assert(mm_link(info->path, LINKNAME) == 0); // Skip test if file could not be open if (info->mode & S_IRUSR) ck_assert(are_files_same(info->path, LINKNAME) == true); ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert(mm_stat(LINKNAME, &st_ln, 0) == 0); ck_assert_int_eq(st.nlink, 2); // Compate stat data (must be identical) ck_assert_int_eq(st.dev, st_ln.dev); ck_assert(mm_ino_equal(st.ino, st_ln.ino)); ck_assert_int_eq(st.size, st_ln.size); ck_assert_int_eq(st.ctime, st_ln.ctime); ck_assert_int_eq(st.nlink, st_ln.nlink); ck_assert_int_eq(st.mode, st_ln.mode); ck_assert(mm_unlink(LINKNAME) == 0); ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.nlink, 1); } END_TEST START_TEST(symbolic_link) { char target[128]; struct mm_stat st, st_ln; const struct file_info* info = &init_setup_files[_i]; // Make link and check content is identical ck_assert(mm_symlink(info->path, LINKNAME) == 0); // Skip test if file could not be open if (info->mode & S_IRUSR) ck_assert(are_files_same(info->path, LINKNAME) == true); ck_assert(mm_stat(LINKNAME, &st_ln, MM_NOFOLLOW) == 0); ck_assert(S_ISLNK(st_ln.mode)); ck_assert_int_eq(st_ln.size, strlen(info->path)+1); // Check that value of symlink is the one expected ck_assert(mm_readlink(LINKNAME, target, sizeof(target)) == 0); ck_assert(strcmp(target, info->path) == 0); // Check behavior of mm_readlink() if buffer too small ck_assert(mm_readlink(LINKNAME, target, 3) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EOVERFLOW); // Check that symlink do not increase link number of // original file ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.nlink, 1); // Check that symlink and original file are indeed // different files on filesystem ck_assert(!mm_ino_equal(st.ino, st_ln.ino)); // Compare stat data of pointed file (must be identical) ck_assert(mm_stat(LINKNAME, &st_ln, 0) == 0); ck_assert_int_eq(st.dev, st_ln.dev); ck_assert(mm_ino_equal(st.ino, st_ln.ino)); ck_assert_int_eq(st.size, st_ln.size); ck_assert_int_eq(st.ctime, st_ln.ctime); ck_assert_int_eq(st.nlink, st_ln.nlink); ck_assert_int_eq(st.mode, st_ln.mode); ck_assert(mm_unlink(LINKNAME) == 0); ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.nlink, 1); } END_TEST START_TEST(dir_symbolic_link) { int fd; struct mm_stat st = {0}; struct mm_stat st_ref = {0}; mm_mkdir("somedir", 0777, 0); // Create symlink to somefile parent dir ck_assert(mm_symlink("somedir", "link-to-somedir") == 0); ck_assert(mm_stat("link-to-somedir", &st, MM_NOFOLLOW) == 0); ck_assert(S_ISLNK(st.mode)); ck_assert(mm_stat("link-to-somedir", &st, 0) == 0); ck_assert(S_ISDIR(st.mode)); // Create file in somedir fd = mm_open("somedir/somefile", O_CREAT|O_WRONLY, 0666); mm_close(fd); mm_stat("somedir/somefile", &st_ref, 0); // Check file opened with symlinked parent dir point to same file fd = mm_open("link-to-somedir/somefile", O_RDONLY, 0); ck_assert(mm_fstat(fd, &st) == 0); ck_assert(mm_ino_equal(st.ino, st_ref.ino)); ck_assert_int_eq(st.dev, st_ref.dev); mm_close(fd); ck_assert(mm_unlink("link-to-somedir") == 0); mm_rmdir("somedir"); } END_TEST // On Windows following a dangling directory symlink returns EACCES error // while on POSIX platforms it returns ENOENT. Since the cost to harmonize // this is rather high while the inconveniency is rather small, we leave it // as it is #if defined(WIN32) # define DANGLING_DIR_SYM_ERROR EACCES #else # define DANGLING_DIR_SYM_ERROR ENOENT #endif static void assert_dangling_symlink(const char* linkname, int exp_err) { ck_assert(mm_open(linkname, O_RDONLY, 0) == -1); ck_assert_int_eq(mm_get_lasterror_number(), exp_err); } START_TEST(dangling_symlink) { int fd; // Create symlink on non existing target ck_assert(mm_symlink("./does-not-exist", LINKNAME) == 0); assert_dangling_symlink(LINKNAME, ENOENT); assert_dangling_symlink(LINKNAME"/afile", ENOENT); ck_assert(mm_unlink(LINKNAME) == 0); // Create symlink to disappearing target dir ck_assert(mm_mkdir("adir", 0777, 0) == 0); ck_assert(mm_symlink("adir", LINKNAME) == 0); ck_assert(mm_remove("adir", MM_DT_ANY) == 0); assert_dangling_symlink(LINKNAME, DANGLING_DIR_SYM_ERROR); assert_dangling_symlink(LINKNAME"/afile", ENOENT); ck_assert(mm_unlink(LINKNAME) == 0); // Create symlink to disappearing target file fd = mm_open("afile", O_CREAT|O_WRONLY, 0666); mm_close(fd); ck_assert(mm_symlink("afile", LINKNAME) == 0); mm_remove("afile", MM_DT_ANY); assert_dangling_symlink(LINKNAME, ENOENT); ck_assert(mm_unlink(LINKNAME) == 0); } END_TEST START_TEST(copy_file) { struct mm_stat st, st_cp; const struct file_info* info = &init_setup_files[_i]; // Check number of link is initially 1 ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert_int_eq(st.nlink, 1); // Skip test if file could not be open if (!(info->mode & S_IRUSR)) { ck_assert(mm_copy(info->path, TEST_FILE, 0, 0666)); ck_assert(mm_get_lasterror_number() == EACCES); return; } // Make file copy ck_assert(mm_copy(info->path, TEST_FILE, 0, 0666) == 0); ck_assert(are_files_same(info->path, TEST_FILE) == true); ck_assert(mm_stat(info->path, &st, 0) == 0); ck_assert(mm_stat(TEST_FILE, &st_cp, 0) == 0); // Compare stat data (ino must be different) ck_assert_int_eq(st.dev, st_cp.dev); ck_assert(!mm_ino_equal(st.ino, st_cp.ino)); ck_assert_int_eq(st_cp.nlink, 1); ck_assert_int_eq(st.nlink, 1); ck_assert(mm_unlink(TEST_FILE) == 0); } END_TEST START_TEST(copy_symlink) { struct mm_stat st, st_cp; const char* afile = init_setup_files[0].path; mm_symlink(afile, LINKNAME); // Make copy of symlink's target ck_assert(mm_copy(LINKNAME, TEST_FILE, 0, 0666) == 0); ck_assert(are_files_same(afile, TEST_FILE) == true); ck_assert(mm_stat(TEST_FILE, &st_cp, MM_NOFOLLOW) == 0); ck_assert(S_ISREG(st_cp.mode)); ck_assert(mm_unlink(TEST_FILE) == 0); // Make symlink copy ck_assert(mm_copy(LINKNAME, TEST_FILE, MM_NOFOLLOW, 0666) == 0); ck_assert(are_files_same(afile, TEST_FILE) == true); ck_assert(mm_stat(LINKNAME, &st, MM_NOFOLLOW) == 0); ck_assert(mm_stat(TEST_FILE, &st_cp, MM_NOFOLLOW) == 0); ck_assert(S_ISLNK(st_cp.mode)); ck_assert_int_eq(st.dev, st_cp.dev); ck_assert(!mm_ino_equal(st.ino, st_cp.ino)); ck_assert(mm_unlink(TEST_FILE) == 0); ck_assert(mm_unlink(LINKNAME) == 0); } END_TEST START_TEST(copy_fail) { const char* afile = init_setup_files[0].path; mm_symlink(afile, LINKNAME); // Test not existing source fails with ENOENT ck_assert(mm_copy("does-not-exist", TEST_FILE, 0, 0666) == -1); ck_assert(mm_get_lasterror_number() == ENOENT); // test copy directory fails mm_mkdir("dir", 0777, O_CREAT); ck_assert(mm_copy("dir", TEST_FILE, 0, 0666) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EISDIR); mm_rmdir("dir"); // test regular file copy does not overwrite file mm_copy(afile, TEST_FILE, 0, 0666); ck_assert(mm_copy(afile, TEST_FILE, 0, 0666) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EEXIST); mm_unlink(TEST_FILE); // test regular file copy does not overwrite file mm_copy(LINKNAME, TEST_FILE, 0, 0666); ck_assert(mm_copy(LINKNAME, TEST_FILE, MM_NOFOLLOW, 0666) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EEXIST); mm_unlink(TEST_FILE); mm_unlink(LINKNAME); } END_TEST START_TEST(check_access_not_exist) { ck_assert_int_eq(mm_check_access("does-not-exist", F_OK), ENOENT); } END_TEST START_TEST(check_access_mode) { int exp_rv; const struct file_info* info = &init_setup_files[_i]; // File must exist ck_assert_int_eq(mm_check_access(info->path, F_OK), 0); // Test read access exp_rv = (info->mode & S_IRUSR) ? 0 : EACCES; ck_assert_int_eq(mm_check_access(info->path, R_OK), exp_rv); // Test write access exp_rv = (info->mode & S_IWUSR) ? 0 : EACCES; ck_assert_int_eq(mm_check_access(info->path, W_OK), exp_rv); // Test execute access exp_rv = (info->mode & S_IXUSR) ? 0 : EACCES; ck_assert_int_eq(mm_check_access(info->path, X_OK), exp_rv); } END_TEST START_TEST(unlink_before_close) { int fd, fd2; char str[] = "Hello world!"; char buf[64]; fd = mm_open(TEST_FILE, O_CREAT|O_TRUNC|O_WRONLY, S_IWUSR|S_IRUSR); ck_assert(fd >= 0); fd2 = mm_open(TEST_FILE, O_RDONLY, 0); ck_assert(fd2 >= 0); // Remove file for file system ck_assert(mm_unlink(TEST_FILE) == 0); ck_assert_int_eq(mm_check_access(TEST_FILE, F_OK), ENOENT); // Write to one fd ck_assert_int_eq(mm_write(fd, str, sizeof(str)), sizeof(str)); ck_assert(mm_close(fd) == 0); // Read from other fd ck_assert_int_eq(mm_read(fd2, buf, sizeof(str)), sizeof(str)); ck_assert(memcmp(buf, str, sizeof(str)) == 0); ck_assert(mm_close(fd2) == 0); } END_TEST START_TEST(one_way_pipe) { int fds[2]; int i; ssize_t rsz; size_t data_sz; char* data; struct mm_error_state errstate; char buff[sizeof(TEST_DATA)+42]; ck_assert(mm_pipe(fds) == 0); // Write to proper endpoint and read from the other and check data // is correct for (i = 0; i < 10; i++) { data = TEST_DATA + i; data_sz = sizeof(TEST_DATA) - i; rsz = mm_write(fds[1], data, data_sz); ck_assert_int_eq(rsz, data_sz); rsz = mm_read(fds[0], buff, sizeof(buff)); ck_assert_int_eq(rsz, data_sz); ck_assert(memcmp(buff, data, data_sz) == 0); } mm_save_errorstate(&errstate); // Check writing is rejected on read endpoint and reading is rejected // on write endpoint ck_assert(mm_write(fds[0], TEST_DATA, sizeof(TEST_DATA)) == -1); ck_assert(mm_read(fds[1], TEST_DATA, sizeof(TEST_DATA)) == -1); mm_set_errorstate(&errstate); ck_assert(mm_close(fds[0]) == 0); ck_assert(mm_close(fds[1]) == 0); } END_TEST START_TEST(read_closed_pipe) { int fds[2]; ssize_t rsz, wsz; char buff[128]; gen_rand_data(buff, sizeof(buff)); // Create connected pipe endpoints ck_assert(mm_pipe(fds) == 0); // Write random and close the write end rsz = mm_write(fds[1], buff, sizeof(buff)/2); ck_assert(rsz == sizeof(buff)/2); mm_close(fds[1]); // Read bigger size, ensure that data is cropped to what was written wsz = mm_read(fds[0], buff, sizeof(buff)); ck_assert(rsz == wsz); // At this point, the pipe is empty // Ensure that all read read return 0 ck_assert(mm_read(fds[0], buff, sizeof(buff)) == 0); ck_assert(mm_read(fds[0], buff, sizeof(buff)) == 0); ck_assert(mm_read(fds[0], buff, sizeof(buff)) == 0); mm_close(fds[0]); } END_TEST START_TEST(rename_simple_file) { int fd; fd = mm_open(TEST_FILE, O_CREAT, S_IWUSR|S_IRUSR); ck_assert(fd >= 0); mm_close(fd); // check that renaming works while the file is not used ck_assert(mm_rename(TEST_FILE, "new") == 0); fd = mm_open("new", O_RDWR, S_IWUSR|S_IRUSR); ck_assert(fd >= 0); // check that renaming works while the file is opened ck_assert(mm_rename("new", TEST_FILE) == 0); mm_close(fd); ck_assert(mm_check_access(TEST_FILE, F_OK) == 0); } END_TEST START_TEST(rename_empty_directory) { MM_DIR * dir; ck_assert(mm_mkdir("dir", 0777, O_CREAT) == 0); // check that renaming works while the directory is not used ck_assert(mm_rename("dir", "newdir") == 0); ck_assert(mm_check_access("newdir", F_OK) == 0); dir = mm_opendir("newdir"); ck_assert(dir != NULL); // check that renaming works while the directory is opened ck_assert(mm_rename("newdir", "otherdir") == 0); ck_assert(mm_check_access("otherdir", F_OK) == 0); mm_closedir(dir); mm_rmdir("otherdir"); } END_TEST START_TEST(file_times) { const char* afile = init_setup_files[0].path; const struct mm_timespec ts1 = {.tv_sec = 1234567890, .tv_nsec = 321}; const struct mm_timespec ts2 = {.tv_sec = 1239999890, .tv_nsec = 111}; const struct mm_timespec ts3 = {.tv_sec = 2239999890, .tv_nsec = 444}; struct mm_timespec ts[2]; struct mm_stat buf; ts[0] = ts1; ts[1] = ts2; ck_assert(mm_utimens(afile, ts, 0) == 0); mm_stat(afile, &buf, 0); ck_assert_int_eq(buf.atime, ts1.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = ts3; ts[1] = (struct mm_timespec) {.tv_nsec = UTIME_OMIT, .tv_sec = 1}; ck_assert(mm_utimens(afile, ts, 0) == 0); mm_stat(afile, &buf, 0); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = (struct mm_timespec) {.tv_nsec = UTIME_OMIT, .tv_sec = 1}; ts[1] = ts1; ck_assert(mm_utimens(afile, ts, 0) == 0); mm_stat(afile, &buf, 0); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_eq(buf.mtime, ts1.tv_sec); } END_TEST START_TEST(file_times_now) { const char* afile = init_setup_files[0].path; const struct mm_timespec ts1 = {.tv_sec = 1234567890, .tv_nsec = 321}; const struct mm_timespec ts2 = {.tv_sec = 1239999890, .tv_nsec = 111}; const struct mm_timespec ts3 = {.tv_sec = 2239999890, .tv_nsec = 444}; struct mm_timespec ts[2]; struct mm_stat buf; struct mm_timespec tmin, tmax; ts[0] = ts1; ts[1] = ts2; ck_assert(mm_utimens(afile, ts, 0) == 0); mm_stat(afile, &buf, 0); ck_assert_int_eq(buf.atime, ts1.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = ts3; ts[1] = (struct mm_timespec) {.tv_nsec = UTIME_NOW, .tv_sec = 1}; mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_utimens(afile, ts, 0) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_stat(afile, &buf, 0); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_ge(buf.mtime, tmin.tv_sec); ck_assert_int_le(buf.mtime, tmax.tv_sec); ts[0] = (struct mm_timespec) {.tv_nsec = UTIME_NOW, .tv_sec = 1}; ts[1] = ts1; mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_utimens(afile, ts, 0) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_stat(afile, &buf, 0); ck_assert_int_ge(buf.atime, tmin.tv_sec); ck_assert_int_le(buf.atime, tmax.tv_sec); ck_assert_int_eq(buf.mtime, ts1.tv_sec); ts[0] = ts1; ts[1] = ts2; ck_assert(mm_utimens(afile, ts, 0) == 0); mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_utimens(afile, NULL, 0) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_stat(afile, &buf, 0); ck_assert_int_ge(buf.atime, tmin.tv_sec); ck_assert_int_le(buf.atime, tmax.tv_sec); ck_assert_int_eq(buf.mtime, buf.atime); } END_TEST START_TEST(file_fd_times) { const struct mm_timespec ts1 = {.tv_sec = 1234567890, .tv_nsec = 321}; const struct mm_timespec ts2 = {.tv_sec = 1239999890, .tv_nsec = 111}; const struct mm_timespec ts3 = {.tv_sec = 2239999890, .tv_nsec = 444}; struct mm_timespec ts[2]; struct mm_stat buf; int fd = mm_open(init_setup_files[0].path, O_RDONLY, 0); ts[0] = ts1; ts[1] = ts2; ck_assert(mm_futimens(fd, ts) == 0); mm_fstat(fd, &buf); ck_assert_int_eq(buf.atime, ts1.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = ts3; ts[1] = (struct mm_timespec) {.tv_nsec = UTIME_OMIT, .tv_sec = 1}; ck_assert(mm_futimens(fd, ts) == 0); mm_fstat(fd, &buf); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = (struct mm_timespec) {.tv_nsec = UTIME_OMIT, .tv_sec = 1}; ts[1] = ts1; ck_assert(mm_futimens(fd, ts) == 0); mm_fstat(fd, &buf); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_eq(buf.mtime, ts1.tv_sec); mm_close(fd); } END_TEST START_TEST(file_fd_times_now) { const struct mm_timespec ts1 = {.tv_sec = 1234567890, .tv_nsec = 321}; const struct mm_timespec ts2 = {.tv_sec = 1239999890, .tv_nsec = 111}; const struct mm_timespec ts3 = {.tv_sec = 2239999890, .tv_nsec = 444}; struct mm_timespec ts[2]; struct mm_stat buf; struct mm_timespec tmin, tmax; int fd = mm_open(init_setup_files[0].path, O_RDONLY, 0); ts[0] = ts1; ts[1] = ts2; ck_assert(mm_futimens(fd, ts) == 0); mm_fstat(fd, &buf); ck_assert_int_eq(buf.atime, ts1.tv_sec); ck_assert_int_eq(buf.mtime, ts2.tv_sec); ts[0] = ts3; ts[1] = (struct mm_timespec) {.tv_nsec = UTIME_NOW, .tv_sec = 1}; mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_futimens(fd, ts) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_fstat(fd, &buf); ck_assert_int_eq(buf.atime, ts3.tv_sec); ck_assert_int_ge(buf.mtime, tmin.tv_sec); ck_assert_int_le(buf.mtime, tmax.tv_sec); ts[0] = (struct mm_timespec) {.tv_nsec = UTIME_NOW, .tv_sec = 1}; ts[1] = ts1; mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_futimens(fd, ts) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_fstat(fd, &buf); ck_assert_int_ge(buf.atime, tmin.tv_sec); ck_assert_int_le(buf.atime, tmax.tv_sec); ck_assert_int_eq(buf.mtime, ts1.tv_sec); ts[0] = ts1; ts[1] = ts2; ck_assert(mm_futimens(fd, ts) == 0); mm_gettime(MM_CLK_REALTIME, &tmin); ck_assert(mm_futimens(fd, NULL) == 0); mm_gettime(MM_CLK_REALTIME, &tmax); mm_fstat(fd, &buf); ck_assert_int_ge(buf.atime, tmin.tv_sec); ck_assert_int_le(buf.atime, tmax.tv_sec); ck_assert_int_eq(buf.mtime, buf.atime); mm_close(fd); } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ static void cleanup_testdir(void) { int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); mm_chdir(BUILDDIR); mm_remove(TEST_DIR, MM_DT_ANY|MM_RECURSIVE); mm_error_set_flags(flags, MM_ERROR_IGNORE); } static void init_testdir(void) { int i; mm_remove(TEST_DIR, MM_DT_ANY|MM_RECURSIVE); if (mm_mkdir(TEST_DIR, S_IRWXU, MM_RECURSIVE) || mm_chdir(TEST_DIR) ) return; for (i = 0; i < MM_NELEM(init_setup_files); i++) { if (create_entry(&init_setup_files[i])) return; } } LOCAL_SYMBOL TCase* create_file_tcase(void) { TCase *tc = tcase_create("file"); tcase_add_unchecked_fixture(tc, init_testdir, cleanup_testdir); tcase_add_loop_test(tc, path_stat, 0, NUM_FILE_CASE); tcase_add_loop_test(tc, fd_stat, 0, NUM_FILE_CASE); tcase_add_test(tc, check_access_not_exist); tcase_add_loop_test(tc, check_access_mode, 0, NUM_FILE_CASE); tcase_add_loop_test(tc, hard_link, 0, NUM_FILE_CASE); tcase_add_loop_test(tc, symbolic_link, 0, NUM_FILE_CASE); tcase_add_test(tc, dir_symbolic_link); tcase_add_test(tc, dangling_symlink); tcase_add_loop_test(tc, copy_file, 0, NUM_FILE_CASE); tcase_add_test(tc, copy_symlink); tcase_add_test(tc, copy_fail); tcase_add_test(tc, unlink_before_close); tcase_add_test(tc, one_way_pipe); tcase_add_test(tc, read_closed_pipe); tcase_add_test(tc, rename_simple_file); tcase_add_test(tc, rename_empty_directory); tcase_add_test(tc, file_times); tcase_add_test(tc, file_times_now); tcase_add_test(tc, file_fd_times); tcase_add_test(tc, file_fd_times_now); return tc; } mmlib-1.4.2/tests/file_advanced_tests.c000066400000000000000000000164411435717460000201270ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmlib.h" #include "mmlog.h" #include "mmdlfcn.h" #include "mmsysio.h" #include "mmerrno.h" #include "mmtime.h" #include "api-testcases.h" #define DEST_DIR BUILDDIR"/test_directory_" #define SRC_DIR BUILDDIR"/handmaid/"LT_OBJDIR #define BINARY_PATH SRC_DIR"/handmaid"EXEEXT #define CPY_BINARY_PATH "/handmaid"EXEEXT #define RENAMED_BINARY_PATH "/renamed_handmaid"EXEEXT #if defined(_WIN32) # define SHARED_LIB_PATH SRC_DIR"/libhandmaid-1"LT_MODULE_EXT # define CPY_SHARED_LIB_PATH "/libhandmaid-1"LT_MODULE_EXT #else # define SHARED_LIB_PATH SRC_DIR"/libhandmaid"LT_MODULE_EXT".1.0.0" # define CPY_SHARED_LIB_PATH "/libhandmaid"LT_MODULE_EXT".1" #endif #define RENAMED_SHARED_LIB_PATH "/renamed_libhandmaid"LT_MODULE_EXT #define DEST_DIR_SIZE sizeof(DEST_DIR) + 11 #define BINARY_NAME_SIZE sizeof(DEST_DIR) + 11 + sizeof(CPY_BINARY_PATH) #define RENAMED_BINARY_NAME_SIZE \ sizeof(DEST_DIR) + 11 + sizeof(RENAMED_BINARY_PATH) #define SHARED_LIB_NAME_SIZE sizeof(DEST_DIR) + 11 + sizeof(CPY_SHARED_LIB_PATH) #define RENAMED_SHARED_LIB_NAME_SIZE \ sizeof(DEST_DIR) + 11 + sizeof(RENAMED_SHARED_LIB_PATH) struct context { mm_pid_t pid; int r; int father_to_son; int son_to_father; char dir_name[DEST_DIR_SIZE]; char binary_name[BINARY_NAME_SIZE]; char renamed_binary_name[RENAMED_BINARY_NAME_SIZE]; char shared_lib_name[SHARED_LIB_NAME_SIZE]; char renamed_shared_lib_name[RENAMED_SHARED_LIB_NAME_SIZE]; char * env; int is_init; }; static struct context context = { .is_init = 0, }; static void cpy_file(const char * file_to_cpy, const char * cpy) { struct mm_stat stat; int fd_to_cpy, fd_cpy; char * buf; fd_to_cpy = mm_open(file_to_cpy, O_RDONLY, 0666); mm_check(fd_to_cpy != -1); fd_cpy = mm_open(cpy, O_CREAT|O_TRUNC|O_WRONLY, 0777); mm_check(fd_cpy != -1); mm_check(mm_fstat(fd_to_cpy, &stat) == 0); mm_check(stat.size > 0); buf = malloc(stat.size); mm_check(buf != NULL); mm_check(mm_read(fd_to_cpy, buf, stat.size) > 0); mm_check(mm_write(fd_cpy, buf, stat.size) > 0); free(buf); mm_close(fd_to_cpy); mm_close(fd_cpy); } static void create_proc(mm_pid_t* pid_ptr, struct context * c) { int fd_son_to_father[2], fd_father_to_son[2]; struct mm_remap_fd fdmap[2]; char* argv[2]; char started; // creation of anonymous pipes mm_check(mm_pipe(fd_son_to_father) == 0); mm_check(mm_pipe(fd_father_to_son) == 0); c->father_to_son = fd_father_to_son[1]; c->son_to_father = fd_son_to_father[0]; // Configure fdmap to keep all fd of the pipe in child fdmap[0].child_fd = 1; fdmap[0].parent_fd = fd_son_to_father[1]; fdmap[1].child_fd = 0; fdmap[1].parent_fd = fd_father_to_son[0]; argv[0] = c->binary_name; argv[1] = NULL; // Start process mm_check(mm_spawn(pid_ptr, argv[0], 2, fdmap, 0, argv, NULL) == 0); mm_close(fd_son_to_father[1]); mm_close(fd_father_to_son[0]); // wait for the son to start its execution mm_read(c->son_to_father, &started, sizeof(char)); } static int context_init(struct context * c) { const char * ld_path_env; srand(clock()); c->r = rand(); sprintf(c->dir_name, "%s%d", DEST_DIR, c->r); mm_mkdir(c->dir_name, 0777, O_CREAT); sprintf(c->binary_name, "%s%d%s", DEST_DIR, c->r, CPY_BINARY_PATH); sprintf(c->renamed_binary_name, "%s%d%s", DEST_DIR, c->r, RENAMED_BINARY_PATH); sprintf(c->shared_lib_name, "%s%d%s", DEST_DIR, c->r, CPY_SHARED_LIB_PATH); sprintf(c->renamed_shared_lib_name, "%s%d%s", DEST_DIR, c->r, RENAMED_SHARED_LIB_PATH); // copy the executable and shared library cpy_file(BINARY_PATH, c->binary_name); cpy_file(SHARED_LIB_PATH, c->shared_lib_name); // save old environment ld_path_env = mm_getenv("LD_LIBRARY_PATH", NULL); if (ld_path_env) c->env = strdup(ld_path_env); // set new environment mm_setenv("LD_LIBRARY_PATH", c->dir_name, MM_ENV_PREPEND); create_proc(&c->pid, c); c->is_init = 1; return 0; } static void context_deinit(struct context * c) { char end = 'b'; if (!c->is_init) return; // indicate the son that he can finish its execution mm_write(c->father_to_son, &end, sizeof(char)); mm_remove(c->dir_name, MM_RECURSIVE|MM_DT_DIR|MM_DT_REG); mm_wait_process(c->pid, NULL); // close the pipes mm_close(c->father_to_son); mm_close(c->son_to_father); if (c->env) mm_setenv("LD_LIBRARY_PATH", c->env, MM_ENV_OVERWRITE); else mm_unsetenv("LD_LIBRARY_PATH"); free(c->env); *c = (struct context) { .father_to_son = -1, .son_to_father = -1, .is_init = 0, }; } static void teardown_tests(void) { int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); context_deinit(&context); mm_error_set_flags(flags, MM_ERROR_IGNORE); } static void setup_tests(void) { mm_check(context_init(&context) == 0); } START_TEST(rename_exec_linked_with_shared_lib) { // rename the file while the son is executing its code ck_assert(mm_rename(context.binary_name, context.renamed_binary_name) == 0); //check that the rename really works ck_assert(mm_check_access(context.renamed_binary_name, F_OK) == 0); } END_TEST START_TEST(rename_shared_lib_while_used_by_exec) { // rename the file while the son is executing its code ck_assert(mm_rename(context.shared_lib_name, context.renamed_shared_lib_name) == 0); //check that the rename really works ck_assert(mm_check_access(context.renamed_shared_lib_name, F_OK) == 0); } END_TEST START_TEST(rename_opened_shared_lib) { mm_dynlib_t * shared_lib; shared_lib = mm_dlopen(context.shared_lib_name, MM_LD_NOW); // rename the file while the shared library is opened ck_assert(mm_rename(context.shared_lib_name, context.renamed_shared_lib_name) == 0); //check that the rename really works ck_assert(mm_check_access(context.renamed_shared_lib_name, F_OK) == 0); mm_dlclose(shared_lib); } END_TEST START_TEST(unlink_exec_linked_with_shared_lib) { // unlink the file while the son is executing its code ck_assert(mm_unlink(context.binary_name) == 0); //check that the unlink really works ck_assert(mm_check_access(context.binary_name, F_OK) == ENOENT); } END_TEST START_TEST(unlink_shared_lib_while_used_by_exec) { // unlink the file while the son is executing its code ck_assert(mm_unlink(context.shared_lib_name) == 0); //check that the unlink really works ck_assert(mm_check_access(context.shared_lib_name, F_OK) == ENOENT); } END_TEST START_TEST(unlink_opened_shared_lib) { mm_dynlib_t * shared_lib; shared_lib = mm_dlopen(context.shared_lib_name, MM_LD_NOW); // unlink the file while the shared library is opened ck_assert(mm_unlink(context.shared_lib_name) == 0); //check that the unlink really works ck_assert(mm_check_access(context.shared_lib_name, F_OK) == ENOENT); mm_dlclose(shared_lib); } END_TEST LOCAL_SYMBOL TCase* create_advanced_file_tcase(void) { TCase *tc = tcase_create("advanced_file_tcase"); tcase_add_checked_fixture(tc, setup_tests, teardown_tests); tcase_add_test(tc, rename_exec_linked_with_shared_lib); tcase_add_test(tc, rename_shared_lib_while_used_by_exec); tcase_add_test(tc, rename_opened_shared_lib); tcase_add_test(tc, unlink_exec_linked_with_shared_lib); tcase_add_test(tc, unlink_shared_lib_while_used_by_exec); tcase_add_test(tc, unlink_opened_shared_lib); return tc; } mmlib-1.4.2/tests/handmaid/000077500000000000000000000000001435717460000155345ustar00rootroot00000000000000mmlib-1.4.2/tests/handmaid/Makefile.am000066400000000000000000000010651435717460000175720ustar00rootroot00000000000000MMLIB = $(top_builddir)/src/libmmlib.la AM_CPPFLAGS = -I$(top_srcdir)/src # shared library needed for the tests of rename and unlink check_LTLIBRARIES = libhandmaid.la libhandmaid_la_SOURCES = \ lib_handmaid.c \ lib_handmaid.h \ $(eol) # -rpath /nowhere permits to create the .so libhandmaid_la_LDFLAGS = \ -version-info 1:0:0 \ -rpath /nowhere \ -no-undefined \ $(AM_LDFLAGS) \ $(eol) # executable needed for the tests of rename and unlink handmaid_SOURCES = handmaid.c handmaid_LDADD = \ libhandmaid.la \ $(MMLIB) \ $(eol) check_PROGRAMS = handmaid mmlib-1.4.2/tests/handmaid/handmaid.c000066400000000000000000000006601435717460000174470ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #include #include #include "lib_handmaid.h" #include "mmsysio.h" int main (void) { char buf = 'a'; // indicate to its father that the process started if (mm_write(1, &buf, sizeof(char)) != 1) return -1; // call the shared library fnct(); // wait for the father to stop the process if (mm_read(0, &buf, sizeof(char)) == -1) return -1; return 0; } mmlib-1.4.2/tests/handmaid/lib_handmaid.c000066400000000000000000000001731435717460000202740ustar00rootroot00000000000000#if HAVE_CONFIG_H # include #endif #include #include "lib_handmaid.h" void fnct(void) { return; } mmlib-1.4.2/tests/handmaid/lib_handmaid.h000066400000000000000000000001101435717460000202700ustar00rootroot00000000000000#ifndef LIB_HANDMAID_H #define LIB_HANDMAID_H void fnct(void); #endif mmlib-1.4.2/tests/handmaid/meson.build000066400000000000000000000006511435717460000177000ustar00rootroot00000000000000# shared library needed for the tests of rename and unlink lib_handmaid = shared_library('handmaid', files('lib_handmaid.c', 'lib_handmaid.h'), include_directories : configuration_inc, version : '1.0.0', ) # executable needed for the tests of rename and unlink executable('handmaid', 'handmaid.c', include_directories : configuration_inc, link_with : [lib_handmaid, mmlib], ) mmlib-1.4.2/tests/internals-testcases.h000066400000000000000000000003171435717460000201340ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef INTERNALS_TESTCASES_H #define INTERNALS_TESTCASES_H #include TCase* create_case_log_internals(void); TCase* create_case_startup_win32_internals(void); #endif mmlib-1.4.2/tests/ipc-api-tests-exported.c000066400000000000000000000025421435717460000204500ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmsysio.h" #include "mmthread.h" #include "mmtime.h" #include "tests-child-proc.h" #include "ipc-api-tests-exported.h" API_EXPORTED intptr_t test_client_process(void * arg) { int fd; char buf[256]; int exit_value = -1; int recvfd = -1; struct ipc_test_ctx * ctx = arg; const char data[] = "ipc client test msg"; char line[80] = "client message in shared object\n"; fd = mm_ipc_connect(IPC_ADDR); if (fd == -1) goto cleanup; if (mm_ipc_build_send_msg(fd, data, sizeof(data), -1) < 0 || recv_msg_and_fd(fd, buf, sizeof(buf), &recvfd) < 0) goto cleanup; if (ctx->shared_object == SHARED_FILE || ctx->shared_object == SHARED_MEM) mm_seek(recvfd, 0, SEEK_SET); mm_write(recvfd, line, strlen(line)); mm_close(recvfd); recvfd = -1; /* send another message after we finished writing */ if (mm_ipc_build_send_msg(fd, data, sizeof(data), -1) < 0) goto cleanup; exit_value = 0; cleanup: if (exit_value != 0) { exit_value = mm_get_lasterror_number(); fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); } mm_close(fd); mm_close(recvfd); return exit_value; } mmlib-1.4.2/tests/ipc-api-tests-exported.h000066400000000000000000000043721435717460000204600ustar00rootroot00000000000000#ifndef IPC_API_TESTS_EXPORTED_H #define IPC_API_TESTS_EXPORTED_H #include #include #include API_EXPORTED intptr_t test_client_process(void * arg); enum { SHARED_FILE, SHARED_MEM, SHARED_PIPE, SHARED_IPC, }; struct ipc_test_ctx { int nclients; int run_mode; int index; int shared_object; int fd; }; /* small helper for debug purposes */ static inline void dump_ipc_test_ctx(const struct ipc_test_ctx * c) { if (c == NULL) fprintf(stdout, "{NULL}\n"); else fprintf(stdout, "{nclients=%d, run_mode=%d, index=%d, " "shared_object=%d, fd=%d}\n", c->nclients, c->run_mode, c->index, c->shared_object, c->fd); } #define IPC_ADDR "mmlib-test-ipc-addr" #define IPC_TMPFILE "ipc-test-tmp-file" /* small helper to send a mm_ipc msg with a file descriptor in the metadatas */ static inline ssize_t mm_ipc_build_send_msg(int fd, const void * data, size_t len, int sentfd) { struct iovec vec = {.iov_len = len, .iov_base = (void*) data}; struct mm_ipc_msg msg = { .iov = &vec, .num_iov = 1, }; if (sentfd > 0) { msg.fds = &sentfd; msg.num_fds = 1; } return mm_ipc_sendmsg(fd, &msg); } /* small helper to receive a mm_ipc msg with a file descriptor in the metadatas */ static inline ssize_t recv_msg_and_fd(int fd, void* data, size_t len, int* recvfd) { struct iovec vec = {.iov_len = len, .iov_base = data}; struct mm_ipc_msg msg = { .iov = &vec, .num_iov = 1, .fds = recvfd, .num_fds_max = 1, }; return mm_ipc_recvmsg(fd, &msg); } /* Can open 1 OR 2 file descriptors. * Will return the one intended to be sent to the client */ static inline int open_shared_object_of_type(const struct ipc_test_ctx * ctx, int * rvfd) { char filename[64]; switch (ctx->shared_object) { case SHARED_FILE: sprintf(filename, "%s-%d", IPC_TMPFILE, ctx->index); *rvfd = mm_open(filename, O_CREAT|O_TRUNC|O_RDWR, S_IWUSR|S_IRUSR); return *rvfd; case SHARED_MEM: *rvfd = mm_anon_shm(); return *rvfd; case SHARED_PIPE: mm_pipe(rvfd); return rvfd[1]; case SHARED_IPC: mm_ipc_connected_pair(rvfd); return rvfd[1]; default: fprintf(stderr, "Test bug: no shared object type given"); return -1; } } #endif /* IPC_API_TESTS_EXPORTED_H */ mmlib-1.4.2/tests/ipc-api-tests.c000066400000000000000000000252241435717460000166220ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "mmerrno.h" #include "mmlib.h" #include "mmlog.h" #include "mmpredefs.h" #include "mmsysio.h" #include "mmthread.h" #include "mmtime.h" #include "api-testcases.h" #include "tests-child-proc.h" #include "ipc-api-tests-exported.h" #define MAX_NCLIENTS 32 static int nclients = 0; static struct mm_ipc_srv * srv; static void test_teardown(void) { int i; char filename[64]; int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); mm_ipc_srv_destroy(srv); srv = NULL; for (i = 0 ; i < 5 ; i ++) { sprintf(filename, "%s-%d", IPC_TMPFILE, i); mm_unlink(filename); } mm_error_set_flags(flags, MM_ERROR_IGNORE); } START_TEST(ipc_create_simple) { struct mm_ipc_srv * server = mm_ipc_srv_create(IPC_ADDR); ck_assert(server != NULL); mm_ipc_srv_destroy(server); } END_TEST /* check what happens if you give a null-terminated string longer that the maximum */ START_TEST(ipc_create_invalid) { char name[257]; memset(name, 'a', sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; struct mm_ipc_srv *server = mm_ipc_srv_create(name); ck_assert(server == NULL); ck_assert(mm_get_lasterror_number() == ENAMETOOLONG); mm_ipc_srv_destroy(server); } END_TEST START_TEST(ipc_create_double) { struct mm_ipc_srv * srv1, * srv2; srv1 = mm_ipc_srv_create(IPC_ADDR); ck_assert(srv1 != NULL); srv2 = mm_ipc_srv_create(IPC_ADDR); ck_assert(srv2 == NULL); ck_assert(mm_get_lasterror_number() == EADDRINUSE); mm_ipc_srv_destroy(srv1); mm_ipc_srv_destroy(srv2); } END_TEST static void* test_handle_client(void * arg) { struct ipc_test_ctx * ctx = arg; char buf[256]; int recvfd = -1; int pipe[2] = {-1, -1}; /* 0:read, 1:write */ int tmpfd; const char data[] = "ipc server test msg"; char line[80]; void * exit_value = (void*) -1; if (recv_msg_and_fd(ctx->fd, buf, sizeof(buf), &recvfd) < 0) goto cleanup; /* send the message with a file descriptor */ tmpfd = open_shared_object_of_type(ctx, (int*) &pipe); if (tmpfd < 0) goto cleanup; if (mm_ipc_build_send_msg(ctx->fd, data, sizeof(data), tmpfd) < 0) goto cleanup; tmpfd = pipe[0]; mm_close(pipe[1]); pipe[1] = -1; /* get another message from the client. * (sent after the client finished writing to tmpfile)*/ if (recv_msg_and_fd(ctx->fd, buf, sizeof(buf), &recvfd) < 0) goto cleanup; /* check the client message */ if (ctx->shared_object == SHARED_FILE || ctx->shared_object == SHARED_MEM) mm_seek(tmpfd, 0, SEEK_SET); if (mm_read(tmpfd, line, sizeof(line)) < 0 || strncmp(line, "client message in shared object\n", sizeof("client message in shared object\n") - 1)) { ck_abort_msg("server failed to read the message written by the " "client in the shared file"); goto cleanup; } exit_value = NULL; cleanup: if (exit_value != NULL) { exit_value = (void*)(intptr_t) mm_get_lasterror_number(); fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); } mm_close(pipe[0]); mm_close(pipe[1]); mm_close(recvfd); return exit_value; } /* * accept up to nclients connections, then return */ static void* test_server_process(void * arg) { int i; struct ipc_test_ctx * global_ctx = arg; struct ipc_test_ctx ctx[MAX_NCLIENTS]; mm_thread_t thid[MAX_NCLIENTS]; for (i = 0; i < global_ctx->nclients; i++) ctx[i] = *global_ctx; for (i = 0; i < global_ctx->nclients; i++) { ctx[i].index = i; ctx[i].fd = mm_ipc_srv_accept(srv); if (ctx[i].fd == -1) goto cleanup; mm_thr_create(&thid[i], test_handle_client, &ctx[i]); } for (i = 0; i < global_ctx->nclients; i++) { intptr_t rv = 0; mm_thr_join(thid[i], (void**)rv); mm_close(ctx[i].fd); ctx[i].fd = 0; } test_teardown(); return NULL; cleanup: if (mm_get_lasterror_number() != 0) fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); while (--i > 0) { mm_thr_join(thid[i], NULL); mm_close(ctx[i].fd); ctx[i].fd = 0; } test_teardown(); return (void*) -1; } static intptr_t test_server_process_pending(void * arg) { int i; struct ipc_test_ctx * global_ctx = arg; struct ipc_test_ctx ctx[MAX_NCLIENTS]; mm_thread_t thid[MAX_NCLIENTS]; srv = mm_ipc_srv_create(IPC_ADDR); if (srv == NULL) return -1; for (i = 0; i < global_ctx->nclients; i++) { ctx[i] = *global_ctx; ctx[i].index = i; ctx[i].fd = mm_ipc_srv_accept(srv); if (ctx[i].fd == -1) goto cleanup; mm_thr_create(&thid[i], test_handle_client, &ctx[i]); } for (i = 0; i < global_ctx->nclients; i++) { mm_thr_join(thid[i], NULL); mm_close(ctx[i].fd); ctx[i].fd = 0; } test_teardown(); return 0; cleanup: if (mm_get_lasterror_number() != 0) fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); while (--i > 0) { mm_thr_join(thid[i], NULL); mm_close(ctx[i].fd); ctx[i].fd = 0; } test_teardown(); return -1; } /* * Create a server * Create nclients children, each child connect to ipc server * Check the server read the expected pattern (datagram boundaries and order) * Exchange file descriptor * Client writes, server checks. * * The ipc server handling is split in two; and shared amongst all threads. * (only works with threads) */ static void run_test_core_connected_file(struct ipc_test_ctx * ctx) { int i; thread_proc_id clt_id[MAX_NCLIENTS]; srv = mm_ipc_srv_create(IPC_ADDR); ck_assert_msg(srv != NULL, "failed to create ipc server"); /* prepare N clients waiting for the server to launch */ for (i = 0; i < ctx->nclients; i++) { run_function(&clt_id[i], test_client_process, ctx, RUN_AS_THREAD); } /* wait just a little to make sure the clients are all waiting * (this seems to always be the case anyway) */ mm_relative_sleep_ms(100); /* * start and launch the server. It will: * - handle the N pending clients immediately * - clean and return * * test_server_process() makes uses of the global srv variable ! */ if (test_server_process(ctx) != NULL) { ck_abort_msg("test_server_process failed"); } /* wait for the clients to return */ for (i = 0; i < ctx->nclients; i++) clean_function(clt_id[i], RUN_AS_THREAD); test_teardown(); } static void run_test_core_pending(struct ipc_test_ctx * ctx) { int i; thread_proc_id srv_id; thread_proc_id clt_id[MAX_NCLIENTS]; /* launch the server. It will: * - enter a waiting state * - handle N new clients * - clean and return */ run_function(&srv_id, test_server_process_pending, ctx, RUN_AS_THREAD); /* wait just a little to make sure the server is ready */ mm_relative_sleep_ms(100); /* launch the clients to attack the server */ for (i = 0; i < ctx->nclients; i++) { run_function(&clt_id[i], test_client_process, ctx, ctx->run_mode); } /* wait for the clients to return */ for (i = 0; i < ctx->nclients; i++) clean_function(clt_id[i], ctx->run_mode); /* wait for the server to return, then clean */ clean_function(srv_id, RUN_AS_THREAD); } /* * Create IPC connected pair and ensure that data communication is really * full duplex */ #define TEST_STR1 "test string for pair" #define TEST_STR2 "second test string for pair" START_TEST(full_duplex) { int fds[2]; ssize_t rsz; char buffer[42]; ck_assert(mm_ipc_connected_pair(fds) == 0); rsz = mm_write(fds[0], TEST_STR1, sizeof(TEST_STR1)); ck_assert_int_eq(rsz, sizeof(TEST_STR1)); rsz = mm_write(fds[1], TEST_STR2, sizeof(TEST_STR2)); ck_assert_int_eq(rsz, sizeof(TEST_STR2)); rsz = mm_read(fds[1], buffer, sizeof(buffer)); ck_assert_int_eq(rsz, sizeof(TEST_STR1)); ck_assert(memcmp(buffer, TEST_STR1, sizeof(TEST_STR1)) == 0); rsz = mm_read(fds[0], buffer, sizeof(buffer)); ck_assert_int_eq(rsz, sizeof(TEST_STR2)); ck_assert(memcmp(buffer, TEST_STR2, sizeof(TEST_STR2)) == 0); mm_close(fds[0]); mm_close(fds[1]); } END_TEST START_TEST(broken_pipe) { int fds[2]; ssize_t rsz; char buffer[42]; ck_assert(mm_ipc_connected_pair(fds) == 0); rsz = mm_write(fds[0], TEST_STR1, sizeof(TEST_STR1)); ck_assert_int_eq(rsz, sizeof(TEST_STR1)); rsz = mm_read(fds[1], buffer, sizeof(buffer)); ck_assert_int_eq(rsz, sizeof(TEST_STR1)); ck_assert(memcmp(buffer, TEST_STR1, sizeof(TEST_STR1)) == 0); mm_close(fds[1]); rsz = mm_write(fds[0], TEST_STR1, sizeof(TEST_STR1)); ck_assert(rsz == -1); ck_assert(mm_get_lasterror_number() == EPIPE); mm_close(fds[0]); } END_TEST /* * test to pass msg and file descriptors * * Create a server * Create nclients children, each child connect to ipc server * Check the server read the expected pattern (datagram boundaries and order) * Exchange file descriptor * Client writes, server checks. */ START_TEST(test_file_connected_thr) { ck_assert(nclients > 0); struct ipc_test_ctx ctx = { .nclients = nclients, .run_mode = RUN_AS_THREAD, .shared_object = SHARED_FILE, }; run_test_core_connected_file(&ctx); } END_TEST static const struct ipc_test_ctx test_pending_cases[] = { {.run_mode = RUN_AS_THREAD, .shared_object = SHARED_PIPE}, {.run_mode = RUN_AS_PROCESS, .shared_object = SHARED_PIPE}, {.run_mode = RUN_AS_THREAD, .shared_object = SHARED_FILE}, {.run_mode = RUN_AS_PROCESS, .shared_object = SHARED_FILE}, {.run_mode = RUN_AS_THREAD, .shared_object = SHARED_MEM}, {.run_mode = RUN_AS_PROCESS, .shared_object = SHARED_MEM}, {.run_mode = RUN_AS_THREAD, .shared_object = SHARED_IPC}, {.run_mode = RUN_AS_PROCESS, .shared_object = SHARED_IPC}, }; #define NUM_PENDING_CASE (MM_NELEM(test_pending_cases)) /* * Create a server * Create nclients children, each child connect to ipc server * Check the server read the expected pattern (datagram boundaries and order) * Parent create a shared object and pass it to child * Children write a pattern, parent assert the pattern */ START_TEST(test_core_pending) { ck_assert(nclients > 0); struct ipc_test_ctx ctx = { .nclients = nclients, .run_mode = test_pending_cases[_i].run_mode, .shared_object = test_pending_cases[_i].shared_object, }; run_test_core_pending(&ctx); } END_TEST LOCAL_SYMBOL TCase* create_ipc_tcase(void) { TCase * tc; nclients = atoi(mm_getenv("TC_IPC_NCLIENTS", "5")); tc = tcase_create("ipc"); tcase_add_checked_fixture(tc, NULL, test_teardown); tcase_add_test(tc, ipc_create_simple); tcase_add_test(tc, ipc_create_invalid); tcase_add_test(tc, ipc_create_double); tcase_add_test(tc, full_duplex); tcase_add_test(tc, broken_pipe); /* test the ipc server with both * connections pending before accept * server ready before accepting connections */ tcase_add_test(tc, test_file_connected_thr); /* server is up and running before starting the clients */ tcase_add_loop_test(tc, test_core_pending, 0, NUM_PENDING_CASE); return tc; } mmlib-1.4.2/tests/log-internals.c000066400000000000000000000016711435717460000167160ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "../src/log.c" #include #include #include "internals-testcases.h" static size_t format_string(char* buff, size_t buflen, const char* msg, ...) { size_t r; va_list args; va_start(args, msg); r = format_log_str(buff, buflen, MM_LOG_DEBUG, "here", msg, args); va_end(args); return r; } START_TEST(log_overflow) { size_t len; char buff[MM_LOG_LINE_MAXLEN + 32]; char arg[MM_LOG_LINE_MAXLEN + 32]; // Fill a message that must overflow the log string memset(arg, 'a', sizeof(arg)-1); arg[sizeof(arg)-1] = '\0'; // Format log string len = format_string(buff, MM_LOG_LINE_MAXLEN, "hello %s", arg); ck_assert(len <= MM_LOG_LINE_MAXLEN); ck_assert(buff[len-1] == '\n'); } END_TEST LOCAL_SYMBOL TCase* create_case_log_internals(void) { TCase *tc = tcase_create("log internals"); tcase_add_test(tc, log_overflow); return tc; } mmlib-1.4.2/tests/meson.build000066400000000000000000000102371435717460000161340ustar00rootroot00000000000000unittest_args = [ '-DCHECK_SUPPORT_TAP=1', '-DSRCDIR="' + meson.project_source_root() + '"', '-DBUILDDIR="' + meson.current_build_dir() + '"', '-DTOP_BUILDDIR="' + meson.project_build_root() + '"', '-DLT_OBJDIR="."', ] testlog_sources = files('testlog.c') testlog = executable('testlog', testlog_sources, c_args : unittest_args, include_directories : configuration_inc, link_with : mmlib, ) test('testlog', testlog) testerrno_sources = files('testerrno.c') testerrno = executable('testerrno', testerrno_sources, c_args : unittest_args, include_directories : configuration_inc, link_with : mmlib, ) test('testerrno', testerrno) testprofile_sources = files('testprofile.c') testprofile = executable('testprofile', testprofile_sources, c_args : unittest_args, include_directories : configuration_inc, link_with : mmlib, ) test('testprofile', testprofile) child_proc_sources = files('child-proc.c') executable('child-proc', child_proc_sources, c_args : unittest_args, include_directories : configuration_inc, link_with : mmlib, ) tests_child_proc_files = files('tests-child-proc.c', 'tests-child-proc.h', 'process-testlib.c', 'process-testlib.h', 'threaddata-manipulation.h', 'threaddata-manipulation.c', 'socket-testlib.h', 'socket-testlib.c', 'ipc-api-tests-exported.c', 'ipc-api-tests-exported.h', ) executable('tests-child-proc', tests_child_proc_files, c_args : unittest_args + ['-DMMLOG_MODULE_NAME="tests_child_proc"'], export_dynamic : true, include_directories : configuration_inc, link_with : mmlib, ) perflock_sources = files('perflock.c') perflock = executable('perflock', perflock_sources, include_directories : configuration_inc, c_args : unittest_args, link_with : mmlib, dependencies: [libcheck], ) dynlib_test_sources = files('dynlib-api.h', 'dynlib-test.c') shared_module('dynlib-test', dynlib_test_sources, name_prefix : '', # do not prefix with 'lib' include_directories : configuration_inc, c_args : unittest_args, ) test_internals_sources = files( 'internals-testcases.h', 'log-internals.c', 'testinternals.c', ) if host_machine.system() == 'windows' test_internals_sources += files('startup-win32-internals.c') endif testinternals = executable('testinternals', test_internals_sources, c_args : unittest_args + cflags, include_directories : configuration_inc, link_with : mmlib_static, dependencies: libcheck, ) test('internal tests', testinternals, env: ['CK_VERBOSITY=silent'], protocol: 'tap', ) # create the binary and shared library for the rename and unlink tests subdir('handmaid') testapi_sources = files( 'alloc-api-tests.c', 'api-testcases.h', 'argparse-api-tests.c', 'dirtests.c', 'dlfcn-api-tests.c', 'file_advanced_tests.c', 'file-api-tests.c', 'ipc-api-tests.c', 'ipc-api-tests-exported.c', 'ipc-api-tests-exported.h', 'process-api-tests.c', 'shm-api-tests.c', 'socket-api-tests.c', 'socket-testlib.c', 'socket-testlib.h', 'testapi.c', 'tests-child-proc.h', 'tests-run-func.c', 'thread-api-tests.c', 'threaddata-manipulation.c', 'threaddata-manipulation.h', 'time-api-tests.c', 'utils-api-tests.c' ) libmath = cc.find_library('m', required : true) testapi_deps = [libcheck, libmath] if host_machine.system() == 'windows' testapi_deps += libws2_32 endif testapi = executable('testapi', testapi_sources, c_args : unittest_args + cflags, include_directories : configuration_inc, link_with : mmlib, dependencies: testapi_deps, ) # increase timeout to 5 min (300s) for windows # as long as *all* the API tests are part of the same program, it makes # sense anyway test('unit api tests', testapi, env: ['CK_VERBOSITY=silent'], timeout : 300, protocol: 'tap', ) mmlib-1.4.2/tests/perflock.c000066400000000000000000000066211435717460000157450ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include "mmlib.h" #include "mmpredefs.h" #include "mmprofile.h" #include "mmthread.h" #define TEST_LOCK_REFEREE_SERVER_BIN TOP_BUILDDIR"/src/"LT_OBJDIR"/lock-referee.exe" /************************************************************************* * * * Performance tests * * * *************************************************************************/ struct perf_data { mm_thr_mutex_t mtx; int iter; char fill_up[64-sizeof(mm_thr_mutex_t)+sizeof(int)]; }; #define NUM_ITERATION 10000 #define NUM_THREAD_PER_LOCK_DEFAULT 16 #define NUM_LOCK_DEFAULT 4 #define NUM_THREAD_PER_LOCK_MAX 128 #define NUM_LOCK_MAX 32 static struct perf_data data_array[32]; static int num_lock = NUM_LOCK_DEFAULT; static int num_thread_per_lock = NUM_THREAD_PER_LOCK_DEFAULT; static void* lock_perf_routine(void* arg) { struct perf_data* data; int i, ind; ind = *(int*)arg; for (i = 0; i < NUM_ITERATION; i++) { data = &data_array[ind]; mm_thr_mutex_lock(&data->mtx); if (ind == 0) mm_toc(); data->iter++; if (ind == 0) mm_tic(); mm_thr_mutex_unlock(&data->mtx); if (++ind == num_lock) ind = 0; } return NULL; } static int run_perf_lock_contended(int flags) { mm_thread_t thids[NUM_THREAD_PER_LOCK_MAX*NUM_LOCK_MAX]; int initial_lock_index[MM_NELEM(thids)]; int i; int num_thids = num_thread_per_lock*num_lock; mm_profile_reset(0); for (i = 0; i < num_lock; i++) { mm_thr_mutex_init(&data_array[i].mtx, flags); mm_thr_mutex_lock(&data_array[i].mtx); } // Spawn threads for (i = 0; i < num_thids; i++) { initial_lock_index[i] = i % num_lock; mm_thr_create(&thids[i], lock_perf_routine, &initial_lock_index[i]); } mm_relative_sleep_ms(100); // Unlock mutex now for (i = 0; i < num_lock; i++) { if (i == 0) mm_tic(); mm_thr_mutex_unlock(&data_array[i].mtx); } // Wait until all theads have finished for (i = 0; i < num_thids; i++) mm_thr_join(thids[i], NULL); for (i = 0; i < num_lock; i++) mm_thr_mutex_deinit(&data_array[0].mtx); printf("\ncontended case with flags=0x%08x:\n", flags); fflush(stdout); mm_profile_print(PROF_DEFAULT, 1); return 0; } static int run_perf_lock_uncontended(int flags) { int i; mm_profile_reset(0); mm_thr_mutex_init(&data_array[0].mtx, flags); for (i = 0; i < NUM_ITERATION; i++) { mm_tic(); mm_thr_mutex_lock(&data_array[0].mtx); mm_thr_mutex_unlock(&data_array[0].mtx); mm_toc(); } mm_thr_mutex_deinit(&data_array[0].mtx); printf("\nuncontended case with flags=0x%08x\n", flags); fflush(stdout); mm_profile_print(PROF_DEFAULT, 1); return 0; } int main(int argc, char* argv[]) { #if _WIN32 mm_setenv("MMLIB_LOCKREF_BIN", TEST_LOCK_REFEREE_SERVER_BIN, 1); setbuf(stdout, NULL); #endif if (argc > 1) num_lock = atoi(argv[1]); if (argc > 2) num_thread_per_lock = atoi(argv[2]); printf("num_lock=%i num_thread_per_lock=%i\n", num_lock, num_thread_per_lock); run_perf_lock_uncontended(0); run_perf_lock_uncontended(MM_THR_PSHARED); printf("\n\n"); run_perf_lock_contended(0); run_perf_lock_contended(MM_THR_PSHARED); return EXIT_SUCCESS; } mmlib-1.4.2/tests/process-api-tests.c000066400000000000000000000344461435717460000175330ustar00rootroot00000000000000/* @mindmaze_header@ */ #if defined (HAVE_CONFIG_H) # include #endif #include #include #include #include #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmsysio.h" #include "api-testcases.h" #include "process-testlib.h" #include "tests-child-proc.h" #define UNSET_PID_VALUE ((mm_pid_t) -23) #define MAX(a, b) ((a) > (b) ? (a) : (b)) // workaround for libtool on windows: we need to execute directly the // binary (the folder of mmlib dll is added at startup of testapi). On // other platform we must use the normal wrapper script located in BUILDDIR #if defined(_WIN32) # define CHILDPROC_BINPATH BUILDDIR"/"LT_OBJDIR"/child-proc.exe" #else # define CHILDPROC_BINPATH BUILDDIR"/child-proc" #endif #define TEST_DATADIR "process-data" #define NOEXEC_FILE "file-noexec" #define UNKBINFMT_FILE "file-unkfmt" #define NOTEXIST_FILE "does-not-exists" static struct process_test_data* curr_data_in_test; static char* path_envvar_saved = NULL; static int full_read(int fd, void* buf, size_t len) { char* cbuf = buf; ssize_t rsz; while (len > 0) { rsz = mm_read(fd, cbuf, len); if (rsz < 0) { perror("failed to read file"); return -1; } if (rsz == 0) { fprintf(stderr, "EOF reached (missing %i bytes)\n", (unsigned int)len); return -1; } cbuf += rsz; len -= rsz; } return 0; } static int check_expected_fd_content(struct process_test_data* data) { int i; int parent_fd, child_fd; size_t exp_sz; char line[128], expected[128]; for (i = 0; i < NUM_FILE; i++) { parent_fd = data->fd_map[i].parent_fd; child_fd = data->fd_map[i].child_fd; exp_sz = sprintf(expected, "fd = %i", child_fd); lseek(parent_fd, 0, SEEK_SET); if (full_read(parent_fd, line, exp_sz)) return -1; if (memcmp(line, expected, exp_sz)) { fprintf(stderr, "failure:\nexpected: %s\ngot: %s\n", expected, line); return -1; } } return 0; } static struct process_test_data* create_process_test_data(const char* file) { int i, child_last_fd, pipe_fds[2]; char name[32]; const char* argv[] = {"check-open-files", MM_STRINGIFY(NUM_FILE)}; struct process_test_data* data; data = malloc(sizeof(*data)); *data = (struct process_test_data) { .pid = UNSET_PID_VALUE, .pipe_wr = -1, .pipe_rd = -1, }; // Initial process command and args strcpy(data->cmd, file); for (i = 0; i < MM_NELEM(argv); i++) strcpy(data->argv_data[data->argv_data_len++], argv[i]); // Create open file descriptor (to pass to child for some of them) for (i = 0; i < MM_NELEM(data->fds); i++) { sprintf(name, "file-test-%i", i); data->fds[i] = mm_open(name, O_RDWR|O_TRUNC|O_CREAT, S_IRWXU); mm_unlink(name); } // Initialize fd parent->child fd mapping fprintf(stderr, "map_fd = ["); child_last_fd = 3; // first fd after STDERR for (i = 0; i < NUM_FILE; i++) { data->fd_map[i].child_fd = child_last_fd++; data->fd_map[i].parent_fd = data->fds[NUM_FILE+i]; fprintf(stderr, " %i:%i", data->fd_map[i].child_fd, data->fd_map[i].parent_fd); } fprintf(stderr, " ]\n"); // Add final remap for pipe mm_pipe(pipe_fds); data->pipe_rd = pipe_fds[0]; data->pipe_wr = pipe_fds[1]; data->fd_map[NUM_FILE] = (struct mm_remap_fd) { .child_fd = child_last_fd++, .parent_fd = data->pipe_wr, }; // Store current process data for later in teardown curr_data_in_test = data; return data; } static int spawn_child(int spawn_flags, struct process_test_data* data) { char* argv[NUM_ARGS_MAX+2] = {NULL}; int i; argv[0] = data->cmd; for (i = 0; i < data->argv_data_len; i++) argv[i+1] = data->argv_data[i]; if (mm_spawn(&data->pid, data->cmd, NUM_FDMAP, data->fd_map, spawn_flags, argv, NULL) != 0) { fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); return -1; } return 0; } static int wait_pipe_close(struct process_test_data* data) { char unused_buffer[1]; ssize_t rsz; if (data->pipe_wr != -1) { mm_close(data->pipe_wr); data->pipe_wr = -1; } rsz = mm_read(data->pipe_rd, unused_buffer, sizeof(unused_buffer)); return (rsz < 0) ? -1 : 0; } static int wait_child(struct process_test_data* data) { int rv; if (data->pid == UNSET_PID_VALUE) return 0; rv = mm_wait_process(data->pid, NULL); data->pid = UNSET_PID_VALUE; return rv; } static void close_fds(struct process_test_data* data) { int i; for (i = 0; i < MM_NELEM(data->fds); i++) { if (data->fds[i] == -1) continue; mm_close(data->fds[i]); data->fds[i] = -1; } if (data->pipe_wr != -1) { mm_close(data->pipe_wr); data->pipe_wr = -1; } if (data->pipe_rd != -1) { mm_close(data->pipe_rd); data->pipe_rd = -1; } } static void destroy_process_test_data(struct process_test_data* data) { if (data == NULL) return; wait_child(data); close_fds(data); free(data); curr_data_in_test = NULL; } static void test_teardown(void) { destroy_process_test_data(curr_data_in_test); } static void case_setup(void) { int i, fd; int garbage_data[128]; char childproc_dir[sizeof(CHILDPROC_BINPATH)]; // backup PATH environment for the tests path_envvar_saved = strdup(mm_getenv("PATH", NULL)); // prepend PATH folders specific for this test case to PATH envvar mm_dirname(childproc_dir, CHILDPROC_BINPATH); mm_setenv("PATH", childproc_dir, MM_ENV_PREPEND); mm_setenv("PATH", BUILDDIR"/"TEST_DATADIR, MM_ENV_PREPEND); for (i = 0; i < MM_NELEM(garbage_data); i+=2) { garbage_data[i] = 0xDEADBEEF; garbage_data[i+1] = 0x7E1705; } mm_mkdir(TEST_DATADIR, 0777, MM_RECURSIVE); // Write a regular file that DOES NOT have the exec permission fd = mm_open(TEST_DATADIR "/" NOEXEC_FILE, O_CREAT|O_TRUNC|O_RDWR, 0666); mm_write(fd, garbage_data, sizeof(garbage_data)); mm_close(fd); // Write a regular executable file with unknown binary format fd = mm_open(TEST_DATADIR "/" UNKBINFMT_FILE, O_CREAT|O_TRUNC|O_RDWR, 0777); mm_write(fd, garbage_data, sizeof(garbage_data)); mm_close(fd); } static void case_teardown(void) { mm_remove(TEST_DATADIR, MM_DT_ANY|MM_RECURSIVE); // Restore PATH environment variable mm_setenv("PATH", path_envvar_saved, 1); free(path_envvar_saved); } /************************************************************************** * * * process tests implementation * * * **************************************************************************/ static const char* binpath_cases[] = { CHILDPROC_BINPATH, "child-proc"EXEEXT, "child-proc", }; START_TEST(spawn_simple) { int rv ; const char* file = binpath_cases[_i]; struct process_test_data* data = create_process_test_data(file); rv = spawn_child(0, data); ck_assert(rv == 0); ck_assert(data->pid != UNSET_PID_VALUE); ck_assert(wait_child(data) == 0); ck_assert(check_expected_fd_content(data) == 0); } END_TEST START_TEST(execv_simple) { int i, rv, last_kept_fd; const char* file = binpath_cases[_i]; struct process_test_data* data = create_process_test_data(file); // Find the latest fd in the fds array of data last_kept_fd = MAX(data->pipe_rd, data->pipe_wr); for (i = 0; i < MM_NELEM(data->fds); i++) { if (data->fds[i] > last_kept_fd) last_kept_fd = data->fds[i]; } rv = run_as_process(&data->pid, "test_execv_process", data, sizeof(*data), last_kept_fd); ck_assert(rv == 0); ck_assert(wait_child(data) == 0); ck_assert(check_expected_fd_content(data) == 0); } END_TEST START_TEST(spawn_daemon) { const char* file = binpath_cases[_i]; struct process_test_data* data = create_process_test_data(file); ck_assert(spawn_child(MM_SPAWN_DAEMONIZE, data) == 0); wait_pipe_close(data); // wait for the daemon process to finish ck_assert(check_expected_fd_content(data) == 0); } END_TEST static const struct { const char* path; int exp_err; int rv; } error_cases[] = { {.path = "/", .exp_err = EACCES}, {.path = "./"TEST_DATADIR"/"NOEXEC_FILE, .exp_err = EACCES}, {.path = "./"TEST_DATADIR"/"UNKBINFMT_FILE, .exp_err = ENOEXEC}, {.path = "./"TEST_DATADIR"/"NOTEXIST_FILE, .exp_err = ENOENT}, {.path = BUILDDIR"/"TEST_DATADIR"/"NOEXEC_FILE, .exp_err = EACCES}, {.path = BUILDDIR"/"TEST_DATADIR"/"UNKBINFMT_FILE, .exp_err = ENOEXEC}, {.path = BUILDDIR"/"TEST_DATADIR"/"NOTEXIST_FILE, .exp_err = ENOENT}, {.path = NOEXEC_FILE, .exp_err = EACCES}, {.path = NOTEXIST_FILE, .exp_err = ENOENT}, }; START_TEST(spawn_error) { int rv; mm_pid_t pid = UNSET_PID_VALUE; const char* path = error_cases[_i].path; /* will spawn an immediately defunct process: path to process is NULL */ rv = mm_spawn(&pid, path, 0, NULL, 0, NULL, NULL); ck_assert(rv != 0); ck_assert(pid == UNSET_PID_VALUE); // no process should be able to launch ck_assert_int_eq(mm_get_lasterror_number(), error_cases[_i].exp_err); } END_TEST #ifndef _WIN32 #include static const int errlimits_spawn_mode_cases[] = {0, MM_SPAWN_DAEMONIZE}; #define NUM_ERRLIMITS_CASES MM_NELEM(errlimits_spawn_mode_cases) START_TEST(spawn_error_limits) { int rv; struct rlimit rlim_orig, rlim; int spawn_mode = errlimits_spawn_mode_cases[_i]; struct process_test_data* data = create_process_test_data(CHILDPROC_BINPATH); /* set RLIMIT_NPROC to 1 process */ ck_assert(getrlimit(RLIMIT_NPROC, &rlim_orig) == 0); rlim = rlim_orig; rlim.rlim_cur = 1; ck_assert(setrlimit(RLIMIT_NPROC, &rlim) == 0); rv = spawn_child(spawn_mode, data); /* restore RLIMIT_NPROC to original value */ ck_assert(setrlimit(RLIMIT_NPROC, &rlim_orig) == 0); ck_assert(rv != 0); ck_assert(data->pid == UNSET_PID_VALUE); ck_assert(mm_get_lasterror_number() == EAGAIN); } END_TEST #endif /* _WIN32 */ START_TEST(spawn_daemon_error) { int rv; mm_pid_t pid = UNSET_PID_VALUE; const char* path = error_cases[_i].path; /* will spawn an immediately defunct process: path to process is NULL */ rv = mm_spawn(&pid, path, 0, NULL, MM_SPAWN_DAEMONIZE, NULL, NULL); ck_assert(rv == -1); ck_assert(pid == UNSET_PID_VALUE); // no process should be able to launch ck_assert_int_eq(mm_get_lasterror_number(), error_cases[_i].exp_err); } END_TEST static const struct { const char* path; int flags; } inval_cases[] = { {.path = NULL, .flags = 0}, {.path = NULL, .flags = MM_SPAWN_KEEP_FDS}, {.path = NULL, .flags = MM_SPAWN_DAEMONIZE}, {.path = NULL, .flags = MM_SPAWN_KEEP_FDS | MM_SPAWN_DAEMONIZE}, {.path = CHILDPROC_BINPATH, .flags = (MM_SPAWN_KEEP_FDS << 2)}, #if defined(_WIN32) {.path = CHILDPROC_BINPATH, .flags = MM_SPAWN_KEEP_FDS}, {.path = CHILDPROC_BINPATH, .flags = MM_SPAWN_KEEP_FDS | MM_SPAWN_DAEMONIZE}, #endif }; START_TEST(spawn_invalid_args) { int rv; mm_pid_t pid = UNSET_PID_VALUE; const char* path = inval_cases[_i].path; int flags = inval_cases[_i].flags; /* cannot run "/" */ rv = mm_spawn(&pid, path, 0, NULL, flags, NULL, NULL); ck_assert(rv == -1); ck_assert(pid == UNSET_PID_VALUE); // no process should be able to launch #if defined(_WIN32) if (path && flags & MM_SPAWN_KEEP_FDS) { ck_assert_int_eq(mm_get_lasterror_number(), ENOTSUP); return; } #endif ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); } END_TEST /* this is to make sure that mm_wait_process() fails if the process * does not exists. * The system SHOULD not have given the same pid again just after we finish * waiting for it */ START_TEST(wait_twice) { const char* file = CHILDPROC_BINPATH; struct process_test_data* data = create_process_test_data(file); mm_pid_t pid; ck_assert(spawn_child(0, data) == 0); ck_assert(data->pid != UNSET_PID_VALUE); pid = data->pid; data->pid = UNSET_PID_VALUE; /* wait once */ ck_assert(mm_wait_process(pid, NULL) == 0); ck_assert(check_expected_fd_content(data) == 0); /* wait a sacond time with the same pid */ ck_assert(mm_wait_process(pid, NULL) != 0); } END_TEST static const int exit_code_cases[] = {0, 1, 2, 3, 15, 16, 127, 128, 255}; START_TEST(wait_exit) { int status; mm_pid_t pid; char arg[32]; char* cmd[] = {CHILDPROC_BINPATH, "check-exit", arg, NULL}; int expected_code = exit_code_cases[_i]; sprintf(arg, "%i", expected_code); ck_assert(mm_spawn(&pid, cmd[0], 0, NULL, 0, cmd, NULL) == 0); ck_assert(mm_wait_process(pid, &status) == 0); // Check process is exited and exit code is the one expected ck_assert(status & MM_WSTATUS_EXITED); ck_assert_int_eq(status & MM_WSTATUS_CODEMASK, expected_code); } END_TEST static const int wait_signal_cases[] = { SIGSEGV, SIGILL, SIGFPE, #if !defined(_WIN32) SIGTERM, SIGABRT, SIGINT, #endif }; START_TEST(wait_signal) { int status; mm_pid_t pid; char arg[32]; int signum = wait_signal_cases[_i]; char* cmd[] = {CHILDPROC_BINPATH, "check-signal", arg, NULL}; sprintf(arg, "%i", signum); ck_assert(mm_spawn(&pid, cmd[0], 0, NULL, 0, cmd, NULL) == 0); ck_assert(mm_wait_process(pid, &status) == 0); // Check process is exited and exit code is the one expected ck_assert(status & MM_WSTATUS_SIGNALED); ck_assert_int_eq(status & MM_WSTATUS_CODEMASK, signum); } END_TEST /************************************************************************** * * * process test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_process_tcase(void) { TCase * tc; tc = tcase_create("process"); tcase_add_unchecked_fixture(tc, case_setup, case_teardown); tcase_add_checked_fixture(tc, NULL, test_teardown); tcase_add_loop_test(tc, spawn_simple, 0, MM_NELEM(binpath_cases)); tcase_add_loop_test(tc, execv_simple, 0, MM_NELEM(binpath_cases)); tcase_add_loop_test(tc, spawn_daemon, 0, MM_NELEM(binpath_cases)); tcase_add_loop_test(tc, spawn_error, 0, MM_NELEM(error_cases)); tcase_add_loop_test(tc, spawn_daemon_error, 0, MM_NELEM(error_cases)); tcase_add_loop_test(tc, spawn_invalid_args, 0, MM_NELEM(inval_cases)); tcase_add_test(tc, wait_twice); tcase_add_loop_test(tc, wait_exit, 0, MM_NELEM(exit_code_cases)); tcase_add_loop_test(tc, wait_signal, 0, MM_NELEM(wait_signal_cases)); #ifndef _WIN32 tcase_add_loop_test(tc, spawn_error_limits, 0, NUM_ERRLIMITS_CASES); #endif return tc; } mmlib-1.4.2/tests/process-testlib.c000066400000000000000000000032141435717460000172550ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H #include #endif #include #include #include "mmerrno.h" #include "mmsysio.h" #include "mmthread.h" #include "mmlog.h" #include "process-testlib.h" #define TEST_PATTERN "++test pattern++" static void* write_pipe_thread(void* data) { int* pipe_fds = data; char buffer[] = TEST_PATTERN; ssize_t rsz; while (1) { rsz = mm_write(pipe_fds[1], buffer, sizeof(buffer)); if (rsz != sizeof(buffer)) break; } return NULL; } static void* read_pipe_thread(void* data) { int* pipe_fds = data; char buffer[sizeof(TEST_PATTERN)]; ssize_t rsz; while (1) { memset(buffer, 0, sizeof(buffer)); rsz = mm_read(pipe_fds[0], buffer, sizeof(buffer)); if (rsz != sizeof(buffer)) break; mm_check(memcmp(buffer, TEST_PATTERN, sizeof(buffer)) == 0); } return NULL; } static int exec_child(struct process_test_data* data) { char* argv[NUM_ARGS_MAX+2] = {NULL}; int i; argv[0] = data->cmd; for (i = 0; i < data->argv_data_len; i++) argv[i+1] = data->argv_data[i]; if (mm_execv(data->cmd, NUM_FDMAP, data->fd_map, 0, argv, NULL) != 0) { fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); return -1; } return 0; } API_EXPORTED intptr_t test_execv_process(void * arg) { int pipe_fds[2]; mm_thread_t t1, t2; // Create a pipe and read and write thread to create process // activity in other threads when mm_execv() will be called mm_check(mm_pipe(pipe_fds) == 0); mm_check(mm_thr_create(&t1, read_pipe_thread, pipe_fds) == 0); mm_check(mm_thr_create(&t2, write_pipe_thread, pipe_fds) == 0); return exec_child(arg); } mmlib-1.4.2/tests/process-testlib.h000066400000000000000000000007421435717460000172650ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef PROCESS_TESTLIB_H #define PROCESS_TESTLIB_H #include "mmsysio.h" #define NUM_FILE 3 #define NUM_ARGS_MAX 4 #define NUM_FDS (2*NUM_FILE) #define NUM_FDMAP (NUM_FILE+1) struct process_test_data { mm_pid_t pid; int fds[NUM_FDS]; int pipe_wr; int pipe_rd; struct mm_remap_fd fd_map[NUM_FDMAP]; int argv_data_len; char argv_data[NUM_ARGS_MAX][32]; char cmd[128]; }; intptr_t test_execv_process(void * arg); #endif mmlib-1.4.2/tests/shm-api-tests.c000066400000000000000000000113461435717460000166360ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "api-testcases.h" #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmsysio.h" #define TEST_SHM "test-shm-name" #define TEST_FILE "test-file-name" static struct { int oflags; int mflags; } valid_map_cases[] = { {.oflags = O_RDWR, .mflags = MM_MAP_RDWR |MM_MAP_SHARED}, {.oflags = O_RDWR, .mflags = MM_MAP_READ |MM_MAP_SHARED}, {.oflags = O_RDWR, .mflags = MM_MAP_WRITE|MM_MAP_SHARED}, {.oflags = O_RDWR, .mflags = MM_MAP_RDWR |MM_MAP_PRIVATE}, {.oflags = O_RDWR, .mflags = MM_MAP_READ |MM_MAP_PRIVATE}, {.oflags = O_RDWR, .mflags = MM_MAP_WRITE|MM_MAP_PRIVATE}, {.oflags = O_RDONLY, .mflags = MM_MAP_READ |MM_MAP_SHARED}, {.oflags = O_RDONLY, .mflags = MM_MAP_READ |MM_MAP_PRIVATE}, }; #define NUM_VALID_MAP_CASES MM_NELEM(valid_map_cases) static void test_teardown(void) { int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); mm_shm_unlink(TEST_SHM); mm_unlink(TEST_FILE); mm_error_set_flags(flags, MM_ERROR_IGNORE); } static void create_rw_shm(const char* path) { int fd_shm; fd_shm = mm_shm_open(path, O_RDWR|O_CREAT|O_EXCL, 0600); ck_assert(fd_shm > 0); ck_assert(mm_ftruncate(fd_shm, MM_PAGESZ) == 0); mm_close(fd_shm); } START_TEST(shm_open_test) { int fd; fd = mm_shm_open(TEST_SHM, O_CREAT, 0); ck_assert(fd > 0); ck_assert(mm_shm_unlink(TEST_SHM) == 0); mm_close(fd); } END_TEST START_TEST(mm_mapfile_test_simple) { int fd; void * map; fd = mm_open(TEST_FILE, O_CREAT|O_RDWR, 0666); ck_assert(fd > 0); mm_ftruncate(fd, 42); map = mm_mapfile(fd, 0, 42, MM_MAP_RDWR|MM_MAP_SHARED); ck_assert(map != NULL); mm_unmap(map); mm_unlink(TEST_FILE); mm_close(fd); } END_TEST START_TEST(mm_mapfile_test_smaller) { int fd; void * map; fd = mm_open(TEST_FILE, O_CREAT|O_RDWR, 0666); ck_assert(fd > 0); mm_ftruncate(fd, 42); map = mm_mapfile(fd, 0, 23, MM_MAP_RDWR|MM_MAP_SHARED); ck_assert(map != NULL); mm_unmap(map); mm_unlink(TEST_FILE); mm_close(fd); } END_TEST START_TEST(mm_mapfile_test_larger) { int fd; void * map; fd = mm_open(TEST_FILE, O_CREAT|O_RDWR, 0666); ck_assert(fd > 0); mm_ftruncate(fd, 23); map = mm_mapfile(fd, 0, 43, MM_MAP_RDWR|MM_MAP_SHARED); #ifdef _WIN32 ck_assert(map == NULL); ck_assert_int_eq(mm_get_lasterror_number(), EOVERFLOW); #else ck_assert(map != NULL); mm_unmap(map); #endif mm_unlink(TEST_FILE); mm_close(fd); } END_TEST START_TEST(shm_open_duplicate_test) { int fd1, fd2; fd1 = mm_shm_open(TEST_SHM, O_CREAT|O_EXCL, S_IWUSR); ck_assert(fd1 > 0); fd2 = mm_shm_open(TEST_SHM, O_CREAT|O_EXCL, S_IWUSR); ck_assert(fd2 == -1); ck_assert(mm_shm_unlink(TEST_SHM) == 0); mm_close(fd1); } END_TEST START_TEST(mapfile_test) { int fd; void * map; int oflags = valid_map_cases[_i].oflags; int mflags = valid_map_cases[_i].mflags; // Create a shm object located at TEST_SHM with file size // MM_PAGESZ so that we can access it even if oflags is O_RDONLY create_rw_shm(TEST_SHM); fd = mm_shm_open(TEST_SHM, oflags, 0); ck_assert(fd > 0); map = mm_mapfile(fd, 0, MM_PAGESZ, mflags); ck_assert(map != NULL); mm_unmap(map); ck_assert(mm_shm_unlink(TEST_SHM) == 0); mm_close(fd); } END_TEST START_TEST(mapfile_invalid_offset_test) { int fd; void * map; fd = mm_shm_open(TEST_SHM, O_RDWR|O_CREAT|O_EXCL, 0); ck_assert(fd > 0); mm_ftruncate(fd, MM_PAGESZ); map = mm_mapfile(fd, 42, MM_PAGESZ, MM_MAP_EXEC|MM_MAP_SHARED); ck_assert(map == NULL); ck_assert(mm_shm_unlink(TEST_SHM) == 0); mm_close(fd); } END_TEST START_TEST(invalid_unmap_test) { ck_assert(mm_unmap((void * ) -1) == -1); } END_TEST #define N 10000 START_TEST(multiple_maps_test) { int i; int fds[N]; void * maps[N]; for (i = 0 ; i < N ; i++) { fds[i] = mm_anon_shm(); if (fds[i] == -1) { ck_assert(mm_get_lasterror_number() == EMFILE); break; } mm_ftruncate(fds[i], MM_PAGESZ); maps[i] = mm_mapfile(fds[i], 0, MM_PAGESZ, MM_MAP_RDWR|MM_MAP_SHARED); if (maps[i] == NULL) break; } while (--i > 0) { ck_assert(fds[i] > 0); ck_assert(maps[i] != NULL); mm_close(fds[i]); } } END_TEST #undef N LOCAL_SYMBOL TCase* create_shm_tcase(void) { TCase * tc; tc = tcase_create("shm"); tcase_add_checked_fixture(tc, NULL, test_teardown); tcase_add_test(tc, shm_open_test); tcase_add_test(tc, mm_mapfile_test_simple); tcase_add_test(tc, mm_mapfile_test_smaller); tcase_add_test(tc, mm_mapfile_test_larger); tcase_add_test(tc, shm_open_duplicate_test); tcase_add_loop_test(tc, mapfile_test, 0, NUM_VALID_MAP_CASES); tcase_add_test(tc, mapfile_invalid_offset_test); tcase_add_test(tc, invalid_unmap_test); tcase_add_test(tc, multiple_maps_test); return tc; } mmlib-1.4.2/tests/socket-api-tests.c000066400000000000000000001057651435717460000173500ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include "api-testcases.h" #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmsysio.h" #include "socket-testlib.h" #include "tests-child-proc.h" #define PORT 32145 #define MULTIMSG_LEN 6 #define IOV_MAXLEN 8 static size_t socket_data_len[] = { 4, 16, 64, 325, 512, 1000, 4096, 3000, 6951, 2412, 8192, }; struct testsocket_config_case { int domain; int socktype; }; static struct testsocket_config_case test_cases[] = { {.domain = AF_INET, .socktype = SOCK_STREAM}, {.domain = AF_INET, .socktype = SOCK_DGRAM}, {.domain = AF_INET6, .socktype = SOCK_STREAM}, {.domain = AF_INET6, .socktype = SOCK_DGRAM}, }; #define FIRST_IPV6_TEST_CASE 2 struct childproc { int wr_pipe_fd; int rd_pipe_fd; mm_pid_t pid; int sockfd; struct sockaddr_storage peer_addr; socklen_t peer_addr_len; }; /** * spawn_childproc() - spawn a child test process with connected pipes * @child; childproc structure to init * ...: NULL terminated variable argument list to be supplied to child * process spawn * * Execute the program tests-child-proc in a child process and connect it with * 2 pipe, one for reading and one for writing. The program will execute the * function "run_socket_client" with the argument passed in the variable * argument list (terminated by NULL). The data regarding child and pipes are * stored in @child. */ static void spawn_childproc(struct childproc* child, ...) { int i, pipe_fds_tochild[2], pipe_fds_fromchild[2]; struct mm_remap_fd fdmap[2]; va_list args; char *argv[16]; char* arg; // Setup argument list array for passing to child creation function i = 0; argv[i++] = TESTS_CHILD_BIN; argv[i++] = "run_socket_client"; va_start(args, child); do { arg = va_arg(args, char*); argv[i++] = arg; } while (arg); va_end(args); // Create pipe ck_assert(mm_pipe(pipe_fds_tochild) == 0); ck_assert(mm_pipe(pipe_fds_fromchild) == 0); child->wr_pipe_fd = pipe_fds_tochild[1]; child->rd_pipe_fd = pipe_fds_fromchild[0]; fdmap[0] = (struct mm_remap_fd){WR_PIPE_FD, pipe_fds_fromchild[1]}; fdmap[1] = (struct mm_remap_fd){RD_PIPE_FD, pipe_fds_tochild[0]}; // spawn child process ck_assert(mm_spawn(&child->pid, argv[0], 2, fdmap, 0, argv, NULL) == 0); // Close pipe end meant for child process mm_close(fdmap[0].parent_fd); mm_close(fdmap[1].parent_fd); } /** * clean_childproc() - stop and clean child * @child: pointer to childproc holding the data relative to child * * Call this function when you want to stop the child process. The order of the * connection of connection stop will make the child process to stop normally. * * Typically called in the teardown function of the tests */ static void clean_childproc(struct childproc* child) { mm_close(child->wr_pipe_fd); mm_close(child->sockfd); mm_close(child->rd_pipe_fd); child->wr_pipe_fd = -1; child->sockfd = -1; child->rd_pipe_fd = -1; if (child->pid) mm_wait_process(child->pid, NULL); child->pid = 0; } /** * childproc_order_read() - instruct child to read from its socket end and return data to pipe * @child: pointer to childproc holding the data relative to child * @len: length of the data to try to read from socket * @data: socket_data structure to use to get the data that child will return */ static void childproc_order_read(struct childproc* child, size_t len, struct socket_data* data) { ssize_t rsz; struct testsocket_order order = { .cmd = READ_SOCKET_DATA, .opt_len = len, }; // Instruct (over pipe) child to read data from its socket endpoint rsz = mm_write(child->wr_pipe_fd, &order, sizeof(order)); ck_assert(rsz == (ssize_t)sizeof(order)); // The child should have returned what it has read from socket onto the // pipe, so get it ck_assert(pipe_read_socketdata(child->rd_pipe_fd, data) == 0); } /** * childproc_order_read() - instruct child to write data on its socket end * @child: pointer to childproc holding the data relative to child * @data: socket_data structure containing data child has to send to socket */ static void childproc_order_write(struct childproc* child, struct socket_data* data) { ssize_t rsz; struct testsocket_order order = { .cmd = WRITE_SOCKET_DATA, }; // Instruct (over pipe) child to write data on its socket endpoint rsz = mm_write(child->wr_pipe_fd, &order, sizeof(order)); ck_assert(rsz == (ssize_t)sizeof(order)); // Send over the pipe the data that child must send on the socket ck_assert(pipe_write_socketdata(child->wr_pipe_fd, data) == 0); } /** * create_connected_socket_and_child() - setup child and socket to test * @child: pointer to childproc holding the data relative to child * @domain: communications domain in which a socket is to be created (AF_INET or AF_INET6) * @socktype: type of socket to create (SOCK_STREAM or SOCK_DGRAM) * * This function creates a server socket according to @domain and @socktype, * spawn a child process executing tests-child-proc with the function * "run_socket_client" which will try to connect the server socket. This * process will then: * * - if TCP: accept the connection and return the connected socket * - if UDP: read the first datagram that client is supposed to send in order * to bind the server socket to it. * * This means that in any case, this function return a connected socket to * client (no matter it is SOCK_DGRAM or SOCK_STREAM). * * This internal data use to communicate with child process is stored in * @child. * * Return: file descriptor of connected socket to child */ static int create_connected_socket_and_child(struct childproc* child, int domain, int socktype) { int fd; int timeout = 500; char domain_str[16], socktype_str[16]; // Create listening socket fd = create_server_socket(domain, socktype, PORT); ck_assert(fd != -1); // Make server socket listening in case of TCP if (socktype == SOCK_STREAM) ck_assert(mm_listen(fd, 10) == 0); // Spawn child for the test sprintf(domain_str, "%i", domain); sprintf(socktype_str, "%i", socktype); spawn_childproc(child, domain_str, socktype_str, "localhost", MM_STRINGIFY(PORT), NULL); child->peer_addr_len = sizeof(child->peer_addr); if (socktype == SOCK_DGRAM) { struct msghdr msg = { .msg_name = &child->peer_addr, .msg_namelen = child->peer_addr_len, }; // Get first packet from client (when UDP, the child process // send a first packet with empty payload). And use it to know // the address and connect to the client socket ck_assert(mm_recvmsg(fd, &msg, 0) == 0); ck_assert(mm_connect(fd, msg.msg_name, msg.msg_namelen) == 0); child->peer_addr_len = msg.msg_namelen; child->sockfd = fd; } else { // Accept incoming TCP connection and close listening socket child->sockfd = mm_accept(fd, (struct sockaddr*)&child->peer_addr, &child->peer_addr_len); mm_close(fd); ck_assert(child->sockfd != -1); } ck_assert(mm_setsockopt(child->sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0); return child->sockfd; } /** * gen_random_buffer() - fill buffer with random data * @buffer: pointer to buffer to fill * @len: size of buffer */ static void gen_random_buffer(void* buffer, size_t len) { int* int_buf = buffer; int rand_val; while (len >= sizeof(int)) { *(int_buf++) = rand(); len -= sizeof(int); } rand_val = rand(); memcpy(int_buf, &rand_val, len); } /** * gen_socket_data() - generate random a piece of specific size * @data: socket data to initialize * @len: length of the data */ static void gen_socket_data(struct socket_data* data, size_t len) { ck_assert(len <= sizeof(data->buf)); data->len = len; gen_random_buffer(data->buf, len); } /** * gen_random_int() - draw a random int from uniform distribution of [0,max] * @max: maximum value that the function can generate * * Return: random int between 0 and @max */ static int gen_random_int(int max) { return (max+1)*((double)rand()/RAND_MAX); } /** * gen_random_msg_iov() - generate random split of scatter/gather buffers of msg * @num_iov_max: maximum number of scatter/gather buffers * @msg: msghdr structure whose iov should be configured * @buf: buffer that will back the scatter/gather buffers * @buflen: size of @buf * * This function configures the msg_iov split of @msg with a random lookup * partition of a buffer @buf of size @buflen. The partition is random, nothing * prevents that the elements in @msg->msg_iov will not overlap. * * It is however guaranteed that: * - the element in @msg->msg_iov will always point inside @buf * - the combined size of @msg->msg_iov elements will be less or equal to @buflen */ static void gen_random_msg_iov(int num_iov_max, struct msghdr* msg, void* buf, size_t buflen) { int i, offset; size_t len, rem_sz; char* cbuf = buf; rem_sz = buflen; for (i = 0; i < num_iov_max; i++) { offset = gen_random_int(buflen-1); len = gen_random_int(buflen-offset); if (len > rem_sz) len = rem_sz; msg->msg_iov[i].iov_base = cbuf + offset; msg->msg_iov[i].iov_len = len; rem_sz -= len; if (rem_sz == 0) { i += 1; break; } } msg->msg_iovlen = i; } /** * copy_msg_data_to_buffer() - copy scatter/gather buffers content to flat buffer * @msg: msghdr whose scatter/gather buffers must be copied from * @buffer: flat buffer to which the data must be copied */ static void copy_msg_data_to_buffer(const struct msghdr* msg, void* buffer) { int i; size_t iovlen; char* dstbuf; int num_iov = msg->msg_iovlen; dstbuf = buffer; for (i = 0; i < num_iov; i++) { iovlen = msg->msg_iov[i].iov_len; memcpy(dstbuf, msg->msg_iov[i].iov_base, iovlen); dstbuf += iovlen; } } /** * copy_buffer_to_msg_data() - copy flat buffer to scatter/gather buffers * @buffer: flat buffer from which which the data must be copied * @msg: msghdr whose scatter/gather buffers must receive the data */ static void copy_buffer_to_msg_data(const void* buffer, const struct msghdr* msg) { int i; size_t iovlen; const char* srcbuf; srcbuf = buffer; for (i = 0; i < (int)msg->msg_iovlen; i++) { iovlen = msg->msg_iov[i].iov_len; memcpy(msg->msg_iov[i].iov_base, srcbuf, iovlen); srcbuf += iovlen; } } /** * cmp_msg_dat() compare content of scatter/gather buffers of 2 msghdr * msg1: first message to compare * msg2: second message to compare * * Return: 0 if the data contained in scatter/gather buffers of both message * match, non-zero otherwise. */ static int cmp_msg_data(const struct msghdr* msg1, const struct msghdr* msg2) { int i, r; size_t iovlen; struct iovec *iov1 = msg1->msg_iov; struct iovec *iov2 = msg2->msg_iov; int num_iov = msg1->msg_iovlen; if (msg1->msg_iovlen != msg2->msg_iovlen) return -1; for (i = 0; i < num_iov; i++) { iovlen = iov1[i].iov_len; if (iovlen != iov2[i].iov_len) return -1; r = memcmp(iov1[i].iov_base, iov2[i].iov_base, iovlen); if (r != 0) return r; } return 0; } /** * get_iov_size() - compute the total size held scatter/gather buffers * @msg: msghdr whose scatter/gather buffers must be inspected * * Return: summed size of @msg->msg_iov[*].iov_len. */ static size_t get_iov_size(const struct msghdr* msg) { size_t sz; int i; int num_iov = msg->msg_iovlen; sz = 0; for (i = 0; i < num_iov; i++) sz += msg->msg_iov[i].iov_len; return sz; } /** * copy_msg_iov() - copy the split of scatter/gather buffer of a message * @src: source message * @dst: message to which the split must be copied * * This function copy the split of @scr into @dst. The buffers element are * still backed by the buffer used in @src. Use retarget_msg_iov() to change * this. */ static void copy_msg_iov(const struct msghdr* src, struct msghdr* dst) { memcpy(dst->msg_iov, src->msg_iov, src->msg_iovlen*sizeof(src->msg_iov[0])); dst->msg_iovlen = src->msg_iovlen; } /** * retarget_msg_iov() - change the backing flat buffer of the scatter/gather buffers * @msg: message whose iov structures must be changed * @prev: pointer to the previous backing buffer * @new: pointer to the new backing buffer * * This function assumes that the scatter/gather buffers of the message * point inside the same buffer. While this is not the case in general, it * is true in the context of those current tests. * * This takes the @new and @prev argument to compute the offset to apply * to the element @msg->msg_iov[*].iov_base so that the new element now * point inside the buffer @new. (this assumes that the allocated size of * @new and @prev are consistent). */ static void retarget_msg_iov(struct msghdr* msg, const void* prev, const void* new) { int i; char* base_ptr; int num_iov = msg->msg_iovlen; intptr_t offset = (intptr_t)new - (intptr_t)prev; for (i = 0; i < num_iov; i++) { base_ptr = msg->msg_iov[i].iov_base; msg->msg_iov[i].iov_base = base_ptr + offset; } } /** * setup_recvmsg_test_iteration() - prepare messages for recvmsg and request write from child * @child: pointer to childproc holding the data relative to child * @size_in_iov: size held by scatter/buffer in messages * @msg_ref: reference message * @buff_ref: flat buffer backing @msg_ref * @msg_test: test message * @buff_rest: flat buffer backing @msg_test * * setup 2 message using the same split for their scatter/gather buffers but * backed by 2 different flat buffers. Generate random data that is copied * into @msg_ref (simulating read from net interface into @msg_ref) and * order the child to write the same random data onto the socket. */ static void setup_recvmsg_test_iteration(struct childproc* child, size_t size_in_iov, struct msghdr* msg_ref, void* buff_ref, struct msghdr* msg_test, void* buff_test) { struct socket_data data; memset(buff_ref, 0, DGRAM_MAXSIZE); memset(buff_test, 0, DGRAM_MAXSIZE); // Generate 2 msg with identical iov split, but backed by 2 different // buffers (ie, iov element have same size and same offset but point to // 2 different buffers) gen_random_msg_iov(IOV_MAXLEN, msg_ref, buff_ref, size_in_iov); copy_msg_iov(msg_ref, msg_test); retarget_msg_iov(msg_test, buff_ref, buff_test); // generate random data that child will feed to socket gen_socket_data(&data, get_iov_size(msg_ref)); copy_buffer_to_msg_data(data.buf, msg_ref); // instruct child to read data from pipe and issue a send call to // socket with it childproc_order_write(child, &data); } /************************************************************************** * * * socket tests implementation * * * **************************************************************************/ static struct childproc child = { .wr_pipe_fd = -1, .rd_pipe_fd = -1, .pid = 0, .sockfd = -1, }; START_TEST(recv_on_localhost) { int sockfd, i, flags; size_t req_sz; ssize_t rsz; struct socket_data data; char buffer[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; flags = (socktype == SOCK_STREAM) ? MSG_WAITALL : 0; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { gen_socket_data(&data, socket_data_len[i]); childproc_order_write(&child, &data); // If not SOCK_STREAM, message boundaries must be kept. So in // such a case, received size must equal equal to what we have // sent, ie if we request more, we must received what has been // sent into the pipe req_sz = (socktype == SOCK_STREAM) ? data.len : sizeof(buffer); rsz = mm_recv(sockfd, buffer, req_sz, flags); ck_assert_int_eq(rsz, data.len); ck_assert(memcmp(data.buf, buffer, data.len) == 0); } } END_TEST START_TEST(send_on_localhost) { int sockfd, i; ssize_t rsz; struct socket_data data; char buffer[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; size_t buf_sz; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { buf_sz = socket_data_len[i]; gen_random_buffer(buffer, buf_sz); rsz = mm_send(sockfd, buffer, buf_sz, 0); ck_assert(rsz >= 0); childproc_order_read(&child, sizeof(data.buf), &data); ck_assert_int_eq(rsz, data.len); ck_assert(memcmp(data.buf, buffer, data.len) == 0); } } END_TEST START_TEST(read_on_localhost) { int sockfd, i, k; ssize_t rsz, rsz_tmp; size_t req_sz; struct socket_data data; char buffer[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { gen_socket_data(&data, socket_data_len[i]); childproc_order_write(&child, &data); if (socktype == SOCK_STREAM) { // If SOCK_STREAM (TCP), the mm_read() call is allowed // to read less than requested req_sz = data.len; rsz = 0; for (k = 0; (k < 100) && (rsz < (ssize_t)req_sz); k++) { rsz_tmp = mm_read(sockfd, buffer+rsz, req_sz-rsz); if (rsz_tmp < 0) break; rsz += rsz_tmp; } } else { // If not SOCK_STREAM, message boundaries must be kept. // So in such a case, received size must equal to what // we have sent, ie if we request more, we must // received what has been sent into the pipe rsz = mm_read(sockfd, buffer, sizeof(buffer)); } ck_assert_int_eq(rsz, data.len); ck_assert(memcmp(data.buf, buffer, data.len) == 0); } } END_TEST START_TEST(write_on_localhost) { int sockfd, i; ssize_t rsz; struct socket_data data; char buffer[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; size_t buf_sz; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { buf_sz = socket_data_len[i]; gen_random_buffer(buffer, buf_sz); rsz = mm_write(sockfd, buffer, buf_sz); ck_assert_int_eq(rsz, buf_sz); childproc_order_read(&child, sizeof(data.buf), &data); ck_assert_int_eq(rsz, data.len); ck_assert(memcmp(data.buf, buffer, data.len) == 0); } } END_TEST START_TEST(recvmsg_on_localhost) { int sockfd, i, flags; ssize_t rsz; char buffer_test[DGRAM_MAXSIZE], buffer_ref[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; struct iovec iov_test[IOV_MAXLEN], iov_ref[IOV_MAXLEN]; struct msghdr msg_test = {.msg_iov = iov_test}; struct msghdr msg_ref = {.msg_iov = iov_ref}; flags = (socktype == SOCK_STREAM) ? MSG_WAITALL : 0; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { setup_recvmsg_test_iteration(&child, socket_data_len[i], &msg_ref, buffer_ref, &msg_test, buffer_test); rsz = mm_recvmsg(sockfd, &msg_test, flags); // Check data receing from mm_recvmsg match expected results ck_assert_int_eq(rsz, get_iov_size(&msg_ref)); ck_assert(cmp_msg_data(&msg_ref, &msg_test) == 0); ck_assert(memcmp(buffer_ref, buffer_test, sizeof(buffer_test)) == 0); } } END_TEST START_TEST(sendmsg_on_localhost) { int sockfd, i; ssize_t rsz; struct socket_data data; char data_src[DGRAM_MAXSIZE], buffer_ref[DGRAM_MAXSIZE]; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; struct iovec iov[IOV_MAXLEN]; struct msghdr msg = {.msg_iov = iov}; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to socket and check that child has received // it properly (check this by inspecting what child send over pipe // which should match what we sent on socket) for (i = 0; i < MM_NELEM(socket_data_len); i++) { // Generate random data and iov (within size limits) gen_random_msg_iov(IOV_MAXLEN, &msg, data_src, socket_data_len[i]); gen_random_buffer(data_src, DGRAM_MAXSIZE); // order child to read data from socket obtained through // mm_sendmsg rsz = mm_sendmsg(sockfd, &msg, 0); ck_assert(rsz >= 0); childproc_order_read(&child, sizeof(data.buf), &data); // Simulate what does sendmsg do copy_msg_data_to_buffer(&msg, buffer_ref); // Check data received from mm_recvmsg match expected results ck_assert_int_eq(rsz, get_iov_size(&msg)); ck_assert_int_eq(rsz, data.len); ck_assert(memcmp(data.buf, buffer_ref, data.len) == 0); } } END_TEST START_TEST(recv_multimsg_on_localhost) { int sockfd, i, j, num_msg, flags; size_t len; char fbuffer_test[MULTIMSG_LEN*DGRAM_MAXSIZE]; char fbuffer_ref[MULTIMSG_LEN*DGRAM_MAXSIZE]; char *buff_test, *buff_ref; struct iovec iov[MULTIMSG_LEN*IOV_MAXLEN]; struct iovec iov_ref[MULTIMSG_LEN*IOV_MAXLEN]; struct msghdr *msg_test, *msg_ref; struct mm_sock_multimsg msgvec_ref[MULTIMSG_LEN] = {{.datalen = 0}}; struct mm_sock_multimsg msgvec_test[MULTIMSG_LEN] = {{.datalen = 0}}; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; flags = (socktype == SOCK_STREAM) ? MSG_WAITALL : 0; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { // Setup multiple msg (ref and test) and issue write order to // child so that we can retrieve all those msg data in one call // to mm_recv_multimsg() for (j = 0; j < MULTIMSG_LEN; j++) { msg_ref = &msgvec_ref[j].msg; msg_test = &msgvec_test[j].msg; buff_ref = fbuffer_ref + j*DGRAM_MAXSIZE; buff_test = fbuffer_test + j*DGRAM_MAXSIZE; msg_ref->msg_iov = iov_ref + j*IOV_MAXLEN; msg_test->msg_iov = iov + j*IOV_MAXLEN; setup_recvmsg_test_iteration(&child, socket_data_len[i], msg_ref, buff_ref, msg_test, buff_test); } // Read all message data num_msg = mm_recv_multimsg(sockfd, MULTIMSG_LEN, msgvec_test, flags, NULL); ck_assert_int_eq(num_msg, MULTIMSG_LEN); // Check data receing from each individual msg match expected results for (j = 0; j < MULTIMSG_LEN; j++) { msg_ref = &msgvec_ref[j].msg; msg_test = &msgvec_test[j].msg; len = msgvec_test[j].datalen; ck_assert_int_eq(len, get_iov_size(msg_ref)); ck_assert(cmp_msg_data(msg_ref, msg_test) == 0); } ck_assert(memcmp(fbuffer_ref, fbuffer_test, sizeof(fbuffer_test)) == 0); } } END_TEST START_TEST(send_multimsg_on_localhost) { int sockfd, i, j, num_msg; ssize_t len; struct socket_data data; char fdata_src[MULTIMSG_LEN*DGRAM_MAXSIZE], buff_ref[DGRAM_MAXSIZE]; char* data_src; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; struct iovec iov[DGRAM_MAXSIZE * IOV_MAXLEN]; struct msghdr* msg; struct mm_sock_multimsg msgvec[MULTIMSG_LEN] = {{.datalen = 0}}; // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); // Send a piece of data to pipe and check that child has sent this // data on socket for (i = 0; i < MM_NELEM(socket_data_len); i++) { for (j = 0; j < MULTIMSG_LEN; j++) { msg = &msgvec[j].msg; data_src = fdata_src + j*DGRAM_MAXSIZE; msg->msg_iov = iov + j*IOV_MAXLEN; // Generate random data and iov (within size limits) gen_random_msg_iov(IOV_MAXLEN, msg, data_src, socket_data_len[i]); gen_random_buffer(data_src, DGRAM_MAXSIZE); } num_msg = mm_send_multimsg(sockfd, MULTIMSG_LEN, msgvec, 0); ck_assert_int_eq(num_msg, MULTIMSG_LEN); for (j = 0; j < MULTIMSG_LEN; j++) { msg = &msgvec[j].msg; len = msgvec[j].datalen; ck_assert_int_eq(len, get_iov_size(msg)); // Simulate what does sendmsg do childproc_order_read(&child, len, &data); copy_msg_data_to_buffer(msg, buff_ref); // Check data receing from mm_recvmsg match expected results ck_assert_int_eq(len, data.len); ck_assert(memcmp(data.buf, buff_ref, data.len) == 0); } } } END_TEST START_TEST(test_poll_all_negative) { struct mm_pollfd pollfds = { .fd = -1, .events = POLLIN | POLLOUT, }; ck_assert(mm_poll(&pollfds, 1, 100) == 0); } END_TEST START_TEST(test_poll_simple) { struct mm_pollfd pollfds = { .fd = create_server_socket(AF_INET, SOCK_DGRAM, PORT), .events = POLLIN | POLLOUT, }; ck_assert(pollfds.fd > 0); ck_assert(mm_poll(&pollfds, 1, 100) == 1); ck_assert((pollfds.revents & POLLOUT) != 0); // can write mm_close(pollfds.fd); } END_TEST /* test that if the fd is negative then the corresponding events field is * ignored and the revents field returns zero */ START_TEST(test_poll_ignore_negative_socket) { struct mm_pollfd pollfds[] = { { .fd = -1, .events = POLLIN | POLLOUT, }, { .fd = create_server_socket(AF_INET, SOCK_DGRAM, PORT), .events = POLLIN | POLLOUT, }, }; ck_assert(mm_poll(pollfds, 2, 100) == 1); // only one valid fd ck_assert((pollfds[0].revents) == 0); // empty event ck_assert((pollfds[1].revents & POLLOUT) != 0); // can write mm_close(pollfds[0].fd); mm_close(pollfds[1].fd); } END_TEST START_TEST(test_getsockname) { int rv, fd; char service[16]; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); struct addrinfo *res = NULL; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_DGRAM,}; snprintf(service, sizeof(service), "%i", PORT); rv = mm_getaddrinfo(NULL, service, &hints, &res); ck_assert(rv == 0 && res != NULL); fd = create_server_socket(AF_INET, SOCK_DGRAM, PORT); ck_assert(fd > 0); rv = mm_getsockname(fd,(struct sockaddr *) &addr, &addrlen); ck_assert(rv == 0); ck_assert(addr.sin_family == AF_INET); ck_assert(ntohs(addr.sin_port) == PORT); ck_assert(addr.sin_addr.s_addr == ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr); mm_freeaddrinfo(res); mm_close(fd); } END_TEST START_TEST(test_getpeername) { int sockfd; int domain = test_cases[_i].domain; int socktype = test_cases[_i].socktype; struct sockaddr_storage addr; socklen_t addrlen = sizeof(addr); // Create connected socket and child process (the created child and // socket are cleaned up in teardown) sockfd = create_connected_socket_and_child(&child, domain, socktype); ck_assert(mm_getpeername(sockfd, (struct sockaddr*)&addr, &addrlen) != -1); ck_assert_int_eq(addrlen, child.peer_addr_len); ck_assert(memcmp(&addr, &child.peer_addr, addrlen) == 0); } END_TEST static void assert_addrinfo(struct addrinfo* rp, int exp_socktype, short exp_port) { struct sockaddr_in* addrin; struct sockaddr_in6* addrin6; ck_assert(rp->ai_socktype == exp_socktype); if (rp->ai_family == AF_INET) { addrin = (struct sockaddr_in*)rp->ai_addr; ck_assert_int_eq(htons(addrin->sin_port), exp_port); } else if (rp->ai_family == AF_INET6) { addrin6 = (struct sockaddr_in6*)rp->ai_addr; ck_assert_int_eq(htons(addrin6->sin6_port), exp_port); } else { ck_abort_msg("Not matching address family"); } } START_TEST(getaddrinfo_valid) { struct addrinfo *rp, *res = NULL; struct addrinfo hints = {.ai_family = AF_UNSPEC}; ck_assert(mm_getaddrinfo("localhost", "ssh", &hints, &res) == 0); for (rp = res; rp != NULL; rp = rp->ai_next) assert_addrinfo(rp, SOCK_STREAM, 22); mm_freeaddrinfo(res); hints.ai_flags = AI_NUMERICSERV; hints.ai_socktype = SOCK_DGRAM; ck_assert(mm_getaddrinfo("localhost", "42", &hints, &res) == 0); for (rp = res; rp != NULL; rp = rp->ai_next) assert_addrinfo(rp, SOCK_DGRAM, 42); mm_freeaddrinfo(res); } END_TEST START_TEST(getaddrinfo_error) { struct addrinfo *res = NULL; struct addrinfo hints = {.ai_family = AF_INET}; ck_assert(mm_getaddrinfo("notanhost.localdomain", "ssh", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), MM_ENONAME); ck_assert(mm_getaddrinfo("localhost", "joke", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), MM_ENOTFOUND); hints.ai_socktype = SOCK_DGRAM; ck_assert(mm_getaddrinfo("localhost", "ssh", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), MM_ENOTFOUND); hints.ai_socktype = 0; ck_assert(mm_getaddrinfo(NULL, NULL, &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); hints.ai_flags = AI_CANONNAME; ck_assert(mm_getaddrinfo(NULL, "ssh", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); hints.ai_flags = 0; hints.ai_flags = AI_NUMERICSERV; ck_assert(mm_getaddrinfo("localhost", "123joke", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); hints.ai_flags = 0; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_UDP; ck_assert(mm_getaddrinfo("localhost", "ssh", &hints, &res) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EPROTOTYPE); hints.ai_socktype = 0; hints.ai_protocol = 0; } END_TEST /************************************************************************** * * * socket helper tests * * * **************************************************************************/ static int num_fd_to_close = 0; static int fds_to_close[8]; static void clean_helper_test_data(void) { while (num_fd_to_close) mm_close(fds_to_close[--num_fd_to_close]); } static void add_fd_to_close(int fd) { ck_assert(num_fd_to_close != MM_NELEM(fds_to_close)); fds_to_close[num_fd_to_close++] = fd; } static int get_socktype(int fd) { int socktype = -1; socklen_t len = sizeof(socktype); if (mm_getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &len) || len != sizeof(socktype)) return -1; return socktype; } static int get_peer_port(int fd) { struct sockaddr_in6 addr = {.sin6_port = 0}; socklen_t len = sizeof(addr); if (mm_getpeername(fd, (struct sockaddr*)&addr, &len) || len != sizeof(addr)) return -1; return ntohs(addr.sin6_port); } static const struct { const char* uri; int exp_socktype; int exp_port; } sockclient_cases[] = { #if _WIN32 {"msnp://localhost", SOCK_STREAM, 1863}, #else {"socks://localhost", SOCK_STREAM, 1080}, #endif {"ntp://localhost", SOCK_DGRAM, 123}, {"tcp://localhost:" MM_STRINGIFY(PORT), SOCK_STREAM, PORT}, {"udp://localhost:" MM_STRINGIFY(PORT), SOCK_DGRAM, PORT}, }; START_TEST(create_sockclient) { int fd, socktype, port; int exp_socktype = sockclient_cases[_i].exp_socktype; int exp_port = sockclient_cases[_i].exp_port; const char* uri = sockclient_cases[_i].uri; // Try to create listening server socket (stream). If the address // is already bound, the port is already opened out of the process: // this is fine for us, we can use this one. if (exp_socktype == SOCK_STREAM) { fd = create_server_socket(AF_INET6, SOCK_STREAM, exp_port); if (fd != -1) { ck_assert(mm_listen(fd, 1) == 0); add_fd_to_close(fd); } else if (mm_get_lasterror_number() != EADDRINUSE) { ck_abort_msg("Cannot create server socket"); } } fd = mm_create_sockclient(uri); if (fd == -1) { fprintf(stderr, "Failed to create server socker with uri: %s, port %d\n" "If you do not support this protocol, or if this port is closed " "you can ignore this failure\n", uri, exp_port); } ck_assert(fd != -1); socktype = get_socktype(fd); port = get_peer_port(fd); mm_close(fd); ck_assert_int_eq(socktype, exp_socktype); ck_assert_int_eq(port, exp_port); } END_TEST START_TEST(create_invalid_sockclient) { ck_assert(mm_create_sockclient(NULL) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); ck_assert(mm_create_sockclient("localhost") == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); ck_assert(mm_create_sockclient("dummy://localhost") == -1); ck_assert_int_eq(mm_get_lasterror_number(), MM_ENOTFOUND); ck_assert(mm_create_sockclient("ssh://localhost:10") == -1); ck_assert_int_eq(mm_get_lasterror_number(), ECONNREFUSED); ck_assert(mm_create_sockclient("tcp://localhost") == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ static void socket_test_teardown(void) { int flags = mm_error_set_flags(MM_ERROR_SET, MM_ERROR_IGNORE); clean_childproc(&child); clean_helper_test_data(); mm_error_set_flags(flags, MM_ERROR_IGNORE); } LOCAL_SYMBOL TCase* create_socket_tcase(void) { int num_cases; TCase *tc = tcase_create("socket"); tcase_add_checked_fixture(tc, NULL, socket_test_teardown); num_cases = MM_NELEM(test_cases); if (!strcmp(mm_getenv("MMLIB_DISABLE_IPV6_TESTS", "no"), "yes")) { fputs("Disable IPv6 socket tests\n", stderr); num_cases = FIRST_IPV6_TEST_CASE; } tcase_add_loop_test(tc, recv_on_localhost, 0, num_cases); tcase_add_loop_test(tc, send_on_localhost, 0, num_cases); tcase_add_loop_test(tc, read_on_localhost, 0, num_cases); tcase_add_loop_test(tc, write_on_localhost, 0, num_cases); tcase_add_loop_test(tc, recvmsg_on_localhost, 0, num_cases); tcase_add_loop_test(tc, sendmsg_on_localhost, 0, num_cases); tcase_add_loop_test(tc, recv_multimsg_on_localhost, 0, num_cases); tcase_add_loop_test(tc, send_multimsg_on_localhost, 0, num_cases); tcase_add_test(tc, test_poll_all_negative); tcase_add_test(tc, test_poll_simple); tcase_add_test(tc, test_poll_ignore_negative_socket); tcase_add_test(tc, test_getsockname); tcase_add_loop_test(tc, test_getpeername, 0, num_cases); tcase_add_test(tc, getaddrinfo_valid); tcase_add_test(tc, getaddrinfo_error); tcase_add_loop_test(tc, create_sockclient, 0, MM_NELEM(sockclient_cases)); tcase_add_test(tc, create_invalid_sockclient); return tc; } mmlib-1.4.2/tests/socket-testlib.c000066400000000000000000000110651435717460000170720ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "socket-testlib.h" #include "mmsysio.h" #include "mmerrno.h" #include "mmlog.h" #include static int full_mm_read(int fd, void* buf, size_t len) { char* cbuf = buf; ssize_t rsz; struct mm_error_state errstate; mm_save_errorstate(&errstate); while (len) { rsz = mm_read(fd, cbuf, len); if (rsz <= 0) { if (mm_get_lasterror_number() == EINTR) continue; return -1; } cbuf += rsz; len -= rsz; } mm_set_errorstate(&errstate); return 0; } static int full_mm_write(int fd, const void* buf, size_t len) { const char* cbuf = buf; ssize_t rsz; struct mm_error_state errstate; mm_save_errorstate(&errstate); while (len) { rsz = mm_write(fd, cbuf, len); if (rsz < 0) { if (mm_get_lasterror_number() == EINTR) continue; return -1; } cbuf += rsz; len -= rsz; } mm_set_errorstate(&errstate); return 0; } LOCAL_SYMBOL int create_server_socket(int domain, int type, int port) { int fd = -1, reuse = 1; char service[16]; struct addrinfo *res = NULL; struct addrinfo hints = {.ai_family = domain, .ai_socktype = type}; // Name resolution snprintf(service, sizeof(service), "%i", port); if (mm_getaddrinfo(NULL, service, &hints, &res)) goto error; // Create bound socket if ((fd = mm_socket(domain, type, 0)) < 0 || mm_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) || mm_bind(fd, res->ai_addr, res->ai_addrlen) ) goto error; mm_freeaddrinfo(res); return fd; error: fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); mm_freeaddrinfo(res); mm_close(fd); return -1; } LOCAL_SYMBOL int create_client_socket(int domain, int type, const char* host, int port) { int fd = -1; char service[16]; int timeout = 500; struct addrinfo *res; struct addrinfo hints = {.ai_family = domain, .ai_socktype = type}; // Name resolution snprintf(service, sizeof(service), "%i", port); if (mm_getaddrinfo(host, service, &hints, &res)) goto error; // Create connected socket if ((fd = mm_socket(domain, type, 0)) < 0 || mm_connect(fd, res->ai_addr, res->ai_addrlen) || mm_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) ) goto error; mm_freeaddrinfo(res); // If UDP send an empty datagram so that peer can get its address if (type == SOCK_DGRAM) { if (mm_send(fd, NULL, 0, 0) != 0) goto error; } return fd; error: fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); mm_freeaddrinfo(res); mm_close(fd); return -1; } LOCAL_SYMBOL int pipe_write_socketdata(int pipefd, const struct socket_data* data) { mm_check(data->len <= sizeof(data->buf)); if (full_mm_write(pipefd, &data->len, sizeof(data->len)) || full_mm_write(pipefd, data->buf, data->len) ) return -1; return 0; } LOCAL_SYMBOL int pipe_read_socketdata(int pipefd, struct socket_data* data) { if (full_mm_read(pipefd, &data->len, sizeof(data->len))) return -1; mm_check(data->len <= sizeof(data->buf)); if (full_mm_read(pipefd, data->buf, data->len)) return -1; return 0; } static int handle_read_data(int sockfd, int wr_pipe_fd, size_t len) { ssize_t rsz; struct socket_data data; mm_check(len <= sizeof(data.buf)); rsz = mm_recv(sockfd, data.buf, len, 0); if (rsz < 0) return -1; data.len = rsz; if (pipe_write_socketdata(wr_pipe_fd, &data)) return -1; return 0; } static int handle_write_data(int sockfd, int rd_pipe_fd) { ssize_t rsz; struct socket_data data; if (pipe_read_socketdata(rd_pipe_fd, &data) || (rsz = mm_send(sockfd, data.buf, data.len, 0)) < 0 ) return -1; if (rsz < (ssize_t)data.len) { mm_log_error("data sent too short"); return -1; } return 0; } LOCAL_SYMBOL void run_socket_client(int wr_fd, int rd_fd, int narg, char* argv[]) { int sockfd, ret; int domain, socktype, port; const char* host; struct testsocket_order order; ssize_t rsz; if (narg < 4) { fprintf(stderr, "not enough argument"); return; } domain = atoi(argv[0]); socktype = atoi(argv[1]); host = argv[2]; port = atoi(argv[3]); sockfd = create_client_socket(domain, socktype, host, port); if (sockfd == -1) return; ret = 0; while (ret == 0) { rsz = mm_read(rd_fd, &order, sizeof(order)); if (rsz < (ssize_t)sizeof(order)) break; switch (order.cmd) { case WRITE_SOCKET_DATA: ret = handle_write_data(sockfd, rd_fd); break; case READ_SOCKET_DATA: ret = handle_read_data(sockfd, wr_fd, order.opt_len); break; } } if (mm_get_lasterror_number() != 0) fprintf(stderr, "%s() failed: %s", __func__, mm_get_lasterror_desc()); } mmlib-1.4.2/tests/socket-testlib.h000066400000000000000000000011741435717460000170770ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef SOCKET_TESTLIB_H #define SOCKET_TESTLIB_H #include #define DGRAM_MAXSIZE 8192 struct socket_data { size_t len; char buf[DGRAM_MAXSIZE]; }; struct testsocket_order { enum {READ_SOCKET_DATA, WRITE_SOCKET_DATA} cmd; size_t opt_len; }; int create_server_socket(int domain, int type, int port); int create_client_socket(int domain, int type, const char* host, int port); int pipe_write_socketdata(int pipefd, const struct socket_data* data); int pipe_read_socketdata(int pipefd, struct socket_data* data); void run_socket_client(int wr_fd, int rd_fd, int narg, char* argv[]); #endif mmlib-1.4.2/tests/startup-win32-internals.c000066400000000000000000000032111435717460000205670ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "../src/startup-win32.c" #include #include "internals-testcases.h" struct cmdline_case { const char* cmdline; int argc; char* argv[16]; }; struct cmdline_case cmdline_cases[] = { {.cmdline = "prog", .argc = 1, .argv = {"prog"}}, {.cmdline = "\"prog space\"", .argc = 1, .argv = {"prog space"}}, {.cmdline = "prog \"arg\"", .argc = 2, .argv = {"prog", "arg"}}, {.cmdline = "prog \\\"arg\\\"", .argc = 2, .argv = {"prog", "\"arg\""}}, {.cmdline = "prog \\\\\"arg1 arg2\\\\\"", .argc = 2, .argv = {"prog", "\\arg1 arg2\\"}}, {.cmdline = "prog \\\"arg arg2\\\"", .argc = 3, .argv = {"prog", "\"arg", "arg2\""}}, {.cmdline = "prog arg1 arg2", .argc = 3, .argv = {"prog", "arg1", "arg2"}}, {.cmdline = "prog \"arg1 arg2\"remaining", .argc = 2, .argv = {"prog", "arg1 arg2remaining"}}, {.cmdline = "prog ", .argc = 1, .argv = {"prog"}}, {.cmdline = "prog arg", .argc = 2, .argv = {"prog", "arg"}}, {.cmdline = "prog arg arg2", .argc = 3, .argv = {"prog", "arg", "arg2"}}, }; START_TEST(split_cmdline) { int i, argc; const char* cmdline = cmdline_cases[_i].cmdline; char** ref_argv = cmdline_cases[_i].argv; int ref_argc = cmdline_cases[_i].argc; char** argv; argc = parse_cmdline(&argv, cmdline); ck_assert_int_eq(argc, ref_argc); for (i = 0; i < argc; i++) ck_assert_str_eq(argv[i], ref_argv[i]); free(argv); } END_TEST LOCAL_SYMBOL TCase* create_case_startup_win32_internals(void) { TCase *tc = tcase_create("startup-win32_internals"); tcase_add_loop_test(tc, split_cmdline, 0, MM_NELEM(cmdline_cases)); return tc; } mmlib-1.4.2/tests/testapi.c000066400000000000000000000027321435717460000156100ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include "mmlib.h" #include "mmsysio.h" #include "api-testcases.h" #define TEST_LOCK_REFEREE_SERVER_BIN TOP_BUILDDIR"/src/"LT_OBJDIR"/lock-referee.exe" static void flush_stdout(void) { fflush(stdout); } static Suite* api_suite(void) { Suite *s; int i; TCase* tc[] = { create_allocation_tcase(), create_time_tcase(), create_thread_tcase(), create_file_tcase(), create_socket_tcase(), create_ipc_tcase(), create_dir_tcase(), create_shm_tcase(), create_dlfcn_tcase(), create_process_tcase(), create_argparse_tcase(), create_utils_tcase(), create_advanced_file_tcase(), }; s = suite_create("API"); for (i = 0; i < MM_NELEM(tc); i++) { tcase_add_checked_fixture(tc[i], flush_stdout, flush_stdout); suite_add_tcase(s, tc[i]); } return s; } int main(void) { Suite* suite; SRunner* runner; int exitcode = EXIT_SUCCESS; mm_chdir(BUILDDIR); #if defined(_WIN32) mm_setenv("MMLIB_LOCKREF_BIN", TEST_LOCK_REFEREE_SERVER_BIN, MM_ENV_OVERWRITE); mm_setenv("PATH", TOP_BUILDDIR"/src/"LT_OBJDIR, MM_ENV_PREPEND); #endif suite = api_suite(); runner = srunner_create(suite); #ifdef CHECK_SUPPORT_TAP srunner_set_tap(runner, "-"); #endif srunner_run_all(runner, CK_ENV); #ifndef CHECK_SUPPORT_TAP if (srunner_ntests_failed(runner) != 0) exitcode = EXIT_FAILURE; #endif srunner_free(runner); return exitcode; } mmlib-1.4.2/tests/testerrno.c000066400000000000000000000037631435717460000161710ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #define print_errno_info(errnum) \ printf("%s (%i) : %s\n", #errnum , errnum, mm_strerror(errnum)) struct mm_error_state state_in_thread3; static int bar(const char* name) { if (!strcmp(name, "thread3")) return mm_raise_error_with_extid(MM_ENOTFOUND, "mmscam-calib-out", "Calibration of %s is outdated", name); return 0; } static int foo(const char* name) { if (!strcmp(name, "thread2")) return mm_raise_error(EINVAL, "Wrong param: bad luck"); if (bar(name)) { mm_log_error("something wrong in callee of foo() in %s", name); return -1; } return 0; } static void* thread_func(void* data) { const char* thread_name = data; foo(thread_name); mm_print_lasterror("error state in %s", thread_name); if (!strcmp(thread_name, "thread3")) mm_save_errorstate(&state_in_thread3); return NULL; } int main(void) { int rv; char buffer[512]; setlocale(LC_ALL, ""); print_errno_info(MM_EDISCONNECTED); print_errno_info(MM_EBADFMT); print_errno_info(MM_ENOTFOUND); print_errno_info(MM_ENONAME); mm_thread_t t1, t2, t3, t4; mm_thr_create(&t1, thread_func, "thread1"); mm_thr_create(&t2, thread_func, "thread2"); mm_thr_create(&t3, thread_func, "thread3"); mm_thr_create(&t4, thread_func, "thread4"); mm_thr_join(t1, NULL); mm_thr_join(t2, NULL); mm_thr_join(t3, NULL); mm_thr_join(t4, NULL); mm_set_errorstate(&state_in_thread3); printf("\nretrieve thread3 error state in main:\n * errnum=%i\n extid=%s * in %s at %s\n * %s\n", mm_get_lasterror_number(), mm_get_lasterror_module(), mm_get_lasterror_extid(), mm_get_lasterror_location(), mm_get_lasterror_desc()); rv = mm_strerror_r(mm_get_lasterror_number(), buffer, sizeof(buffer)); printf("\n%s to get lase error msg from number: %s\n", rv == 0 ? "Succeeded" : "Failed", buffer); return EXIT_SUCCESS; } mmlib-1.4.2/tests/testinternals.c000066400000000000000000000014111435717460000170270ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include "internals-testcases.h" static Suite* internal_suite(void) { Suite *s = suite_create("Internals"); suite_add_tcase(s, create_case_log_internals()); #ifdef _WIN32 suite_add_tcase(s, create_case_startup_win32_internals()); #endif return s; } int main(void) { Suite* suite; SRunner* runner; int exitcode = EXIT_SUCCESS; suite = internal_suite(); runner = srunner_create(suite); #ifdef CHECK_SUPPORT_TAP srunner_set_tap(runner, "-"); #endif srunner_run_all(runner, CK_ENV); #ifndef CHECK_SUPPORT_TAP if (srunner_ntests_failed(runner) != 0) exitcode = EXIT_FAILURE; #endif srunner_free(runner); return exitcode; } mmlib-1.4.2/tests/testlog.c000066400000000000000000000032061435717460000156150ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include static void logged_func(void) { mm_log_warn("logged_func"); } #undef MM_LOG_MODULE_NAME #define MM_LOG_MODULE_NAME "testlog" // Stolen from here: // https://stackoverflow.com/questions/8934879/how-to-handle-sigabrt-signal jmp_buf env; static void on_sigabrt (int signum) { (void)signum; longjmp (env, 1); } // Returns 1 if the function does not abort static int try_and_catch_abort (void (*func)(void)) { if (setjmp (env) == 0) { signal(SIGABRT, &on_sigabrt); (*func)(); return 1; } else { return 0; } } static void provoke_a_crash(void) { mm_crash("We should crash"); } static void do_not_provoke_a_crash(void) { mm_log_info("This should not crash"); } static int test_crash(void) { return !try_and_catch_abort(&provoke_a_crash) && try_and_catch_abort(&do_not_provoke_a_crash); } static void provoke_a_check_failure(void) { mm_check(119 == 7*16); } static void do_not_provoke_a_check_failure(void) { mm_check(119 == 7*17); } static int test_check(void) { return !try_and_catch_abort(&provoke_a_check_failure) && try_and_catch_abort(&do_not_provoke_a_check_failure); } static int test_basic_logging(void) { int i; mm_log_info("Starting logging..."); for (i = -5; i<5; i++) { logged_func(); if (i != 0) mm_log_warn("Everything is fine (%i)", i); else mm_log_error("Null value (i == %i)", i); } mm_log_info("Stop logging."); return 1; } int main(void) { return (test_basic_logging() && test_crash() && test_check())? EXIT_SUCCESS : EXIT_FAILURE; } mmlib-1.4.2/tests/testprofile.c000066400000000000000000000033361435717460000165000ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "mmprofile.h" #include #include #include #define OUTFD 1 //STDOUT_FILENO static void print_profile(void) { int i, j; volatile int x; for (i = 0; i < 100; i++) { x = 2; mm_tic(); x /= 2; if (x == 1) x /= 10; mm_toc(); for (j = 0; j < 10; j++) x *= 2; mm_toc(); x = 2; for (j = 0; j < 50; j++) x *= 2; mm_toc(); } mm_profile_print(PROF_MEAN|PROF_MIN|PROF_MAX, OUTFD); mm_profile_print(PROF_DEFAULT|PROF_FORCE_NSEC, OUTFD); mm_profile_print(PROF_DEFAULT|PROF_FORCE_USEC, OUTFD); mm_profile_print(PROF_DEFAULT|PROF_FORCE_MSEC, OUTFD); mm_profile_print(PROF_DEFAULT|PROF_FORCE_SEC, OUTFD); } static void print_profile_labelled(void) { int i, j; volatile int x; for (i = 0; i < 100; i++) { x = 2; mm_tic(); x /= 2; if (x == 1) x /= 10; mm_toc_label("First step"); for (j = 0; j < 10; j++) x *= 2; mm_toc_label("Second step"); x = 2; for (j = 0; j < 50; j++) x *= 2; mm_toc_label("Last step"); } mm_profile_print(PROF_MEAN|PROF_MIN|PROF_MAX, OUTFD); } int main(void) { printf("Timing with default settings\n"); fflush(stdout); print_profile(); printf("\nTiming with wall clock timer\n"); fflush(stdout); mm_profile_reset(0); print_profile(); printf("\nTiming with CPU based timer\n"); fflush(stdout); mm_profile_reset(PROF_RESET_CPUCLOCK); print_profile(); printf("\nLabelled mm_toc() 1st\n"); fflush(stdout); mm_profile_reset(PROF_RESET_CPUCLOCK); print_profile_labelled(); printf("\nLabelled mm_toc() label kept\n"); fflush(stdout); mm_profile_reset(PROF_RESET_CPUCLOCK|PROF_RESET_KEEPLABEL); print_profile_labelled(); return EXIT_SUCCESS; } mmlib-1.4.2/tests/tests-child-proc.c000066400000000000000000000032531435717460000173220ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H #include #endif #include #include #include #include "mmerrno.h" #include "mmlog.h" #include "mmsysio.h" #include "socket-testlib.h" #include "tests-child-proc.h" #include "threaddata-manipulation.h" #define PASSED_FD 3 int main(int argc, char* argv[]) { void* map = NULL; int fd; unsigned int mapsz; int exitcode; void *hndl = NULL; union { void* ptr; intptr_t (*fn)(void*); } symbol; // If 3 argument looks like "mapfile-%i-%u", it means that the caller // want us to map the file and size as specified in the argument if (argc >= 3 && sscanf(argv[2], "mapfile-%i-%u", &fd, &mapsz) == 2) { map = mm_mapfile(fd, 0, mapsz, MM_MAP_RDWR|MM_MAP_SHARED); if (!map) { mm_print_lasterror("mm_mapfile(%i, %u) failed", fd, mapsz); return EXIT_FAILURE; } } exitcode = EXIT_SUCCESS; if (argc < 2) { fprintf(stderr, "Missing argument\n"); exitcode = EXIT_FAILURE; goto exit; } else if (!strcmp(argv[1], "run_socket_client")) { run_socket_client(WR_PIPE_FD, RD_PIPE_FD, argc-2, argv+2); } else { exitcode = EXIT_FAILURE; hndl = mm_dlopen(NULL, MM_LD_LAZY); if (hndl == NULL) goto exit; /* Use union to allow cast between func pointer and void* */ symbol.ptr = mm_dlsym(hndl, argv[1]); if (symbol.ptr == NULL) { fprintf(stderr, "Unknown arg: %s\n", argv[1]); exitcode = EXIT_FAILURE; goto exit; } fprintf(stderr, "Running: %s\n", argv[1]); /* function should return NULL on success */ exitcode = symbol.fn(map); mm_log_debug("%s exited: %d", argv[1], exitcode); } exit: mm_dlclose(hndl); mm_unmap(map); return exitcode; } mmlib-1.4.2/tests/tests-child-proc.h000066400000000000000000000021471435717460000173300ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef TESTS_CHILD_PROC_H #define TESTS_CHILD_PROC_H #include #include // workaround for libtool on windows: we need to execute directly the // binary (the folder of mmlib dll is added at startup of testapi). On // other platform we must use the normal wrapper script located in BUILDDIR #if defined(_WIN32) # define TESTS_CHILD_BIN BUILDDIR "/"LT_OBJDIR "/tests-child-proc.exe" #else # define TESTS_CHILD_BIN BUILDDIR "/tests-child-proc" #endif #define WR_PIPE_FD 3 #define RD_PIPE_FD 4 typedef union { mm_thread_t thread_id; mm_pid_t proc_id; } thread_proc_id; enum { RUN_AS_THREAD, RUN_AS_PROCESS, }; #define run_function(id, f, args, mode)\ _run_function((id), (f), #f, (args), sizeof(*args), (mode)) int _run_function(thread_proc_id * id, intptr_t (*fn)(void*), char * fn_name, void * args, size_t argslen, int run_mode); int run_as_process(mm_pid_t* pid_ptr, char * fn_name, void* args, size_t argslen, int last_fd_kept); void clean_function(thread_proc_id id, int run_mode); #endif /* ifndef TESTS_CHILD_PROC_H */ mmlib-1.4.2/tests/tests-run-func.c000066400000000000000000000066611435717460000170410ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H #include #endif #include "tests-child-proc.h" #include #include #include #include #include #define FDMAP_MAXLEN 16 LOCAL_SYMBOL int run_as_process(mm_pid_t* pid_ptr, char * fn_name, void* args, size_t argslen, int last_fd_kept) { struct mm_remap_fd fdmap[FDMAP_MAXLEN]; int rv, i, shm_fd, fdmap_len; char mapfile_arg_str[64]; char* argv[] = {TESTS_CHILD_BIN, fn_name, mapfile_arg_str, NULL}; void* map; shm_fd = mm_anon_shm(); ck_assert(shm_fd != -1); mm_ftruncate(shm_fd, MM_PAGESZ); map = mm_mapfile(shm_fd, 0, MM_PAGESZ, MM_MAP_RDWR|MM_MAP_SHARED); ck_assert(map != NULL); memcpy(map, args, argslen); // Configure fdmap to keep all fd in child up to last_fd_kept for (i = 0; i <= last_fd_kept; i++) { fdmap[i].child_fd = i; fdmap[i].parent_fd = i; } // Ensure shm_fd is inherited in child fdmap_len = last_fd_kept+2; sprintf(mapfile_arg_str, "mapfile-%i-4096", last_fd_kept+1); fdmap[last_fd_kept+1].child_fd = last_fd_kept+1; fdmap[last_fd_kept+1].parent_fd = shm_fd; // Start process rv = mm_spawn(pid_ptr, argv[0], fdmap_len, fdmap, 0, argv, NULL); mm_unmap(map); mm_close(shm_fd); return rv; } /* * _run_function(): takes a function and run it as a thread or a process. * @id: id of the thread or the process returned * @fn: the function to run * @fn_name: the function name as a string. Filled by the run_function macro * @args: arguments that will be passed to the function * @argslen: length of the arguments. Filled by the run_function macro * @run_mode: can be RUN_AS_THREAD, or RUN_AS_PROCESS * * This should not be called directly, use the run_function macro below instead. * * See mm_thr_create() and mm_spawn(). * * the function ran should follow the prototype: * intptr_t custom_fn(void *) * The function is expected to return zero on success. * * Returns: 0 on success, aborts otherwise */ LOCAL_SYMBOL int _run_function(thread_proc_id * id, intptr_t (*fn)(void*), char * fn_name, void * args, size_t argslen, int run_mode) { union { intptr_t (*proc)(void*); void* (*thread)(void*); } cast_fn; int rv; // Use union to cast function type (no compiler should warn this) cast_fn.proc = fn; switch (run_mode) { case RUN_AS_THREAD: rv = mm_thr_create(&id->thread_id, cast_fn.thread, args); ck_assert_msg(rv == 0, "can't create thread for %s", fn_name); break; case RUN_AS_PROCESS: rv = run_as_process(&id->proc_id, fn_name, args, argslen, 2); ck_assert_msg(rv == 0, "can't create process for %s", fn_name); break; default: ck_abort_msg("invalid mode (%i) with %s", run_mode, fn_name); } return 0; } /** * clean_function(): clean a thread or a process that has been launched using run_function() * @id: the thread or process id as filled by run_function() * @run_mode: can be RUN_AS_THREAD, or RUN_AS_PROCESS */ LOCAL_SYMBOL void clean_function(thread_proc_id id, int run_mode) { intptr_t iptrval; int ival; switch (run_mode) { case RUN_AS_THREAD: mm_thr_join(id.thread_id, (void**)&iptrval); if (iptrval != 0) { ck_abort_msg("thread returned %i", (int)iptrval); } break; case RUN_AS_PROCESS: if (mm_wait_process(id.proc_id, &ival) != 0 || (ival ^ MM_WSTATUS_EXITED) != 0 ) { ck_abort_msg("process returned %i\n", ival); } break; default: ck_assert_msg(0, "invalid mode %i", run_mode); break; } } mmlib-1.4.2/tests/thread-api-tests.c000066400000000000000000000340241435717460000173140ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include "api-testcases.h" #include "mmpredefs.h" #include "mmsysio.h" #include "mmthread.h" #include "mmtime.h" #include "tests-child-proc.h" #include "threaddata-manipulation.h" #define EXPECTED_VALUE (int)0xdeadbeef #define NUM_CONCURRENCY 12 static int mutex_type_flags[] = { 0, MM_THR_PSHARED, }; #define NUM_MUTEX_TYPE MM_NELEM(mutex_type_flags) #define FIRST_PSHARED_MUTEX_TYPE 1 static void* simple_write_proc(void* arg) { int* pval = arg; *pval = EXPECTED_VALUE; return NULL; } /* * Test that a write in another thread affect shared data */ START_TEST(data_write_in_thread) { int value = 0; mm_thread_t thid; ck_assert(mm_thr_create(&thid, simple_write_proc, &value) == 0); mm_thr_join(thid, NULL); ck_assert(value == EXPECTED_VALUE); } END_TEST /************************************************************************** * * * mutex tests * * * **************************************************************************/ static void* protected_write_threadproc(void* arg) { run_write_shared_data(arg); return NULL; } static void init_shared_write_data(struct shared_write_data* shdata, int mutex_flags, int num_runner, int num_iter, bool do_sleep) { shdata->sleep_in_touch = do_sleep; shdata->num_iteration = num_iter; shdata->value = SHARED_WRITE_INIT_VALUE; shdata->failed = false; shdata->num_runner_remaining = num_runner; ck_assert(mm_thr_mutex_init(&shdata->mutex, mutex_flags) == 0); } /** * runtest_mutex_protection_on_write() - test a concurrent write on shared data * @mutex_flags: flags to use for mutex in the test * @num_iter: number of times each runner has to touch the shared data * @do_sleep: true if runner has to sleep between setting shared data and restore * * This function initialized the shared data and mutex and spawn the thread * that will concurrently modify the shared data (protected by mutex). If * the mutex protection fails, one (or several) thread will see inconsistent * state in shared data and will report failure in shdata->failed. */ static void runtest_mutex_protection_on_write(int mutex_flags, int num_iter, bool do_sleep) { int i, num_thread; mm_thread_t thid[NUM_CONCURRENCY]; struct shared_write_data shdata; init_shared_write_data(&shdata, mutex_flags, NUM_CONCURRENCY, num_iter, do_sleep); // Spawn all thread for the test num_thread = 0; for (i = 0; i < NUM_CONCURRENCY; i++) { if (mm_thr_create(&thid[i], protected_write_threadproc, &shdata) != 0) break; num_thread++; } // Join only threads that have been created for (i = 0; i < num_thread; i++) mm_thr_join(thid[i], NULL); ck_assert(num_thread == NUM_CONCURRENCY); ck_assert(shdata.num_runner_remaining == 0); ck_assert(shdata.failed == false); } /** * runtest_mutex_on_pshared_write() - test a concurrent write on shared data * @shdata: structure holding test setup and shared data * @mutex_flags: flags to use for mutex in the test * * This function initialized the shared data and mutex and spawn the thread * that will concurrently modify the shared data (protected by mutex). If * the mutex protection fails, one (or several) thread will see inconsistent * state in shared data and will report failure in shdata->failed. */ static void runtest_mutex_on_pshared_write(int mutex_flags, bool sleep_in_touch, int num_iter) { int i, num_proc = 0; int shm_fd, num_runner_remaining = 0; mm_pid_t pid[NUM_CONCURRENCY]; struct shared_write_data* shdata; void* map = NULL; bool failed = true; struct mm_remap_fd fdmap; char* argv[] = {TESTS_CHILD_BIN, "run_write_shared_data", "mapfile-3-4096", NULL}; if ((shm_fd = mm_anon_shm()) == -1 || mm_ftruncate(shm_fd, MM_PAGESZ) || !(map = mm_mapfile(shm_fd, 0, MM_PAGESZ, MM_MAP_RDWR|MM_MAP_SHARED)) ) { goto exit; } shdata = map; init_shared_write_data(shdata, mutex_flags, NUM_CONCURRENCY, num_iter, sleep_in_touch); // Spawn all children for the test fdmap.child_fd = 3; fdmap.parent_fd = shm_fd; num_proc = 0; for (i = 0; i < NUM_CONCURRENCY; i++) { if (mm_spawn(&pid[i], argv[0], 1, &fdmap, 0, argv, NULL)) break; num_proc++; } // Wait that all children finish for (i = 0; i < num_proc; i++) mm_wait_process(pid[i], NULL); failed = shdata->failed; num_runner_remaining = shdata->num_runner_remaining; exit: mm_unmap(map); if (shm_fd != -1) mm_close(shm_fd); ck_assert(num_proc == NUM_CONCURRENCY); ck_assert(num_runner_remaining == 0); ck_assert(failed == false); } /* * perform concurrent modification on a shared data protected by mutex. The * access protected by the mutex should restore the value as it at the * beginning if the mutex protection works. */ START_TEST(mutex_protection_on_write_normal) { runtest_mutex_protection_on_write(mutex_type_flags[_i], 100000, false); } END_TEST /* * Same test as mutex_protection_on_write_normal but introduce a sleep while * a thread is holding the lock and is modifying shared data. This increases * further the probability of contended lock situation. */ START_TEST(mutex_protection_on_write_sleep) { runtest_mutex_protection_on_write(mutex_type_flags[_i], 10, true); } END_TEST START_TEST(mutex_protection_on_pshared_write_normal) { runtest_mutex_on_pshared_write(mutex_type_flags[_i], false, 100000); } END_TEST START_TEST(mutex_protection_on_pshared_write_sleep) { runtest_mutex_on_pshared_write(mutex_type_flags[_i], true, 10); } END_TEST static const int robust_sleep_on_lock_cases[] = {false, true}; #define NUM_ROBUST_CASES MM_NELEM(robust_sleep_on_lock_cases) #define EXPECTED_CRASH_ITER (NUM_CONCURRENCY/2) START_TEST(robust_mutex) { int i, num_proc = 0; int num_iter = 0, num_iter_finished = 0, detected_crash_iter = 0; int shm_fd; mm_pid_t pid[NUM_CONCURRENCY]; struct robust_mutex_write_data* rdata; void* map = NULL; struct mm_remap_fd fdmap; char* argv[] = {TESTS_CHILD_BIN, "run_robust_mutex_write_data", "mapfile-3-4096", NULL}; if ((shm_fd = mm_anon_shm()) == -1 || mm_ftruncate(shm_fd, MM_PAGESZ) || !(map = mm_mapfile(shm_fd, 0, MM_PAGESZ, MM_MAP_RDWR|MM_MAP_SHARED)) ) { goto exit; } rdata = map; rdata->iter = 0; rdata->iter_finished = 0; rdata->sleep_after_first_lock = robust_sleep_on_lock_cases[_i]; rdata->crash_at_iter = EXPECTED_CRASH_ITER; rdata->detected_iter_after_crash = -1; ck_assert(mm_thr_mutex_init(&rdata->mutex, MM_THR_PSHARED) == 0); // Spawn all children for the test fdmap.child_fd = 3; fdmap.parent_fd = shm_fd; num_proc = 0; for (i = 0; i < NUM_CONCURRENCY; i++) { if (mm_spawn(&pid[i], argv[0], 1, &fdmap, 0, argv, NULL)) break; num_proc++; } // Wait that all children finish for (i = 0; i < num_proc; i++) mm_wait_process(pid[i], NULL); num_iter = rdata->iter; num_iter_finished = rdata->iter_finished; detected_crash_iter = rdata->detected_iter_after_crash; exit: mm_unmap(map); if (shm_fd != -1) mm_close(shm_fd); ck_assert(num_proc == NUM_CONCURRENCY); ck_assert(num_iter == NUM_CONCURRENCY); ck_assert(num_iter_finished == NUM_CONCURRENCY-1); ck_assert(detected_crash_iter == EXPECTED_CRASH_ITER); } END_TEST /************************************************************************** * * * condition variable tests * * * **************************************************************************/ static void* notif_var_threadproc(void* arg) { run_notif_data(arg); return NULL; } static void init_notif_data(struct notif_data* ndata, int mutex_flags) { memset(ndata, 0, sizeof(*ndata)); mm_thr_mutex_init(&ndata->mutex, mutex_flags); mm_thr_cond_init(&ndata->cv1, mutex_flags); mm_thr_cond_init(&ndata->cv2, mutex_flags); } static void deinit_notif_data(struct notif_data* ndata) { mm_thr_mutex_deinit(&ndata->mutex); mm_thr_cond_deinit(&ndata->cv1); mm_thr_cond_deinit(&ndata->cv2); } static void do_notif_and_inspect(struct notif_data* ndata, bool do_broadcast, int num_runner) { mm_thr_mutex_lock(&ndata->mutex); // Wait for all started thread be ready while (ndata->nwaiter < num_runner) mm_thr_cond_wait(&ndata->cv1, &ndata->mutex); if (!do_broadcast) { // Notify one thread to wakeup and give it time respond ndata->todo = true; mm_thr_cond_signal(&ndata->cv2); mm_thr_mutex_unlock(&ndata->mutex); mm_relative_sleep_ms(500); mm_thr_mutex_lock(&ndata->mutex); } // Copy done value for later inspection and notify all threads to // quit ndata->quit = true; mm_thr_cond_broadcast(&ndata->cv2); mm_thr_mutex_unlock(&ndata->mutex); } /** * runtest_signal_or_broadcast_thread() - test thread notification * @mutex_flags: flags to use for mutex in the test * @do_broadcast: if true, use broadcast instead of signal */ static void runtest_signal_or_broadcast_thread(int mutex_flags, bool do_broadcast) { int i, num_thread; mm_thread_t thid[NUM_CONCURRENCY]; struct notif_data ndata; init_notif_data(&ndata, mutex_flags); // Spawn all thread for the test num_thread = 0; for (i = 0; i < NUM_CONCURRENCY; i++) { if (mm_thr_create(&thid[i], notif_var_threadproc, &ndata) != 0) break; num_thread++; } do_notif_and_inspect(&ndata, do_broadcast, num_thread); // Join only threads that have been created for (i = 0; i < num_thread; i++) mm_thr_join(thid[i], NULL); // Verify expected outcome if (do_broadcast) ck_assert(ndata.numquit == NUM_CONCURRENCY); else ck_assert(ndata.done >= 1); ck_assert(num_thread == NUM_CONCURRENCY); deinit_notif_data(&ndata); } /** * runtest_signal_or_broadcast_thread() - test thread notification * @mutex_flags: flags to use for mutex in the test * @do_broadcast: if true, use broadcast instead of signal */ static void runtest_signal_or_broadcast_process(int mutex_flags, bool do_broadcast) { int i, num_proc = 0, value_done = 0, value_numquit = 0; struct notif_data* ndata; mm_pid_t pid[NUM_CONCURRENCY]; int shm_fd; void* map = NULL; struct mm_remap_fd fdmap; char* argv[] = {TESTS_CHILD_BIN, "run_notif_data", "mapfile-3-4096", NULL}; if ((shm_fd = mm_anon_shm()) == -1 || mm_ftruncate(shm_fd, MM_PAGESZ) || !(map = mm_mapfile(shm_fd, 0, MM_PAGESZ, MM_MAP_RDWR|MM_MAP_SHARED)) ) { goto exit; } ndata = map; init_notif_data(ndata, mutex_flags); // Spawn all children for the test fdmap.child_fd = 3; fdmap.parent_fd = shm_fd; num_proc = 0; for (i = 0; i < NUM_CONCURRENCY; i++) { if (mm_spawn(&pid[i], argv[0], 1, &fdmap, 0, argv, NULL)) break; num_proc++; } do_notif_and_inspect(ndata, do_broadcast, num_proc); // Wait that all children finish for (i = 0; i < num_proc; i++) mm_wait_process(pid[i], NULL); value_numquit = ndata->numquit; value_done = ndata->done; deinit_notif_data(ndata); exit: mm_unmap(map); if (shm_fd != -1) mm_close(shm_fd); // Verify expected outcome if (do_broadcast) ck_assert(value_numquit == NUM_CONCURRENCY); else ck_assert(value_done >= 1); ck_assert(num_proc == NUM_CONCURRENCY); } START_TEST(signal_thread_data) { runtest_signal_or_broadcast_thread(mutex_type_flags[_i], false); } END_TEST START_TEST(broadcast_thread_data) { runtest_signal_or_broadcast_thread(mutex_type_flags[_i], true); } END_TEST START_TEST(signal_pshared_data) { runtest_signal_or_broadcast_process(mutex_type_flags[_i], false); } END_TEST START_TEST(broadcast_pshared_data) { runtest_signal_or_broadcast_process(mutex_type_flags[_i], true); } END_TEST /************************************************************************** * * * one-time init tests * * * **************************************************************************/ static atomic_int once_val1 = 0; static atomic_int once_val2 = 0; static mm_thr_once_t once = MM_THR_ONCE_INIT; static void one_time_proc(void) { int readval; readval = atomic_fetch_add(&once_val1, 1); mm_relative_sleep_ms(1); atomic_fetch_add(&once_val2, readval+1); } static void* once_test_proc(void* data) { (void)data; mm_thr_once(&once, one_time_proc); return NULL; } START_TEST(concurrent_once) { mm_thread_t thid[NUM_CONCURRENCY]; int i; for (i = 0; i < MM_NELEM(thid); i++) ck_assert(mm_thr_create(&thid[i], once_test_proc, NULL) == 0); for (i = 0; i < MM_NELEM(thid); i++) mm_thr_join(thid[i], NULL); ck_assert_int_eq(once_val1, 1); ck_assert_int_eq(once_val2, 1); } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_thread_tcase(void) { TCase *tc = tcase_create("thread"); tcase_add_test(tc, data_write_in_thread); tcase_add_loop_test(tc, mutex_protection_on_write_normal, 0, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, mutex_protection_on_write_sleep, 0, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, mutex_protection_on_pshared_write_normal, FIRST_PSHARED_MUTEX_TYPE, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, mutex_protection_on_pshared_write_sleep, FIRST_PSHARED_MUTEX_TYPE, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, robust_mutex, 0, NUM_ROBUST_CASES); tcase_add_loop_test(tc, signal_thread_data, 0, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, broadcast_thread_data, 0, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, signal_pshared_data, FIRST_PSHARED_MUTEX_TYPE, NUM_MUTEX_TYPE); tcase_add_loop_test(tc, broadcast_pshared_data, FIRST_PSHARED_MUTEX_TYPE, NUM_MUTEX_TYPE); tcase_add_test(tc, concurrent_once); return tc; } mmlib-1.4.2/tests/threaddata-manipulation.c000066400000000000000000000061341435717460000207360ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include "threaddata-manipulation.h" #include "mmpredefs.h" #include "mmtime.h" #include #include #include #include #ifdef _MSC_VER # include # define _Atomic # define atomic_store_i64(obj, val) _InterlockedExchange64(obj, val) # define atomic_fetch_sub_i64(obj, val) _InterlockedExchangeAdd64(obj, -val) #else # include # define atomic_store_i64(obj, val) atomic_store(obj, val) # define atomic_fetch_sub_i64(obj, val) atomic_fetch_sub(obj, val) #endif /** * touch_data() - modify data and restore it * @data: address of shared data to modify * @tid: data identifying uniquely the calling thread * @do_sleep: if true, sleep between data modification and data value restoration * * Return: true if no inconsistency has been detected, false otherwise */ static bool touch_data(_Atomic int64_t* data, int64_t tid, bool do_sleep) { int64_t prev; atomic_store_i64(data, tid); if (do_sleep) mm_relative_sleep_ms(1); prev = atomic_fetch_sub_i64(data, tid); return (prev == tid) ? true : false; } /** * run_write_shared_data() - run thread function for concurrent write test * @shdata: structure holding test setup and shared data * * In each iteration, lock the mutex, touch the shared data (modify and * restore its value), and unlock the mutex. If an inconsistent state of the * shared value is detect while touch the data, it means the mutex failed to * protect and the test fails (reported in @shdata->failed). */ API_EXPORTED intptr_t run_write_shared_data(struct shared_write_data* shdata) { int i; int num_iter = shdata->num_iteration; _Atomic int64_t* data = &shdata->value; int64_t tid = (int64_t)mm_thr_self(); bool match, do_sleep; do_sleep = shdata->sleep_in_touch; for (i = 0; i < num_iter; i++) { mm_thr_mutex_lock(&shdata->mutex); match = touch_data(data, tid, do_sleep); mm_thr_mutex_unlock(&shdata->mutex); if (!match) { shdata->failed = true; break; } } atomic_fetch_sub_i64(&shdata->num_runner_remaining, 1); return 0; } API_EXPORTED intptr_t run_notif_data(struct notif_data* ndata) { mm_thr_mutex_lock(&ndata->mutex); // Notify that the runner is ready ndata->nwaiter += 1; mm_thr_cond_signal(&ndata->cv1); // Wait for being signal or asked to exit while (!ndata->todo && !ndata->quit) mm_thr_cond_wait(&ndata->cv2, &ndata->mutex); if (!ndata->quit) ndata->done += 1; ndata->numquit += 1; mm_thr_mutex_unlock(&ndata->mutex); return 0; } API_EXPORTED intptr_t run_robust_mutex_write_data(struct robust_mutex_write_data* rdata) { int r, iter; mm_thr_mutex_t* mtx = &rdata->mutex; r = mm_thr_mutex_lock(mtx); if (r == EOWNERDEAD) { rdata->detected_iter_after_crash = rdata->iter_finished; mm_thr_mutex_consistent(mtx); } else if (r != 0) { return -1; } iter = rdata->iter++; if (iter == 0 && rdata->sleep_after_first_lock) mm_relative_sleep_ms(50); if (iter == rdata->crash_at_iter) { abort(); } rdata->iter_finished++; mm_thr_mutex_unlock(mtx); return 0; } mmlib-1.4.2/tests/threaddata-manipulation.h000066400000000000000000000016211435717460000207370ustar00rootroot00000000000000/* @mindmaze_header@ */ #ifndef THREADDATA_MANIPULATION_H #define THREADDATA_MANIPULATION_H #include #include #include #include "mmthread.h" #define SHARED_WRITE_INIT_VALUE 0 struct shared_write_data { _Atomic int64_t value; bool failed; _Atomic int num_runner_remaining; int num_iteration; bool sleep_in_touch; mm_thr_mutex_t mutex; }; struct robust_mutex_write_data { int iter; int iter_finished; bool sleep_after_first_lock; int crash_at_iter; int detected_iter_after_crash; mm_thr_mutex_t mutex; }; struct notif_data { bool todo; int done; bool quit; int nwaiter; int numquit; mm_thr_mutex_t mutex; mm_thr_cond_t cv1; mm_thr_cond_t cv2; }; intptr_t run_write_shared_data(struct shared_write_data* shdata); intptr_t run_notif_data(struct notif_data* ndata); intptr_t run_robust_mutex_write_data(struct robust_mutex_write_data* rdata); #endif mmlib-1.4.2/tests/time-api-tests.c000066400000000000000000000237351435717460000170120ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include "api-testcases.h" #include "mmerrno.h" #include "mmlib.h" #include "mmpredefs.h" #include "mmtime.h" /************************************************************************** * * * timespec manipulation tests * * * **************************************************************************/ struct mm_timespec_case { struct mm_timespec t1; struct mm_timespec t2; int64_t ns, us, ms; }; static const struct mm_timespec_case ts_cases[] = { { .t1 = {.tv_sec = 1, .tv_nsec = 0}, .t2 = {.tv_sec = 0, .tv_nsec = 0}, .ns = 1000000000, .us = 1000000, .ms = 1000, }, { .t1 = {.tv_sec = 1, .tv_nsec = 999999999}, .t2 = {.tv_sec = 0, .tv_nsec = 999999999}, .ns = 1000000000, .us = 1000000, .ms = 1000, }, { .t1 = {.tv_sec = 42, .tv_nsec = 500000000}, .t2 = {.tv_sec = 42, .tv_nsec = 500000000}, .ns = 0, .us = 0, .ms = 0, }, { .t1 = {.tv_sec = 100, .tv_nsec = 499999000}, .t2 = {.tv_sec = 42, .tv_nsec = 500000000}, .ns = 57999999000, .us = 57999999, .ms = 58000, }, }; /* * */ START_TEST(diff_time_ns) { struct mm_timespec t1 = ts_cases[_i].t1; struct mm_timespec t2 = ts_cases[_i].t2; int64_t diff = ts_cases[_i].ns; ck_assert_int_eq(mm_timediff_ns(&t1, &t2), diff); ck_assert_int_eq(mm_timediff_ns(&t2, &t1), -diff); } END_TEST START_TEST(diff_time_us) { struct mm_timespec t1 = ts_cases[_i].t1; struct mm_timespec t2 = ts_cases[_i].t2; int64_t diff = ts_cases[_i].us; ck_assert_int_eq(mm_timediff_us(&t1, &t2), diff); ck_assert_int_eq(mm_timediff_us(&t2, &t1), -diff); } END_TEST START_TEST(diff_time_ms) { struct mm_timespec t1 = ts_cases[_i].t1; struct mm_timespec t2 = ts_cases[_i].t2; int64_t diff = ts_cases[_i].ms; ck_assert_int_eq(mm_timediff_ms(&t1, &t2), diff); ck_assert_int_eq(mm_timediff_ms(&t2, &t1), -diff); } END_TEST START_TEST(add_time_ns) { struct mm_timespec t1; struct mm_timespec t2; int64_t diff = ts_cases[_i].ns; t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_ns(&t2, diff); ck_assert(mm_timediff_ns(&t2, &t1) == 0); t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_ns(&t1, -diff); ck_assert(mm_timediff_ns(&t1, &t2) == 0); } END_TEST START_TEST(add_time_us) { struct mm_timespec t1; struct mm_timespec t2; int64_t diff = ts_cases[_i].us; t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_us(&t2, diff); ck_assert(mm_timediff_us(&t2, &t1) == 0); t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_us(&t1, -diff); ck_assert(mm_timediff_us(&t1, &t2) == 0); } END_TEST START_TEST(add_time_ms) { struct mm_timespec t1; struct mm_timespec t2; int64_t diff = ts_cases[_i].ms; t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_ms(&t2, diff); ck_assert(mm_timediff_ms(&t2, &t1) == 0); t1 = ts_cases[_i].t1; t2 = ts_cases[_i].t2; mm_timeadd_ms(&t1, -diff); ck_assert(mm_timediff_ms(&t1, &t2) == 0); } END_TEST /************************************************************************** * * * timespec query tests * * * **************************************************************************/ static const int monotonic_clks[] = { MM_CLK_MONOTONIC, MM_CLK_MONOTONIC_RAW, MM_CLK_CPU_THREAD, }; static const int all_clks[] = { MM_CLK_REALTIME, MM_CLK_MONOTONIC, MM_CLK_CPU_PROCESS, MM_CLK_CPU_THREAD, MM_CLK_MONOTONIC_RAW, }; START_TEST(clock_resolution) { struct mm_timespec res, ts, prev_ts, start; int64_t diff, res_increment; clockid_t id = all_clks[_i]; // Get clock resolution ck_assert(mm_getres(id, &res) == 0); res_increment = res.tv_sec * NS_IN_SEC + res.tv_nsec; mm_gettime(id, &start); prev_ts = start; do { // Acquire time and compute delta with previous measure ck_assert(mm_gettime(id, &ts) == 0); diff = mm_timediff_ns(&ts, &prev_ts); prev_ts = ts; // Check that when clock increments, it is at least by the // clock resolution if (diff > 0) ck_assert_int_ge(diff, res_increment); } while (mm_timediff_ns(&ts, &start) < NS_IN_SEC); } END_TEST /* Rationale of the chosen WALLCLOCK_MAXDIFF_NS for wallclock_time test: * * If the time() and mm_gettime(MM_CLK_REALTIME) sample their timestamp from * the exact same clock, the difference in the worst case scenario would be * 1 sec (subsecond data is dropped). * * In practice, on some platform or configuration, the underlying clocks may * differ. This is the case on win32 using msvcrt.dll whose time() is based * on GetSystemTimeAsFileTime(), while mm_gettime(MM_CLK_REALTIME) is based * on GetSystemTimeAsPreciseFileTime(). However, even if clock basis differ, * the difference should not be too large, hence 200 ms should be enough to * cover any clock discrepancies. * * Consequently, the tolerance in the measure of UTC time from mm_gettime() * compared to time() is set to 1.2s. */ #define WALLCLOCK_MAXDIFF_NS (1200*1000*1000) // 1.2s in nsec START_TEST(wallclock_time) { int i; int64_t diff_ns; struct mm_timespec ts_realtime, ts_utc = {.tv_nsec = 0}; for (i = 0; i < 100000; i++) { if (mm_gettime(MM_CLK_REALTIME, &ts_realtime) != 0) ck_abort_msg("mm_gettime() failed"); ts_utc.tv_sec = time(NULL); diff_ns = mm_timediff_ns(&ts_utc, &ts_realtime); diff_ns = (diff_ns > 0) ? diff_ns : -diff_ns; if (diff_ns > WALLCLOCK_MAXDIFF_NS) ck_abort_msg("mm_gettime(MM_CLK_REALTIME) differs " "from time(NULL): diff_ns = %li", (long)diff_ns); } } END_TEST START_TEST(monotonic_update) { int i; struct mm_timespec ts, prev_ts; int64_t diff_ns; clockid_t id = monotonic_clks[_i]; mm_gettime(id, &prev_ts); for (i = 0; i < 100000; i++) { if (mm_gettime(id, &ts) != 0) ck_abort_msg("mm_gettime(%i) failed", id); diff_ns = mm_timediff_ns(&ts, &prev_ts); if (diff_ns < 0) ck_abort_msg("mm_gettime(%i) not monotomic " "(diff_ns = %li)", id, (long)diff_ns); prev_ts = ts; } } END_TEST START_TEST(invalid_clock_id) { struct mm_timespec ts; clockid_t inval_clkid = 42; // No platform has this clock id ck_assert(mm_gettime(inval_clkid, &ts) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); ck_assert(mm_getres(inval_clkid, &ts) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); ck_assert(mm_nanosleep(inval_clkid, &ts) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); ck_assert(mm_nanosleep(MM_CLK_CPU_THREAD, &ts) == -1); ck_assert_int_eq(mm_get_lasterror_number(), EINVAL); } END_TEST /************************************************************************** * * * sleep tests * * * **************************************************************************/ static const int waitable_clks[] = { MM_CLK_REALTIME, MM_CLK_MONOTONIC, }; START_TEST(absolute_sleep) { struct mm_timespec ts, now; clockid_t id = waitable_clks[_i]; int64_t delay; for (delay = 100; delay <= 10000000; delay *= 100) { mm_gettime(id, &ts); mm_timeadd_ns(&ts, delay); ck_assert(mm_nanosleep(id, &ts) == 0); mm_gettime(id, &now); ck_assert(mm_timediff_ns(&now, &ts) >= 0); } } END_TEST START_TEST(relative_sleep_ns) { struct mm_timespec ts, start; int i; int64_t delays[] = {50, 500, 10000, 1000000}; for (i = 0; i < MM_NELEM(delays); i++) { mm_gettime(MM_CLK_MONOTONIC, &start); ck_assert(mm_relative_sleep_ns(delays[i]) == 0); mm_gettime(MM_CLK_MONOTONIC, &ts); ck_assert_int_ge(mm_timediff_ns(&ts, &start), delays[i]); } } END_TEST START_TEST(relative_sleep_us) { struct mm_timespec ts, start; int i; int64_t delays[] = {50, 500, 10000}; for (i = 0; i < MM_NELEM(delays); i++) { mm_gettime(MM_CLK_MONOTONIC, &start); ck_assert(mm_relative_sleep_us(delays[i]) == 0); mm_gettime(MM_CLK_MONOTONIC, &ts); ck_assert_int_ge(mm_timediff_us(&ts, &start), delays[i]); } } END_TEST START_TEST(relative_sleep_ms) { struct mm_timespec ts, start; int i; int64_t delays[] = {1, 5, 20, 100, 300}; for (i = 0; i < MM_NELEM(delays); i++) { mm_gettime(MM_CLK_MONOTONIC, &start); ck_assert(mm_relative_sleep_ms(delays[i]) == 0); mm_gettime(MM_CLK_MONOTONIC, &ts); ck_assert_int_ge(mm_timediff_ms(&ts, &start), delays[i]); } } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_time_tcase(void) { TCase *tc = tcase_create("time"); tcase_add_loop_test(tc, diff_time_ns, 0, MM_NELEM(ts_cases)); tcase_add_loop_test(tc, diff_time_us, 0, MM_NELEM(ts_cases)); tcase_add_loop_test(tc, diff_time_ms, 0, MM_NELEM(ts_cases)); tcase_add_loop_test(tc, add_time_ns, 0, MM_NELEM(ts_cases)); tcase_add_loop_test(tc, add_time_us, 0, MM_NELEM(ts_cases)); tcase_add_loop_test(tc, add_time_ms, 0, MM_NELEM(ts_cases)); tcase_add_test(tc, invalid_clock_id); if (!strcmp(mm_getenv("MMLIB_DISABLE_CLOCK_TESTS", "no"), "yes")) { fputs("Disable clock based tests\n", stderr); return tc; } tcase_add_loop_test(tc, clock_resolution, 0, MM_NELEM(all_clks)); tcase_add_test(tc, wallclock_time); tcase_add_loop_test(tc, monotonic_update, 0, MM_NELEM(monotonic_clks)); tcase_add_loop_test(tc, absolute_sleep, 0, MM_NELEM(waitable_clks)); tcase_add_test(tc, relative_sleep_ns); tcase_add_test(tc, relative_sleep_us); tcase_add_test(tc, relative_sleep_ms); return tc; } mmlib-1.4.2/tests/utils-api-tests.c000066400000000000000000000253151435717460000172100ustar00rootroot00000000000000/* @mindmaze_header@ */ #if HAVE_CONFIG_H # include #endif #include #include #include #include "api-testcases.h" #include "mmerrno.h" #include "mmlib.h" #define NUM_ENVVAR 32 START_TEST(get_basedir) { enum mm_known_dir dirtype = _i; if (dirtype >= 0 && dirtype < MM_NUM_DIRTYPE) { // dirtype is valid, the function must not return NULL ck_assert(mm_get_basedir(dirtype) != NULL); } else { // dirtype is NOT valid, the function report EINVAL ck_assert(mm_get_basedir(dirtype) == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); } } END_TEST START_TEST(path_from_base) { enum mm_known_dir dirtype = _i; char* respath; char ref[512]; char* strcase[] = {"a_string", "long/tstring/hello"}; int i; // If dirtype is NOT valid, the function must report EINVAL if (dirtype < 0 || dirtype >= MM_NUM_DIRTYPE) { ck_assert(mm_path_from_basedir(dirtype, strcase[0]) == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); return; } for (i = 0; i < MM_NELEM(strcase); i++) { respath = mm_path_from_basedir(dirtype, strcase[i]); ck_assert(respath != NULL); // Check resulting path is expected sprintf(ref, "%s/%s", mm_get_basedir(dirtype), strcase[i]); ck_assert_str_eq(ref, respath); free(respath); } // Check function fails with invalid arg ck_assert(mm_path_from_basedir(dirtype, NULL) == NULL); ck_assert(mm_get_lasterror_number() == EINVAL); } END_TEST START_TEST(mm_strcasecmp_test) { ck_assert(mm_strcasecmp("teststring", "teststring") == 0); /* mm_strcasecmp() compares as lower case: * 'S' = 0x53 * '_' = 0x5F * 's' = 0x73 * * so: 'S' < '_' < 's' * * therefore strcmp() returns "JOHN_HENRY" > "JOHNSTON" * but mm_strcasecmp() returns "JOHN_HENRY" < "JOHNSTON" */ ck_assert(strcmp("JOHN_HENRY", "JOHNSTON") > 0); ck_assert(mm_strcasecmp("JOHN_HENRY", "JOHNSTON") < 0); } END_TEST #define STR_STARTS_WITH(str, const_str) \ (strlen(str) >= (sizeof(const_str) - 1) \ && memcmp(str, const_str, sizeof(const_str) - 1) == 0) #define STR_ENDS_WITH(str, const_str) \ (strlen(str) >= (sizeof(const_str) - 1) \ && memcmp(str + strlen(str) - sizeof(const_str) + 1, const_str, sizeof(const_str) - 1) == 0) START_TEST(append_prepend_environ) { // Verify DUMMY_VAR is initially unset ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); // Test get default value is used for getenv if variable is unset ck_assert_str_eq(mm_getenv("DUMMY_VAR", "something"), "something"); // test setenv with overwrite works mm_setenv("DUMMY_VAR", "another", MM_ENV_OVERWRITE); ck_assert_str_eq(mm_getenv("DUMMY_VAR", NULL), "another"); // test setenv with prepend flag mm_setenv("DUMMY_VAR", "before", MM_ENV_PREPEND); ck_assert(STR_STARTS_WITH(mm_getenv("DUMMY_VAR", NULL), "before")); // test setenv with append flag mm_setenv("DUMMY_VAR", "after", MM_ENV_APPEND); ck_assert(STR_ENDS_WITH(mm_getenv("DUMMY_VAR", NULL), "after")); // test unsetenv works mm_unsetenv("DUMMY_VAR"); ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); // test setenv append on empty var mm_setenv("DUMMY_VAR", "a val", MM_ENV_APPEND); ck_assert_str_eq(mm_getenv("DUMMY_VAR", NULL), "a val"); // cleaning mm_unsetenv("DUMMY_VAR"); ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); } END_TEST START_TEST(get_set_unset_env) { // Verify DUMMY_VAR is initially unset ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); // Test get default value is used for getenv if variable is unset ck_assert_str_eq(mm_getenv("DUMMY_VAR", "something"), "something"); // test setenv without overwrite works mm_setenv("DUMMY_VAR", "a val", 0); ck_assert_str_eq(mm_getenv("DUMMY_VAR", NULL), "a val"); mm_setenv("DUMMY_VAR", "another", 0); ck_assert_str_eq(mm_getenv("DUMMY_VAR", NULL), "a val"); // test setenv with overwrite works mm_setenv("DUMMY_VAR", "another", 1); ck_assert_str_eq(mm_getenv("DUMMY_VAR", NULL), "another"); // test default value of getenv is not used if variable is set ck_assert_str_eq(mm_getenv("DUMMY_VAR", "something"), "another"); // test unsetenv works mm_unsetenv("DUMMY_VAR"); ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); } END_TEST static const char* get_value_from_envp(const char* const* envp, const char* key) { int i, keylen; keylen = strlen(key); for (i = 0; envp[i] != NULL; i++) { if (memcmp(key, envp[i], keylen) == 0 && envp[i][keylen] == '=') return envp[i] + keylen + 1; } return NULL; } START_TEST(get_environ) { const char* const* envp; // Verify DUMMY_VAR is initially unset ck_assert(mm_getenv("DUMMY_VAR", NULL) == NULL); // Check return envp does not contains DUMMY_VAR envp = mm_get_environ(); ck_assert(envp != NULL); ck_assert(get_value_from_envp(envp, "DUMMY_VAR") == NULL); // Check return envp contains DUMMY_VAR set to value set mm_setenv("DUMMY_VAR", "a_val", 0); envp = mm_get_environ(); ck_assert(envp != NULL); ck_assert_str_eq(get_value_from_envp(envp, "DUMMY_VAR"), "a_val"); // Check return envp contains DUMMY_VAR set to new value set mm_setenv("DUMMY_VAR", "another", 1); envp = mm_get_environ(); ck_assert(envp != NULL); ck_assert_str_eq(get_value_from_envp(envp, "DUMMY_VAR"), "another"); // Check return envp contains no DUMMY_VAR after unset mm_unsetenv("DUMMY_VAR"); envp = mm_get_environ(); ck_assert(envp != NULL); ck_assert(get_value_from_envp(envp, "DUMMY_VAR") == NULL); } END_TEST /* * Instead of setting only one variable and see its value over different * manipulation, here we ensure that setting/unsetting multiple variable does * not have buggy side effect on the other variables. */ START_TEST(multiple_set_unset_env) { char name[NUM_ENVVAR][16], value[NUM_ENVVAR][16]; const char* const* envp; const char* val; int i; // set multiple variable first for (i = 0; i < NUM_ENVVAR; i++) { sprintf(name[i], "ENV-KEY-%i", i); sprintf(value[i], "VAL-%i", i); ck_assert(mm_setenv(name[i], value[i], 0) == 0); } // unset 1 envvar out of 3 for (i = 0; i < NUM_ENVVAR; i++) { if (i % 3 == 0) mm_unsetenv(name[i]); } // check expected state of the envvar for (i = 0; i < NUM_ENVVAR; i++) { val = mm_getenv(name[i], NULL); if (i % 3 == 0) ck_assert(val == NULL); else ck_assert_str_eq(val, value[i]); } // check expected state through mm_get_environ() envp = mm_get_environ(); for (i = 0; i < NUM_ENVVAR; i++) { val = get_value_from_envp(envp, name[i]); if (i % 3 == 0) ck_assert(val == NULL); else ck_assert_str_eq(val, value[i]); } // cleanup for (i = 0; i < NUM_ENVVAR; i++) { mm_unsetenv(name[i]); ck_assert(mm_getenv(name[i], NULL) == NULL); } } END_TEST /************************************************************************** * * * Path component parsing tests * * * **************************************************************************/ static const struct { const char* path; const char* dir; const char* base; } dir_comp_cases[] = { {.path = "/usr/lib", .dir = "/usr", .base = "lib"}, {.path = "/usr/", .dir = "/", .base = "usr"}, {.path = "usr", .dir = ".", .base = "usr"}, {.path = "/", .dir = "/", .base = "/"}, {.path = ".", .dir = ".", .base = "."}, {.path = "..", .dir = ".", .base = ".."}, {.path = "/usr//lib", .dir = "/usr", .base = "lib"}, {.path = "/usr//lib//", .dir = "/usr", .base = "lib"}, {.path = "/usr///", .dir = "/", .base = "usr"}, {.path = "///usr/", .dir = "/", .base = "usr"}, {.path = "///", .dir = "/", .base = "/"}, {.path = "./", .dir = ".", .base = "."}, {.path = "../", .dir = ".", .base = ".."}, {.path = "", .dir = ".", .base = "."}, {.path = "//", .dir = "/", .base = "/"}, {.path = "...", .dir = ".", .base = "..."}, {.path = " ", .dir = ".", .base = " "}, #if defined(_WIN32) {.path = "\\usr\\", .dir = "\\", .base = "usr"}, {.path = "\\usr\\lib", .dir = "\\usr", .base = "lib"}, {.path = "\\usr/lib", .dir = "\\usr", .base = "lib"}, {.path = "\\usr/lib\\hi", .dir = "\\usr/lib", .base = "hi"}, {.path = "\\usr\\lib/hi", .dir = "\\usr\\lib", .base = "hi"}, #else {.path = "/1\\2/3", .dir = "/1\\2", .base = "3"}, #endif }; START_TEST(parse_dirname) { int i, explen; char result[64], path[64]; const char* expected; for (i = 0; i < MM_NELEM(dir_comp_cases); i++) { strcpy(path, dir_comp_cases[i].path); expected = dir_comp_cases[i].dir; explen = strlen(expected); ck_assert(mm_dirname(NULL, path) == explen); // Run on different string buffer ck_assert(mm_dirname(result, path) == explen); ck_assert_str_eq(result, expected); ck_assert_str_eq(path, dir_comp_cases[i].path); // Run on same string buffer ck_assert(mm_dirname(path, path) == explen); ck_assert_str_eq(path, expected); } ck_assert(mm_dirname(NULL, NULL) == 1); ck_assert(mm_dirname(result, NULL) == 1); ck_assert_str_eq(result, "."); } END_TEST START_TEST(parse_basename) { int i, explen; char result[64], path[64]; const char* expected; for (i = 0; i < MM_NELEM(dir_comp_cases); i++) { strcpy(path, dir_comp_cases[i].path); expected = dir_comp_cases[i].base; explen = strlen(expected); ck_assert(mm_basename(NULL, path) == explen); // Run on different string buffer ck_assert(mm_basename(result, path) == explen); ck_assert_str_eq(result, expected); ck_assert_str_eq(path, dir_comp_cases[i].path); // Run on same string buffer ck_assert(mm_basename(path, path) == explen); ck_assert_str_eq(path, expected); } ck_assert(mm_basename(NULL, NULL) == 1); ck_assert(mm_basename(result, NULL) == 1); ck_assert_str_eq(result, "."); } END_TEST /************************************************************************** * * * Test suite setup * * * **************************************************************************/ LOCAL_SYMBOL TCase* create_utils_tcase(void) { TCase *tc = tcase_create("utils"); tcase_add_loop_test(tc, get_basedir, -5, MM_NUM_DIRTYPE+5); tcase_add_loop_test(tc, path_from_base, -5, MM_NUM_DIRTYPE+5); tcase_add_test(tc, mm_strcasecmp_test); tcase_add_test(tc, get_set_unset_env); tcase_add_test(tc, get_environ); tcase_add_test(tc, multiple_set_unset_env); tcase_add_test(tc, append_prepend_environ); tcase_add_test(tc, parse_dirname); tcase_add_test(tc, parse_basename); return tc; } mmlib-1.4.2/tools/000077500000000000000000000000001435717460000137655ustar00rootroot00000000000000mmlib-1.4.2/tools/api-compat-test.sh000077500000000000000000000023651435717460000173410ustar00rootroot00000000000000#!/bin/bash set -e # Create the temporary folder where to install the public headers includedir=$(mktemp -d ./api-compat-XXXXXXX) cleanup() { rm -rf $includedir } trap cleanup EXIT # copy the public headers in the temporary folder cp $@ $includedir randomize_header_list() { for header in $@ ; do echo $header done | sort -R } all_headers=$(randomize_header_list $@) all_included() { for header in $all_headers ; do echo "#include \"$(basename $header)\"" done echo "int main(void) { return 0; }" } # dump compilers versions gcc --version # dump generated file all_included # test headers for compliance with most warnings for std in c99 c11 c17 gnu99 gnu11 gnu17 do echo "Test C language standard : $std" all_included | gcc -std=$std -x c - -I"$includedir" -Werror -Wall -Wextra -pedantic done # also test with clang if available if [ -x "$(which clang)" ] ; then clang --version all_included | clang -x c - -I"$includedir" -Weverything \ -Wno-documentation-unknown-command # silence doxygen-related warnings fi # test headers for C++ compatibility for std in c++11 c++14 c++17 gnu++11 gnu++14 gnu++17 do echo "Test C++ language standard : $std" all_included | gcc -std=$std -x c++ - -I"$includedir" -Werror -Wall -Wextra -pedantic done mmlib-1.4.2/tools/check-doc.sh000077500000000000000000000007731435717460000161530ustar00rootroot00000000000000#!/bin/bash if [ "$(git rev-parse --show-toplevel)" != "$PWD" ] then echo "go to top dir" echo "git clean -dfx && ./autogen.sh && ./configure && make" exit 1 fi FILES=$(sed -e 's/.. kernel-doc::\(.*\)/\1/;t;d' doc/*.rst) expected=$(cat $FILES | grep -c API_EXPORTED) html_exported=$(cat doc/html/*.html | grep -c "class=\"function\"" ) man_pages=$(ls doc/man/*.3 | wc -l) { echo "Expected $expected API functions" echo "Found $html_exported html functions" echo "Found $man_pages man pages" } 1>&2 mmlib-1.4.2/tools/codespell-ignore000066400000000000000000000001271435717460000171430ustar00rootroot00000000000000 * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.108.7376&rep=rep1&type=pdf mmlib-1.4.2/tools/coverage.sh000077500000000000000000000015601435717460000161210ustar00rootroot00000000000000#!/bin/sh set -e COVERAGE_CFLAGS="-O0 -fprofile-arcs -ftest-coverage" test_coverage() { TESTING_COVERAGE=false make clean mkdir -p $covdir lcov --directory . --zerocounters -q make check CFLAGS="$COVERAGE_CFLAGS" lcov --compat-libtool --directory . --capture --output-file $covdir/app.info TESTING_COVERAGE=true make clean genhtml -o $covdir/ $covdir/app.info set -x firefox $covdir/index.html & { set +x; } 2> /dev/null } print_usage() { echo "Usage:" echo "\t$0 run [outdir]" 2>&1 echo "\t$0 clean [outdir]" 2>&1 } if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then echo "Illegal number of parameters" 2>&1 print_usage exit 1 fi action=$1 covdir=${2:-coverage-results} case $action in "run") test_coverage ;; "clean") test "x$TESTING_COVERAGE" = "xtrue" || rm -rf $covdir ;; *) echo "Illegal action: $action" 2>&1 print_usage exit 1 esac mmlib-1.4.2/tools/expand_headers000077500000000000000000000004041435717460000166630ustar00rootroot00000000000000#!/bin/sh # Call copyright-header to expand the copyright statement in each source file. # The script is not called directly using meson.add_dist_script() because this # would make copyright-header mandatory when configuring the project. copyright-autoheader mmlib-1.4.2/tools/meson.build000066400000000000000000000040041435717460000161250ustar00rootroot00000000000000all_lib_c_sources = mmlib_sources + lock_referee_sources if tests_state == 'enabled' all_test_c_sources = (testlog_sources + testerrno_sources + testprofile_sources + child_proc_sources + tests_child_proc_files + perflock_sources + dynlib_test_sources + testapi_sources ) else all_test_c_sources = [] endif if docs_state == 'enabled' all_doc_c_sources = (pshared_parent_doc_example_sources + pshared_child_doc_example_sources + parse_args_doc_example ) else all_doc_c_sources = [] endif all_sources = (all_lib_c_sources + all_test_c_sources + all_doc_c_sources ) codespell = find_program('codespell', required : false) if codespell.found() # check for typo in all possible sources run_target('spelling', command : [ codespell, '-x', files('codespell-ignore'), all_sources, ] ) endif # codespell uncrustify = find_program('uncrustify', required : false) if uncrustify.found() uncrustify_config = files('uncrustify.cfg') # enforce coding style for library sources and documentation # leave the test sources out of this constraint run_target('checkstyle', command : [ uncrustify, '-l', 'c', '-q', '-c', uncrustify_config, '--check', all_lib_c_sources, all_doc_c_sources ], ) run_target('fixstyle', command : [ uncrustify, '-l', 'c', '-q', '-c', uncrustify_config, '--replace', '--no-backup', all_lib_c_sources, all_doc_c_sources ], ) endif # uncrustify # test public headers for most warnings and C++ compatibility api_compat_test = run_target('api-compat-test', command : [ 'bash', files('api-compat-test.sh'), public_headers, ] ) meson.add_dist_script('expand_headers') mmlib-1.4.2/tools/pre-commit000077500000000000000000000022731435717460000157730ustar00rootroot00000000000000#!/bin/sh # check the necesessary tools exist command -v uncrustify >/dev/null if [ $? != 0 ]; then echo "uncrustify missing. Please run: apt-get install uncrustify" 2>&1 exit 1 fi hook_script=${GIT_DIR:-.git}/hooks/pre-commit # go to git top level directory GIT_ROOT_DIR=$(git rev-parse --show-toplevel) cd $GIT_ROOT_DIR # Check for newer (actually, different) versions of the pre-commit script if [ -z "$1" ] && [ -f "$hook_script" ]; then if ! cmp -s "$hook_script" tools/pre-commit; then echo "Pre-commit hook script is outdated, please update!" 2>&1 echo "cmd: cp -uv tools/pre-commit ${hook_script}" 2>&1 exit 1 fi fi CONFIG=tools/uncrustify.cfg if [ ! -e $CONFIG ] ; then echo "uncrustify config file not found." 2>&1 exit 1 fi # consider staged C files only FILE_LIST=$(git diff --staged --name-status HEAD | grep -v "^D" | cut -f2 | grep "\.[ch]$") if [ -z "$FILE_LIST" ] ; then exit 0 fi uncrustify --version uncrustify --check -c ${CONFIG} $FILE_LIST if [ ! $? -eq 0 ] ; then echo "Error: your files do not comply with mmlib coding style. Please run from $GIT_ROOT_DIR:" 2>&1 echo "uncrustify --replace -c tools/uncrustify.cfg $(echo $FILE_LIST)" 2>&1 exit 1 fi mmlib-1.4.2/tools/uncrustify.cfg000066400000000000000000003623171435717460000166750ustar00rootroot00000000000000# Uncrustify-0.72.0_f # # General options # # The type of line endings. # # Default: auto newlines = lf # lf/crlf/cr/auto # The original size of tabs in the input. # # Default: 8 input_tab_size = 8 # unsigned number # The size of tabs in the output (only used if align_with_tabs=true). # # Default: 8 output_tab_size = 8 # unsigned number # The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^). # # Default: 92 string_escape_char = 92 # unsigned number # Alternate string escape char (usually only used for Pawn). # Only works right before the quote char. string_escape_char2 = 0 # unsigned number # Replace tab characters found in string literals with the escape sequence \t # instead. string_replace_tab_chars = false # true/false # Allow interpreting '>=' and '>>=' as part of a template in code like # 'void f(list>=val);'. If true, 'assert(x<0 && y>=3)' will be broken. # Improvements to template detection may make this option obsolete. tok_split_gte = false # true/false # Disable formatting of NL_CONT ('\\n') ended lines (e.g. multiline macros) disable_processing_nl_cont = false # true/false # Specify the marker used in comments to disable processing of part of the # file. # The comment should be used alone in one line. # # Default: *INDENT-OFF* disable_processing_cmt = "STYLE-EXCEPTION-BEGIN" # string # Specify the marker used in comments to (re)enable processing in a file. # The comment should be used alone in one line. # # Default: *INDENT-ON* enable_processing_cmt = "STYLE-EXCEPTION-BEGIN" # string # Enable parsing of digraphs. enable_digraphs = false # true/false # Add or remove the UTF-8 BOM (recommend 'remove'). utf8_bom = ignore # ignore/add/remove/force # If the file contains bytes with values between 128 and 255, but is not # UTF-8, then output as UTF-8. utf8_byte = false # true/false # Force the output encoding to UTF-8. utf8_force = false # true/false # Add or remove space between 'do' and '{'. sp_do_brace_open = ignore # ignore/add/remove/force # Add or remove space between '}' and 'while'. sp_brace_close_while = ignore # ignore/add/remove/force # Add or remove space between 'while' and '('. sp_while_paren_open = ignore # ignore/add/remove/force # # Spacing options # # Add or remove space around non-assignment symbolic operators ('+', '/', '%', # '<<', and so forth). sp_arith = ignore # ignore/add/remove/force # Add or remove space around arithmetic operators '+' and '-'. # # Overrides sp_arith. sp_arith_additive = ignore # ignore/add/remove/force # Add or remove space around assignment operator '=', '+=', etc. sp_assign = ignore # ignore/add/remove/force # Add or remove space around '=' in C++11 lambda capture specifications. # # Overrides sp_assign. sp_cpp_lambda_assign = ignore # ignore/add/remove/force # Add or remove space after the capture specification of a C++11 lambda when # an argument list is present, as in '[] (int x){ ... }'. sp_cpp_lambda_square_paren = ignore # ignore/add/remove/force # Add or remove space after the capture specification of a C++11 lambda with # no argument list is present, as in '[] { ... }'. sp_cpp_lambda_square_brace = ignore # ignore/add/remove/force # Add or remove space after the argument list of a C++11 lambda, as in # '[](int x) { ... }'. sp_cpp_lambda_paren_brace = ignore # ignore/add/remove/force # Add or remove space between a lambda body and its call operator of an # immediately invoked lambda, as in '[]( ... ){ ... } ( ... )'. sp_cpp_lambda_fparen = ignore # ignore/add/remove/force # Add or remove space around assignment operator '=' in a prototype. # # If set to ignore, use sp_assign. sp_assign_default = ignore # ignore/add/remove/force # Add or remove space before assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. sp_after_assign = ignore # ignore/add/remove/force # Add or remove space in 'NS_ENUM ('. sp_enum_paren = ignore # ignore/add/remove/force # Add or remove space around assignment '=' in enum. sp_enum_assign = ignore # ignore/add/remove/force # Add or remove space before assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_before_assign = ignore # ignore/add/remove/force # Add or remove space after assignment '=' in enum. # # Overrides sp_enum_assign. sp_enum_after_assign = ignore # ignore/add/remove/force # Add or remove space around assignment ':' in enum. sp_enum_colon = ignore # ignore/add/remove/force # Add or remove space around preprocessor '##' concatenation operator. # # Default: add sp_pp_concat = add # ignore/add/remove/force # Add or remove space after preprocessor '#' stringify operator. # Also affects the '#@' charizing operator. sp_pp_stringify = ignore # ignore/add/remove/force # Add or remove space before preprocessor '#' stringify operator # as in '#define x(y) L#y'. sp_before_pp_stringify = ignore # ignore/add/remove/force # Add or remove space around boolean operators '&&' and '||'. sp_bool = ignore # ignore/add/remove/force # Add or remove space around compare operator '<', '>', '==', etc. sp_compare = ignore # ignore/add/remove/force # Add or remove space inside '(' and ')'. sp_inside_paren = ignore # ignore/add/remove/force # Add or remove space between nested parentheses, i.e. '((' vs. ') )'. sp_paren_paren = ignore # ignore/add/remove/force # Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('. sp_cparen_oparen = ignore # ignore/add/remove/force # Whether to balance spaces inside nested parentheses. sp_balance_nested_parens = false # true/false # Add or remove space between ')' and '{'. sp_paren_brace = ignore # ignore/add/remove/force # Add or remove space between nested braces, i.e. '{{' vs '{ {'. sp_brace_brace = ignore # ignore/add/remove/force # Add or remove space before pointer star '*'. sp_before_ptr_star = ignore # ignore/add/remove/force # Add or remove space before pointer star '*' that isn't followed by a # variable name. If set to ignore, sp_before_ptr_star is used instead. sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force # Add or remove space between pointer stars '*'. sp_between_ptr_star = ignore # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a word. # # Overrides sp_type_func. sp_after_ptr_star = ignore # ignore/add/remove/force # Add or remove space after pointer caret '^', if followed by a word. sp_after_ptr_block_caret = ignore # ignore/add/remove/force # Add or remove space after pointer star '*', if followed by a qualifier. sp_after_ptr_star_qualifier = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by a function # prototype or function definition. # # Overrides sp_after_ptr_star and sp_type_func. sp_after_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space after a pointer star '*', if followed by an open # parenthesis, as in 'void* (*)(). sp_ptr_star_paren = ignore # ignore/add/remove/force # Add or remove space before a pointer star '*', if followed by a function # prototype or function definition. sp_before_ptr_star_func = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&'. sp_before_byref = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&' that isn't followed by a # variable name. If set to ignore, sp_before_byref is used instead. sp_before_unnamed_byref = ignore # ignore/add/remove/force # Add or remove space after reference sign '&', if followed by a word. # # Overrides sp_type_func. sp_after_byref = ignore # ignore/add/remove/force # Add or remove space after a reference sign '&', if followed by a function # prototype or function definition. # # Overrides sp_after_byref and sp_type_func. sp_after_byref_func = ignore # ignore/add/remove/force # Add or remove space before a reference sign '&', if followed by a function # prototype or function definition. sp_before_byref_func = ignore # ignore/add/remove/force # Add or remove space between type and word. In cases where total removal of # whitespace would be a syntax error, a value of 'remove' is treated the same # as 'force'. # # This also affects some other instances of space following a type that are # not covered by other options; for example, between the return type and # parenthesis of a function type template argument, between the type and # parenthesis of an array parameter, or between 'decltype(...)' and the # following word. # # Default: force sp_after_type = force # ignore/add/remove/force # Add or remove space between 'decltype(...)' and word. # # Overrides sp_after_type. sp_after_decltype = ignore # ignore/add/remove/force # (D) Add or remove space before the parenthesis in the D constructs # 'template Foo(' and 'class Foo('. sp_before_template_paren = ignore # ignore/add/remove/force # Add or remove space between 'template' and '<'. # If set to ignore, sp_before_angle is used. sp_template_angle = ignore # ignore/add/remove/force # Add or remove space before '<'. sp_before_angle = ignore # ignore/add/remove/force # Add or remove space inside '<' and '>'. sp_inside_angle = ignore # ignore/add/remove/force # Add or remove space inside '<>'. sp_inside_angle_empty = ignore # ignore/add/remove/force # Add or remove space between '>' and ':'. sp_angle_colon = ignore # ignore/add/remove/force # Add or remove space after '>'. sp_after_angle = ignore # ignore/add/remove/force # Add or remove space between '>' and '(' as found in 'new List(foo);'. sp_angle_paren = ignore # ignore/add/remove/force # Add or remove space between '>' and '()' as found in 'new List();'. sp_angle_paren_empty = ignore # ignore/add/remove/force # Add or remove space between '>' and a word as in 'List m;' or # 'template static ...'. sp_angle_word = ignore # ignore/add/remove/force # Add or remove space between '>' and '>' in '>>' (template stuff). # # Default: add sp_angle_shift = add # ignore/add/remove/force # (C++11) Permit removal of the space between '>>' in 'foo >'. Note # that sp_angle_shift cannot remove the space without this option. sp_permit_cpp11_shift = false # true/false # Add or remove space before '(' of control statements ('if', 'for', 'switch', # 'while', etc.). sp_before_sparen = ignore # ignore/add/remove/force # Add or remove space inside '(' and ')' of control statements. sp_inside_sparen = ignore # ignore/add/remove/force # Add or remove space after '(' of control statements. # # Overrides sp_inside_sparen. sp_inside_sparen_open = ignore # ignore/add/remove/force # Add or remove space before ')' of control statements. # # Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force # Add or remove space after ')' of control statements. sp_after_sparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of of control statements. sp_sparen_brace = ignore # ignore/add/remove/force # (D) Add or remove space between 'invariant' and '('. sp_invariant_paren = ignore # ignore/add/remove/force # (D) Add or remove space after the ')' in 'invariant (C) c'. sp_after_invariant_paren = ignore # ignore/add/remove/force # Add or remove space before empty statement ';' on 'if', 'for' and 'while'. sp_special_semi = ignore # ignore/add/remove/force # Add or remove space before ';'. # # Default: remove sp_before_semi = remove # ignore/add/remove/force # Add or remove space before ';' in non-empty 'for' statements. sp_before_semi_for = ignore # ignore/add/remove/force # Add or remove space before a semicolon of an empty part of a for statement. sp_before_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space after ';', except when followed by a comment. # # Default: add sp_after_semi = add # ignore/add/remove/force # Add or remove space after ';' in non-empty 'for' statements. # # Default: force sp_after_semi_for = force # ignore/add/remove/force # Add or remove space after the final semicolon of an empty part of a for # statement, as in 'for ( ; ; )'. sp_after_semi_for_empty = ignore # ignore/add/remove/force # Add or remove space before '[' (except '[]'). sp_before_square = ignore # ignore/add/remove/force # Add or remove space before '[' for a variable definition. # # Default: remove sp_before_vardef_square = remove # ignore/add/remove/force # Add or remove space before '[' for asm block. sp_before_square_asm_block = ignore # ignore/add/remove/force # Add or remove space before '[]'. sp_before_squares = ignore # ignore/add/remove/force # Add or remove space before C++17 structured bindings. sp_cpp_before_struct_binding = ignore # ignore/add/remove/force # Add or remove space inside a non-empty '[' and ']'. sp_inside_square = ignore # ignore/add/remove/force # Add or remove space inside '[]'. sp_inside_square_empty = ignore # ignore/add/remove/force # (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and # ']'. If set to ignore, sp_inside_square is used. sp_inside_square_oc_array = ignore # ignore/add/remove/force # Add or remove space after ',', i.e. 'a,b' vs. 'a, b'. sp_after_comma = ignore # ignore/add/remove/force # Add or remove space before ','. # # Default: remove sp_before_comma = remove # ignore/add/remove/force # (C#) Add or remove space between ',' and ']' in multidimensional array type # like 'int[,,]'. sp_after_mdatype_commas = ignore # ignore/add/remove/force # (C#) Add or remove space between '[' and ',' in multidimensional array type # like 'int[,,]'. sp_before_mdatype_commas = ignore # ignore/add/remove/force # (C#) Add or remove space between ',' in multidimensional array type # like 'int[,,]'. sp_between_mdatype_commas = ignore # ignore/add/remove/force # Add or remove space between an open parenthesis and comma, # i.e. '(,' vs. '( ,'. # # Default: force sp_paren_comma = force # ignore/add/remove/force # Add or remove space before the variadic '...' when preceded by a # non-punctuator. sp_before_ellipsis = ignore # ignore/add/remove/force # Add or remove space between a type and '...'. sp_type_ellipsis = ignore # ignore/add/remove/force # (D) Add or remove space between a type and '?'. sp_type_question = ignore # ignore/add/remove/force # Add or remove space between ')' and '...'. sp_paren_ellipsis = ignore # ignore/add/remove/force # Add or remove space between ')' and a qualifier such as 'const'. sp_paren_qualifier = ignore # ignore/add/remove/force # Add or remove space between ')' and 'noexcept'. sp_paren_noexcept = ignore # ignore/add/remove/force # Add or remove space after class ':'. sp_after_class_colon = ignore # ignore/add/remove/force # Add or remove space before class ':'. sp_before_class_colon = ignore # ignore/add/remove/force # Add or remove space after class constructor ':'. sp_after_constr_colon = ignore # ignore/add/remove/force # Add or remove space before class constructor ':'. sp_before_constr_colon = ignore # ignore/add/remove/force # Add or remove space before case ':'. # # Default: remove sp_before_case_colon = remove # ignore/add/remove/force # Add or remove space between 'operator' and operator sign. sp_after_operator = ignore # ignore/add/remove/force # Add or remove space between the operator symbol and the open parenthesis, as # in 'operator ++('. sp_after_operator_sym = ignore # ignore/add/remove/force # Overrides sp_after_operator_sym when the operator has no arguments, as in # 'operator *()'. sp_after_operator_sym_empty = ignore # ignore/add/remove/force # Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or # '(int)a' vs. '(int) a'. sp_after_cast = ignore # ignore/add/remove/force # Add or remove spaces inside cast parentheses. sp_inside_paren_cast = ignore # ignore/add/remove/force # Add or remove space between the type and open parenthesis in a C++ cast, # i.e. 'int(exp)' vs. 'int (exp)'. sp_cpp_cast_paren = ignore # ignore/add/remove/force # Add or remove space between 'sizeof' and '('. sp_sizeof_paren = ignore # ignore/add/remove/force # Add or remove space between 'sizeof' and '...'. sp_sizeof_ellipsis = ignore # ignore/add/remove/force # Add or remove space between 'sizeof...' and '('. sp_sizeof_ellipsis_paren = ignore # ignore/add/remove/force # Add or remove space between 'decltype' and '('. sp_decltype_paren = ignore # ignore/add/remove/force # (Pawn) Add or remove space after the tag keyword. sp_after_tag = ignore # ignore/add/remove/force # Add or remove space inside enum '{' and '}'. sp_inside_braces_enum = ignore # ignore/add/remove/force # Add or remove space inside struct/union '{' and '}'. sp_inside_braces_struct = ignore # ignore/add/remove/force # (OC) Add or remove space inside Objective-C boxed dictionary '{' and '}' sp_inside_braces_oc_dict = ignore # ignore/add/remove/force # Add or remove space after open brace in an unnamed temporary # direct-list-initialization. sp_after_type_brace_init_lst_open = ignore # ignore/add/remove/force # Add or remove space before close brace in an unnamed temporary # direct-list-initialization. sp_before_type_brace_init_lst_close = ignore # ignore/add/remove/force # Add or remove space inside an unnamed temporary direct-list-initialization. sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space inside '{' and '}'. sp_inside_braces = ignore # ignore/add/remove/force # Add or remove space inside '{}'. sp_inside_braces_empty = ignore # ignore/add/remove/force # Add or remove space around trailing return operator '->'. sp_trailing_return = ignore # ignore/add/remove/force # Add or remove space between return type and function name. A minimum of 1 # is forced except for pointer return types. sp_type_func = ignore # ignore/add/remove/force # Add or remove space between type and open brace of an unnamed temporary # direct-list-initialization. sp_type_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function declaration. sp_func_proto_paren = ignore # ignore/add/remove/force # Add or remove space between function name and '()' on function declaration # without parameters. sp_func_proto_paren_empty = ignore # ignore/add/remove/force # Add or remove space between function name and '(' with a typedef specifier. sp_func_type_paren = ignore # ignore/add/remove/force # Add or remove space between alias name and '(' of a non-pointer function type typedef. sp_func_def_paren = ignore # ignore/add/remove/force # Add or remove space between function name and '()' on function definition # without parameters. sp_func_def_paren_empty = ignore # ignore/add/remove/force # Add or remove space inside empty function '()'. # Overrides sp_after_angle unless use_sp_after_angle_always is set to true. sp_inside_fparens = ignore # ignore/add/remove/force # Add or remove space inside function '(' and ')'. sp_inside_fparen = ignore # ignore/add/remove/force # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. sp_inside_tparen = ignore # ignore/add/remove/force # Add or remove space between the ')' and '(' in a function type, as in # 'void (*x)(...)'. sp_after_tparen_close = ignore # ignore/add/remove/force # Add or remove space between ']' and '(' when part of a function call. sp_square_fparen = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of function. sp_fparen_brace = ignore # ignore/add/remove/force # Add or remove space between ')' and '{' of a function call in object # initialization. # # Overrides sp_fparen_brace. sp_fparen_brace_initializer = ignore # ignore/add/remove/force # (Java) Add or remove space between ')' and '{{' of double brace initializer. sp_fparen_dbrace = ignore # ignore/add/remove/force # Add or remove space between function name and '(' on function calls. sp_func_call_paren = ignore # ignore/add/remove/force # Add or remove space between function name and '()' on function calls without # parameters. If set to ignore (the default), sp_func_call_paren is used. sp_func_call_paren_empty = ignore # ignore/add/remove/force # Add or remove space between the user function name and '(' on function # calls. You need to set a keyword to be a user function in the config file, # like: # set func_call_user tr _ i18n sp_func_call_user_paren = ignore # ignore/add/remove/force # Add or remove space inside user function '(' and ')'. sp_func_call_user_inside_fparen = ignore # ignore/add/remove/force # Add or remove space between nested parentheses with user functions, # i.e. '((' vs. '( ('. sp_func_call_user_paren_paren = ignore # ignore/add/remove/force # Add or remove space between a constructor/destructor and the open # parenthesis. sp_func_class_paren = ignore # ignore/add/remove/force # Add or remove space between a constructor without parameters or destructor # and '()'. sp_func_class_paren_empty = ignore # ignore/add/remove/force # Add or remove space between 'return' and '('. sp_return_paren = ignore # ignore/add/remove/force # Add or remove space between 'return' and '{'. sp_return_brace = ignore # ignore/add/remove/force # Add or remove space between '__attribute__' and '('. sp_attribute_paren = ignore # ignore/add/remove/force # Add or remove space between 'defined' and '(' in '#if defined (FOO)'. sp_defined_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and '(' in 'throw (something)'. sp_throw_paren = ignore # ignore/add/remove/force # Add or remove space between 'throw' and anything other than '(' as in # '@throw [...];'. sp_after_throw = ignore # ignore/add/remove/force # Add or remove space between 'catch' and '(' in 'catch (something) { }'. # If set to ignore, sp_before_sparen is used. sp_catch_paren = ignore # ignore/add/remove/force # (OC) Add or remove space between '@catch' and '(' # in '@catch (something) { }'. If set to ignore, sp_catch_paren is used. sp_oc_catch_paren = ignore # ignore/add/remove/force # (OC) Add or remove space before Objective-C protocol list # as in '@protocol Protocol' or '@interface MyClass : NSObject'. sp_before_oc_proto_list = ignore # ignore/add/remove/force # (OC) Add or remove space between class name and '(' # in '@interface className(categoryName):BaseClass' sp_oc_classname_paren = ignore # ignore/add/remove/force # (D) Add or remove space between 'version' and '(' # in 'version (something) { }'. If set to ignore, sp_before_sparen is used. sp_version_paren = ignore # ignore/add/remove/force # (D) Add or remove space between 'scope' and '(' # in 'scope (something) { }'. If set to ignore, sp_before_sparen is used. sp_scope_paren = ignore # ignore/add/remove/force # Add or remove space between 'super' and '(' in 'super (something)'. # # Default: remove sp_super_paren = remove # ignore/add/remove/force # Add or remove space between 'this' and '(' in 'this (something)'. # # Default: remove sp_this_paren = remove # ignore/add/remove/force # Add or remove space between a macro name and its definition. sp_macro = ignore # ignore/add/remove/force # Add or remove space between a macro function ')' and its definition. sp_macro_func = ignore # ignore/add/remove/force # Add or remove space between 'else' and '{' if on the same line. sp_else_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'else' if on the same line. sp_brace_else = ignore # ignore/add/remove/force # Add or remove space between '}' and the name of a typedef on the same line. sp_brace_typedef = ignore # ignore/add/remove/force # Add or remove space before the '{' of a 'catch' statement, if the '{' and # 'catch' are on the same line, as in 'catch (decl) {'. sp_catch_brace = ignore # ignore/add/remove/force # (OC) Add or remove space before the '{' of a '@catch' statement, if the '{' # and '@catch' are on the same line, as in '@catch (decl) {'. # If set to ignore, sp_catch_brace is used. sp_oc_catch_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'catch' if on the same line. sp_brace_catch = ignore # ignore/add/remove/force # (OC) Add or remove space between '}' and '@catch' if on the same line. # If set to ignore, sp_brace_catch is used. sp_oc_brace_catch = ignore # ignore/add/remove/force # Add or remove space between 'finally' and '{' if on the same line. sp_finally_brace = ignore # ignore/add/remove/force # Add or remove space between '}' and 'finally' if on the same line. sp_brace_finally = ignore # ignore/add/remove/force # Add or remove space between 'try' and '{' if on the same line. sp_try_brace = ignore # ignore/add/remove/force # Add or remove space between get/set and '{' if on the same line. sp_getset_brace = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for C++ uniform # initialization. sp_word_brace_init_lst = ignore # ignore/add/remove/force # Add or remove space between a variable and '{' for a namespace. # # Default: add sp_word_brace_ns = add # ignore/add/remove/force # Add or remove space before the '::' operator. sp_before_dc = ignore # ignore/add/remove/force # Add or remove space after the '::' operator. sp_after_dc = ignore # ignore/add/remove/force # (D) Add or remove around the D named array initializer ':' operator. sp_d_array_colon = ignore # ignore/add/remove/force # Add or remove space after the '!' (not) unary operator. # # Default: remove sp_not = remove # ignore/add/remove/force # Add or remove space after the '~' (invert) unary operator. # # Default: remove sp_inv = remove # ignore/add/remove/force # Add or remove space after the '&' (address-of) unary operator. This does not # affect the spacing after a '&' that is part of a type. # # Default: remove sp_addr = remove # ignore/add/remove/force # Add or remove space around the '.' or '->' operators. # # Default: remove sp_member = remove # ignore/add/remove/force # Add or remove space after the '*' (dereference) unary operator. This does # not affect the spacing after a '*' that is part of a type. # # Default: remove sp_deref = remove # ignore/add/remove/force # Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. # # Default: remove sp_sign = remove # ignore/add/remove/force # Add or remove space between '++' and '--' the word to which it is being # applied, as in '(--x)' or 'y++;'. # # Default: remove sp_incdec = remove # ignore/add/remove/force # Add or remove space before a backslash-newline at the end of a line. # # Default: add sp_before_nl_cont = add # ignore/add/remove/force # (OC) Add or remove space after the scope '+' or '-', as in '-(void) foo;' # or '+(int) bar;'. sp_after_oc_scope = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in message specs, # i.e. '-(int) f:(int) x;' vs. '-(int) f: (int) x;'. sp_after_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in message specs, # i.e. '-(int) f: (int) x;' vs. '-(int) f : (int) x;'. sp_before_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_after_oc_dict_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in immutable dictionary expression # 'NSDictionary *test = @{@"foo" :@"bar"};'. sp_before_oc_dict_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue: 1];'. sp_after_send_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space before the colon in message specs, # i.e. '[object setValue:1];' vs. '[object setValue :1];'. sp_before_send_oc_colon = ignore # ignore/add/remove/force # (OC) Add or remove space after the (type) in message specs, # i.e. '-(int)f: (int) x;' vs. '-(int)f: (int)x;'. sp_after_oc_type = ignore # ignore/add/remove/force # (OC) Add or remove space after the first (type) in message specs, # i.e. '-(int) f:(int)x;' vs. '-(int)f:(int)x;'. sp_after_oc_return_type = ignore # ignore/add/remove/force # (OC) Add or remove space between '@selector' and '(', # i.e. '@selector(msgName)' vs. '@selector (msgName)'. # Also applies to '@protocol()' constructs. sp_after_oc_at_sel = ignore # ignore/add/remove/force # (OC) Add or remove space between '@selector(x)' and the following word, # i.e. '@selector(foo) a:' vs. '@selector(foo)a:'. sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force # (OC) Add or remove space inside '@selector' parentheses, # i.e. '@selector(foo)' vs. '@selector( foo )'. # Also applies to '@protocol()' constructs. sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force # (OC) Add or remove space before a block pointer caret, # i.e. '^int (int arg){...}' vs. ' ^int (int arg){...}'. sp_before_oc_block_caret = ignore # ignore/add/remove/force # (OC) Add or remove space after a block pointer caret, # i.e. '^int (int arg){...}' vs. '^ int (int arg){...}'. sp_after_oc_block_caret = ignore # ignore/add/remove/force # (OC) Add or remove space between the receiver and selector in a message, # as in '[receiver selector ...]'. sp_after_oc_msg_receiver = ignore # ignore/add/remove/force # (OC) Add or remove space after '@property'. sp_after_oc_property = ignore # ignore/add/remove/force # (OC) Add or remove space between '@synchronized' and the open parenthesis, # i.e. '@synchronized(foo)' vs. '@synchronized (foo)'. sp_after_oc_synchronized = ignore # ignore/add/remove/force # Add or remove space around the ':' in 'b ? t : f'. sp_cond_colon = ignore # ignore/add/remove/force # Add or remove space before the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_before = ignore # ignore/add/remove/force # Add or remove space after the ':' in 'b ? t : f'. # # Overrides sp_cond_colon. sp_cond_colon_after = ignore # ignore/add/remove/force # Add or remove space around the '?' in 'b ? t : f'. sp_cond_question = ignore # ignore/add/remove/force # Add or remove space before the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_before = ignore # ignore/add/remove/force # Add or remove space after the '?' in 'b ? t : f'. # # Overrides sp_cond_question. sp_cond_question_after = ignore # ignore/add/remove/force # In the abbreviated ternary form '(a ?: b)', add or remove space between '?' # and ':'. # # Overrides all other sp_cond_* options. sp_cond_ternary_short = ignore # ignore/add/remove/force # Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make # sense here. sp_case_label = ignore # ignore/add/remove/force # (D) Add or remove space around the D '..' operator. sp_range = ignore # ignore/add/remove/force # Add or remove space after ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_after_for_colon = ignore # ignore/add/remove/force # Add or remove space before ':' in a Java/C++11 range-based 'for', # as in 'for (Type var : expr)'. sp_before_for_colon = ignore # ignore/add/remove/force # (D) Add or remove space between 'extern' and '(' as in 'extern (C)'. sp_extern_paren = ignore # ignore/add/remove/force # Add or remove space after the opening of a C++ comment, # i.e. '// A' vs. '//A'. sp_cmt_cpp_start = ignore # ignore/add/remove/force # If true, space is added with sp_cmt_cpp_start will be added after doxygen # sequences like '///', '///<', '//!' and '//!<'. sp_cmt_cpp_doxygen = false # true/false # If true, space is added with sp_cmt_cpp_start will be added after Qt # translator or meta-data comments like '//:', '//=', and '//~'. sp_cmt_cpp_qttr = false # true/false # Add or remove space between #else or #endif and a trailing comment. sp_endif_cmt = ignore # ignore/add/remove/force # Add or remove space after 'new', 'delete' and 'delete[]'. sp_after_new = ignore # ignore/add/remove/force # Add or remove space between 'new' and '(' in 'new()'. sp_between_new_paren = ignore # ignore/add/remove/force # Add or remove space between ')' and type in 'new(foo) BAR'. sp_after_newop_paren = ignore # ignore/add/remove/force # Add or remove space inside parenthesis of the new operator # as in 'new(foo) BAR'. sp_inside_newop_paren = ignore # ignore/add/remove/force # Add or remove space after the open parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_open = ignore # ignore/add/remove/force # Add or remove space before the close parenthesis of the new operator, # as in 'new(foo) BAR'. # # Overrides sp_inside_newop_paren. sp_inside_newop_paren_close = ignore # ignore/add/remove/force # Add or remove space before a trailing or embedded comment. sp_before_tr_emb_cmt = ignore # ignore/add/remove/force # Number of spaces before a trailing or embedded comment. sp_num_before_tr_emb_cmt = 1 # unsigned number # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force # If true, vbrace tokens are dropped to the previous token and skipped. sp_skip_vbrace_tokens = false # true/false # Add or remove space after 'noexcept'. sp_after_noexcept = ignore # ignore/add/remove/force # Add or remove space after '_'. sp_vala_after_translation = ignore # ignore/add/remove/force # If true, a is inserted after #define. force_tab_after_define = false # true/false # # Indenting options # # The number of columns to indent per level. Usually 2, 3, 4, or 8. # # Default: 8 indent_columns = 8 # unsigned number # The continuation indent. If non-zero, this overrides the indent of '(', '[' # and '=' continuation indents. Negative values are OK; negative value is # absolute and not increased for each '(' or '[' level. # # For FreeBSD, this is set to 4. indent_continue = 0 # number # The continuation indent, only for class header line(s). If non-zero, this # overrides the indent of 'class' continuation indents. indent_continue_class_head = 0 # unsigned number # Whether to indent empty lines (i.e. lines which contain only spaces before # the newline character). indent_single_newlines = false # true/false # The continuation indent for func_*_param if they are true. If non-zero, this # overrides the indent. indent_param = 0 # unsigned number # How to use tabs when indenting code. # # 0: Spaces only # 1: Indent with tabs to brace level, align with spaces (default) # 2: Indent and align with tabs, using spaces when not on a tabstop # # Default: 1 indent_with_tabs = 1 # unsigned number # Whether to indent comments that are not at a brace level with tabs on a # tabstop. Requires indent_with_tabs=2. If false, will use spaces. indent_cmt_with_tabs = false # true/false # Whether to indent strings broken by '\' so that they line up. indent_align_string = false # true/false # The number of spaces to indent multi-line XML strings. # Requires indent_align_string=true. indent_xml_string = 0 # unsigned number # Spaces to indent '{' from level. indent_brace = 0 # unsigned number # Whether braces are indented to the body level. indent_braces = false # true/false # Whether to disable indenting function braces if indent_braces=true. indent_braces_no_func = false # true/false # Whether to disable indenting class braces if indent_braces=true. indent_braces_no_class = false # true/false # Whether to disable indenting struct braces if indent_braces=true. indent_braces_no_struct = false # true/false # Whether to indent based on the size of the brace parent, # i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. indent_brace_parent = false # true/false # Whether to indent based on the open parenthesis instead of the open brace # in '({\n'. indent_paren_open_brace = false # true/false # (C#) Whether to indent the brace of a C# delegate by another level. indent_cs_delegate_brace = false # true/false # (C#) Whether to indent a C# delegate (to handle delegates with no brace) by # another level. indent_cs_delegate_body = false # true/false # Whether to indent the body of a 'namespace'. indent_namespace = false # true/false # Whether to indent only the first namespace, and not any nested namespaces. # Requires indent_namespace=true. indent_namespace_single_indent = false # true/false # The number of spaces to indent a namespace block. # If set to zero, use the value indent_columns indent_namespace_level = 0 # unsigned number # If the body of the namespace is longer than this number, it won't be # indented. Requires indent_namespace=true. 0 means no limit. indent_namespace_limit = 0 # unsigned number # Whether the 'extern "C"' body is indented. indent_extern = false # true/false # Whether the 'class' body is indented. indent_class = false # true/false # Whether to indent the stuff after a leading base class colon. indent_class_colon = false # true/false # Whether to indent based on a class colon instead of the stuff after the # colon. Requires indent_class_colon=true. indent_class_on_colon = false # true/false # Whether to indent the stuff after a leading class initializer colon. indent_constr_colon = false # true/false # Virtual indent from the ':' for member initializers. # # Default: 2 indent_ctor_init_leading = 2 # unsigned number # Additional indent for constructor initializer list. # Negative values decrease indent down to the first column. indent_ctor_init = 0 # number # Whether to indent 'if' following 'else' as a new block under the 'else'. # If false, 'else\nif' is treated as 'else if' for indenting purposes. indent_else_if = false # true/false # Amount to indent variable declarations after a open brace. # # <0: Relative # >=0: Absolute indent_var_def_blk = 0 # number # Whether to indent continued variable declarations instead of aligning. indent_var_def_cont = false # true/false # Whether to indent continued shift expressions ('<<' and '>>') instead of # aligning. Set align_left_shift=false when enabling this. indent_shift = false # true/false # Whether to force indentation of function definitions to start in column 1. indent_func_def_force_col1 = false # true/false # Whether to indent continued function call parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_call_param = false # true/false # Whether to indent continued function definition parameters one indent level, # rather than aligning parameters under the open parenthesis. indent_func_def_param = false # true/false # for function definitions, only if indent_func_def_param is false # Allows to align params when appropriate and indent them when not # behave as if it was true if paren position is more than this value # if paren position is more than the option value indent_func_def_param_paren_pos_threshold = 0 # unsigned number # Whether to indent continued function call prototype one indent level, # rather than aligning parameters under the open parenthesis. indent_func_proto_param = false # true/false # Whether to indent continued function call declaration one indent level, # rather than aligning parameters under the open parenthesis. indent_func_class_param = false # true/false # Whether to indent continued class variable constructors one indent level, # rather than aligning parameters under the open parenthesis. indent_func_ctor_var_param = false # true/false # Whether to indent continued template parameter list one indent level, # rather than aligning parameters under the open parenthesis. indent_template_param = false # true/false # Double the indent for indent_func_xxx_param options. # Use both values of the options indent_columns and indent_param. indent_func_param_double = false # true/false # Indentation column for standalone 'const' qualifier on a function # prototype. indent_func_const = 0 # unsigned number # Indentation column for standalone 'throw' qualifier on a function # prototype. indent_func_throw = 0 # unsigned number # How to indent within a macro followed by a brace on the same line # This allows reducing the indent in macros that have (for example) # `do { ... } while (0)` blocks bracketing them. # # true: add an indent for the brace on the same line as the macro # false: do not add an indent for the brace on the same line as the macro # # Default: true indent_macro_brace = true # true/false # The number of spaces to indent a continued '->' or '.'. # Usually set to 0, 1, or indent_columns. indent_member = 0 # unsigned number # Whether lines broken at '.' or '->' should be indented by a single indent. # The indent_member option will not be effective if this is set to true. indent_member_single = false # true/false # Spaces to indent single line ('//') comments on lines before code. indent_sing_line_comments = 0 # unsigned number # When opening a paren for a control statement (if, for, while, etc), increase # the indent level by this value. Negative values decrease the indent level. indent_sparen_extra = 0 # number # Whether to indent trailing single line ('//') comments relative to the code # instead of trying to keep the same absolute column. indent_relative_single_line_comments = false # true/false # Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. indent_switch_case = 0 # unsigned number # indent 'break' with 'case' from 'switch'. indent_switch_break_with_case = false # true/false # Whether to indent preprocessor statements inside of switch statements. # # Default: true indent_switch_pp = true # true/false # Spaces to shift the 'case' line, without affecting any other lines. # Usually 0. indent_case_shift = 0 # unsigned number # Spaces to indent '{' from 'case'. By default, the brace will appear under # the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. indent_case_brace = 0 # number # Whether to indent comments found in first column. indent_col1_comment = false # true/false # Whether to indent multi string literal in first column. indent_col1_multi_string_literal = false # true/false # How to indent goto labels. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_label = 1 # number # How to indent access specifiers that are followed by a # colon. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent # # Default: 1 indent_access_spec = 1 # number # Whether to indent the code after an access specifier by one level. # If true, this option forces 'indent_access_spec=0'. indent_access_spec_body = false # true/false # If an open parenthesis is followed by a newline, whether to indent the next # line so that it lines up after the open parenthesis (not recommended). indent_paren_nl = false # true/false # How to indent a close parenthesis after a newline. # # 0: Indent to body level (default) # 1: Align under the open parenthesis # 2: Indent to the brace level indent_paren_close = 0 # unsigned number # Whether to indent the open parenthesis of a function definition, # if the parenthesis is on its own line. indent_paren_after_func_def = false # true/false # Whether to indent the open parenthesis of a function declaration, # if the parenthesis is on its own line. indent_paren_after_func_decl = false # true/false # Whether to indent the open parenthesis of a function call, # if the parenthesis is on its own line. indent_paren_after_func_call = false # true/false # Whether to indent a comma when inside a parenthesis. # If true, aligns under the open parenthesis. indent_comma_paren = false # true/false # Whether to indent a Boolean operator when inside a parenthesis. # If true, aligns under the open parenthesis. indent_bool_paren = false # true/false # Whether to indent a semicolon when inside a for parenthesis. # If true, aligns under the open for parenthesis. indent_semicolon_for_paren = false # true/false # Whether to align the first expression to following ones # if indent_bool_paren=true. indent_first_bool_expr = false # true/false # Whether to align the first expression to following ones # if indent_semicolon_for_paren=true. indent_first_for_expr = false # true/false # If an open square is followed by a newline, whether to indent the next line # so that it lines up after the open square (not recommended). indent_square_nl = false # true/false # (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies. indent_preserve_sql = false # true/false # Whether to align continued statements at the '='. If false or if the '=' is # followed by a newline, the next line is indent one tab. # # Default: true indent_align_assign = true # true/false # If true, the indentation of the chunks after a '=' sequence will be set at # LHS token indentation column before '='. indent_off_after_assign = false # true/false # Whether to align continued statements at the '('. If false or the '(' is # followed by a newline, the next line indent is one tab. # # Default: true indent_align_paren = true # true/false # (OC) Whether to indent Objective-C code inside message selectors. indent_oc_inside_msg_sel = false # true/false # (OC) Whether to indent Objective-C blocks at brace level instead of usual # rules. indent_oc_block = false # true/false # (OC) Indent for Objective-C blocks in a message relative to the parameter # name. # # =0: Use indent_oc_block rules # >0: Use specified number of spaces to indent indent_oc_block_msg = 0 # unsigned number # (OC) Minimum indent for subsequent parameters indent_oc_msg_colon = 0 # unsigned number # (OC) Whether to prioritize aligning with initial colon (and stripping spaces # from lines, if necessary). # # Default: true indent_oc_msg_prioritize_first_colon = true # true/false # (OC) Whether to indent blocks the way that Xcode does by default # (from the keyword if the parameter is on its own line; otherwise, from the # previous indentation level). Requires indent_oc_block_msg=true. indent_oc_block_msg_xcode_style = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a # message keyword. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_keyword = false # true/false # (OC) Whether to indent blocks from where the brace is, relative to a message # colon. Requires indent_oc_block_msg=true. indent_oc_block_msg_from_colon = false # true/false # (OC) Whether to indent blocks from where the block caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_caret = false # true/false # (OC) Whether to indent blocks from where the brace caret is. # Requires indent_oc_block_msg=true. indent_oc_block_msg_from_brace = false # true/false # When indenting after virtual brace open and newline add further spaces to # reach this minimum indent. indent_min_vbrace_open = 0 # unsigned number # Whether to add further spaces after regular indent to reach next tabstop # when indenting after virtual brace open and newline. indent_vbrace_open_on_tabstop = false # true/false # How to indent after a brace followed by another token (not a newline). # true: indent all contained lines to match the token # false: indent all contained lines to match the brace # # Default: true indent_token_after_brace = true # true/false # Whether to indent the body of a C++11 lambda. indent_cpp_lambda_body = false # true/false # How to indent compound literals that are being returned. # true: add both the indent from return & the compound literal open brace (ie: # 2 indent levels) # false: only indent 1 level, don't add the indent for the open brace, only add # the indent for the return. # # Default: true indent_compound_literal_return = true # true/false # (C#) Whether to indent a 'using' block if no braces are used. # # Default: true indent_using_block = true # true/false # How to indent the continuation of ternary operator. # # 0: Off (default) # 1: When the `if_false` is a continuation, indent it under `if_false` # 2: When the `:` is a continuation, indent it under `?` indent_ternary_operator = 0 # unsigned number # Whether to indent the statments inside ternary operator. indent_inside_ternary_operator = false # true/false # If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. indent_off_after_return = false # true/false # If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. indent_off_after_return_new = false # true/false # If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token. indent_single_after_return = false # true/false # Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they # have their own indentation). indent_ignore_asm_block = false # true/false # Don't indent the close parenthesis of a function definition, # if the parenthesis is on its own line. donot_indent_func_def_close_paren = false # true/false # # Newline adding and removing options # # Whether to collapse empty blocks between '{' and '}'. # If true, overrides nl_inside_empty_func nl_collapse_empty_body = false # true/false # Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'. nl_assign_leave_one_liners = false # true/false # Don't split one-line braced statements inside a 'class xx { }' body. nl_class_leave_one_liners = false # true/false # Don't split one-line enums, as in 'enum foo { BAR = 15 };' nl_enum_leave_one_liners = false # true/false # Don't split one-line get or set functions. nl_getset_leave_one_liners = false # true/false # (C#) Don't split one-line property get or set functions. nl_cs_property_leave_one_liners = false # true/false # Don't split one-line function definitions, as in 'int foo() { return 0; }'. # might modify nl_func_type_name nl_func_leave_one_liners = false # true/false # Don't split one-line C++11 lambdas, as in '[]() { return 0; }'. nl_cpp_lambda_leave_one_liners = false # true/false # Don't split one-line if/else statements, as in 'if(...) b++;'. nl_if_leave_one_liners = false # true/false # Don't split one-line while statements, as in 'while(...) b++;'. nl_while_leave_one_liners = false # true/false # Don't split one-line for statements, as in 'for(...) b++;'. nl_for_leave_one_liners = false # true/false # (OC) Don't split one-line Objective-C messages. nl_oc_msg_leave_one_liner = false # true/false # (OC) Add or remove newline between method declaration and '{'. nl_oc_mdef_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline between Objective-C block signature and '{'. nl_oc_block_brace = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@interface' statement. nl_oc_before_interface = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@implementation' statement. nl_oc_before_implementation = ignore # ignore/add/remove/force # (OC) Add or remove blank line before '@end' statement. nl_oc_before_end = ignore # ignore/add/remove/force # (OC) Add or remove newline between '@interface' and '{'. nl_oc_interface_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline between '@implementation' and '{'. nl_oc_implementation_brace = ignore # ignore/add/remove/force # Add or remove newlines at the start of the file. nl_start_of_file = ignore # ignore/add/remove/force # The minimum number of newlines at the start of the file (only used if # nl_start_of_file is 'add' or 'force'). nl_start_of_file_min = 0 # unsigned number # Add or remove newline at the end of the file. nl_end_of_file = ignore # ignore/add/remove/force # The minimum number of newlines at the end of the file (only used if # nl_end_of_file is 'add' or 'force'). nl_end_of_file_min = 0 # unsigned number # Add or remove newline between '=' and '{'. nl_assign_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force # Add or remove newline between '[]' and '{'. nl_tsquare_brace = ignore # ignore/add/remove/force # (D) Add or remove newline after '= ['. Will also affect the newline before # the ']'. nl_after_square_assign = ignore # ignore/add/remove/force # Add or remove newline between a function call's ')' and '{', as in # 'list_for_each(item, &list) { }'. nl_fcall_brace = ignore # ignore/add/remove/force # Add or remove newline between 'enum' and '{'. nl_enum_brace = ignore # ignore/add/remove/force # Add or remove newline between 'enum' and 'class'. nl_enum_class = ignore # ignore/add/remove/force # Add or remove newline between 'enum class' and the identifier. nl_enum_class_identifier = ignore # ignore/add/remove/force # Add or remove newline between 'enum class' type and ':'. nl_enum_identifier_colon = ignore # ignore/add/remove/force # Add or remove newline between 'enum class identifier :' and type. nl_enum_colon_type = ignore # ignore/add/remove/force # Add or remove newline between 'struct and '{'. nl_struct_brace = ignore # ignore/add/remove/force # Add or remove newline between 'union' and '{'. nl_union_brace = ignore # ignore/add/remove/force # Add or remove newline between 'if' and '{'. nl_if_brace = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'else'. nl_brace_else = ignore # ignore/add/remove/force # Add or remove newline between 'else if' and '{'. If set to ignore, # nl_if_brace is used instead. nl_elseif_brace = ignore # ignore/add/remove/force # Add or remove newline between 'else' and '{'. nl_else_brace = ignore # ignore/add/remove/force # Add or remove newline between 'else' and 'if'. nl_else_if = ignore # ignore/add/remove/force # Add or remove newline before '{' opening brace nl_before_opening_brace_func_class_def = ignore # ignore/add/remove/force # Add or remove newline before 'if'/'else if' closing parenthesis. nl_before_if_closing_paren = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'finally'. nl_brace_finally = ignore # ignore/add/remove/force # Add or remove newline between 'finally' and '{'. nl_finally_brace = ignore # ignore/add/remove/force # Add or remove newline between 'try' and '{'. nl_try_brace = ignore # ignore/add/remove/force # Add or remove newline between get/set and '{'. nl_getset_brace = ignore # ignore/add/remove/force # Add or remove newline between 'for' and '{'. nl_for_brace = ignore # ignore/add/remove/force # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) {'. nl_catch_brace = ignore # ignore/add/remove/force # (OC) Add or remove newline before the '{' of a '@catch' statement, as in # '@catch (decl) {'. If set to ignore, nl_catch_brace is used. nl_oc_catch_brace = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'catch'. nl_brace_catch = ignore # ignore/add/remove/force # (OC) Add or remove newline between '}' and '@catch'. If set to ignore, # nl_brace_catch is used. nl_oc_brace_catch = ignore # ignore/add/remove/force # Add or remove newline between '}' and ']'. nl_brace_square = ignore # ignore/add/remove/force # Add or remove newline between '}' and ')' in a function invocation. nl_brace_fparen = ignore # ignore/add/remove/force # Add or remove newline between 'while' and '{'. nl_while_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between 'unittest' and '{'. nl_unittest_brace = ignore # ignore/add/remove/force # (D) Add or remove newline between 'version (x)' and '{'. nl_version_brace = ignore # ignore/add/remove/force # (C#) Add or remove newline between 'using' and '{'. nl_using_brace = ignore # ignore/add/remove/force # Add or remove newline between two open or close braces. Due to general # newline/brace handling, REMOVE may not work. nl_brace_brace = ignore # ignore/add/remove/force # Add or remove newline between 'do' and '{'. nl_do_brace = ignore # ignore/add/remove/force # Add or remove newline between '}' and 'while' of 'do' statement. nl_brace_while = ignore # ignore/add/remove/force # Add or remove newline between 'switch' and '{'. nl_switch_brace = ignore # ignore/add/remove/force # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = ignore # ignore/add/remove/force # Add a newline between ')' and '{' if the ')' is on a different line than the # if/for/etc. # # Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and # nl_catch_brace. nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines nl_multi_line_sparen_open = ignore # ignore/add/remove/force # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. nl_multi_line_sparen_close = ignore # ignore/add/remove/force # Force a newline in a define after the macro name for multi-line defines. nl_multi_line_define = false # true/false # Whether to add a newline before 'case', and a blank line before a 'case' # statement that follows a ';' or '}'. nl_before_case = false # true/false # Whether to add a newline after a 'case' statement. nl_after_case = false # true/false # Add or remove newline between a case ':' and '{'. # # Overrides nl_after_case. nl_case_colon_brace = ignore # ignore/add/remove/force # Add or remove newline between ')' and 'throw'. nl_before_throw = ignore # ignore/add/remove/force # Add or remove newline between 'namespace' and '{'. nl_namespace_brace = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class. nl_template_class = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class declaration. # # Overrides nl_template_class. nl_template_class_decl = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized class declaration. # # Overrides nl_template_class_decl. nl_template_class_decl_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template class definition. # # Overrides nl_template_class. nl_template_class_def = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized class definition. # # Overrides nl_template_class_def. nl_template_class_def_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function. nl_template_func = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function # declaration. # # Overrides nl_template_func. nl_template_func_decl = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized function # declaration. # # Overrides nl_template_func_decl. nl_template_func_decl_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template function # definition. # # Overrides nl_template_func. nl_template_func_def = ignore # ignore/add/remove/force # Add or remove newline after 'template<>' of a specialized function # definition. # # Overrides nl_template_func_def. nl_template_func_def_special = ignore # ignore/add/remove/force # Add or remove newline after 'template<...>' of a template variable. nl_template_var = ignore # ignore/add/remove/force # Add or remove newline between 'template<...>' and 'using' of a templated # type alias. nl_template_using = ignore # ignore/add/remove/force # Add or remove newline between 'class' and '{'. nl_class_brace = ignore # ignore/add/remove/force # Add or remove newline before or after (depending on pos_class_comma, # may not be IGNORE) each',' in the base class list. nl_class_init_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in the constructor member # initialization. Related to nl_constr_colon, pos_constr_colon and # pos_constr_comma. nl_constr_init_args = ignore # ignore/add/remove/force # Add or remove newline before first element, after comma, and after last # element, in 'enum'. nl_enum_own_lines = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a function # definition. # might be modified by nl_func_leave_one_liners nl_func_type_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name inside a class # definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name # is used instead. nl_func_type_name_class = ignore # ignore/add/remove/force # Add or remove newline between class specification and '::' # in 'void A::f() { }'. Only appears in separate member implementation (does # not appear with in-line implementation). nl_func_class_scope = ignore # ignore/add/remove/force # Add or remove newline between function scope and name, as in # 'void A :: f() { }'. nl_func_scope_name = ignore # ignore/add/remove/force # Add or remove newline between return type and function name in a prototype. nl_func_proto_type_name = ignore # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # declaration. nl_func_paren = ignore # ignore/add/remove/force # Overrides nl_func_paren for functions with no parameters. nl_func_paren_empty = ignore # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # definition. nl_func_def_paren = ignore # ignore/add/remove/force # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = ignore # ignore/add/remove/force # Add or remove newline between a function name and the opening '(' in the # call. nl_func_call_paren = ignore # ignore/add/remove/force # Overrides nl_func_call_paren for functions with no parameters. nl_func_call_paren_empty = ignore # ignore/add/remove/force # Add or remove newline after '(' in a function declaration. nl_func_decl_start = ignore # ignore/add/remove/force # Add or remove newline after '(' in a function definition. nl_func_def_start = ignore # ignore/add/remove/force # Overrides nl_func_decl_start when there is only one parameter. nl_func_decl_start_single = ignore # ignore/add/remove/force # Overrides nl_func_def_start when there is only one parameter. nl_func_def_start_single = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_start is used instead. nl_func_decl_start_multi_line = false # true/false # Whether to add a newline after '(' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_start is used instead. nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. nl_func_decl_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function definition. nl_func_def_args = ignore # ignore/add/remove/force # Add or remove newline after each ',' in a function call. nl_func_call_args = ignore # ignore/add/remove/force # Whether to add a newline after each ',' in a function declaration if '(' # and ')' are in different lines. If false, nl_func_decl_args is used instead. nl_func_decl_args_multi_line = false # true/false # Whether to add a newline after each ',' in a function definition if '(' # and ')' are in different lines. If false, nl_func_def_args is used instead. nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. nl_func_decl_end = ignore # ignore/add/remove/force # Add or remove newline before the ')' in a function definition. nl_func_def_end = ignore # ignore/add/remove/force # Overrides nl_func_decl_end when there is only one parameter. nl_func_decl_end_single = ignore # ignore/add/remove/force # Overrides nl_func_def_end when there is only one parameter. nl_func_def_end_single = ignore # ignore/add/remove/force # Whether to add a newline before ')' in a function declaration if '(' and ')' # are in different lines. If false, nl_func_decl_end is used instead. nl_func_decl_end_multi_line = false # true/false # Whether to add a newline before ')' in a function definition if '(' and ')' # are in different lines. If false, nl_func_def_end is used instead. nl_func_def_end_multi_line = false # true/false # Add or remove newline between '()' in a function declaration. nl_func_decl_empty = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function definition. nl_func_def_empty = ignore # ignore/add/remove/force # Add or remove newline between '()' in a function call. nl_func_call_empty = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function call, # has preference over nl_func_call_start_multi_line. nl_func_call_start = ignore # ignore/add/remove/force # Whether to add a newline before ')' in a function call. nl_func_call_end = ignore # ignore/add/remove/force # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. nl_func_call_start_multi_line = false # true/false # Whether to add a newline after each ',' in a function call if '(' and ')' # are in different lines. nl_func_call_args_multi_line = false # true/false # Whether to add a newline before ')' in a function call if '(' and ')' are in # different lines. nl_func_call_end_multi_line = false # true/false # Whether to respect nl_func_call_XXX option incase of closure args. nl_func_call_args_multi_line_ignore_closures = false # true/false # Whether to add a newline after '<' of a template parameter list. nl_template_start = false # true/false # Whether to add a newline after each ',' in a template parameter list. nl_template_args = false # true/false # Whether to add a newline before '>' of a template parameter list. nl_template_end = false # true/false # (OC) Whether to put each Objective-C message parameter on a separate line. # See nl_oc_msg_leave_one_liner. nl_oc_msg_args = false # true/false # Add or remove newline between function signature and '{'. nl_fdef_brace = ignore # ignore/add/remove/force # Add or remove newline between function signature and '{', # if signature ends with ')'. Overrides nl_fdef_brace. nl_fdef_brace_cond = ignore # ignore/add/remove/force # Add or remove newline between C++11 lambda signature and '{'. nl_cpp_ldef_brace = ignore # ignore/add/remove/force # Add or remove newline between 'return' and the return expression. nl_return_expr = ignore # ignore/add/remove/force # Whether to add a newline after semicolons, except in 'for' statements. nl_after_semicolon = false # true/false # (Java) Add or remove newline between the ')' and '{{' of the double brace # initializer. nl_paren_dbrace_open = ignore # ignore/add/remove/force # Whether to add a newline after the type in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst = ignore # ignore/add/remove/force # Whether to add a newline after the open brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_open = ignore # ignore/add/remove/force # Whether to add a newline before the close brace in an unnamed temporary # direct-list-initialization. nl_type_brace_init_lst_close = ignore # ignore/add/remove/force # Whether to add a newline after '{'. This also adds a newline before the # matching '}'. nl_after_brace_open = false # true/false # Whether to add a newline between the open brace and a trailing single-line # comment. Requires nl_after_brace_open=true. nl_after_brace_open_cmt = false # true/false # Whether to add a newline after a virtual brace open with a non-empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open = false # true/false # Whether to add a newline after a virtual brace open with an empty body. # These occur in un-braced if/while/do/for statement bodies. nl_after_vbrace_open_empty = false # true/false # Whether to add a newline after '}'. Does not apply if followed by a # necessary ';'. nl_after_brace_close = false # true/false # Whether to add a newline after a virtual brace close, # as in 'if (foo) a++; return;'. nl_after_vbrace_close = false # true/false # Add or remove newline between the close brace and identifier, # as in 'struct { int a; } b;'. Affects enumerations, unions and # structures. If set to ignore, uses nl_after_brace_close. nl_brace_struct_var = ignore # ignore/add/remove/force # Whether to alter newlines in '#define' macros. nl_define_macro = false # true/false # Whether to alter newlines between consecutive parenthesis closes. The number # of closing parentheses in a line will depend on respective open parenthesis # lines. nl_squeeze_paren_close = false # true/false # Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and # '#endif'. Does not affect top-level #ifdefs. nl_squeeze_ifdef = false # true/false # Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. nl_squeeze_ifdef_top_level = false # true/false # Add or remove blank line before 'if'. nl_before_if = ignore # ignore/add/remove/force # Add or remove blank line after 'if' statement. Add/Force work only if the # next token is not a closing brace. nl_after_if = ignore # ignore/add/remove/force # Add or remove blank line before 'for'. nl_before_for = ignore # ignore/add/remove/force # Add or remove blank line after 'for' statement. nl_after_for = ignore # ignore/add/remove/force # Add or remove blank line before 'while'. nl_before_while = ignore # ignore/add/remove/force # Add or remove blank line after 'while' statement. nl_after_while = ignore # ignore/add/remove/force # Add or remove blank line before 'switch'. nl_before_switch = ignore # ignore/add/remove/force # Add or remove blank line after 'switch' statement. nl_after_switch = ignore # ignore/add/remove/force # Add or remove blank line before 'synchronized'. nl_before_synchronized = ignore # ignore/add/remove/force # Add or remove blank line after 'synchronized' statement. nl_after_synchronized = ignore # ignore/add/remove/force # Add or remove blank line before 'do'. nl_before_do = ignore # ignore/add/remove/force # Add or remove blank line after 'do/while' statement. nl_after_do = ignore # ignore/add/remove/force # Whether to put a blank line before 'return' statements, unless after an open # brace. nl_before_return = false # true/false # Whether to put a blank line after 'return' statements, unless followed by a # close brace. nl_after_return = false # true/false # Whether to put a blank line before a member '.' or '->' operators. nl_before_member = ignore # ignore/add/remove/force # (Java) Whether to put a blank line after a member '.' or '->' operators. nl_after_member = ignore # ignore/add/remove/force # Whether to double-space commented-entries in 'struct'/'union'/'enum'. nl_ds_struct_enum_cmt = false # true/false # Whether to force a newline before '}' of a 'struct'/'union'/'enum'. # (Lower priority than eat_blanks_before_close_brace.) nl_ds_struct_enum_close_brace = false # true/false # Add or remove newline before or after (depending on pos_class_colon) a class # colon, as in 'class Foo : public Bar'. nl_class_colon = ignore # ignore/add/remove/force # Add or remove newline around a class constructor colon. The exact position # depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma. nl_constr_colon = ignore # ignore/add/remove/force # Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }' # into a single line. If true, prevents other brace newline rules from turning # such code into four lines. nl_namespace_two_to_one_liner = false # true/false # Whether to remove a newline in simple unbraced if statements, turning them # into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'. nl_create_if_one_liner = false # true/false # Whether to remove a newline in simple unbraced for statements, turning them # into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'. nl_create_for_one_liner = false # true/false # Whether to remove a newline in simple unbraced while statements, turning # them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'. nl_create_while_one_liner = false # true/false # Whether to collapse a function definition whose body (not counting braces) # is only one line so that the entire definition (prototype, braces, body) is # a single line. nl_create_func_def_one_liner = false # true/false # Whether to collapse a function definition whose body (not counting braces) # is only one line so that the entire definition (prototype, braces, body) is # a single line. nl_create_list_one_liner = false # true/false # Whether to split one-line simple unbraced if statements into two lines by # adding a newline, as in 'if(b) i++;'. nl_split_if_one_liner = false # true/false # Whether to split one-line simple unbraced for statements into two lines by # adding a newline, as in 'for (...) stmt;'. nl_split_for_one_liner = false # true/false # Whether to split one-line simple unbraced while statements into two lines by # adding a newline, as in 'while (expr) stmt;'. nl_split_while_one_liner = false # true/false # Don't add a newline before a cpp-comment in a parameter list of a function # call. donot_add_nl_before_cpp_comment = false # true/false # # Blank line options # # The maximum number of consecutive newlines (3 = 2 blank lines). nl_max = 0 # unsigned number # The maximum number of consecutive newlines in a function. nl_max_blank_in_func = 0 # unsigned number # The number of newlines inside an empty function body. # This option is overridden by nl_collapse_empty_body=true nl_inside_empty_func = 0 # unsigned number # The number of newlines before a function prototype. nl_before_func_body_proto = 0 # unsigned number # The number of newlines before a multi-line function definition. nl_before_func_body_def = 0 # unsigned number # The number of newlines before a class constructor/destructor prototype. nl_before_func_class_proto = 0 # unsigned number # The number of newlines before a class constructor/destructor definition. nl_before_func_class_def = 0 # unsigned number # The number of newlines after a function prototype. nl_after_func_proto = 0 # unsigned number # The number of newlines after a function prototype, if not followed by # another function prototype. nl_after_func_proto_group = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype. nl_after_func_class_proto = 0 # unsigned number # The number of newlines after a class constructor/destructor prototype, # if not followed by another constructor/destructor prototype. nl_after_func_class_proto_group = 0 # unsigned number # Whether one-line method definitions inside a class body should be treated # as if they were prototypes for the purposes of adding newlines. # # Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def # and nl_before_func_class_def for one-liners. nl_class_leave_one_liner_groups = false # true/false # The number of newlines after '}' of a multi-line function body. nl_after_func_body = 0 # unsigned number # The number of newlines after '}' of a multi-line function body in a class # declaration. Also affects class constructors/destructors. # # Overrides nl_after_func_body. nl_after_func_body_class = 0 # unsigned number # The number of newlines after '}' of a single line function body. Also # affects class constructors/destructors. # # Overrides nl_after_func_body and nl_after_func_body_class. nl_after_func_body_one_liner = 0 # unsigned number # The number of blank lines after a block of variable definitions at the top # of a function body. # # 0: No change (default). nl_func_var_def_blk = 0 # unsigned number # The number of newlines before a block of typedefs. If nl_after_access_spec # is non-zero, that option takes precedence. # # 0: No change (default). nl_typedef_blk_start = 0 # unsigned number # The number of newlines after a block of typedefs. # # 0: No change (default). nl_typedef_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of typedefs. # # 0: No change (default). nl_typedef_blk_in = 0 # unsigned number # The number of newlines before a block of variable definitions not at the top # of a function body. If nl_after_access_spec is non-zero, that option takes # precedence. # # 0: No change (default). nl_var_def_blk_start = 0 # unsigned number # The number of newlines after a block of variable definitions not at the top # of a function body. # # 0: No change (default). nl_var_def_blk_end = 0 # unsigned number # The maximum number of consecutive newlines within a block of variable # definitions. # # 0: No change (default). nl_var_def_blk_in = 0 # unsigned number # The minimum number of newlines before a multi-line comment. # Doesn't apply if after a brace open or another multi-line comment. nl_before_block_comment = 0 # unsigned number # The minimum number of newlines before a single-line C comment. # Doesn't apply if after a brace open or other single-line C comments. nl_before_c_comment = 0 # unsigned number # The minimum number of newlines before a CPP comment. # Doesn't apply if after a brace open or other CPP comments. nl_before_cpp_comment = 0 # unsigned number # Whether to force a newline after a multi-line comment. nl_after_multiline_comment = false # true/false # Whether to force a newline after a label's colon. nl_after_label_colon = false # true/false # The number of newlines after '}' or ';' of a struct/enum/union definition. nl_after_struct = 0 # unsigned number # The number of newlines before a class definition. nl_before_class = 0 # unsigned number # The number of newlines after '}' or ';' of a class definition. nl_after_class = 0 # unsigned number # The number of newlines before a namespace. nl_before_namespace = 0 # unsigned number # The number of newlines after '{' of a namespace. This also adds newlines # before the matching '}'. # # 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if # applicable, otherwise no change. # # Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace. nl_inside_namespace = 0 # unsigned number # The number of newlines after '}' of a namespace. nl_after_namespace = 0 # unsigned number # The number of newlines before an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). nl_before_access_spec = 0 # unsigned number # The number of newlines after an access specifier label. This also includes # the Qt-specific 'signals:' and 'slots:'. Will not change the newline count # if after a brace open. # # 0: No change (default). # # Overrides nl_typedef_blk_start and nl_var_def_blk_start. nl_after_access_spec = 0 # unsigned number # The number of newlines between a function definition and the function # comment, as in '// comment\n void foo() {...}'. # # 0: No change (default). nl_comment_func_def = 0 # unsigned number # The number of newlines after a try-catch-finally block that isn't followed # by a brace close. # # 0: No change (default). nl_after_try_catch_finally = 0 # unsigned number # (C#) The number of newlines before and after a property, indexer or event # declaration. # # 0: No change (default). nl_around_cs_property = 0 # unsigned number # (C#) The number of newlines between the get/set/add/remove handlers. # # 0: No change (default). nl_between_get_set = 0 # unsigned number # (C#) Add or remove newline between property and the '{'. nl_property_brace = ignore # ignore/add/remove/force # Whether to remove blank lines after '{'. eat_blanks_after_open_brace = false # true/false # Whether to remove blank lines before '}'. eat_blanks_before_close_brace = false # true/false # How aggressively to remove extra newlines not in preprocessor. # # 0: No change (default) # 1: Remove most newlines not handled by other config # 2: Remove all newlines and reformat completely by config nl_remove_extra_newlines = 0 # unsigned number # (Java) Add or remove newline after an annotation statement. Only affects # annotations that are after a newline. nl_after_annotation = ignore # ignore/add/remove/force # (Java) Add or remove newline between two annotations. nl_between_annotation = ignore # ignore/add/remove/force # The number of newlines before a whole-file #ifdef. # # 0: No change (default). nl_before_whole_file_ifdef = 0 # unsigned number # The number of newlines after a whole-file #ifdef. # # 0: No change (default). nl_after_whole_file_ifdef = 0 # unsigned number # The number of newlines before a whole-file #endif. # # 0: No change (default). nl_before_whole_file_endif = 0 # unsigned number # The number of newlines after a whole-file #endif. # # 0: No change (default). nl_after_whole_file_endif = 0 # unsigned number # # Positioning options # # The position of arithmetic operators in wrapped expressions. pos_arith = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of assignment in wrapped expressions. Do not affect '=' # followed by '{'. pos_assign = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of Boolean operators in wrapped expressions. pos_bool = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of comparison operators in wrapped expressions. pos_compare = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of conditional operators, as in the '?' and ':' of # 'expr ? stmt : stmt', in wrapped expressions. pos_conditional = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in wrapped expressions. pos_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in enum entries. pos_enum_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the base class list if there is more than one # line. Affects nl_class_init_args. pos_class_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of the comma in the constructor initialization list. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. pos_constr_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of trailing/leading class colon, between class and base class # list. Affects nl_class_colon. pos_class_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of colons between constructor and member initialization. # Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. pos_constr_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # The position of shift operators in wrapped expressions. pos_shift = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force # # Line splitting options # # Try to limit code width to N columns. code_width = 0 # unsigned number # Whether to fully split long 'for' statements at semi-colons. ls_for_split_full = false # true/false # Whether to fully split long function prototypes/calls at commas. # The option ls_code_width has priority over the option ls_func_split_full. ls_func_split_full = false # true/false # Whether to split lines as close to code_width as possible and ignore some # groupings. # The option ls_code_width has priority over the option ls_func_split_full. ls_code_width = false # true/false # # Code alignment options (not left column spaces/tabs) # # Whether to keep non-indenting tabs. align_keep_tabs = false # true/false # Whether to use tabs for aligning. align_with_tabs = false # true/false # Whether to bump out to the next tab when aligning. align_on_tabstop = false # true/false # Whether to right-align numbers. align_number_right = false # true/false # Whether to keep whitespace not required for alignment. align_keep_extra_space = false # true/false # Whether to align variable definitions in prototypes and functions. align_func_params = false # true/false # The span for aligning parameter definitions in function on parameter name. # # 0: Don't align (default). align_func_params_span = 0 # unsigned number # The threshold for aligning function parameter definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_params_thresh = 0 # number # The gap for aligning function parameter definitions. align_func_params_gap = 0 # unsigned number # The span for aligning constructor value. # # 0: Don't align (default). align_constr_value_span = 0 # unsigned number # The threshold for aligning constructor value. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_constr_value_thresh = 0 # number # The gap for aligning constructor value. align_constr_value_gap = 0 # unsigned number # Whether to align parameters in single-line functions that have the same # name. The function names must already be aligned with each other. align_same_func_call_params = false # true/false # The span for aligning function-call parameters for single line functions. # # 0: Don't align (default). align_same_func_call_params_span = 0 # unsigned number # The threshold for aligning function-call parameters for single line # functions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_same_func_call_params_thresh = 0 # number # The span for aligning variable definitions. # # 0: Don't align (default). align_var_def_span = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of variable definitions. # # 0: Part of the type 'void * foo;' (default) # 1: Part of the variable 'void *foo;' # 2: Dangling 'void *foo;' # Dangling: the '*' will not be taken into account when aligning. align_var_def_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of variable definitions. # # 0: Part of the type 'long & foo;' (default) # 1: Part of the variable 'long &foo;' # 2: Dangling 'long &foo;' # Dangling: the '&' will not be taken into account when aligning. align_var_def_amp_style = 0 # unsigned number # The threshold for aligning variable definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_def_thresh = 0 # number # The gap for aligning variable definitions. align_var_def_gap = 0 # unsigned number # Whether to align the colon in struct bit fields. align_var_def_colon = false # true/false # The gap for aligning the colon in struct bit fields. align_var_def_colon_gap = 0 # unsigned number # Whether to align any attribute after the variable name. align_var_def_attribute = false # true/false # Whether to align inline struct/enum/union variable definitions. align_var_def_inline = false # true/false # The span for aligning on '=' in assignments. # # 0: Don't align (default). align_assign_span = 0 # unsigned number # The span for aligning on '=' in function prototype modifier. # # 0: Don't align (default). align_assign_func_proto_span = 0 # unsigned number # The threshold for aligning on '=' in assignments. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_assign_thresh = 0 # number # How to apply align_assign_span to function declaration "assignments", i.e. # 'virtual void foo() = 0' or '~foo() = {default|delete}'. # # 0: Align with other assignments (default) # 1: Align with each other, ignoring regular assignments # 2: Don't align align_assign_decl_func = 0 # unsigned number # The span for aligning on '=' in enums. # # 0: Don't align (default). align_enum_equ_span = 0 # unsigned number # The threshold for aligning on '=' in enums. # Use a negative number for absolute thresholds. # # 0: no limit (default). align_enum_equ_thresh = 0 # number # The span for aligning class member definitions. # # 0: Don't align (default). align_var_class_span = 0 # unsigned number # The threshold for aligning class member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_class_thresh = 0 # number # The gap for aligning class member definitions. align_var_class_gap = 0 # unsigned number # The span for aligning struct/union member definitions. # # 0: Don't align (default). align_var_struct_span = 0 # unsigned number # The threshold for aligning struct/union member definitions. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_var_struct_thresh = 0 # number # The gap for aligning struct/union member definitions. align_var_struct_gap = 0 # unsigned number # The span for aligning struct initializer values. # # 0: Don't align (default). align_struct_init_span = 0 # unsigned number # The span for aligning single-line typedefs. # # 0: Don't align (default). align_typedef_span = 0 # unsigned number # The minimum space between the type and the synonym of a typedef. align_typedef_gap = 0 # unsigned number # How to align typedef'd functions with other typedefs. # # 0: Don't mix them at all (default) # 1: Align the open parenthesis with the types # 2: Align the function type name with the other type names align_typedef_func = 0 # unsigned number # How to consider (or treat) the '*' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int * pint;' (default) # 1: Part of type name: 'typedef int *pint;' # 2: Dangling: 'typedef int *pint;' # Dangling: the '*' will not be taken into account when aligning. align_typedef_star_style = 0 # unsigned number # How to consider (or treat) the '&' in the alignment of typedefs. # # 0: Part of the typedef type, 'typedef int & intref;' (default) # 1: Part of type name: 'typedef int &intref;' # 2: Dangling: 'typedef int &intref;' # Dangling: the '&' will not be taken into account when aligning. align_typedef_amp_style = 0 # unsigned number # The span for aligning comments that end lines. # # 0: Don't align (default). align_right_cmt_span = 0 # unsigned number # Minimum number of columns between preceding text and a trailing comment in # order for the comment to qualify for being aligned. Must be non-zero to have # an effect. align_right_cmt_gap = 0 # unsigned number # If aligning comments, whether to mix with comments after '}' and #endif with # less than three spaces before the comment. align_right_cmt_mix = false # true/false # Whether to only align trailing comments that are at the same brace level. align_right_cmt_same_level = false # true/false # Minimum column at which to align trailing comments. Comments which are # aligned beyond this column, but which can be aligned in a lesser column, # may be "pulled in". # # 0: Ignore (default). align_right_cmt_at_col = 0 # unsigned number # The span for aligning function prototypes. # # 0: Don't align (default). align_func_proto_span = 0 # unsigned number # The threshold for aligning function prototypes. # Use a negative number for absolute thresholds. # # 0: No limit (default). align_func_proto_thresh = 0 # number # Minimum gap between the return type and the function name. align_func_proto_gap = 0 # unsigned number # Whether to align function prototypes on the 'operator' keyword instead of # what follows. align_on_operator = false # true/false # Whether to mix aligning prototype and variable declarations. If true, # align_var_def_XXX options are used instead of align_func_proto_XXX options. align_mix_var_proto = false # true/false # Whether to align single-line functions with function prototypes. # Uses align_func_proto_span. align_single_line_func = false # true/false # Whether to align the open brace of single-line functions. # Requires align_single_line_func=true. Uses align_func_proto_span. align_single_line_brace = false # true/false # Gap for align_single_line_brace. align_single_line_brace_gap = 0 # unsigned number # (OC) The span for aligning Objective-C message specifications. # # 0: Don't align (default). align_oc_msg_spec_span = 0 # unsigned number # Whether to align macros wrapped with a backslash and a newline. This will # not work right if the macro contains a multi-line comment. align_nl_cont = false # true/false # Whether to align macro functions and variables together. align_pp_define_together = false # true/false # The span for aligning on '#define' bodies. # # =0: Don't align (default) # >0: Number of lines (including comments) between blocks align_pp_define_span = 0 # unsigned number # The minimum space between label and value of a preprocessor define. align_pp_define_gap = 0 # unsigned number # Whether to align lines that start with '<<' with previous '<<'. # # Default: true align_left_shift = true # true/false # Whether to align comma-separated statements following '<<' (as used to # initialize Eigen matrices). align_eigen_comma_init = false # true/false # Whether to align text after 'asm volatile ()' colons. align_asm_colon = false # true/false # (OC) Span for aligning parameters in an Objective-C message call # on the ':'. # # 0: Don't align. align_oc_msg_colon_span = 0 # unsigned number # (OC) Whether to always align with the first parameter, even if it is too # short. align_oc_msg_colon_first = false # true/false # (OC) Whether to align parameters in an Objective-C '+' or '-' declaration # on the ':'. align_oc_decl_colon = false # true/false # (OC) Whether to not align parameters in an Objectve-C message call if first # colon is not on next line of the message call (the same way Xcode does # aligment) align_oc_msg_colon_xcode_like = false # true/false # # Comment modification options # # Try to wrap comments at N columns. cmt_width = 80 # unsigned number # How to reflow comments. # # 0: No reflowing (apart from the line wrapping due to cmt_width) (default) # 1: No touching at all # 2: Full reflow cmt_reflow_mode = 0 # unsigned number # Whether to convert all tabs to spaces in comments. If false, tabs in # comments are left alone, unless used for indenting. cmt_convert_tab_to_spaces = false # true/false # Whether to apply changes to multi-line comments, including cmt_width, # keyword substitution and leading chars. # # Default: true cmt_indent_multi = true # true/false # Whether to group c-comments that look like they are in a block. cmt_c_group = false # true/false # Whether to put an empty '/*' on the first line of the combined c-comment. cmt_c_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined c-comment. cmt_c_nl_end = false # true/false # Whether to change cpp-comments into c-comments. cmt_cpp_to_c = false # true/false # Whether to group cpp-comments that look like they are in a block. Only # meaningful if cmt_cpp_to_c=true. cmt_cpp_group = false # true/false # Whether to put an empty '/*' on the first line of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_start = false # true/false # Whether to add a newline before the closing '*/' of the combined cpp-comment # when converting to a c-comment. # # Requires cmt_cpp_to_c=true and cmt_cpp_group=true. cmt_cpp_nl_end = false # true/false # Whether to put a star on subsequent comment lines. cmt_star_cont = true # true/false # The number of spaces to insert at the start of subsequent comment lines. cmt_sp_before_star_cont = 0 # unsigned number # The number of spaces to insert after the star on subsequent comment lines. cmt_sp_after_star_cont = 0 # unsigned number # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length. # # Default: true cmt_multi_check_last = false # true/false # For multi-line comments with a '*' lead, remove leading spaces if the first # and last lines of the comment are the same length AND if the length is # bigger as the first_len minimum. # # Default: 4 cmt_multi_first_len_minimum = 4 # unsigned number # Path to a file that contains text to insert at the beginning of a file if # the file doesn't start with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_header = "" # string # Path to a file that contains text to insert at the end of a file if the # file doesn't end with a C/C++ comment. If the inserted text contains # '$(filename)', that will be replaced with the current file's name. cmt_insert_file_footer = "" # string # Path to a file that contains text to insert before a function definition if # the function isn't preceded by a C/C++ comment. If the inserted text # contains '$(function)', '$(javaparam)' or '$(fclass)', these will be # replaced with, respectively, the name of the function, the javadoc '@param' # and '@return' stuff, or the name of the class to which the member function # belongs. cmt_insert_func_header = "" # string # Path to a file that contains text to insert before a class if the class # isn't preceded by a C/C++ comment. If the inserted text contains '$(class)', # that will be replaced with the class name. cmt_insert_class_header = "" # string # Path to a file that contains text to insert before an Objective-C message # specification, if the method isn't preceded by a C/C++ comment. If the # inserted text contains '$(message)' or '$(javaparam)', these will be # replaced with, respectively, the name of the function, or the javadoc # '@param' and '@return' stuff. cmt_insert_oc_msg_header = "" # string # Whether a comment should be inserted if a preprocessor is encountered when # stepping backwards from a function name. # # Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and # cmt_insert_class_header. cmt_insert_before_preproc = false # true/false # Whether a comment should be inserted if a function is declared inline to a # class definition. # # Applies to cmt_insert_func_header. # # Default: true cmt_insert_before_inlines = true # true/false # Whether a comment should be inserted if the function is a class constructor # or destructor. # # Applies to cmt_insert_func_header. cmt_insert_before_ctor_dtor = false # true/false # # Code modifying options (non-whitespace) # # Add or remove braces on a single-line 'do' statement. mod_full_brace_do = ignore # ignore/add/remove/force # Add or remove braces on a single-line 'for' statement. mod_full_brace_for = ignore # ignore/add/remove/force # (Pawn) Add or remove braces on a single-line function definition. mod_full_brace_function = ignore # ignore/add/remove/force # Add or remove braces on a single-line 'if' statement. Braces will not be # removed if the braced statement contains an 'else'. mod_full_brace_if = ignore # ignore/add/remove/force # Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either # have, or do not have, braces. If true, braces will be added if any block # needs braces, and will only be removed if they can be removed from all # blocks. # # Overrides mod_full_brace_if. mod_full_brace_if_chain = false # true/false # Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. # If true, mod_full_brace_if_chain will only remove braces from an 'if' that # does not have an 'else if' or 'else'. mod_full_brace_if_chain_only = false # true/false # Add or remove braces on single-line 'while' statement. mod_full_brace_while = ignore # ignore/add/remove/force # Add or remove braces on single-line 'using ()' statement. mod_full_brace_using = ignore # ignore/add/remove/force # Don't remove braces around statements that span N newlines mod_full_brace_nl = 0 # unsigned number # Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks # which span multiple lines. # # Affects: # mod_full_brace_for # mod_full_brace_if # mod_full_brace_if_chain # mod_full_brace_if_chain_only # mod_full_brace_while # mod_full_brace_using # # Does not affect: # mod_full_brace_do # mod_full_brace_function mod_full_brace_nl_block_rem_mlcond = false # true/false # Add or remove unnecessary parenthesis on 'return' statement. mod_paren_on_return = ignore # ignore/add/remove/force # (Pawn) Whether to change optional semicolons to real semicolons. mod_pawn_semicolon = false # true/false # Whether to fully parenthesize Boolean expressions in 'while' and 'if' # statement, as in 'if (a && b > c)' => 'if (a && (b > c))'. mod_full_paren_if_bool = false # true/false # Whether to remove superfluous semicolons. mod_remove_extra_semicolon = false # true/false # If a function body exceeds the specified number of newlines and doesn't have # a comment after the close brace, a comment will be added. mod_add_long_function_closebrace_comment = 0 # unsigned number # If a namespace body exceeds the specified number of newlines and doesn't # have a comment after the close brace, a comment will be added. mod_add_long_namespace_closebrace_comment = 0 # unsigned number # If a class body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_class_closebrace_comment = 0 # unsigned number # If a switch body exceeds the specified number of newlines and doesn't have a # comment after the close brace, a comment will be added. mod_add_long_switch_closebrace_comment = 0 # unsigned number # If an #ifdef body exceeds the specified number of newlines and doesn't have # a comment after the #endif, a comment will be added. mod_add_long_ifdef_endif_comment = 0 # unsigned number # If an #ifdef or #else body exceeds the specified number of newlines and # doesn't have a comment after the #else, a comment will be added. mod_add_long_ifdef_else_comment = 0 # unsigned number # Whether to take care of the case by the mod_sort_xx options. mod_sort_case_sensitive = false # true/false # Whether to sort consecutive single-line 'import' statements. mod_sort_import = false # true/false # (C#) Whether to sort consecutive single-line 'using' statements. mod_sort_using = false # true/false # Whether to sort consecutive single-line '#include' statements (C/C++) and # '#import' statements (Objective-C). Be aware that this has the potential to # break your code if your includes/imports have ordering dependencies. mod_sort_include = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # filename without extension when sorting is enabled. mod_sort_incl_import_prioritize_filename = false # true/false # Whether to prioritize '#include' and '#import' statements that does not # contain extensions when sorting is enabled. mod_sort_incl_import_prioritize_extensionless = false # true/false # Whether to prioritize '#include' and '#import' statements that contain # angle over quotes when sorting is enabled. mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false # Whether to ignore file extension in '#include' and '#import' statements # for sorting comparison. mod_sort_incl_import_ignore_extension = false # true/false # Whether to group '#include' and '#import' statements when sorting is enabled. mod_sort_incl_import_grouping_enabled = false # true/false # Whether to move a 'break' that appears after a fully braced 'case' before # the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. mod_move_case_break = false # true/false # Add or remove braces around a fully braced case statement. Will only remove # braces if there are no variable declarations in the block. mod_case_brace = ignore # ignore/add/remove/force # Whether to remove a void 'return;' that appears as the last statement in a # function. mod_remove_empty_return = false # true/false # Add or remove the comma after the last value of an enumeration. mod_enum_last_comma = ignore # ignore/add/remove/force # (OC) Whether to organize the properties. If true, properties will be # rearranged according to the mod_sort_oc_property_*_weight factors. mod_sort_oc_properties = false # true/false # (OC) Weight of a class property modifier. mod_sort_oc_property_class_weight = 0 # number # (OC) Weight of 'atomic' and 'nonatomic'. mod_sort_oc_property_thread_safe_weight = 0 # number # (OC) Weight of 'readwrite' when organizing properties. mod_sort_oc_property_readwrite_weight = 0 # number # (OC) Weight of a reference type specifier ('retain', 'copy', 'assign', # 'weak', 'strong') when organizing properties. mod_sort_oc_property_reference_weight = 0 # number # (OC) Weight of getter type ('getter=') when organizing properties. mod_sort_oc_property_getter_weight = 0 # number # (OC) Weight of setter type ('setter=') when organizing properties. mod_sort_oc_property_setter_weight = 0 # number # (OC) Weight of nullability type ('nullable', 'nonnull', 'null_unspecified', # 'null_resettable') when organizing properties. mod_sort_oc_property_nullability_weight = 0 # number # # Preprocessor options # # Add or remove indentation of preprocessor directives inside #if blocks # at brace level 0 (file-level). pp_indent = ignore # ignore/add/remove/force # Whether to indent #if/#else/#endif at the brace level. If false, these are # indented from column 1. pp_indent_at_level = false # true/false # Specifies the number of columns to indent preprocessors per level # at brace level 0 (file-level). If pp_indent_at_level=false, also specifies # the number of columns to indent preprocessors per level # at brace level > 0 (function-level). # # Default: 1 pp_indent_count = 1 # unsigned number # Add or remove space after # based on pp_level of #if blocks. pp_space = ignore # ignore/add/remove/force # Sets the number of spaces per level added with pp_space. pp_space_count = 0 # unsigned number # The indent for '#region' and '#endregion' in C# and '#pragma region' in # C/C++. Negative values decrease indent down to the first column. pp_indent_region = 0 # number # Whether to indent the code between #region and #endregion. pp_region_indent_code = false # true/false # If pp_indent_at_level=true, sets the indent for #if, #else and #endif when # not at file-level. Negative values decrease indent down to the first column. # # =0: Indent preprocessors using output_tab_size # >0: Column at which all preprocessors will be indented pp_indent_if = 0 # number # Whether to indent the code between #if, #else and #endif. pp_if_indent_code = false # true/false # Whether to indent '#define' at the brace level. If false, these are # indented from column 1. pp_define_at_level = false # true/false # Whether to ignore the '#define' body while formatting. pp_ignore_define_body = false # true/false # Whether to indent case statements between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the case statements # directly inside of. # # Default: true pp_indent_case = true # true/false # Whether to indent whole function definitions between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the function definition # is directly inside of. # # Default: true pp_indent_func_def = true # true/false # Whether to indent extern C blocks between #if, #else, and #endif. # Only applies to the indent of the preprocesser that the extern block is # directly inside of. # # Default: true pp_indent_extern = true # true/false # Whether to indent braces directly inside #if, #else, and #endif. # Only applies to the indent of the preprocesser that the braces are directly # inside of. # # Default: true pp_indent_brace = true # true/false # # Sort includes options # # The regex for include category with priority 0. include_category_0 = "" # string # The regex for include category with priority 1. include_category_1 = "" # string # The regex for include category with priority 2. include_category_2 = "" # string # # Use or Do not Use options # # true: indent_func_call_param will be used (default) # false: indent_func_call_param will NOT be used # # Default: true use_indent_func_call_param = true # true/false # The value of the indentation for a continuation line is calculated # differently if the statement is: # - a declaration: your case with QString fileName ... # - an assignment: your case with pSettings = new QSettings( ... # # At the second case the indentation value might be used twice: # - at the assignment # - at the function call (if present) # # To prevent the double use of the indentation value, use this option with the # value 'true'. # # true: indent_continue will be used only once # false: indent_continue will be used every time (default) use_indent_continue_only_once = false # true/false # The value might be used twice: # - at the assignment # - at the opening brace # # To prevent the double use of the indentation value, use this option with the # value 'true'. # # true: indentation will be used only once # false: indentation will be used every time (default) indent_cpp_lambda_only_once = false # true/false # Whether sp_after_angle takes precedence over sp_inside_fparen. This was the # historic behavior, but is probably not the desired behavior, so this is off # by default. use_sp_after_angle_always = false # true/false # Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially, # this tries to format these so that they match Qt's normalized form (i.e. the # result of QMetaObject::normalizedSignature), which can slightly improve the # performance of the QObject::connect call, rather than how they would # otherwise be formatted. # # See options_for_QT.cpp for details. # # Default: true use_options_overriding_for_qt_macros = true # true/false # If true: the form feed character is removed from the list # of whitespace characters. # See https://en.cppreference.com/w/cpp/string/byte/isspace use_form_feed_no_more_as_whitespace_character = false # true/false # # Warn levels - 1: error, 2: warning (default), 3: note # # (C#) Warning is given if doing tab-to-\t replacement and we have found one # in a C# verbatim string literal. # # Default: 2 warn_level_tabs_found_in_verbatim_string_literals = 2 # unsigned number # Limit the number of loops. # Used by uncrustify.cpp to exit from infinite loop. # 0: no limit. debug_max_number_of_loops = 0 # number # Set the number of the line to protocol; # Used in the function prot_the_line if the 2. parameter is zero. # 0: nothing protocol. debug_line_number_to_protocol = 0 # number # Set the number of second(s) before terminating formatting the current file, # 0: no timeout. # only for linux debug_timeout = 0 # number # Meaning of the settings: # Ignore - do not do any changes # Add - makes sure there is 1 or more space/brace/newline/etc # Force - makes sure there is exactly 1 space/brace/newline/etc, # behaves like Add in some contexts # Remove - removes space/brace/newline/etc # # # - Token(s) can be treated as specific type(s) with the 'set' option: # `set tokenType tokenString [tokenString...]` # # Example: # `set BOOL __AND__ __OR__` # # tokenTypes are defined in src/token_enum.h, use them without the # 'CT_' prefix: 'CT_BOOL' => 'BOOL' # # # - Token(s) can be treated as type(s) with the 'type' option. # `type tokenString [tokenString...]` # # Example: # `type int c_uint_8 Rectangle` # # This can also be achieved with `set TYPE int c_uint_8 Rectangle` # # # To embed whitespace in tokenStrings use the '\' escape character, or quote # the tokenStrings. These quotes are supported: "'` # # # - Support for the auto detection of languages through the file ending can be # added using the 'file_ext' command. # `file_ext langType langString [langString..]` # # Example: # `file_ext CPP .ch .cxx .cpp.in` # # langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use # them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP' # # # - Custom macro-based indentation can be set up using 'macro-open', # 'macro-else' and 'macro-close'. # `(macro-open | macro-else | macro-close) tokenString` # # Example: # `macro-open BEGIN_TEMPLATE_MESSAGE_MAP` # `macro-open BEGIN_MESSAGE_MAP` # `macro-close END_MESSAGE_MAP` # # # option(s) with 'not default' value: 0 #