diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..08cdcb5 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,49 @@ +version: 0.0.0-{build} +configuration: Release +platform: +- x86 +#- x64 +cache: + - C:/Tools/vcpkg/installed/ +environment: + APPVEYOR_SAVE_CACHE_ON_ERROR: true + matrix: + - GENERATOR: "Visual Studio 14 2015" + TARGET: x86-windows + - GENERATOR: "Visual Studio 14 2015 Win64" + TARGET: x64-windows +install: +# Update vcpkg +- cd c:\tools\vcpkg +- cmd: git fetch +- cmd: git checkout 2021.05.12 +- cmd: bootstrap-vcpkg.bat +- cmd: vcpkg install gtest:x86-windows +- cmd: vcpkg install gtest:x64-windows +- cmd: vcpkg update +- cmd: vcpkg upgrade --no-dry-run +build_script: +- cmd: if exist "C:\projects\pkcs11test\build" rd /s /q C:\projects\pkcs11test\build +- cmd: mkdir C:\projects\pkcs11test\build +- cmd: cd C:\projects\pkcs11test\build +- cmd: vcpkg integrate install +- cmd: cmake .. -G "%GENERATOR%" -DVCPKG_TARGET_TRIPLET=%TARGET% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake +- cmd: cmake --build . --config RelWithDebInfo +- cmd: ctest -C RelWithDebInfo --progress --verbose +- cmd: cpack +artifacts: + - path: "build/*.zip" + name: artifacts-zip + +deploy: + release: pkcs11test-v$(APPVEYOR_BUILD_VERSION) + description: 'Windows binaries auto-built by AppVeyor' + provider: GitHub + auth_token: + secure: KX9JW8h86LzNA1TDJy2adlFIV+E455sZqXTc/HQDPLSbHRTwEv5zpcdRA/n9xJGp + artifact: artifacts-zip + draft: true + prerelease: false + on: + branch: dev +# APPVEYOR_REPO_TAG: true # deploy on tag push only diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d46ff47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.16) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +project(p11test) + +set(VERSION "0.0.0") +set(VERSION_MAJOR 0) +set(VERSION_MINOR 0) +set(VERSION_PATCH 0) + +set(SOURCES cipher.cc + describe.cc + digest.cc + dual.cc + globals.cc + hmac.cc + init.cc + key.cc + keypair.cc + login.cc + object.cc + pkcs11-describe.cc + pkcs11test.cc + rng.cc + session.cc + sign.cc + slot.cc + tookan.cc +) + +if(MSVC) + list(APPEND INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/netbsd) + + list(APPEND SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/netbsd/getopt_long.c) + +endif() + +list(APPEND INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/third_party/pkcs11) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTRICT_P11") +find_package(GTest CONFIG REQUIRED COMPONENTS CXX_LIB) +add_executable(${PROJECT_NAME} ${SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE GTest::gtest) +target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS} ) + +if(${VCPKG_TARGET_TRIPLET} MATCHES ".*windows-static$") + message(FATAL_ERROR "GTest library doesn't support static linking for now") + # switch to MultiThreaded runtime library (MT) vs MultiThreaded DLL (MD) + set_property(TARGET ${PROJECT_NAME} PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/p11test.exe DESTINATION bin) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/p11test.pdb DESTINATION bin OPTIONAL) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo/gtest.dll DESTINATION bin) + +# Packaging +set(CPACK_PACKAGE_NAME ${PACKAGE_NAME}) +set(CPACK_PACKAGE_VENDOR "plcs11test") +set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH}) +set(CPACK_GENERATOR "ZIP") +set(CPACK_SOURCE_GENERATOR "ZIP") +set(CPACK_SOURCE_IGNORE_FILES "build/*;\.git/*") + +include(CPack) diff --git a/NOTES.WIN.md b/NOTES.WIN.md new file mode 100644 index 0000000..9f31bfa --- /dev/null +++ b/NOTES.WIN.md @@ -0,0 +1,38 @@ +# Building pkcs11test for Windows + +This document describes process of building both 32-bit and 64-bit versions of pkcs11test on 64-bit Windows machine. + +## Required software + +- [Visual Studio](https://visualstudio.microsoft.com/vs/community/) (Community) +- [C/C++ dependency manager from Microsoft](https://vcpkg.io/) +- [CMake](https://cmake.org/) + +## Prepare working directories + + set P11TEST_HOME=C:\Projects\pkcs11test + set VCPKG_HOME=C:\Projects\vcpkg + +## Build GTest + + cd %VCPKG_HOME% + bootstrap-vcpkg.bat + vcpkg install gtest:x86-windows + vcpkg install gtest:x64-windows + vcpkg integrate install + +## Build pkcs11test + git clone https://github.com/google/pkcs11test.git %P11TEST_HOME% +## x86 + mkdir %P11TEST_HOME%\tmp + cd %P11TEST_HOME%\tmp + cmake .. -A Win32 -DCMAKE_TOOLCHAIN_FILE=%VCPKG_HOME%/scripts/buildsystems/vcpkg.cmake + cmake --build . --config RelWithDebInfo + cmake -DCMAKE_INSTALL_PREFIX=%P11TEST_HOME%\out32 -DCMAKE_INSTALL_CONFIG_NAME=RelWithDebInfo -P cmake_install.cmake + +## x64 + mkdir %P11TEST_HOME%\tmp + cd %P11TEST_HOME%\tmp + cmake .. -A x64 -DVCPKG_TARGET_TRIPLET=x64-windows -DCMAKE_TOOLCHAIN_FILE=%VCPKG_HOME%/scripts/buildsystems/vcpkg.cmake + cmake --build . --config RelWithDebInfo + cmake -DCMAKE_INSTALL_PREFIX=%P11TEST_HOME%\out64 -DCMAKE_INSTALL_CONFIG_NAME=RelWithDebInfo -P cmake_install.cmake diff --git a/cipher.cc b/cipher.cc index 42302a9..f08eb2e 100644 --- a/cipher.cc +++ b/cipher.cc @@ -577,7 +577,7 @@ TEST_F(ReadOnlySessionTest, CreateSecretKeyAttributes) { {CKA_DECRYPT, (CK_VOID_PTR)&g_ck_true, sizeof(CK_BBOOL)}, {CKA_CLASS, &key_class, sizeof(key_class)}, {CKA_KEY_TYPE, (CK_VOID_PTR)&key_type, sizeof(key_type)}, - {CKA_VALUE, (CK_VOID_PTR)key.data(), key.size()}, + {CKA_VALUE, (CK_VOID_PTR)key.data(), static_cast(key.size())}, }; CK_OBJECT_HANDLE key_object; ASSERT_CKR_OK(g_fns->C_CreateObject(session_, attrs.data(), attrs.size(), &key_object)); @@ -626,7 +626,7 @@ TEST_F(ReadOnlySessionTest, SecretKeyTestVectors) { {CKA_DECRYPT, (CK_VOID_PTR)&g_ck_true, sizeof(CK_BBOOL)}, {CKA_CLASS, &key_class, sizeof(key_class)}, {CKA_KEY_TYPE, (CK_VOID_PTR)&key_type, sizeof(key_type)}, - {CKA_VALUE, (CK_VOID_PTR)key.data(), key.size()}, + {CKA_VALUE, (CK_VOID_PTR)key.data(), static_cast(key.size())}, }; CK_OBJECT_HANDLE key_object; ASSERT_CKR_OK(g_fns->C_CreateObject(session_, attrs.data(), attrs.size(), &key_object)); diff --git a/digest.cc b/digest.cc index 3433e6f..419554d 100644 --- a/digest.cc +++ b/digest.cc @@ -22,6 +22,12 @@ #include #include "pkcs11test.h" + +#ifdef _WIN32 +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + + #include #include #include diff --git a/hmac.cc b/hmac.cc index 38c56c8..c2491bf 100644 --- a/hmac.cc +++ b/hmac.cc @@ -188,7 +188,7 @@ TEST_F(ReadOnlySessionTest, HmacTestVectors) { {CKA_VERIFY, (CK_VOID_PTR)&g_ck_true, sizeof(CK_BBOOL)}, {CKA_CLASS, &key_class, sizeof(key_class)}, {CKA_KEY_TYPE, (CK_VOID_PTR)&key_type, sizeof(key_type)}, - {CKA_VALUE, (CK_VOID_PTR)key.data(), key.size()}, + {CKA_VALUE, (CK_VOID_PTR)key.data(), static_cast(key.size())}, }; CK_OBJECT_HANDLE key_object; ASSERT_CKR_OK(g_fns->C_CreateObject(session_, attrs.data(), attrs.size(), &key_object)); diff --git a/keypair.cc b/keypair.cc index 449b10f..173a7cb 100644 --- a/keypair.cc +++ b/keypair.cc @@ -234,8 +234,8 @@ TEST_F(ReadOnlySessionTest, CreateKeyPairObjects) { {CKA_PRIVATE, (CK_VOID_PTR)&g_ck_false, sizeof(CK_BBOOL)}, {CKA_CLASS, &public_key_class, sizeof(public_key_class)}, {CKA_KEY_TYPE, (CK_VOID_PTR)&key_type, sizeof(key_type)}, - {CKA_PUBLIC_EXPONENT, (CK_VOID_PTR)public_exponent.data(), public_exponent.size()}, - {CKA_MODULUS, (CK_VOID_PTR)public_modulus.data(), public_modulus.size()}, + {CKA_PUBLIC_EXPONENT, (CK_VOID_PTR)public_exponent.data(), static_cast(public_exponent.size())}, + {CKA_MODULUS, (CK_VOID_PTR)public_modulus.data(), static_cast(public_modulus.size())}, }; EXPECT_CKR_OK(g_fns->C_CreateObject(session_, public_attrs.data(), @@ -254,34 +254,34 @@ TEST_F(ReadOnlySessionTest, CreateKeyPairObjects) { {CKA_PRIVATE, (CK_VOID_PTR)&g_ck_false, sizeof(CK_BBOOL)}, {CKA_CLASS, &private_key_class, sizeof(private_key_class)}, {CKA_KEY_TYPE, (CK_VOID_PTR)&key_type, sizeof(key_type)}, - {CKA_PUBLIC_EXPONENT, (CK_VOID_PTR)public_exponent.data(), public_exponent.size()}, - {CKA_PRIVATE_EXPONENT, (CK_BYTE_PTR)private_exponent.data(), private_exponent.size()}, - {CKA_MODULUS, (CK_VOID_PTR)public_modulus.data(), public_modulus.size()}, + {CKA_PUBLIC_EXPONENT, (CK_VOID_PTR)public_exponent.data(), static_cast(public_exponent.size())}, + {CKA_PRIVATE_EXPONENT, (CK_BYTE_PTR)private_exponent.data(), static_cast(private_exponent.size())}, + {CKA_MODULUS, (CK_VOID_PTR)public_modulus.data(), static_cast(public_modulus.size())}, }; string prime1data; if (!keydata.prime1.empty()) { prime1data = hex_decode(keydata.prime1); - private_attrs.push_back({CKA_PRIME_1, (CK_BYTE_PTR)prime1data.data(), prime1data.size()}); + private_attrs.push_back({CKA_PRIME_1, (CK_BYTE_PTR)prime1data.data(), static_cast(prime1data.size())}); } string prime2data; if (!keydata.prime2.empty()) { prime2data = hex_decode(keydata.prime2); - private_attrs.push_back({CKA_PRIME_2, (CK_BYTE_PTR)prime2data.data(), prime2data.size()}); + private_attrs.push_back({CKA_PRIME_2, (CK_BYTE_PTR)prime2data.data(), static_cast(prime2data.size())}); } string exponent1data; if (!keydata.exponent1.empty()) { exponent1data = hex_decode(keydata.exponent1); - private_attrs.push_back({CKA_EXPONENT_1, (CK_BYTE_PTR)exponent1data.data(), exponent1data.size()}); + private_attrs.push_back({CKA_EXPONENT_1, (CK_BYTE_PTR)exponent1data.data(),static_cast(exponent1data.size())}); } string exponent2data; if (!keydata.exponent2.empty()) { exponent2data = hex_decode(keydata.exponent2); - private_attrs.push_back({CKA_EXPONENT_2, (CK_BYTE_PTR)exponent2data.data(), exponent2data.size()}); + private_attrs.push_back({CKA_EXPONENT_2, (CK_BYTE_PTR)exponent2data.data(), static_cast(exponent2data.size())}); } string coefficientdata; if (!keydata.coefficient.empty()) { coefficientdata = hex_decode(keydata.coefficient); - private_attrs.push_back({CKA_COEFFICIENT, (CK_BYTE_PTR)coefficientdata.data(), coefficientdata.size()}); + private_attrs.push_back({CKA_COEFFICIENT, (CK_BYTE_PTR)coefficientdata.data(), static_cast(coefficientdata.size())}); } EXPECT_CKR_OK(g_fns->C_CreateObject(session_, private_attrs.data(), diff --git a/pkcs11-env.h b/pkcs11-env.h index b20b813..b5c2916 100644 --- a/pkcs11-env.h +++ b/pkcs11-env.h @@ -19,12 +19,52 @@ # pragma pack(push, 1) #endif + + +#ifdef _WIN32 + +/* Specifies that the function is a DLL entry point. */ +#define CK_IMPORT_SPEC __declspec(dllimport) + +#ifdef CRYPTOKI_EXPORTS + /* Specified that the function is an exported DLL entry point. */ +#define CK_EXPORT_SPEC __declspec(dllexport) +#else +#define CK_EXPORT_SPEC CK_IMPORT_SPEC +#endif + + /* Ensures the calling convention for Win32 builds */ +#define CK_CALL_SPEC __cdecl + +#define CK_PTR * + +#define CK_DEFINE_FUNCTION(returnType, name) \ + returnType CK_EXPORT_SPEC CK_CALL_SPEC name + +#define CK_DECLARE_FUNCTION(returnType, name) \ + returnType CK_EXPORT_SPEC CK_CALL_SPEC name + +#define CK_DECLARE_FUNCTION_POINTER(returnType, name) \ + returnType CK_IMPORT_SPEC (CK_CALL_SPEC CK_PTR name) + +#define CK_CALLBACK_FUNCTION(returnType, name) \ + returnType (CK_CALL_SPEC CK_PTR name) + + + +#else //_WIN32 + +/* UNIX version */ /* The following definitions need to be provided to the preprocessor before the PKCS#11 header file can be included */ #define CK_PTR * #define CK_DEFINE_FUNCTION(returnType, name) returnType name #define CK_DECLARE_FUNCTION(returnType, name) returnType name #define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType (*name) #define CK_CALLBACK_FUNCTION(returnType, name) returnType (* name) + + +#endif //_WIN32 + #ifndef NULL_PTR #define NULL_PTR 0 #endif diff --git a/pkcs11test.cc b/pkcs11test.cc index f55c12e..b7eb56e 100644 --- a/pkcs11test.cc +++ b/pkcs11test.cc @@ -13,8 +13,16 @@ // limitations under the License. // C headers +#include +#ifdef _WIN32 +constexpr auto FILEPATH_NATIVE = '\\'; +#include +#include +#else +constexpr auto FILEPATH_NATIVE = '/'; #include #include +#endif // C++ headers #include @@ -111,8 +119,8 @@ CK_C_GetFunctionList load_pkcs11_library(const char* libpath, const char* libnam string fullname; if (libpath != nullptr) { fullname = libpath; - if (fullname.at(fullname.size() - 1) != '/') { - fullname += '/'; + if (fullname.at(fullname.size() - 1) != FILEPATH_NATIVE) { + fullname += FILEPATH_NATIVE; } } fullname += libname; @@ -121,13 +129,24 @@ CK_C_GetFunctionList load_pkcs11_library(const char* libpath, const char* libnam exit(1); } - void* lib = dlopen(fullname.c_str(), RTLD_NOW); + void* lib; +#if defined WIN32 + lib = LoadLibraryA(fullname.c_str()); +#else + lib = dlopen(fullname.c_str(), RTLD_NOW); +#endif if (lib == nullptr) { cerr << "Failed to dlopen(" << fullname << ")" << endl; exit(1); } - void* fn = dlsym(lib, "C_GetFunctionList"); + void* fn; +#if defined WIN32 + fn = GetProcAddress((HMODULE)lib, "C_GetFunctionList"); +#else + fn = dlsym(lib, "C_GetFunctionList"); +#endif + if (fn == nullptr) { cerr<< "Failed to dlsym(\"C_GetFunctionList\")" << endl; exit(1); @@ -196,6 +215,7 @@ int main(int argc, char* argv[]) { // Load the module. CK_C_GetFunctionList get_fn_list = load_pkcs11_library(module_path, module_name); + // Retrieve the set of function pointers (C_GetFunctionList is the only function it's OK to call before C_Initialize). if (get_fn_list(&g_fns) != CKR_OK) { cerr << "Failed to retrieve list of functions" << endl; @@ -212,6 +232,7 @@ int main(int argc, char* argv[]) { if (use_slot_index || !explicit_slotid) { CK_SLOT_ID slots[32]; CK_ULONG slot_count = 32; + rv = g_fns->C_GetSlotList(CK_TRUE, slots, &slot_count); if (rv == CKR_OK) { if (slot_count == 0) { diff --git a/third_party/netbsd/getopt.h b/third_party/netbsd/getopt.h new file mode 100644 index 0000000..f501655 --- /dev/null +++ b/third_party/netbsd/getopt.h @@ -0,0 +1,91 @@ +/* $OpenBSD: getopt.h,v 1.3 2013/11/22 21:32:49 millert Exp $ */ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +#ifdef _WIN32 + /* from */ + /* C++ needs to know that types and declarations are C, not C++. */ +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS +# define __END_DECLS +#endif + +extern char opterrmsg[]; // error message + +#else //_WIN32 +#include +#endif // _WIN32 + +/* + * GNU-like getopt_long() + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +__BEGIN_DECLS +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DEFINED_ +#define _GETOPT_DEFINED_ +int getopt(int, char * const *, const char *); + +extern char *optarg; /* getopt(3) external variables */ +extern int opterr; +extern int optind; +extern int optopt; +extern int optreset; +#endif +__END_DECLS + +#endif /* !_GETOPT_H_ */ diff --git a/third_party/netbsd/getopt_long.c b/third_party/netbsd/getopt_long.c new file mode 100644 index 0000000..a985d6c --- /dev/null +++ b/third_party/netbsd/getopt_long.c @@ -0,0 +1,558 @@ +/* $OpenBSD: getopt_long.c,v 1.32 2020/05/27 22:25:09 schwarze Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _WIN32 +#include +#else //_WIN32 +#pragma warning(disable : 4996) //getenv +/* warnx definition for windows*/ +#include +#include +#include + +char opterrmsg[128]; + +static void warnx(const char *fmt, ...) +{ + const int TRUNCATED = -1; + va_list ap; + va_start(ap, fmt); + + if (fmt != NULL) { + int c = _vsnprintf_s(opterrmsg, sizeof(opterrmsg) - 4, _TRUNCATE, fmt, ap); + + // enough space are reserved to add continuation mark or NL + if (c == TRUNCATED) { + c = sizeof(opterrmsg) - 5; + opterrmsg[c++] = '.'; + opterrmsg[c++] = '.'; + opterrmsg[c++] = '.'; + opterrmsg[c++] = '\n'; + opterrmsg[c++] = '\0'; + } + else{ + opterrmsg[c++] = '\n'; + opterrmsg[c++] = '\0'; + } + } + else { + opterrmsg[0] = '\0'; + } + + OutputDebugStringA(opterrmsg); + fprintf(stderr, opterrmsg); + + va_end(ap); +} + +#endif //_WIN32 + +#include +#include +#include +#include + +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#if 0 +/* DEF_* only work on initialized (non-COMMON) variables */ +DEF_WEAK(opterr); +DEF_WEAK(optind); +DEF_WEAK(optopt); +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + ((char **)nargv)[pos] = nargv[cstart]; + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = place; + match = -1; + exact_match = 0; + second_partial_match = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1 || optreset) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too, flags); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (oli = strchr(options, optchar)) == NULL) { + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0, flags); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +}