"
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/installers/mac/html/license.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 | Terms And Conditions For Accessing Or Otherwise Using WS-Discovery Host Daemon
15 |
16 | WS-Discovery Host Daemon software and documentation are licensed under the
17 | 3-Clause BSD License.
18 |
19 |
20 | The 3-Clause BSD License
21 | Copyright © 2022, Eugene Gershnik
22 | All rights reserved.
23 |
24 | Redistribution and use in source and binary forms, with or without
25 | modification, are permitted provided that the following conditions are met:
26 |
27 |
28 | 1. Redistributions of source code must retain the above copyright notice, this
29 | list of conditions and the following disclaimer.
30 |
31 |
32 | 2. Redistributions in binary form must reproduce the above copyright notice,
33 | this list of conditions and the following disclaimer in the documentation
34 | and/or other materials provided with the distribution.
35 |
36 |
37 | 3. Neither the name of the copyright holder nor the names of its
38 | contributors may be used to endorse or promote products derived from
39 | this software without specific prior written permission.
40 |
41 |
42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
43 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
44 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
45 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
46 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
47 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
48 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
49 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
51 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/config_mac.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #if HAVE_APPLE_SAMBA
5 |
6 | #include "config.h"
7 |
8 | auto Config::detectAppleWinNetInfo(bool useNetbiosHostName) -> std::optional {
9 |
10 | cf_ptr store = cf_attach(SCDynamicStoreCreate(nullptr, CFSTR("detectWinNetInfo"), nullptr, nullptr));
11 | if (!store) {
12 | WSDLOG_ERROR("Unable to create configuration store");
13 | return std::nullopt;
14 | }
15 | cf_ptr smb = cf_attach(SCDynamicStoreCopyValue (store.get(), CFSTR("com.apple.smb")));
16 | if (!smb || CFGetTypeID(smb.get()) != CFDictionaryGetTypeID()) {
17 | WSDLOG_WARN("SMB info is not present in configuration store");
18 | return std::nullopt;
19 | }
20 |
21 | auto dict = (CFDictionaryRef)smb.get();
22 | sys_string_cfstr workgroup = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("Workgroup"));
23 | sys_string_cfstr hostname = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("NetBIOSName"));
24 | sys_string_cfstr desc = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("ServerDescription"));
25 | if (!workgroup.cf_str()) {
26 | WSDLOG_WARN("Workgroup is missing in configuration store");
27 | }
28 |
29 | sys_string_cfstr domain;
30 | cf_ptr ad = cf_attach(SCDynamicStoreCopyValue (store.get(), CFSTR("com.apple.opendirectoryd.ActiveDirectory")));
31 | if (ad && CFGetTypeID(ad.get()) != CFDictionaryGetTypeID()) {
32 |
33 | dict = (CFDictionaryRef)ad.get();
34 | domain = (CFStringRef)CFDictionaryGetValue(dict, CFSTR("DomainNameFlat"));
35 | }
36 |
37 | if (!workgroup.cf_str() && !domain.cf_str()) {
38 | WSDLOG_WARN("Cannot detect either workgroup or domain from configuration store");
39 | return std::nullopt;
40 | }
41 |
42 | WinNetInfo ret;
43 | sys_string_builder builder;
44 |
45 | if (domain.cf_str()) {
46 | for(auto c: sys_string_cfstr::utf8_view(domain))
47 | builder.append(c);
48 | ret.memberOf.emplace(builder.build());
49 | } else {
50 | for(auto c: sys_string_cfstr::utf8_view(workgroup))
51 | builder.append(c);
52 | ret.memberOf.emplace(builder.build());
53 | }
54 |
55 | if (useNetbiosHostName && hostname.cf_str()) {
56 | for(auto c: sys_string_cfstr::utf8_view(hostname))
57 | builder.append(c);
58 | ret.hostName = builder.build();
59 | } else {
60 | if (useNetbiosHostName)
61 | ret.hostName = m_simpleHostName.to_upper();
62 | else
63 | ret.hostName = m_simpleHostName;
64 | }
65 |
66 | if (desc.cf_str()) {
67 | for(auto c: sys_string_cfstr::utf8_view(desc))
68 | builder.append(c);
69 | ret.hostDescription = builder.build();
70 | } else {
71 | ret.hostDescription = m_simpleHostName;
72 | }
73 |
74 | return ret;
75 | }
76 |
77 | #endif
78 |
--------------------------------------------------------------------------------
/installers/mac/wrapper/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022, Eugene Gershnik
2 | # SPDX-License-Identifier: BSD-3-Clause
3 |
4 |
5 | configure_file(Info.template.plist Info.plist @ONLY)
6 |
7 | file(GLOB_RECURSE ASSET_FILES CONFIGURE_DEPENDS Assets.xcassets/*)
8 |
9 | set(GENERATED_ASSETS
10 | ${CMAKE_CURRENT_BINARY_DIR}/assets/AppIcon.icns
11 | ${CMAKE_CURRENT_BINARY_DIR}/assets/Assets.car
12 | )
13 |
14 | add_custom_command(
15 | OUTPUT ${GENERATED_ASSETS}
16 | DEPENDS ${ASSET_FILES}
17 | COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/assets
18 | COMMAND actool --output-format human-readable-text --notices --warnings
19 | --app-icon AppIcon --accent-color AccentColor
20 | --output-partial-info-plist ${CMAKE_CURRENT_BINARY_DIR}/assets/PartialInfo.plist
21 | --enable-on-demand-resources NO --development-region en --target-device mac
22 | --minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET} --platform macosx
23 | --compile ${CMAKE_CURRENT_BINARY_DIR}/assets
24 | ${CMAKE_CURRENT_LIST_DIR}/Assets.xcassets
25 |
26 | )
27 |
28 | add_executable(wrapper MACOSX_BUNDLE)
29 |
30 | set_target_properties(wrapper PROPERTIES
31 | OUTPUT_NAME wsdd-native
32 | CXX_EXTENSIONS OFF
33 | CXX_STANDARD 20
34 | C_STANDARD 11
35 | CXX_STANDARD_REQUIRED True
36 | C_STANDARD_REQUIRED True
37 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist
38 | )
39 |
40 | target_link_libraries(wrapper
41 | PRIVATE
42 | "-framework Foundation"
43 | "-framework CoreServices"
44 | )
45 |
46 | target_sources(wrapper
47 | PRIVATE
48 | main.mm
49 | ${GENERATED_ASSETS}
50 | )
51 | set_source_files_properties(${GENERATED_ASSETS} PROPERTIES
52 | MACOSX_PACKAGE_LOCATION Resources
53 | )
54 |
55 | target_link_options(wrapper
56 | PRIVATE
57 | "$<$:-Wl,-object_path_lto,${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lto.o>"
58 | "$<$:-Wl,-cache_path_lto,${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/LTOCache>"
59 | "$<$:-Wl,-no_adhoc_codesign>"
60 | "$<$,$,13.5>>:-Wl,-reproducible>"
61 | "$<$,$,15.0>>:LINKER:-no_warn_duplicate_libraries>" #see https://gitlab.kitware.com/cmake/cmake/-/issues/25297
62 | )
63 |
64 | if (CMAKE_GENERATOR MATCHES "Xcode")
65 | set_target_properties(wrapper PROPERTIES
66 | XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/Info.plist
67 | XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
68 | XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS YES
69 | XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT dwarf-with-dsym
70 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${WSDDN_BUNDLE_IDENTIFIER}.wrapper
71 | )
72 | else()
73 | add_custom_command(TARGET wrapper POST_BUILD
74 | COMMAND dsymutil "$" -o "$.dSYM"
75 | COMMAND codesign --force --sign - --timestamp=none
76 | --options runtime "$"
77 | )
78 | endif()
--------------------------------------------------------------------------------
/installers/freebsd/make-toolchain.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env -S python3 -u
2 |
3 | # Copyright (c) 2022, Eugene Gershnik
4 | # SPDX-License-Identifier: BSD-3-Clause
5 |
6 | import sys
7 | import argparse
8 | import subprocess
9 | import json
10 | import shutil
11 | import hashlib
12 | from pathlib import Path
13 |
14 |
15 | parser = argparse.ArgumentParser()
16 |
17 | parser.add_argument('platform', type=str)
18 | parser.add_argument('dir', type=Path)
19 |
20 | args = parser.parse_args()
21 |
22 | platform = args.platform
23 | outdir: Path = args.dir
24 |
25 | if platform == 'arm64':
26 | arch = 'aarch64'
27 | elif platform == 'amd64':
28 | arch = 'x86_64'
29 | else:
30 | print(f'no platform->arch mapping for {platform}', file=sys.stderr)
31 | sys.exit(1)
32 |
33 | verRes = subprocess.run(['freebsd-version'], check=False, capture_output=True, encoding='utf-8')
34 | if verRes.returncode != 0:
35 | sys.exit(1)
36 | fullVersion = verRes.stdout.strip()
37 | version = fullVersion[:fullVersion.rfind('-')]
38 |
39 | toolchain = f'''
40 | set(CMAKE_SYSROOT ${{CMAKE_CURRENT_LIST_DIR}})
41 | set(CMAKE_C_COMPILER_TARGET {arch}-unknown-freebsd{version})
42 | set(CMAKE_CXX_COMPILER_TARGET {arch}-unknown-freebsd{version})
43 |
44 | set(CMAKE_FIND_ROOT_PATH ${{CMAKE_CURRENT_LIST_DIR}})
45 |
46 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
47 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
48 |
49 | set(CMAKE_EXE_LINKER_FLAGS "${{CMAKE_EXE_LINKER_FLAGS}} -stdlib=libc++")
50 |
51 | set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES
52 | "${{CMAKE_CURRENT_LIST_DIR}}/usr/include"
53 | "${{CMAKE_CURRENT_LIST_DIR}}/usr/include/sys"
54 | )
55 | set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
56 | "${{CMAKE_CURRENT_LIST_DIR}}/usr/include/c++/v1"
57 | ${{CMAKE_C_STANDARD_INCLUDE_DIRECTORIES}}
58 | )
59 | '''.lstrip()
60 | toolchainHash = hashlib.sha256(toolchain.encode('utf-8')).hexdigest()
61 |
62 | newInfo = {
63 | 'version': version,
64 | 'platform': platform,
65 | 'toolchainHash': toolchainHash
66 | }
67 |
68 | try:
69 | info = json.loads((outdir / '.info.json').read_text())
70 | if info == newInfo:
71 | print(f'{outdir} already contains the requested toolchain')
72 | sys.exit(0)
73 | print(f'{outdir} contains non-matching existing toolchain: \n{json.dumps(info, indent=4)}\nwill overwrite')
74 | except FileNotFoundError:
75 | pass
76 |
77 | if outdir.exists():
78 | shutil.rmtree(outdir)
79 | outdir.mkdir(parents=True, exist_ok=False)
80 |
81 | procCurl = subprocess.Popen(['curl', '-LSs',
82 | f'https://download.freebsd.org/ftp/releases/{platform}/{fullVersion}/base.txz'
83 | ], stdout=subprocess.PIPE)
84 | procTar = subprocess.Popen(['tar', 'Jxf', '-',
85 | '--include', './usr/include/*',
86 | '--include', './usr/lib/*',
87 | '--include', './lib/*'
88 | ], cwd=outdir, stdin=procCurl.stdout)
89 | procCurl.stdout.close()
90 | procTar.wait()
91 | procCurl.wait()
92 | if procTar.returncode != 0 or procCurl.returncode != 0:
93 | print('unpacking failed', file=sys.stderr)
94 | sys.exit(1)
95 |
96 |
97 | (outdir / 'toolchain.cmake').write_text(toolchain)
98 | (outdir / '.info.json').write_text(json.dumps(newInfo, indent = 4))
99 |
--------------------------------------------------------------------------------
/src/pid_file.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #ifndef HEADER_PID_FILE_H_INCLUDED
5 | #define HEADER_PID_FILE_H_INCLUDED
6 |
7 | #include "sys_util.h"
8 |
9 | class PidFile {
10 | public:
11 | PidFile() = default;
12 | PidFile(PidFile &&) noexcept = default;
13 | PidFile & operator=(PidFile && src) noexcept {
14 | this->~PidFile();
15 | new (this) PidFile(std::move(src));
16 | return *this;
17 | }
18 |
19 | static auto open(std::filesystem::path filename,
20 | std::optional owner = std::nullopt) -> std::optional {
21 |
22 | const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
23 |
24 | createMissingDirs(filename.parent_path(), mode | S_IXUSR | S_IXGRP | S_IXOTH, owner);
25 |
26 | for ( ; ; ) {
27 | auto fd = ptl::FileDescriptor::open(filename, O_WRONLY | O_CREAT, mode);
28 | if (!ptl::tryLockFile(fd, ptl::FileLock::Exclusive))
29 | return std::nullopt;
30 | struct stat openFileStatus;
31 | ptl::getStatus(fd, openFileStatus);
32 | struct stat fileSystemFileStatus;
33 | std::error_code ec;
34 | ptl::getStatus(filename, fileSystemFileStatus, ec);
35 | //if we cannot get the status from filesystem or it is a different file,
36 | //it means some other process got there before us and created a new pid file
37 | //and so we need to retry.
38 | if (ec ||
39 | openFileStatus.st_dev != fileSystemFileStatus.st_dev ||
40 | openFileStatus.st_ino != fileSystemFileStatus.st_ino) {
41 |
42 | continue;
43 | }
44 |
45 | auto pid = getpid();
46 | initFile(fd, mode, owner, pid);
47 | return PidFile(std::move(fd), std::move(filename), pid);
48 | }
49 |
50 | }
51 |
52 | ~PidFile() noexcept {
53 | if (!m_fd)
54 | return;
55 |
56 | if (getpid() != m_lockingProcess)
57 | return;
58 |
59 | std::error_code ec;
60 | std::filesystem::remove(m_path, ec);
61 | if (ec)
62 | WSDLOG_ERROR("unable to remove pidfile {}, error: {}\n", m_path.c_str(), ec.message().c_str());
63 | }
64 | private:
65 | PidFile(ptl::FileDescriptor && fd, std::filesystem::path && path, pid_t proc) noexcept:
66 | m_fd(std::move(fd)), m_path(std::move(path)), m_lockingProcess(proc) {
67 | }
68 |
69 | static void initFile(const ptl::FileDescriptor & fd, mode_t mode, const std::optional & owner, pid_t pid) {
70 | ptl::changeMode(fd, mode);
71 | if (owner)
72 | ptl::changeOwner(fd, owner->uid(), owner->gid());
73 | ptl::truncateFile(fd, 0);
74 | auto strPid = std::to_string(pid);
75 | strPid += '\n';
76 | if ((size_t)writeFile(fd, strPid.data(), strPid.size()) != strPid.size())
77 | throw std::runtime_error("partial write to pid file!");
78 | }
79 | private:
80 | ptl::FileDescriptor m_fd;
81 | std::filesystem::path m_path;
82 | pid_t m_lockingProcess = -1;
83 | };
84 |
85 | static_assert(!std::is_copy_constructible_v);
86 | static_assert(!std::is_copy_assignable_v);
87 |
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/src/config.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #ifndef HEADER_CONFIG_H_INCLUDED
5 | #define HEADER_CONFIG_H_INCLUDED
6 |
7 | #include "sys_util.h"
8 | #include "util.h"
9 | #include "xml_wrapper.h"
10 |
11 | constexpr uint16_t g_WsdUdpPort = 3702;
12 | constexpr uint16_t g_WsdHttpPort = 5357;
13 |
14 | constexpr const char * g_WsdMulticastGroupV4 = "239.255.255.250";
15 | constexpr const char * g_WsdMulticastGroupV6 = "ff02::c"; // link-local
16 |
17 | constexpr size_t g_maxLogFileSize = 1024 * 1024;
18 | constexpr size_t g_maxRotatedLogs = 5;
19 |
20 | struct CommandLine;
21 |
22 | class Config : public ref_counted {
23 | friend ref_counted;
24 | public:
25 | struct WinNetInfo {
26 | sys_string hostName;
27 | sys_string hostDescription;
28 | MemberOf memberOf;
29 | };
30 |
31 | struct SambaParams {
32 | std::optional workgroup;
33 | std::optional security;
34 | std::optional hostName;
35 | std::optional hostDescription;
36 | };
37 | public:
38 | static refcnt_ptr make(const CommandLine & cmdline) {
39 | return refcnt_attach(new Config(cmdline));
40 | }
41 |
42 | auto instanceIdentifier() const -> size_t { return m_instanceIdentifier; };
43 | auto endpointIdentifier() const -> const sys_string & { return m_urnUuid; }
44 | auto httpPath() const -> const sys_string & { return m_strUuid; }
45 | auto winNetInfo() const -> const WinNetInfo & { return m_winNetInfo; }
46 | auto metadataDoc() const -> XmlDoc * { return m_metadataDoc.get(); }
47 |
48 | auto enableIPv4() const -> bool { return m_allowedAddressFamily != IPv6Only; }
49 | auto enableIPv6() const -> bool { return m_allowedAddressFamily != IPv4Only; }
50 | auto hopLimit() const -> int { return m_hopLimit; }
51 | auto isAllowedInterface(const sys_string & name) const -> bool {
52 | return m_interfaceWhitelist.empty() || m_interfaceWhitelist.contains(name);
53 | }
54 | auto sourcePort() const -> uint16_t { return m_sourcePort; }
55 |
56 | auto pageSize() const -> size_t { return m_pageSize; }
57 |
58 | private:
59 | Config(const CommandLine & cmdline);
60 | ~Config() {};
61 |
62 | #if HAVE_APPLE_SAMBA
63 | auto detectAppleWinNetInfo(bool useNetbiosHostName) -> std::optional;
64 | #endif
65 | auto detectWinNetInfo(std::optional smbConf, bool useNetbiosHostName) -> std::optional;
66 | auto sambaParamsToWinNetInfo(const SambaParams & params, bool useNetbiosHostName) -> WinNetInfo;
67 |
68 | auto getHostName() const -> sys_string;
69 |
70 | auto loadMetadaFile(const std::string & filename) const -> std::unique_ptr;
71 | private:
72 | size_t m_instanceIdentifier;
73 | sys_string m_fullHostName;
74 | sys_string m_simpleHostName;
75 | Uuid m_uuid;
76 | sys_string m_strUuid;
77 | sys_string m_urnUuid;
78 | WinNetInfo m_winNetInfo;
79 | std::unique_ptr m_metadataDoc;
80 |
81 | AllowedAddressFamily m_allowedAddressFamily = BothIPv4AndIPv6;
82 | int m_hopLimit = 1;
83 | std::set m_interfaceWhitelist;
84 | uint16_t m_sourcePort;
85 |
86 | size_t m_pageSize;
87 | };
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/dependencies.json:
--------------------------------------------------------------------------------
1 | {
2 | "argum": {
3 | "version": "v2.6",
4 | "url": "https://github.com/gershnik/argum/tarball/${version}",
5 | "homepage": "https://github.com/gershnik/argum",
6 | "sha256": "72b2b6805da7bf022e8111f3c2f3ed08ae6c23daa0ad336de56f2bd133d653c4",
7 | "md5": "59141e9e95b75097b0cf6d9d36fc6304"
8 | },
9 | "asio": {
10 | "version": "1.36.0",
11 | "url": "https://downloads.sourceforge.net/asio/asio-${version}.tar.gz",
12 | "homepage": "https://think-async.com/Asio/",
13 | "sha256": "55d5c64e78b1bedd0004423e695c2cfc191fc71914eaaa7f042329ff99ee6155",
14 | "md5": "a3fc4f4ebd5baac595a7b3fea90f2117"
15 | },
16 | "fmt": {
17 | "version": "12.0.0",
18 | "url": "https://github.com/fmtlib/fmt/tarball/${version}",
19 | "homepage": "https://github.com/fmtlib/fmt",
20 | "sha256": "24b8e5ecad723fa9654ccbc5e322ff7881cc70425ad52dcdf5b88f5873e6d16b",
21 | "md5": "f9d5b80495bb0c8327a988234e9e9524"
22 | },
23 | "isptr": {
24 | "version": "v1.9",
25 | "url": "https://github.com/gershnik/intrusive_shared_ptr/tarball/${version}",
26 | "homepage": "https://github.com/gershnik/intrusive_shared_ptr",
27 | "sha256": "f9095609a2226f3aa6dbfcd4726a8521a56f4fd2f426b0898d92acd1f133aa6d",
28 | "md5": "574eb3ad0a8e5685cdc9e7712804900f"
29 | },
30 | "LibXml2": {
31 | "version": "v2.15.0",
32 | "url": "https://gitlab.gnome.org/GNOME/libxml2/-/archive/${version}/libxml2-${version}.tar.gz",
33 | "homepage": "https://gitlab.gnome.org/GNOME/libxml2",
34 | "sha256": "027e302d24e0d136393a24e02938046cda72281a3e3620d56cbc0995524658bc",
35 | "md5": "6d29889f41d024c5b8b561067aee89fa"
36 | },
37 | "modern-uuid": {
38 | "version": "v2.1",
39 | "url": "https://github.com/gershnik/modern-uuid/tarball/${version}",
40 | "homepage": "https://github.com/gershnik/modern-uuid",
41 | "sha256": "f52bad71a9691fe10a88982cee876f4670885bc4d533fbce8790e0013aad8752",
42 | "md5": "b07ea7836194f192b1bab0bbd446d9de"
43 | },
44 | "outcome": {
45 | "version": "v2.2.13",
46 | "url": "https://github.com/ned14/outcome/tarball/${version}",
47 | "homepage": "https://github.com/ned14/outcome",
48 | "sha256": "3ee937017934371169c2f50d1e3742839dd6ab4fc1f23141cbd2efc3824cfd15",
49 | "md5": "e6d0ae19b6c250ba5e6ceb0ec11eaddb"
50 | },
51 | "ptl": {
52 | "version": "v1.7",
53 | "url": "https://github.com/gershnik/ptl/tarball/${version}",
54 | "homepage": "https://github.com/gershnik/ptl",
55 | "sha256": "e3efb37f71846ba7d10165bef7f62a581dd3e7c8f4ac185bb86d4069bc4ec9ed",
56 | "md5": "f4c503429bd1fa9c8db8823a64590a4b"
57 | },
58 | "spdlog": {
59 | "version": "v1.16.0",
60 | "url": "https://github.com/gabime/spdlog/tarball/${version}",
61 | "homepage": "https://github.com/gabime/spdlog",
62 | "sha256": "72d7aebe7730d5d5573524e11736bdba8c1891f07e8d80cc7e8f7b36ec7c046d",
63 | "md5": "85ad737bf7f042010ccfe998d1c8a566"
64 | },
65 | "sys_string": {
66 | "version": "v2.22",
67 | "url": "https://github.com/gershnik/sys_string/tarball/${version}",
68 | "homepage": "https://github.com/gershnik/sys_string",
69 | "sha256": "774f8b4e39cbdba4b1a2617878a4deccb095c427dbfcd22493a221ba0960b8e4",
70 | "md5": "10f07a548b1dbfd4ceef3ce74de3f45b"
71 | },
72 | "tomlplusplus": {
73 | "version": "v3.4.0",
74 | "url": "https://github.com/marzer/tomlplusplus/tarball/${version}",
75 | "homepage": "https://github.com/marzer/tomlplusplus",
76 | "sha256": "8874014da21de8d1414d9914c8f3c6b5f315c23a75951b33df46048c13dda12f",
77 | "md5": "562036440b3283550d508ef5dd85a839"
78 | }
79 | }
--------------------------------------------------------------------------------
/src/http_request_parser.h:
--------------------------------------------------------------------------------
1 |
2 | // Copyright (c) 2022, Eugene Gershnik
3 | // Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
4 | // SPDX-License-Identifier: BSD-3-Clause
5 |
6 |
7 | #ifndef HEADER_HTTP_REQUEST_PARSER_H_INCLUDED
8 | #define HEADER_HTTP_REQUEST_PARSER_H_INCLUDED
9 |
10 | #include "http_request.h"
11 |
12 | /**
13 | Parser for HTTP 1.x request header
14 | */
15 | class HttpRequestParser
16 | {
17 | private:
18 | static constexpr std::tuple s_minVersion{1, 0};
19 | static constexpr std::tuple s_maxVersion{1, 1};
20 | static constexpr size_t s_maxMethodSize = 10;
21 | static constexpr size_t s_maxUriSize = 2048;
22 | static constexpr size_t s_maxHeadersSize = 8192;
23 | public:
24 | /// Reset to initial parser state.
25 | void reset();
26 |
27 | /// Result of parse.
28 | enum ResultType { Good, Bad, Indeterminate };
29 |
30 | /// Parse some data. The enum return value is Good when a complete request has
31 | /// been parsed, Bad if the data is invalid, Indeterminate when more data is
32 | /// required. The InputIterator return value indicates how much of the input
33 | /// has been consumed.
34 | template
35 | requires(sizeof(typename std::iterator_traits::value_type) == 1)
36 | auto parse(HttpRequest & req, InputIterator begin, InputIterator end) ->
37 | std::tuple
38 | {
39 | while (begin != end)
40 | {
41 | ResultType result = consume(req, uint8_t(*begin++));
42 | if (result == Good || result == Bad)
43 | return {result, begin};
44 | }
45 | return {Indeterminate, begin};
46 | }
47 |
48 | private:
49 | /// Handle the next character of input.
50 | auto consume(HttpRequest & req, uint8_t input) -> ResultType;
51 |
52 | /// Check if a byte is an HTTP character.
53 | static bool isChar(uint8_t c) {
54 | return c <= 127;
55 | }
56 |
57 | /// Check if a byte is an HTTP control character.
58 | static bool isCtl(uint8_t c) {
59 | return c <= 31 || c == 127;
60 | }
61 |
62 | /// Check if a byte is defined as an HTTP tspecial character.
63 | static bool isTSpecial(uint8_t c) {
64 | switch (c)
65 | {
66 | case u8'(': case u8')': case u8'<': case u8'>': case u8'@':
67 | case u8',': case u8';': case u8':': case u8'\\': case u8'"':
68 | case u8'/': case u8'[': case u8']': case u8'?': case u8'=':
69 | case u8'{': case u8'}': case u8' ': case u8'\t':
70 | return true;
71 | default:
72 | return false;
73 | }
74 | }
75 |
76 | /// Check if a byte is a digit.
77 | static bool isDigit(char8_t c) {
78 | return c >= u8'0' && c <= u8'9';
79 | }
80 |
81 | /// The current state of the parser.
82 | enum State
83 | {
84 | method_start,
85 | method,
86 | uri,
87 | http_version_h,
88 | http_version_t_1,
89 | http_version_t_2,
90 | http_version_p,
91 | http_version_slash,
92 | http_version_major_start,
93 | http_version_major,
94 | http_version_minor_start,
95 | http_version_minor,
96 | expecting_newline_1,
97 | header_line_start,
98 | header_lws,
99 | header_name,
100 | space_before_header_value,
101 | header_value,
102 | expecting_newline_2,
103 | expecting_newline_3
104 | } m_state = method_start;
105 |
106 | sys_string_builder m_methodBuilder;
107 | sys_string_builder m_uriBuilder;
108 | unsigned m_versionMajor = 0;
109 | unsigned m_versionMinor = 0;
110 | sys_string_builder m_headerNameBuilder;
111 | sys_string_builder m_headerValueBuilder;
112 | unsigned m_totalHeadersSize = 0;
113 | };
114 |
115 |
116 | #endif
117 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | - '*/**'
8 | paths-ignore:
9 | - 'README.md'
10 | - '.gitignore'
11 | - 'LICENSE'
12 | - 'CHANGELOG.md'
13 | - 'SECURITY.md'
14 | - 'Acknowledgements.md'
15 | - 'config/metadata/**'
16 | - '.github/workflows/publish.yml'
17 | - '.github/workflows/check.yml'
18 | - 'tools/**'
19 |
20 |
21 | jobs:
22 | selfhosted:
23 | concurrency: ${{ matrix.remote_host }}
24 | runs-on: [self-hosted, server]
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | include:
29 | - remote_host: debian-11
30 | installer: deb
31 | - remote_host: debian-11-arm
32 | installer: deb
33 | - remote_host: debian-11-armhf
34 | installer: deb
35 | - remote_host: freebsd-13.1
36 | installer: freebsd
37 | - remote_host: freebsd-14
38 | installer: freebsd
39 | - remote_host: openbsd-7-5
40 | installer: openbsd
41 |
42 | steps:
43 | - name: Run remote build
44 | run: |
45 | "$RUNNER_TOOLS_PATH"/run-agent gh-${{ matrix.remote_host }} <<'EOF'
46 | set -e
47 | if [ ! -d work/wsdd-native ]; then
48 | git clone https://github.com/gershnik/wsdd-native.git work/wsdd-native
49 | fi
50 | cd work/wsdd-native
51 | git fetch --all
52 | git fetch -f --prune --tags
53 | git reset --hard ${{ github.sha }}
54 | if [[ '${{ matrix.installer }}' == 'freebsd' ]]; then
55 | mkdir -p out
56 | echo "::group::AMD64"
57 | [ -d "out/amd64" ] && tools/uncache out/amd64
58 | cmake -S . -B out/amd64 -DCMAKE_BUILD_TYPE=RelWithDebInfo
59 | installers/freebsd/build.py . out/amd64
60 | echo "::endgroup::"
61 |
62 | echo "::group::ARM64"
63 | installers/freebsd/make-toolchain.py arm64 out/toolchain-arm64
64 | [ -d "out/arm64" ] && tools/uncache out/arm64
65 | cmake -S . -B out/arm64 -DCMAKE_TOOLCHAIN_FILE=out/toolchain-arm64/toolchain.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
66 | installers/freebsd/build.py --arch=aarch64 . out/arm64
67 | echo "::endgroup::"
68 | elif [[ '${{ matrix.installer }}' == 'openbsd' ]]; then
69 | [ -d "out" ] && tools/uncache out
70 | cmake -S . -B out -DCMAKE_BUILD_TYPE=RelWithDebInfo
71 | installers/openbsd/build.py . out
72 | else
73 | [ -d "out" ] && tools/uncache out
74 | cmake -S . -B out -DCMAKE_BUILD_TYPE=RelWithDebInfo
75 | installers/${{ matrix.installer }}/build.py --sign . out
76 | fi
77 | EOF
78 |
79 | mac:
80 | runs-on: macos-14
81 | env:
82 | DEVELOPER_DIR: /Applications/Xcode_16.2.0.app
83 |
84 | steps:
85 | - name: Checkout
86 | uses: actions/checkout@v4
87 |
88 | - name: Collect System Info
89 | id: system-info
90 | uses: kenchan0130/actions-system-info@master
91 |
92 | - name: Configure
93 | run: |
94 | [ -d "out" ] && tools/uncache out
95 | cmake -S . -B out \
96 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \
97 | "-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64" \
98 | -DCMAKE_IGNORE_PREFIX_PATH=/opt/local \
99 | -DWSDDN_PREFER_SYSTEM_LIBXML2=ON
100 |
101 | - name: Build
102 | run: |
103 | export SIGN_CERTIFICATE='${{ secrets.SIGN_CERTIFICATE }}'
104 | export SIGN_CERTIFICATE_PWD='${{ secrets.SIGN_CERTIFICATE_PWD }}'
105 | export KEYCHAIN_PWD='${{ secrets.KEYCHAIN_PWD }}'
106 | export NOTARIZE_USER='${{ secrets.NOTARIZE_USER }}'
107 | export NOTARIZE_PWD='${{ secrets.NOTARIZE_PWD }}'
108 | installers/mac/set-github-keychain
109 |
110 | installers/mac/build.py --sign . out
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/exc_handling.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #include "exc_handling.h"
5 |
6 | #if __has_include() //moronic Alpine/MUSL have no backtrace and no replacement
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | using CodePtr = void (*)(void);
13 |
14 | // static auto demangle_free = [](char * ptr) {
15 | // free(ptr);
16 | // };
17 | // using demangled_string = std::unique_ptr;
18 |
19 | class Backtrace {
20 | public:
21 | Backtrace() = default;
22 |
23 | [[gnu::always_inline]] void fill(size_t skip) {
24 |
25 | int res = backtrace(reinterpret_cast(s_backtraceBuffer.data()), static_cast(s_backtraceBuffer.size()));
26 | if (res > 0 && size_t(res) > skip) {
27 | m_pointers.assign(s_backtraceBuffer.begin() + skip, s_backtraceBuffer.begin() + size_t(res) - skip);
28 | } else {
29 | m_pointers.clear();
30 | }
31 | }
32 |
33 | template
34 | void print(OutIt dest) const {
35 |
36 | fmt::format_to(dest, "--------\n");
37 | for (auto ptr: m_pointers)
38 | {
39 | auto addr = reinterpret_cast*const*/ void *>(ptr);
40 | fmt::format_to(dest, " {:p}", addr);
41 | Dl_info info{};
42 | if (dladdr(addr, &info))
43 | {
44 | // if (info.dli_sname)
45 | // {
46 | // int stat = 0;
47 | // demangled_string demangled(abi::__cxa_demangle(info.dli_sname, 0, 0, &stat), demangle_free);
48 | // size_t offset = intptr_t(addr) - intptr_t(info.dli_saddr);
49 | // fmt::format_to(dest, " {} + {}", (demangled ? demangled.get() : info.dli_sname), offset);
50 | // }
51 | if (info.dli_fname)
52 | {
53 | size_t offset = intptr_t(addr) - intptr_t(info.dli_fbase);
54 | const char * basename = strrchr(info.dli_fname, '/');
55 | fmt::format_to(dest, " {} + 0x{:X}", (basename ? basename + 1 : info.dli_fname), offset);
56 | }
57 | }
58 | fmt::format_to(dest, "\n");
59 | }
60 | fmt::format_to(dest, "--------\n");
61 | }
62 | private:
63 | std::vector m_pointers;
64 | static thread_local std::array s_backtraceBuffer;
65 | };
66 |
67 | thread_local std::array Backtrace::s_backtraceBuffer;
68 |
69 | thread_local std::vector g_backtraces;
70 |
71 |
72 | extern "C" {
73 |
74 | #if HAVE_ABI_CXA_THROW
75 | #define CXA_THROW abi::__cxa_throw
76 | #else
77 | #define CXA_THROW __cxa_throw
78 | #endif
79 |
80 | [[gnu::noinline]] __attribute__((__visibility__("default"))) __attribute__ ((noreturn))
81 | void CXA_THROW(void * ex, std::type_info * info, void (*dest)(void *)) {
82 |
83 | auto currentIdx = std::uncaught_exceptions();
84 | g_backtraces.resize(currentIdx + 1);
85 | g_backtraces[currentIdx].fill(/*skip=*/1);
86 |
87 | using __cxa_throw_t = decltype(&CXA_THROW);
88 |
89 | static __cxa_throw_t __attribute__ ((noreturn)) realCxaThrow = (__cxa_throw_t)dlsym(RTLD_NEXT, "__cxa_throw");
90 | realCxaThrow(ex, info, dest);
91 | }
92 | }
93 |
94 | template
95 | static void doPrintCaughtExceptionBacktrace(OutIt dest) {
96 | auto currentIdx = std::uncaught_exceptions();
97 | if (size_t(currentIdx) > g_backtraces.size()) {
98 | fmt::format_to(dest, " \n");
99 | return;
100 | }
101 |
102 | const Backtrace & backtrace = g_backtraces[currentIdx];
103 | backtrace.print(dest);
104 | }
105 |
106 | auto formatCaughtExceptionBacktrace() -> std::string {
107 | std::string ret = "Backtrace:\n";
108 | doPrintCaughtExceptionBacktrace(std::back_inserter(ret));
109 | return ret;
110 | }
111 |
112 | #else
113 |
114 | auto formatCaughtExceptionBacktrace() -> std::string {
115 | return "";
116 | }
117 |
118 | #endif
119 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | prepare:
10 | runs-on: ubuntu-latest
11 | permissions: write-all
12 | steps:
13 | - name: Make release
14 | uses: softprops/action-gh-release@v2
15 | id: create_release
16 | with:
17 | draft: true
18 | prerelease: false
19 | body: ...edit me...
20 |
21 | selfhosted:
22 | concurrency: ${{ matrix.remote_host }}
23 | runs-on: [self-hosted, server]
24 | needs: prepare
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | include:
29 | - remote_host: debian-11
30 | installer: deb
31 | - remote_host: debian-11-arm
32 | installer: deb
33 | - remote_host: debian-11-armhf
34 | installer: deb
35 | - remote_host: freebsd-13.1
36 | installer: freebsd
37 | - remote_host: freebsd-14
38 | installer: freebsd
39 | - remote_host: openbsd-7-5
40 | installer: openbsd
41 |
42 | steps:
43 | - name: Run remote build
44 | run: |
45 | "$RUNNER_TOOLS_PATH"/run-agent gh-${{ matrix.remote_host }} <<'EOF'
46 | set -e
47 | if [ ! -d work/wsdd-native ]; then
48 | git clone https://github.com/gershnik/wsdd-native.git work/wsdd-native
49 | fi
50 | cd work/wsdd-native
51 | git fetch --all
52 | git fetch -f --prune --tags
53 | git reset --hard ${{ github.sha }}
54 | export GH_TOKEN=${{ secrets.GITHUB_TOKEN }}
55 | if [[ '${{ matrix.installer }}' == 'freebsd' ]]; then
56 | mkdir -p out
57 | echo "::group::AMD64"
58 | [ -d "out/amd64" ] && tools/uncache out/amd64
59 | cmake -S . -B out/amd64 -DCMAKE_BUILD_TYPE=RelWithDebInfo
60 | installers/freebsd/build.py --upload-results . out/amd64
61 | echo "::endgroup::"
62 |
63 | echo "::group::ARM64"
64 | installers/freebsd/make-toolchain.py arm64 out/toolchain-arm64
65 | [ -d "out/arm64" ] && tools/uncache out/arm64
66 | cmake -S . -B out/arm64 -DCMAKE_TOOLCHAIN_FILE=out/toolchain-arm64/toolchain.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo
67 | installers/freebsd/build.py --arch=aarch64 --upload-results . out/arm64
68 | echo "::endgroup::"
69 | else
70 | [ -d "out" ] && tools/uncache out
71 | cmake -S . -B out -DCMAKE_BUILD_TYPE=RelWithDebInfo
72 | installers/${{ matrix.installer }}/build.py --upload-results . out
73 | fi
74 | EOF
75 |
76 | mac:
77 | runs-on: macos-14
78 | needs: prepare
79 | env:
80 | DEVELOPER_DIR: /Applications/Xcode_16.2.app
81 |
82 | steps:
83 | - name: Checkout
84 | uses: actions/checkout@v4
85 |
86 | - name: Collect System Info
87 | id: system-info
88 | uses: kenchan0130/actions-system-info@master
89 |
90 | - name: Cache Build Dir
91 | id: cache-build-dir
92 | uses: actions/cache@v4
93 | with:
94 | path: out
95 | key: ${{ runner.os }}-${{ steps.system-info.outputs.release }}-out
96 |
97 | - name: Configure
98 | run: |
99 | [ -d "out" ] && tools/uncache out
100 | cmake -S . -B out \
101 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \
102 | "-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64" \
103 | -DCMAKE_IGNORE_PREFIX_PATH=/opt/local \
104 | -DWSDDN_PREFER_SYSTEM_LIBXML2=ON
105 |
106 |
107 | - name: Make Distribution
108 | env:
109 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
110 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
111 | AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
112 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
113 | run: |
114 | export SIGN_CERTIFICATE='${{ secrets.SIGN_CERTIFICATE }}'
115 | export SIGN_CERTIFICATE_PWD='${{ secrets.SIGN_CERTIFICATE_PWD }}'
116 | export KEYCHAIN_PWD='${{ secrets.KEYCHAIN_PWD }}'
117 | export NOTARIZE_USER='${{ secrets.NOTARIZE_USER }}'
118 | export NOTARIZE_PWD='${{ secrets.NOTARIZE_PWD }}'
119 |
120 | installers/mac/set-github-keychain
121 | installers/mac/build.py --upload-results . out
122 |
--------------------------------------------------------------------------------
/installers/freebsd/build.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env -S python3 -u
2 |
3 | # Copyright (c) 2022, Eugene Gershnik
4 | # SPDX-License-Identifier: BSD-3-Clause
5 |
6 | import sys
7 | import subprocess
8 | import shutil
9 | import re
10 | import argparse
11 | from pathlib import Path
12 |
13 | ARCH = subprocess.run(['uname', '-m'], check=True, capture_output=True, encoding="utf-8").stdout.strip()
14 |
15 | ABI = None
16 | for line in subprocess.run(['pkg', '-vv'], check=True, encoding="utf-8", capture_output=True).stdout.splitlines():
17 | m = re.match(r'ABI\s*=\s*"([^"]+)";', line)
18 | if m:
19 | ABI = m.group(1)
20 | break
21 |
22 | if ABI is None:
23 | print("Unable to determine ABI", file=sys.stderr)
24 | sys.exit(1)
25 |
26 | mydir = Path(sys.argv[0]).parent
27 |
28 | sys.path.append(str(mydir.absolute().parent))
29 |
30 | from common import getVersion, getSrcVersion, buildCode, installCode, copyTemplated
31 |
32 | parser = argparse.ArgumentParser()
33 |
34 | parser.add_argument('srcdir', type=Path)
35 | parser.add_argument('builddir', type=Path)
36 | parser.add_argument('--upload-results', dest='uploadResults', action='store_true', required=False)
37 | parser.add_argument('--arch', required=False)
38 |
39 | args = parser.parse_args()
40 |
41 | srcdir: Path = args.srcdir
42 | builddir: Path = args.builddir
43 |
44 | buildCode(builddir)
45 |
46 | if not args.arch is None:
47 | ABI = ABI[:ABI.rfind(':') + 1] + args.arch
48 | ARCH = args.arch
49 | VERSION = getSrcVersion(srcdir)
50 | else:
51 | VERSION = getVersion(builddir)
52 |
53 | workdir = builddir / 'stage/freebsd'
54 | stagedir = workdir / 'root'
55 | shutil.rmtree(workdir, ignore_errors=True)
56 | stagedir.mkdir(parents=True)
57 |
58 | installCode(builddir, stagedir / 'usr/local')
59 |
60 | shutil.copytree(srcdir / 'config/freebsd/usr', stagedir / 'usr', dirs_exist_ok=True)
61 | docdir = stagedir / 'usr/local/share/doc/wsddn'
62 | docdir.mkdir(parents=True)
63 | shutil.copy(srcdir / 'LICENSE', docdir / 'LICENSE')
64 | shutil.copy(srcdir / 'Acknowledgements.md', docdir / 'Acknowledgements.md')
65 |
66 | copyTemplated(mydir.parent / 'wsddn.conf', stagedir / 'usr/local/etc/wsddn.conf.sample', {
67 | 'SAMPLE_IFACE_NAME': "hn0",
68 | 'RELOAD_INSTRUCTIONS': """
69 | # sudo service wsddn reload
70 | # or
71 | # sudo kill -HUP $(
82 | www: https://github.com/gershnik/wsdd-native
83 | comment: WS-Discovery Host Daemon
84 | desc: Allows your machine to be discovered by Windows 10 and above systems and displayed by their Explorer "Network" views.
85 | prefix: /
86 | """.lstrip())
87 |
88 | with open(workdir / 'plist', 'w', encoding='utf-8') as plist:
89 | for item in stagedir.glob('**/*'):
90 | if not item.is_dir():
91 | print(str(item.relative_to(stagedir)), file=plist)
92 |
93 | shutil.copy(mydir / 'pre', workdir / '+PRE_INSTALL')
94 | shutil.copy(mydir / 'post_install', workdir / '+POST_INSTALL')
95 | shutil.copy(mydir / 'pre', workdir / '+PRE_DEINSTALL')
96 | shutil.copy(mydir / 'post_deinstall', workdir / '+POST_DEINSTALL')
97 |
98 | subprocess.run(['pkg', 'create', '--verbose',
99 | '-m', workdir, '-r', stagedir, '-p', workdir/'plist', '-o', workdir], check=True)
100 |
101 |
102 | subprocess.run(['gzip', '--keep', '--force', builddir / 'wsddn'], check=True)
103 |
104 | if args.uploadResults:
105 | ostype, oslevel, osarch = ABI.split(':')
106 | abiMarker = '-'.join((ostype, oslevel, osarch))
107 |
108 | subprocess.run(['aws', 's3', 'cp',
109 | workdir / f'wsddn-{VERSION}.pkg',
110 | f's3://gershnik-builds/freebsd/wsddn-{VERSION}-{ARCH}-{oslevel}.pkg'],
111 | check=True)
112 | subprocess.run(['aws', 's3', 'cp',
113 | builddir / 'wsddn.gz',
114 | f's3://wsddn-symbols/wsddn-{VERSION}-{abiMarker}.gz'],
115 | check=True)
116 |
117 | shutil.move(workdir / f'wsddn-{VERSION}.pkg', workdir / f'wsddn-{VERSION}-{abiMarker}.pkg')
118 | subprocess.run(['gh', 'release', 'upload', f'v{VERSION}', workdir / f'wsddn-{VERSION}-{abiMarker}.pkg'],
119 | check=True)
120 |
--------------------------------------------------------------------------------
/src/pch.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #ifndef HEADER_PCH_H_INCLUDED
5 | #define HEADER_PCH_H_INCLUDED
6 |
7 | #include
8 |
9 | #ifdef __GNUC__
10 | #define WSDDN_SUPPRESS_WARNINGS_BEGIN _Pragma("GCC diagnostic push")
11 | #define WSDDN_SUPPRESS_WARNING_HELPER0(arg) #arg
12 | #define WSDDN_SUPPRESS_WARNING_HELPER1(name) WSDDN_SUPPRESS_WARNING_HELPER0(GCC diagnostic ignored name)
13 | #define WSDDN_SUPPRESS_WARNING_HELPER2(name) WSDDN_SUPPRESS_WARNING_HELPER1(#name)
14 | #define WSDDN_SUPPRESS_WARNING(name) _Pragma(WSDDN_SUPPRESS_WARNING_HELPER2(name))
15 | #define WSDDN_SUPPRESS_WARNINGS_END _Pragma("GCC diagnostic pop")
16 |
17 | #define WSDDN_IGNORE_DEPRECATED_BEGIN WSDDN_SUPPRESS_WARNINGS_BEGIN \
18 | WSDDN_SUPPRESS_WARNING(-Wdeprecated-declarations)
19 | #define WSDDN_IGNORE_DEPRECATED_END WSDDN_SUPPRESS_WARNINGS_END
20 | #else
21 | #define WSDDN_SUPPRESS_WARNINGS_BEGIN
22 | #define WSDDN_SUPPRESS_WARNING(x)
23 | #define WSDDN_SUPPRESS_WARNINGS_END
24 |
25 | #define WSDDN_IGNORE_DEPRECATED_BEGIN
26 | #define WSDDN_IGNORE_DEPRECATED_END
27 | #endif
28 |
29 | #include
30 | #include
31 | #include
32 |
33 | #include
34 |
35 | //must come before sys_string due to S macro collision
36 | #ifdef __clang__
37 | WSDDN_SUPPRESS_WARNINGS_BEGIN
38 | WSDDN_SUPPRESS_WARNING(-Wshorten-64-to-32)
39 | #endif
40 | #include
41 | #ifdef __clang__
42 | WSDDN_SUPPRESS_WARNINGS_END
43 | #endif
44 |
45 | #include
46 | #include
47 |
48 | #define PTL_USE_STD_FORMAT 0
49 | #include
50 | #include
51 | #include
52 | #include
53 | #include
54 | #include
55 | #include
56 | #include
57 |
58 | #include
59 |
60 | #include
61 | #include
62 | #include
63 | #include
64 |
65 | #include
66 | #include
67 | #include
68 | #include
69 | #include
70 |
71 | #include
72 |
73 | #include
74 |
75 | #include
76 |
77 | #if HAVE_SYSTEMD
78 | #include
79 | #include
80 | #endif
81 |
82 | #include
83 | #include
84 | #include
85 | #include
86 | #include
87 | #include
88 | #include
89 | #include
90 | #include
91 | #include
92 | #include
93 | #include
94 | #include
95 |
96 | #include
97 |
98 | #include
99 |
100 | #if WSDDN_PLATFORM_APPLE
101 |
102 | #include
103 | #include
104 | #include
105 |
106 | #include
107 |
108 | #include
109 |
110 | #endif
111 |
112 | #define WSDLOG_TRACE(...) do { if (spdlog::should_log(spdlog::level::trace)) spdlog::trace(__VA_ARGS__); } while(false)
113 | #define WSDLOG_DEBUG(...) do { if (spdlog::should_log(spdlog::level::debug)) spdlog::debug(__VA_ARGS__); } while(false)
114 | #define WSDLOG_INFO(...) do { if (spdlog::should_log(spdlog::level::info)) spdlog::info(__VA_ARGS__); } while(false)
115 | #define WSDLOG_WARN(...) do { if (spdlog::should_log(spdlog::level::warn)) spdlog::warn(__VA_ARGS__); } while(false)
116 | #define WSDLOG_ERROR(...) do { if (spdlog::should_log(spdlog::level::err)) spdlog::error(__VA_ARGS__); } while(false)
117 | #define WSDLOG_CRITICAL(...) do { if (spdlog::should_log(spdlog::level::critical)) spdlog::critical(__VA_ARGS__); } while(false)
118 |
119 |
120 | using namespace sysstr;
121 | using namespace isptr;
122 |
123 | using Uuid = muuid::uuid;
124 |
125 | namespace ip = asio::ip;
126 | namespace outcome = OUTCOME_V2_NAMESPACE;
127 |
128 |
129 | template <> struct fmt::formatter : private fmt::formatter {
130 |
131 | using super = fmt::formatter;
132 | using super::parse;
133 |
134 | template
135 | auto format(const sys_string & str, FormatContext & ctx) const -> decltype(ctx.out()) {
136 | return super::format(str.c_str(), ctx);
137 | }
138 | };
139 |
140 | template
141 | inline
142 | auto sys_format(fmt::format_string fmtstr, Args &&... args) -> sys_string {
143 | sys_string_builder builder;
144 | fmt::format_to(std::back_inserter(builder.chars()), fmtstr, std::forward(args)...);
145 | return builder.build();
146 | }
147 |
148 |
149 | #endif
150 |
--------------------------------------------------------------------------------
/src/sys_util.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #include "sys_util.h"
5 |
6 | #if HAVE_OS_LOG
7 |
8 | os_log_t OsLogHandle::s_handle = nullptr;
9 | const char * OsLogHandle::s_category = "main";
10 |
11 | #endif
12 |
13 | #if !HAVE_APPLE_USER_CREATION
14 |
15 | static auto runCreateDaemonUserCommands([[maybe_unused]] const sys_string & name) -> bool {
16 |
17 | #if defined(__linux__) && defined(USERADD_PATH)
18 |
19 | (void)run({USERADD_PATH, "-r", "-d", WSDDN_DEFAULT_CHROOT_DIR, "-s", "/bin/false", name.c_str()});
20 | return true;
21 |
22 | #elif defined(__linux__) && defined(IS_ALPINE_LINUX) && defined(ADDUSER_PATH) && defined(ADDGROUP_PATH)
23 |
24 | //The second addgroup instead of -G for adduser is necessary since for some reason -G doesn't
25 | //modify /etc/group when run from here
26 | (void)run({ADDGROUP_PATH, "-S", name.c_str()});
27 | (void)run({ADDUSER_PATH, "-S", "-D", "-H", "-h", "/var/empty", "-s", "/sbin/nologin", "-g", name.c_str(), name.c_str()});
28 | (void)run({ADDGROUP_PATH, name.c_str(), name.c_str()});
29 | return true;
30 |
31 | #elif (defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USERADD_PATH)
32 |
33 | createMissingDirs(WSDDN_DEFAULT_CHROOT_DIR, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP, Identity::admin());
34 | (void)run({USERADD_PATH, "-L", "daemon", "-g", "=uid", "-d", WSDDN_DEFAULT_CHROOT_DIR, "-s", "/sbin/nologin", "-c", "WS-Discovery Daemon", name.c_str()});
35 | return true;
36 |
37 | #elif defined(__HAIKU__) && defined(USERADD_PATH) && defined(GROUPADD_PATH)
38 |
39 | (void)run({GROUPADD_PATH, name.c_str()});
40 | (void)run({USERADD_PATH, "-g", name.c_str(), "-d", WSDDN_DEFAULT_CHROOT_DIR, "-s", "/bin/false", "-n", "WS-Discovery Daemon", name.c_str()});
41 | return true;
42 |
43 | #elif defined(__FreeBSD__) && defined(PW_PATH)
44 |
45 | (void)run({PW_PATH, "adduser", name.c_str(), "-d", WSDDN_DEFAULT_CHROOT_DIR, "-s", "/usr/sbin/nologin", "-c", "WS-Discovery Daemon User"});
46 | return true;
47 |
48 | #elif defined(__sun) && defined(USERADD_PATH) && defined(GROUPADD_PATH)
49 |
50 | (void)run({GROUPADD_PATH, name.c_str()});
51 | (void)run({USERADD_PATH, "-g", name.c_str(), "-d", WSDDN_DEFAULT_CHROOT_DIR, "-s", "/bin/false", "-c", "WS-Discovery Daemon User", name.c_str()});
52 | return true;
53 |
54 | #else
55 |
56 | return false;
57 |
58 | #endif
59 | }
60 |
61 | auto Identity::createDaemonUser(const sys_string & name) -> std::optional {
62 |
63 | if (!runCreateDaemonUserCommands(name))
64 | return {};
65 | auto pwd = ptl::Passwd::getByName(name);
66 | if (!pwd)
67 | throw std::runtime_error(fmt::format("unable to create user {}", name));
68 | return Identity(pwd->pw_uid, pwd->pw_gid);
69 | }
70 |
71 | #endif
72 |
73 | int run(const ptl::StringRefArray & args) {
74 | ptl::SpawnAttr spawnAttr;
75 | #ifndef __HAIKU__
76 | spawnAttr.setFlags(POSIX_SPAWN_SETSIGDEF);
77 | auto sigs = ptl::SignalSet::all();
78 | sigs.del(SIGKILL);
79 | sigs.del(SIGSTOP);
80 | spawnAttr.setSigDefault(sigs);
81 | #endif
82 |
83 | auto proc = spawn(args, ptl::SpawnSettings().attr(spawnAttr).usePath());
84 |
85 | auto stat = proc.wait().value();
86 | if (WIFEXITED(stat))
87 | return WEXITSTATUS(stat);
88 | if (WIFSIGNALED(stat))
89 | return 128+WTERMSIG(stat);
90 | throw std::runtime_error(fmt::format("`{} finished with status 0x{:X}`", args, stat));
91 | }
92 |
93 | void shell(const ptl::StringRefArray & args, bool suppressStdErr, std::function reader) {
94 | auto [read, write] = ptl::Pipe::create();
95 | ptl::SpawnAttr spawnAttr;
96 | #ifndef __HAIKU__
97 | spawnAttr.setFlags(POSIX_SPAWN_SETSIGDEF);
98 | auto sigs = ptl::SignalSet::all();
99 | sigs.del(SIGKILL);
100 | sigs.del(SIGSTOP);
101 | spawnAttr.setSigDefault(sigs);
102 | #endif
103 |
104 | ptl::SpawnFileActions act;
105 | act.addDuplicateTo(write, stdout);
106 | act.addClose(read);
107 | if (suppressStdErr) {
108 | act.addOpen(stderr, "/dev/null", O_WRONLY, 0);
109 | }
110 | auto proc = spawn(args, ptl::SpawnSettings().attr(spawnAttr).fileActions(act).usePath());
111 | write.close();
112 |
113 | reader(read);
114 |
115 | auto stat = proc.wait().value();
116 | if (WIFEXITED(stat)) {
117 | auto res = WEXITSTATUS(stat);
118 | if (res == 0)
119 | return;
120 |
121 | throw std::runtime_error(fmt::format("`{} exited with code {}`", args, res));
122 | }
123 | if (WIFSIGNALED(stat)) {
124 | throw std::runtime_error(fmt::format("`{} exited due to signal {}`", args, WTERMSIG(stat)));
125 | }
126 | throw std::runtime_error(fmt::format("`{} finished with status 0x{:X}`", args, stat));
127 | }
128 |
--------------------------------------------------------------------------------
/installers/openbsd/build.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env python3
2 |
3 | # Copyright (c) 2022, Eugene Gershnik
4 | # SPDX-License-Identifier: BSD-3-Clause
5 |
6 | import sys
7 | import subprocess
8 | import shutil
9 | import re
10 | import argparse
11 | from pathlib import Path
12 |
13 | ARCH = subprocess.run(['uname', '-m'], check=True, capture_output=True, encoding="utf-8").stdout.strip()
14 |
15 | mydir = Path(sys.argv[0]).parent
16 |
17 | sys.path.append(str(mydir.absolute().parent))
18 |
19 | from common import getSrcVersion, buildCode, installCode, copyTemplated
20 |
21 | parser = argparse.ArgumentParser()
22 |
23 | parser.add_argument('srcdir', type=Path)
24 | parser.add_argument('builddir', type=Path)
25 | parser.add_argument('--upload-results', dest='uploadResults', action='store_true', required=False)
26 | #parser.add_argument('--arch', required=False)
27 |
28 | args = parser.parse_args()
29 |
30 | srcdir: Path = args.srcdir
31 | builddir: Path = args.builddir
32 |
33 | buildCode(builddir)
34 |
35 | VERSION = getSrcVersion(srcdir)
36 |
37 | workdir = builddir / 'stage/openbsd'
38 | stagedir = workdir / 'root'
39 | shutil.rmtree(workdir, ignore_errors=True)
40 | stagedir.mkdir(parents=True)
41 |
42 | installCode(builddir, stagedir / 'usr/local')
43 |
44 | shutil.copytree(srcdir / 'config/openbsd', stagedir, dirs_exist_ok=True)
45 | docdir = stagedir / 'usr/local/share/doc/wsddn'
46 | docdir.mkdir(parents=True)
47 | shutil.copy(srcdir / 'LICENSE', docdir / 'LICENSE')
48 | shutil.copy(srcdir / 'Acknowledgements.md', docdir / 'Acknowledgements.md')
49 |
50 | copyTemplated(mydir.parent / 'wsddn.conf', stagedir / 'etc/wsddn/wsddn.conf.sample', {
51 | 'SAMPLE_IFACE_NAME': "em0",
52 | 'RELOAD_INSTRUCTIONS': """
53 | # sudo rcctl reload wsddn
54 | # or
55 | # sudo kill -HUP $('
78 | HOMEPAGE = 'https://github.com/gershnik/wsdd-native'
79 |
80 | LOGCONF = '/var/log/wsddn.log 644 5 1000 * Z /var/run/wsddn.pid'
81 |
82 | (workdir / 'packinglist').write_text(
83 | f"""
84 | @owner 0
85 | @group 0
86 |
87 | @unexec if rcctl check wsddn > /dev/null 2>&1; then rcctl stop wsddn; fi
88 | @unexec-delete rm -rf /var/run/wsddn.pid
89 | @unexec-delete rm -rf /var/log/wsddn.*
90 |
91 | @mode 755
92 | @bin usr/local/bin/wsddn
93 | @rcscript etc/rc.d/wsddn
94 | @dir etc/wsddn
95 |
96 | @mode 644
97 | @man usr/local/man/man8/wsddn.8.gz
98 | @file etc/wsddn/wsddn.conf.sample
99 | @sample etc/wsddn/wsddn.conf
100 |
101 | @newgroup _wsddn:
102 | @newuser _wsddn::_wsddn:daemon:WS-Discovery Daemon:/var/empty:/sbin/nologin
103 |
104 | @exec-add grep -qxF '/var/log/wsddn.log' /etc/newsyslog.conf || echo '{LOGCONF}' >> /etc/newsyslog.conf
105 | @unexec-delete sed -i '/^\/var\/log\/wsddn.log/d' /etc/newsyslog.conf
106 |
107 | """.lstrip())
108 |
109 | subprocess.run(['pkg_create', '-v',
110 | '-A', ARCH,
111 | '-d', f'-{DESC}',
112 | '-f', 'packinglist',
113 | '-D', 'FULLPKGPATH=net/wsddn',
114 | '-D', f'COMMENT={COMMENT}',
115 | '-D', f'MAINTAINER={MAINTAINER}',
116 | '-D', f'HOMEPAGE={HOMEPAGE}',
117 | '-B', stagedir.resolve(),
118 | '-p', '/'] +
119 | libs + [
120 | f'wsddn-{VERSION}.tgz'], cwd=workdir, check=True)
121 |
122 | subprocess.run(['gzip', '--keep', '--force', builddir / 'wsddn'], check=True)
123 |
124 | if args.uploadResults:
125 | subprocess.run(['aws', 's3', 'cp',
126 | workdir / f'wsddn-{VERSION}.tgz', f's3://gershnik-builds/openbsd/wsddn-{VERSION}-{ARCH}.tgz'],
127 | check=True)
128 | subprocess.run(['aws', 's3', 'cp',
129 | builddir / 'wsddn.gz', f's3://wsddn-symbols/wsddn-openbsd-{VERSION}-{ARCH}.tgz'], check=True)
130 |
131 | shutil.move(workdir / f'wsddn-{VERSION}.tgz', workdir / f'wsddn-{VERSION}-OpenBSD-{ARCH}.tgz')
132 | subprocess.run(['gh', 'release', 'upload', f'v{VERSION}', workdir / f'wsddn-{VERSION}-OpenBSD-{ARCH}.tgz'],
133 | check=True)
134 |
135 |
--------------------------------------------------------------------------------
/installers/deb/build.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env -S python3 -u
2 |
3 | # Copyright (c) 2022, Eugene Gershnik
4 | # SPDX-License-Identifier: BSD-3-Clause
5 |
6 | import sys
7 | import subprocess
8 | import shutil
9 | import hashlib
10 | import gzip
11 | from pathlib import Path
12 |
13 | RELEASE = '1'
14 | ARCH = subprocess.run(['dpkg-architecture', '-q', 'DEB_HOST_ARCH'], check=True, capture_output=True, encoding="utf-8").stdout.strip()
15 | #CODENAME = subprocess.run(['lsb_release', '-sc'], check=True, capture_output=True, encoding="utf-8").stdout.strip()
16 |
17 | mydir = Path(sys.argv[0]).parent
18 |
19 | sys.path.append(str(mydir.absolute().parent))
20 |
21 | from common import parseCommandLine, getVersion, buildCode, installCode, copyTemplated
22 |
23 | args = parseCommandLine()
24 | srcdir: Path = args.srcdir
25 | builddir: Path = args.builddir
26 |
27 | buildCode(builddir)
28 |
29 | VERSION = getVersion(builddir)
30 |
31 | workdir = builddir / 'stage/deb'
32 | stagedir = workdir / f'wsddn_{VERSION}-{RELEASE}_{ARCH}'
33 | shutil.rmtree(workdir, ignore_errors=True)
34 | stagedir.mkdir(parents=True)
35 |
36 | installCode(builddir, stagedir / 'usr')
37 |
38 | shutil.copytree(srcdir / 'config/systemd/usr', stagedir / 'usr', dirs_exist_ok=True)
39 | shutil.copytree(srcdir / 'config/firewalls/etc/ufw', stagedir / 'etc/ufw', dirs_exist_ok=True)
40 | shutil.copytree(srcdir / 'config/firewalls/etc/firewalld', stagedir / 'usr/lib/firewalld', dirs_exist_ok=True)
41 | shutil.copytree(srcdir / 'config/sysv/etc', stagedir / 'etc', dirs_exist_ok=True)
42 |
43 | docdir = stagedir / 'usr/share/doc/wsddn'
44 | docdir.mkdir(parents=True)
45 | shutil.copy(mydir / 'copyright', docdir / 'copyright')
46 | shutil.copy(srcdir / 'Acknowledgements.md', docdir / 'Acknowledgements.md')
47 | with open(srcdir / 'CHANGELOG.md', 'rb') as f_in:
48 | with gzip.open(docdir / 'changelog.gz', 'wb') as f_out:
49 | shutil.copyfileobj(f_in, f_out)
50 |
51 | copyTemplated(mydir.parent / 'wsddn.conf', stagedir / "etc/wsddn.conf", {
52 | 'SAMPLE_IFACE_NAME': "eth0",
53 | 'RELOAD_INSTRUCTIONS': """
54 | # sudo systemctl reload wsddn
55 | """.lstrip()
56 | })
57 |
58 |
59 | def calc_sizes():
60 | md5sums = ''
61 | total_size = 0
62 | buffer = bytearray(4096)
63 | view = memoryview(buffer)
64 | for item in stagedir.rglob('*'):
65 | if not item.is_file():
66 | continue
67 | relpath = item.relative_to(stagedir)
68 | if not relpath.parts[0] == 'etc':
69 | md5 = hashlib.md5()
70 | with open(item, "rb") as f:
71 | while True:
72 | size = f.readinto(buffer)
73 | if size == 0:
74 | break
75 | total_size += size
76 | md5.update(view[:size])
77 | md5sums += f'{md5.hexdigest()} {item.relative_to(stagedir)}\n'
78 | else:
79 | total_size += item.stat().st_size
80 | total_size = int(round(total_size / 1024.))
81 | return total_size, md5sums
82 |
83 | total_size, md5sums = calc_sizes()
84 |
85 | debiandir = stagedir/ 'DEBIAN'
86 | debiandir.mkdir()
87 |
88 | # on case insensitive filesystem we don't need and cannot create lowercase 'debian'
89 | if not (stagedir / 'debian').exists():
90 | (stagedir / 'debian').symlink_to(debiandir.absolute())
91 |
92 | control = debiandir / 'control'
93 |
94 | control.write_text(
95 | f"""
96 | Package: wsddn
97 | Source: wsddn
98 | Version: {VERSION}
99 | Architecture: {ARCH}
100 | Installed-Size: {total_size}
101 | Depends: {{shlibs_Depends}}
102 | Maintainer: Eugene Gershnik
103 | Homepage: https://github.com/gershnik/wsdd-native
104 | Description: WS-Discovery Host Daemon
105 | Allows your Linux machine to be discovered by Windows 10 and above systems and displayed by their Explorer "Network" views.
106 |
107 | """.lstrip())
108 |
109 | shutil.copy(mydir / 'preinst', debiandir / 'preinst')
110 | shutil.copy(mydir / 'prerm', debiandir / 'prerm')
111 | shutil.copy(mydir / 'postinst', debiandir / 'postinst')
112 | shutil.copy(mydir / 'postrm', debiandir / 'postrm')
113 | shutil.copy(mydir / 'copyright', debiandir / 'copyright')
114 |
115 | (debiandir / 'conffiles').write_text("""
116 | /etc/init.d/wsddn
117 | /etc/ufw/applications.d/wsddn
118 | /etc/wsddn.conf
119 | """.lstrip())
120 |
121 | (debiandir / 'md5sums').write_text(md5sums)
122 |
123 | deps = subprocess.run(['dpkg-shlibdeps', '-O', '-eusr/bin/wsddn'],
124 | check=True, cwd=stagedir, stdout=subprocess.PIPE, encoding="utf-8").stdout.strip()
125 | key, val = deps.split('=', 1)
126 | key = key.replace(':', "_")
127 | control.write_text(control.read_text().format_map({key: val}))
128 |
129 | if (stagedir / 'debian').is_symlink():
130 | (stagedir / 'debian').unlink()
131 |
132 | subprocess.run(['dpkg-deb', '--build', '--root-owner-group', stagedir, workdir], check=True)
133 |
134 | subprocess.run(['gzip', '--keep', '--force', builddir / 'wsddn'], check=True)
135 |
136 | deb = list(workdir.glob('*.deb'))[0]
137 |
138 | if args.uploadResults:
139 | subprocess.run(['aws', 's3', 'cp', deb, 's3://gershnik-builds/apt/'], check=True)
140 | subprocess.run(['aws', 's3', 'cp', builddir / 'wsddn.gz', f's3://wsddn-symbols/wsddn-deb-{VERSION}-{ARCH}.gz'], check=True)
141 | subprocess.run(['gh', 'release', 'upload', f'v{VERSION}', deb], check=True)
142 |
--------------------------------------------------------------------------------
/src/util.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #ifndef HEADER_UTIL_H_INCLUDED
5 | #define HEADER_UTIL_H_INCLUDED
6 |
7 | /*
8 | Portable utilities
9 | */
10 |
11 | #define WSDDN_DECLARE_MEMBER_DETECTOR(type, member, name) \
12 | struct name##_detector { \
13 | template \
14 | static std::true_type detect(decltype(T::member) *); \
15 | template \
16 | static std::false_type detect(...); \
17 | }; \
18 | constexpr bool name = decltype(name##_detector::detect(nullptr))::value
19 |
20 | enum AllowedAddressFamily {
21 | BothIPv4AndIPv6,
22 | IPv4Only,
23 | IPv6Only
24 | };
25 |
26 | struct WindowsDomain {
27 | template
28 | WindowsDomain(Args && ...args): name(std::forward(args)...) {}
29 |
30 | sys_string name;
31 | };
32 |
33 | struct WindowsWorkgroup {
34 | template
35 | WindowsWorkgroup(Args && ...args): name(std::forward(args)...) {}
36 |
37 | sys_string name;
38 | };
39 |
40 | using MemberOf = std::variant;
41 |
42 | enum class DaemonType {
43 | Unix
44 | #if HAVE_SYSTEMD
45 | , Systemd
46 | #endif
47 | #if HAVE_LAUNCHD
48 | , Launchd
49 | #endif
50 | };
51 |
52 | struct NetworkInterface {
53 | NetworkInterface(int idx, const char * first, const char * last):
54 | name(first, last),
55 | index(idx) {
56 | }
57 | NetworkInterface(int idx, const sys_string & n):
58 | name(n),
59 | index(idx) {
60 | }
61 |
62 | friend auto operator<=>(const NetworkInterface & lhs, const NetworkInterface & rhs) -> std::strong_ordering {
63 | if (auto res = lhs.name <=> rhs.name; res != 0)
64 | return res;
65 | return lhs.index <=> rhs.index;
66 | }
67 | friend auto operator==(const NetworkInterface & lhs, const NetworkInterface & rhs) -> bool {
68 | return lhs.name == rhs.name && lhs.index == rhs.index;
69 | }
70 | friend auto operator!=(const NetworkInterface & lhs, const NetworkInterface & rhs) -> bool {
71 | return !(lhs == rhs);
72 | }
73 |
74 | sys_string name;
75 | int index;
76 | };
77 |
78 | template <> struct fmt::formatter {
79 |
80 | constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
81 | auto it = ctx.begin(), end = ctx.end();
82 | if (it != end && *it != '}') throw format_error("invalid format");
83 | return it;
84 | }
85 | template
86 | auto format(const NetworkInterface & iface, FormatContext & ctx) const -> decltype(ctx.out()) {
87 | return fmt::format_to(ctx.out(), "{}(idx: {})", iface.name, iface.index);
88 | }
89 | };
90 |
91 | template
92 | class RefCountedContainerBuffer{
93 | public:
94 | RefCountedContainerBuffer(T && data):
95 | m_dataPtr(refcnt_attach(new ref_counted_adapter(std::move(data)))),
96 | m_buffer(m_dataPtr->data(), m_dataPtr->size()) {
97 |
98 | }
99 | using value_type = asio::const_buffer;
100 | using const_iterator = const asio::const_buffer *;
101 |
102 | auto begin() const -> const_iterator { return &m_buffer; }
103 | auto end() const -> const_iterator { return &m_buffer + 1; }
104 |
105 | private:
106 | refcnt_ptr> m_dataPtr;
107 | asio::const_buffer m_buffer;
108 | };
109 |
110 |
111 | inline auto makeHttpUrl(const ip::tcp::endpoint & endp) -> sys_string {
112 | auto addr = endp.address();
113 | if (addr.is_v4()) {
114 | return fmt::format("http://{0}:{1}", addr.to_string(), endp.port());
115 | } else {
116 | auto addr6 = addr.to_v6();
117 | addr6.scope_id(0);
118 | return fmt::format("http://[{0}]:{1}", addr6.to_string(), endp.port());
119 | }
120 | }
121 |
122 | inline sys_string to_urn(const Uuid & val) {
123 | std::array buf;
124 | val.to_chars(buf, Uuid::lowercase);
125 |
126 | sys_string_builder builder;
127 | builder.reserve_storage(46);
128 | builder.append(S("urn:uuid:"));
129 | builder.append(buf.data(), buf.size());
130 | return builder.build();
131 | }
132 |
133 | inline sys_string to_sys_string(const Uuid & val) {
134 | std::array buf;
135 | val.to_chars(buf, Uuid::lowercase);
136 | return sys_string(buf.data(), buf.size());
137 | }
138 |
139 |
140 | template <> struct fmt::formatter {
141 |
142 | constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
143 | auto it = ctx.begin(), end = ctx.end();
144 | if (it != end && *it != '}') throw format_error("invalid format");
145 | return it;
146 | }
147 | template
148 | auto format(const ptl::StringRefArray & args, FormatContext & ctx) const -> decltype(ctx.out()) {
149 | auto dest = ctx.out();
150 | *dest++ = '[';
151 | if (auto * str = args.data()) {
152 | dest = fmt::format_to(dest, "\"{}\"", *str);
153 | for (++str; *str; ++str) {
154 | dest = fmt::format_to(dest, ", \"{}\"", *str);
155 | }
156 | }
157 | *dest++ = ']';
158 | return dest;
159 | }
160 | };
161 |
162 | template
163 | constexpr decltype(auto) makeDependentOn(Arg && arg) {
164 | return std::forward(arg);
165 | }
166 |
167 |
168 | extern std::mt19937 g_Random;
169 |
170 |
171 | #endif
172 |
--------------------------------------------------------------------------------
/src/config.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #include "config.h"
5 | #include "command_line.h"
6 |
7 | Config::Config(const CommandLine & cmdline):
8 | m_instanceIdentifier(time(nullptr)),
9 | m_pageSize(size_t(ptl::systemConfig(_SC_PAGESIZE).value_or(4096))) {
10 |
11 | m_hopLimit = cmdline.hoplimit.value_or(1);
12 | m_allowedAddressFamily = cmdline.allowedAddressFamily.value_or(BothIPv4AndIPv6);
13 | m_interfaceWhitelist.insert(cmdline.interfaces.begin(), cmdline.interfaces.end());
14 | m_sourcePort = cmdline.sourcePort.value_or(0);
15 |
16 | m_fullHostName = getHostName();
17 | m_simpleHostName = m_fullHostName.prefix_before_first(U'.').value_or(m_fullHostName);
18 |
19 | if (cmdline.uuid) {
20 | m_uuid = *cmdline.uuid;
21 | } else {
22 | using namespace muuid;
23 | m_uuid = uuid::generate_sha1(uuid("49DAC291-0608-41C9-941C-ED0E7ACCDE1E"),
24 | {m_fullHostName.c_str(), m_fullHostName.storage_size()});
25 | }
26 | m_strUuid = to_sys_string(m_uuid);
27 | m_urnUuid = to_urn(m_uuid);
28 |
29 | bool useNetbiosHostName = cmdline.hostname && cmdline.hostname->empty();
30 |
31 | std::optional systemWinNetInfo;
32 | #if HAVE_APPLE_SAMBA
33 | systemWinNetInfo = detectAppleWinNetInfo(useNetbiosHostName);
34 | #elif CAN_HAVE_SAMBA
35 | systemWinNetInfo = detectWinNetInfo(cmdline.smbConf, useNetbiosHostName);
36 | #endif
37 |
38 | if (cmdline.memberOf) {
39 | m_winNetInfo.memberOf = *cmdline.memberOf;
40 | } else if (systemWinNetInfo) {
41 | m_winNetInfo.memberOf = systemWinNetInfo->memberOf;
42 | } else {
43 | m_winNetInfo.memberOf.emplace(S("WORKGROUP"));
44 | }
45 |
46 | if (cmdline.hostname && !cmdline.hostname->empty()) {
47 | //explict hostname specified
48 | m_winNetInfo.hostName = *cmdline.hostname;
49 | } else if (systemWinNetInfo) {
50 | //we have detected hostname
51 | m_winNetInfo.hostName = systemWinNetInfo->hostName;
52 | } else {
53 | if (useNetbiosHostName)
54 | m_winNetInfo.hostName = m_simpleHostName.to_upper();
55 | else
56 | m_winNetInfo.hostName = m_simpleHostName;
57 | }
58 |
59 | if (systemWinNetInfo)
60 | m_winNetInfo.hostDescription = systemWinNetInfo->hostDescription;
61 |
62 | if (m_winNetInfo.hostDescription.empty()) {
63 | if (cmdline.hostname && !cmdline.hostname->empty())
64 | m_winNetInfo.hostDescription = *cmdline.hostname;
65 | else
66 | m_winNetInfo.hostDescription = m_simpleHostName;
67 | }
68 |
69 |
70 | auto [memberOfType, memberOfName] = std::visit([](auto & val) {
71 |
72 | using ArgType = std::remove_cvref_t;
73 |
74 | if constexpr (std::is_same_v)
75 | return std::make_pair(S("Workgoup"), val.name);
76 | else if constexpr (std::is_same_v)
77 | return std::make_pair(S("Domain"), val.name);
78 | else
79 | static_assert(makeDependentOn(false), "unhandled type");
80 |
81 | }, m_winNetInfo.memberOf);
82 |
83 | if (cmdline.metadataFile) {
84 | m_metadataDoc = loadMetadaFile(cmdline.metadataFile->native());
85 | }
86 |
87 | WSDLOG_INFO("Configuration:\n"
88 | " Hostname: {}\n"
89 | " {}: {}\n"
90 | " Description: {}\n"
91 | " Identifier: {}\n"
92 | " Metadata: {}",
93 | m_winNetInfo.hostName,
94 | memberOfType, memberOfName,
95 | m_winNetInfo.hostDescription,
96 | endpointIdentifier(),
97 | m_metadataDoc ? cmdline.metadataFile->c_str() : "default");
98 | }
99 |
100 | auto Config::getHostName() const -> sys_string {
101 | auto size = size_t(ptl::systemConfig(_SC_HOST_NAME_MAX).value_or(_POSIX_HOST_NAME_MAX));
102 | sys_string_builder builder;
103 | auto & buf = builder.chars();
104 | buf.resize(size + 1);
105 | ptl::getHostName({buf.begin(), buf.end()});
106 | builder.resize_storage(strlen(buf.begin()));
107 | return builder.build();
108 | }
109 |
110 | auto Config::loadMetadaFile(const std::string & filename) const -> std::unique_ptr {
111 | auto file = ptl::FileDescriptor::open(filename, O_RDONLY);
112 | std::vector buf(m_pageSize);
113 | try {
114 | auto read = readFile(file, buf.data(), buf.size());
115 | if (read < 4)
116 | throw std::runtime_error(fmt::format("metada file {} is invalid", filename));
117 | auto templateParsingCtx = XmlParserContext::createPush(buf.data(), int(read), filename.c_str());
118 | for ( ; ; ) {
119 | read = readFile(file, buf.data(), buf.size());
120 | templateParsingCtx->parseChunk(buf.data(), int(read), read == 0);
121 | if (!read) {
122 | break;
123 | }
124 | }
125 |
126 | if (!templateParsingCtx->wellFormed())
127 | throw std::runtime_error(fmt::format("metada file {} is not well formed XML", filename));
128 | return templateParsingCtx->extractDoc();
129 |
130 | } catch (XmlException & ex) {
131 | throw std::runtime_error(fmt::format("metada file {} is not a valid XML", filename));
132 | }
133 |
134 | }
135 |
136 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: Check
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | - '*/**'
8 | paths-ignore:
9 | - 'README.md'
10 | - '.gitignore'
11 | - 'LICENSE'
12 | - 'CHANGELOG.md'
13 | - 'SECURITY.md'
14 | - 'Acknowledgements.md'
15 | - 'config/metadata/**'
16 | - '.github/workflows/publish.yml'
17 | - '.github/workflows/build.yml'
18 | - 'tools/**'
19 | workflow_dispatch:
20 |
21 |
22 | jobs:
23 | linux:
24 | runs-on: ${{ matrix.os }}
25 | strategy:
26 | fail-fast: false
27 | matrix:
28 | include:
29 | - {os: ubuntu-22.04, compiler: gcc, version: 11 }
30 | - {os: ubuntu-22.04, compiler: gcc, version: 12 }
31 | - {os: ubuntu-22.04, compiler: gcc, version: 13 }
32 | - {os: ubuntu-24.04, compiler: gcc, version: 14 }
33 |
34 | - {os: ubuntu-22.04, compiler: clang, version: 13 }
35 | - {os: ubuntu-22.04, compiler: clang, version: 14 }
36 | - {os: ubuntu-22.04, compiler: clang, version: 15 }
37 | - {os: ubuntu-22.04, compiler: clang, version: 16 }
38 | - {os: ubuntu-24.04, compiler: clang, version: 17 }
39 | - {os: ubuntu-24.04, compiler: clang, version: 18 }
40 | - {os: ubuntu-24.04, compiler: clang, version: 19 }
41 | - {os: ubuntu-24.04, compiler: clang, version: 20 }
42 | - {os: ubuntu-24.04, compiler: clang, version: 21 }
43 | steps:
44 | - name: Checkout
45 | uses: actions/checkout@v4
46 |
47 | - name: Setup Linux
48 | run: |
49 | sudo apt-get update
50 | sudo apt-get install -y libsystemd-dev
51 |
52 | if [[ '${{ matrix.compiler }}' == 'clang' ]]; then
53 | wget https://apt.llvm.org/llvm.sh
54 | chmod u+x llvm.sh
55 | sudo ./llvm.sh ${{ matrix.version }}
56 | sudo apt-get install -y clang-tools-${{ matrix.version }} libc++-${{ matrix.version }}-dev libc++abi-${{ matrix.version }}-dev
57 | echo "CC=clang-${{ matrix.version }}" >> $GITHUB_ENV
58 | echo "CXX=clang++-${{ matrix.version }}" >> $GITHUB_ENV
59 | echo "CXXFLAGS=-stdlib=libc++" >> $GITHUB_ENV
60 | fi
61 |
62 | if [[ '${{ matrix.compiler }}' == 'gcc' ]]; then
63 | sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
64 | sudo apt-get update
65 | sudo apt-get install -y gcc-${{ matrix.version }} g++-${{ matrix.version }}
66 | echo "CC=gcc-${{ matrix.version }}" >> $GITHUB_ENV
67 | echo "CXX=g++-${{ matrix.version }}" >> $GITHUB_ENV
68 | fi
69 |
70 | - name: Configure
71 | run: |
72 | [ -d "out" ] && tools/uncache out
73 | cmake -S . -B out \
74 | -DCMAKE_BUILD_TYPE=RelWithDebInfo
75 |
76 | - name: Build
77 | run: |
78 | installers/deb/build.py . out
79 |
80 | container:
81 | runs-on: ubuntu-latest
82 | container: ${{ matrix.container }}
83 | strategy:
84 | fail-fast: false
85 | matrix:
86 | container: [gcc:15.1]
87 |
88 | steps:
89 | - name: Checkout
90 | uses: actions/checkout@v4
91 |
92 | - name: Install pre-requisites
93 | run: |
94 | apt-get update
95 | apt-get install -y ninja-build cmake
96 | apt-get install -y python3-dev libsystemd-dev
97 |
98 | - name: Configure
99 | shell: bash
100 | run: |
101 | [ -d "out" ] && tools/uncache out
102 | cmake -G Ninja -S . -B out \
103 | -DCMAKE_BUILD_TYPE=RelWithDebInfo
104 |
105 | - name: Build and Test
106 | shell: bash
107 | run: |
108 | cmake --build out
109 |
110 | mac:
111 | runs-on: ${{ matrix.os }}
112 | strategy:
113 | fail-fast: false
114 | matrix:
115 | include:
116 | - { os: macos-14, xcode: '15.4.0' }
117 | - { os: macos-15, xcode: '16.4.0' }
118 | - { os: macos-15-intel, xcode: '16.4.0' }
119 | - { os: macos-26, xcode: '26.0' }
120 | env:
121 | DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app
122 | steps:
123 | - name: Checkout
124 | uses: actions/checkout@v4
125 |
126 | - name: Configure
127 | run: |
128 | [ -d "out" ] && tools/uncache out
129 | cmake -S . -B out \
130 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \
131 | "-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64" \
132 | -DCMAKE_IGNORE_PREFIX_PATH=/opt/local \
133 | -DWSDDN_PREFER_SYSTEM_LIBXML2=ON
134 |
135 | - name: Build
136 | run: |
137 | installers/mac/build.py . out
138 |
139 | others:
140 | concurrency: ${{ matrix.remote_host }}
141 | runs-on: [self-hosted, server]
142 | strategy:
143 | fail-fast: false
144 | matrix:
145 | remote_host:
146 | - centos-9
147 | - alpine-3
148 | - archlinux
149 | - netbsd-10
150 | - omnios
151 | - haiku
152 |
153 | steps:
154 | - name: Run remote build
155 | run: |
156 | "$RUNNER_TOOLS_PATH"/run-agent gh-${{ matrix.remote_host }} <<'EOF'
157 | set -e
158 | if [ ! -d work/wsdd-native ]; then
159 | git clone https://github.com/gershnik/wsdd-native.git work/wsdd-native
160 | fi
161 | cd work/wsdd-native
162 | git fetch --all
163 | git fetch -f --prune --tags
164 | git reset --hard ${{ github.sha }}
165 | [ -d "out" ] && tools/uncache out
166 | cmake -S . -B out -DCMAKE_BUILD_TYPE=RelWithDebInfo
167 | cmake --build out --target wsddn
168 | EOF
169 |
--------------------------------------------------------------------------------
/cmake/dependencies.cmake:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022, Eugene Gershnik
2 | # SPDX-License-Identifier: BSD-3-Clause
3 |
4 | include(FetchContent)
5 |
6 | if (DEFINED CACHE{libxml2_SOURCE_DIR} AND NOT DEFINED CACHE{WSDDN_DEPENDENCIES_VERSION})
7 | message(FATAL_ERROR
8 | "Your existing CMake cache cannot be used due to incompatible changes."
9 | "Please delete ${CMAKE_BINARY_DIR}/CMakeCache.txt and rebuild. (sorry!)")
10 | endif()
11 | set(WSDDN_DEPENDENCIES_VERSION 1 CACHE INTERNAL "version of dependencies config")
12 |
13 |
14 | if (NOT DEFINED WSDDN_PREFER_SYSTEM_LIBXML2)
15 | #By default prefer system libxml2 on Mac and source compiled on other platforms
16 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
17 | set(WSDDN_PREFER_SYSTEM_LIBXML2 ON)
18 | else()
19 | set(WSDDN_PREFER_SYSTEM_LIBXML2 OFF)
20 | endif()
21 | endif()
22 |
23 | file(READ dependencies.json DEPENDECIES_JSON)
24 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS dependencies.json)
25 |
26 |
27 | set(DECLARED_DEPENDENCIES "")
28 |
29 | function(fetch_dependency name #extras for FetchContent_Declare
30 | )
31 | string(JSON version GET "${DEPENDECIES_JSON}" ${name} version)
32 | string(JSON url GET "${DEPENDECIES_JSON}" ${name} url)
33 | string(JSON md5 GET "${DEPENDECIES_JSON}" ${name} md5)
34 | string(REPLACE "\$\{version\}" ${version} url "${url}")
35 | string(TOUPPER ${name} uname)
36 | string(TOLOWER ${name} lname)
37 |
38 | set(extras "")
39 | foreach(i RANGE 1 ${ARGC})
40 | list(APPEND extras ${ARGV${i}})
41 | endforeach()
42 |
43 | if ($CACHE{LAST_WSDDN_PREFER_SYSTEM_${uname}})
44 | set(old_prefer 1)
45 | else()
46 | set(old_prefer 0)
47 | endif()
48 |
49 | if (WSDDN_PREFER_SYSTEM_${uname})
50 | set(new_prefer 1)
51 | else()
52 | set(new_prefer 0)
53 | endif()
54 |
55 | if (NOT ${new_prefer} EQUAL ${old_prefer})
56 | unset(${lname}_POPULATED CACHE)
57 | unset(${lname}_SOURCE_DIR CACHE)
58 | unset(${lname}_BINARY_DIR CACHE)
59 | unset(${name}_FOUND CACHE)
60 | if (DEFINED ${lname}_DIR AND "${${lname}_DIR}" STREQUAL "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}")
61 | unset(${lname}_DIR CACHE)
62 | endif()
63 | endif()
64 | set(LAST_WSDDN_PREFER_SYSTEM_${uname} ${WSDDN_PREFER_SYSTEM_${uname}} CACHE INTERNAL "")
65 |
66 | if (WSDDN_PREFER_SYSTEM_${uname})
67 | # string(JSON find_args ERROR_VARIABLE find_args_err GET "${DEPENDECIES_JSON}" ${name} find_args)
68 | # if (find_args_err)
69 | # unset(find_args)
70 | # endif()
71 | set(prefer_system FIND_PACKAGE_ARGS ${find_args})
72 | else()
73 | set(prefer_system "")
74 | endif()
75 | FetchContent_Declare(${name}
76 | URL ${url}
77 | URL_HASH MD5=${md5}
78 | ${extras}
79 | ${prefer_system}
80 | )
81 | set(deplist ${DECLARED_DEPENDENCIES})
82 | list(APPEND deplist ${name})
83 | set(DECLARED_DEPENDENCIES ${deplist} PARENT_SCOPE)
84 | endfunction()
85 |
86 | #################################################
87 |
88 | fetch_dependency(argum)
89 | fetch_dependency(sys_string)
90 | fetch_dependency(isptr)
91 | fetch_dependency(ptl)
92 | fetch_dependency(modern-uuid)
93 |
94 |
95 | set(LIBXML2_WITH_ICONV OFF)
96 | set(LIBXML2_WITH_LZMA OFF)
97 | set(LIBXML2_WITH_HTML OFF)
98 | set(LIBXML2_WITH_HTTP OFF)
99 | set(LIBXML2_WITH_FTP OFF)
100 | set(LIBXML2_WITH_TESTS OFF)
101 | set(LIBXML2_WITH_ZLIB OFF)
102 | set(LIBXML2_WITH_PYTHON OFF)
103 | set(LIBXML2_WITH_LEGACY OFF)
104 | set(LIBXML2_WITH_MODULES OFF)
105 | set(LIBXML2_WITH_PROGRAMS OFF)
106 |
107 | fetch_dependency(LibXml2)
108 |
109 | set(FMT_INSTALL OFF)
110 | fetch_dependency(fmt)
111 |
112 | set(SPDLOG_NO_ATOMIC_LEVELS ON CACHE BOOL "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently)")
113 | set(SPDLOG_NO_TLS ON CACHE BOOL "prevent spdlog from using thread local storage")
114 | set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "Use external fmt library instead of bundled")
115 | fetch_dependency(spdlog)
116 |
117 | fetch_dependency(tomlplusplus)
118 | fetch_dependency(outcome
119 | SOURCE_SUBDIR include #we don't really want to build it
120 | )
121 | fetch_dependency(asio)
122 |
123 | #################################################
124 |
125 | FetchContent_MakeAvailable(${DECLARED_DEPENDENCIES})
126 |
127 | foreach(dep ${DECLARED_DEPENDENCIES})
128 | string(TOUPPER ${dep} udep)
129 | string(TOLOWER ${dep} ldep)
130 | if (DEFINED ${ldep}_SOURCE_DIR)
131 | message(STATUS "${dep} will be built from sources and statically linked")
132 | else()
133 | if (DEFINED ${ldep}_VERSION)
134 | message(STATUS "${dep} will be used from system (current version: ${${ldep}_VERSION})")
135 | else()
136 | if (DEFINED ${udep}_VERSION_STRING)
137 | message(STATUS "${dep} will be used from system (current version: ${${udep}_VERSION_STRING})")
138 | else()
139 | message(STATUS "${dep} will be used from system")
140 | endif()
141 | endif()
142 | endif()
143 | endforeach()
144 |
145 | get_directory_property(KNOWN_SUBDIRECTORIES SUBDIRECTORIES)
146 | foreach(dir ${KNOWN_SUBDIRECTORIES})
147 | if (IS_DIRECTORY ${dir})
148 | foreach(dep ${DECLARED_DEPENDENCIES})
149 | string(TOLOWER ${dep} ldep)
150 | if (DEFINED ${ldep}_SOURCE_DIR)
151 | #check if the subdirectory is "under" the dependency source dir
152 | string(FIND ${dir} ${${ldep}_SOURCE_DIR} match_pos)
153 | if (match_pos EQUAL 0)
154 | #and, if so, exclude it from all to prevent installation
155 | set_property(DIRECTORY ${dir} PROPERTY EXCLUDE_FROM_ALL YES)
156 | break()
157 | endif()
158 | endif()
159 | endforeach()
160 | endif()
161 | endforeach()
162 |
--------------------------------------------------------------------------------
/installers/wsddn.conf:
--------------------------------------------------------------------------------
1 | # Uncomment/modify the following options to configure WS-Discovery Host
2 | #
3 | # The syntax of this file is TOML (https://toml.io/en/).
4 | #
5 | # Any options specified on command line take precedence over options
6 | # in this file
7 | #
8 | # After editing this file you must reload WS-Discovery Host
9 | # if it is running via:
10 | #
11 | {RELOAD_INSTRUCTIONS}
12 |
13 | ###############################################################################
14 | #
15 | # Networking options
16 | #
17 |
18 | # Specify on which interfaces wsddn will be listening on. If no interfaces
19 | # are specified, or the list is empty all suitable detected interfaces will be
20 | # used. Loop-back interfaces are never used, even when explicitly specified.
21 | # For interfaces with IPv6 addresses, only link-local addresses will be used
22 | # for announcing the host on the network.
23 |
24 | #interfaces = ["{SAMPLE_IFACE_NAME}"]
25 |
26 | # Restrict communications to the given address family. Valid values
27 | # are "IPv4" or "IPv6" case-insensitive.
28 |
29 | #allowed-address-family = "IPv4"
30 |
31 | # Set the hop limit for multicast packets. The default is 1 which should
32 | # prevent packets from leaving the local network segment.
33 |
34 | #hoplimit=1
35 |
36 | # Set the source port for outgoing multicast messages, so that replies will
37 | # use this as the destination port.
38 | # This is useful for firewalls that do not detect incoming unicast replies
39 | # to a multicast as part of the flow, so the port needs to be fixed in order
40 | # to be allowed manually.
41 |
42 | #source-port=12345
43 |
44 | ###############################################################################
45 | #
46 | # Machine information
47 | #
48 |
49 | # WS-Discovery protocol requires your machine to have a unique identifier that
50 | # is stable across reboots or changes in networks.
51 | # By default, wsddn uses UUID version 5 with private namespace and the
52 | # host name of the machine. This will remain stable as long as the hostname
53 | # doesn't change. If desired, you can override this with a fixed UUID using
54 | # this option.
55 |
56 | #uuid = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
57 |
58 | # Hostname to be reported to Windows machines. By default the local machine's
59 | # hostname (with domain part, if any, removed) is used.
60 | # If you set the value to ":NETBIOS:" then Netbios hostname will be used.
61 | # The Netbios hostname is either detected from SMB configuration, if found, or
62 | # produced by capitalizing normal machine hostname.
63 |
64 | #hostname = "my-awesome-host"
65 |
66 | # Report whether the host is a member of a given workgroup or domain.
67 | # To specify a workgroup use "Workgroup/name" syntax.
68 | # To specify a domain use "Domain/name"
69 | # The "workgroup/" and "domain/" prefixes are not case sensitive.
70 | # If not specified workgroup/domain membership is detected from SMB
71 | # configuration. If no SMB configuration is found it is set to a workgroup
72 | # named WORKGROUP.
73 |
74 | #member-of = "Workgroup/WORKGROUP"
75 |
76 | # Path to smb.conf file to extract the SMB configuration from. This option is
77 | # not available on macOS. By default wsddn tries to locate this file on
78 | # its own.
79 |
80 | #smb-conf = "/path/to/smb.conf"
81 |
82 | # Path to a custom metadata file. Custom metadata allows you to completely
83 | # replace the information normally supplied by `wsddn` to Windows with your own.
84 | # See https://github.com/gershnik/wsdd-native/blob/master/config/metadata/README.md
85 | # for details about the metadata format and content.
86 |
87 | #metadata = "/path/to/metadata.xml"
88 |
89 |
90 | ###############################################################################
91 | #
92 | # Behavior options
93 | #
94 |
95 | # Set verbosity of the log output. The default value is 4. Log levels range
96 | # from 0 (disable logging) to 6 (detailed trace). Passing values bigger than 6
97 | # is equivalent to 6
98 |
99 | #log-level = 4
100 |
101 | # Set the path of log file. If not specified wsddn outputs the log
102 | # messages as follows
103 | # - If invoked without any daemon flags: to standard output
104 | # - If invoked with --systemd: to standard output, with systemd severity
105 | # prefixes
106 | # - If invoked with --launchd: to standard output
107 | # - If invoked with --unixd: to /dev/null (no logging)
108 |
109 | #log-file = "/path/to/log-file.log"
110 |
111 | # macOS only. Send log output to system log (visible via Console app or
112 | # log command line tool)
113 | # Setting it to true option is mutually exclusive with log-file
114 |
115 | #log-os-log = false
116 |
117 | # Set the path to PID file. If not specified no PID file is written
118 | # Send SIGHUP signal to the process ID in the PID file to reload
119 | # configuration.
120 |
121 | #pid-file = "/path/to/pidfile.pid"
122 |
123 | # Set the identity under which the process that performs network communications
124 | # will run. The value can be either just username or username:groupname.
125 | # If groupname is not specified, primary group of the username is used.
126 | # If this option is not specified then the behavior is as follows:
127 | # - If wsddn process is run under the root account it tries to use a special
128 | # unprivileged account name (_wsddn:_wsddn on macOS, wsddn:wsddn otherwise)
129 | # The user and group are created if they do not exist. Any failures in these
130 | # steps stop the process.
131 | # - Otherwise, wsddn uses the account it is run under.
132 | # The net effect of these rules is that wsddn under no circumstances will
133 | # perform network communications under root account.
134 |
135 | #user = "username:groupname"
136 |
137 | # Set the directory into which process that performs network communications
138 | # should chroot into. This further limits any impact of potential network
139 | # exploits into wsddn. If not specified the behavior is as follows
140 | # - If wsddn process is run under the root account: use /var/empty on macOS
141 | # and /var/run/wsddn on other platforms
142 | # This directory will be created if it does not exist.
143 | # - Otherwise: do not chroot
144 | # Note: do not use external methods to chroot wsddn process (e.g. using
145 | # launchd config plist). Non-networking parts of it need access to the
146 | # normal filesystem to detect things like SMB configuration etc.
147 |
148 | #chroot = "/path/to/an/empty/dir"
149 |
150 |
--------------------------------------------------------------------------------
/cmake/detect_system.cmake:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022, Eugene Gershnik
2 | # SPDX-License-Identifier: BSD-3-Clause
3 |
4 | include(CheckCXXSourceCompiles)
5 | include(CheckIncludeFiles)
6 | include(CheckLibraryExists)
7 | include(CheckFunctionExists)
8 | include(CheckStructHasMember)
9 | include(CMakePushCheckState)
10 |
11 | check_cxx_source_compiles("
12 | #include
13 | #include
14 | int main() {}"
15 | HAVE_NETLINK)
16 |
17 | if (NOT HAVE_NETLINK)
18 |
19 | check_cxx_source_compiles("
20 | #include
21 | #include
22 | #include
23 | #include
24 | int main() {
25 | int x = PF_ROUTE;
26 | size_t s = sizeof(rt_msghdr);
27 | }"
28 | HAVE_PF_ROUTE)
29 |
30 | check_cxx_source_compiles("
31 | #include
32 | #include
33 | #include
34 | #include
35 | int main() {
36 | int x = NET_RT_IFLIST;
37 | }"
38 | HAVE_SYSCTL_PF_ROUTE)
39 |
40 | check_cxx_source_compiles("
41 | #include
42 | #include
43 | #include
44 | #include
45 | int main() {
46 | int x = SIOCGLIFCONF;
47 | lifconf conf{};
48 | }"
49 | HAVE_SIOCGLIFCONF)
50 |
51 | check_cxx_source_compiles("
52 | #include
53 | #include
54 | #include
55 | #include
56 | int main() {
57 | int x = SIOCGIFCONF;
58 | ifconf conf{};
59 | }"
60 | HAVE_SIOCGIFCONF)
61 |
62 | endif()
63 |
64 | check_struct_has_member("struct sockaddr" "sa_len" "sys/types.h;sys/socket.h" HAVE_SOCKADDR_SA_LEN)
65 |
66 | check_include_files(execinfo.h HAVE_EXECINFO_H)
67 | check_library_exists(execinfo backtrace "" HAVE_EXECINFO_LIB)
68 |
69 | check_cxx_source_compiles("
70 | #include
71 | int main() {
72 | int stat;
73 | abi::__cxa_demangle(\"abc\", 0, 0, &stat);
74 | }"
75 | HAVE_CXXABI_H)
76 |
77 |
78 | check_cxx_source_compiles("
79 | #include
80 | int main() {
81 | using x = decltype(abi::__cxa_throw);
82 | }"
83 | HAVE_ABI_CXA_THROW)
84 |
85 | if (NOT DEFINED USERADD_PATH)
86 | find_program(USERADD_PATH useradd PATHS /usr/sbin /bin NO_DEFAULT_PATH)
87 | if (USERADD_PATH)
88 | message(STATUS "Looking for useradd - found at ${USERADD_PATH}")
89 | else()
90 | message(STATUS "Looking for useradd - not found")
91 | endif()
92 | endif()
93 |
94 | if (NOT DEFINED GROUPADD_PATH)
95 | find_program(GROUPADD_PATH groupadd PATHS /usr/sbin /bin NO_DEFAULT_PATH)
96 | if (GROUPADD_PATH)
97 | message(STATUS "Looking for groupadd - found at ${GROUPADD_PATH}")
98 | else()
99 | message(STATUS "Looking for groupadd - not found")
100 | endif()
101 | endif()
102 |
103 | if (NOT DEFINED PW_PATH)
104 | find_program(PW_PATH pw PATHS /usr/sbin NO_DEFAULT_PATH)
105 | if (PW_PATH)
106 | message(STATUS "Looking for pw - found at ${PW_PATH}")
107 | else()
108 | message(STATUS "Looking for pw - not found")
109 | endif()
110 | endif()
111 |
112 | # Alpine Linux needs special treatment
113 |
114 | if (NOT DEFINED IS_ALPINE_LINUX)
115 | if(EXISTS "/etc/os-release")
116 | file(STRINGS "/etc/os-release" OS_RELEASE_CONTENTS)
117 | foreach(line IN LISTS OS_RELEASE_CONTENTS)
118 | if(line MATCHES "^ID=alpine$")
119 | set(IS_ALPINE_LINUX TRUE CACHE INTERNAL "whether this is Alpine Linux")
120 | message(STATUS "This is Alpine Linux")
121 | break()
122 | endif()
123 | endforeach()
124 | endif()
125 | if (NOT DEFINED IS_ALPINE_LINUX)
126 | set(IS_ALPINE_LINUX FALSE CACHE INTERNAL "whether this is Alpine Linux")
127 | endif()
128 | endif()
129 |
130 | if(IS_ALPINE_LINUX)
131 | if (NOT DEFINED ADDUSER_PATH)
132 | find_program(ADDUSER_PATH adduser PATHS /usr/sbin NO_DEFAULT_PATH)
133 | if (ADDUSER_PATH)
134 | message(STATUS "Looking for adduser - found at ${ADDUSER_PATH}")
135 | else()
136 | message(STATUS "Looking for adduser - not found")
137 | endif()
138 | endif()
139 |
140 | if (NOT DEFINED ADDGROUP_PATH)
141 | find_program(ADDGROUP_PATH addgroup PATHS /usr/sbin NO_DEFAULT_PATH)
142 | if (ADDGROUP_PATH)
143 | message(STATUS "Looking for addgroup - found at ${ADDGROUP_PATH}")
144 | else()
145 | message(STATUS "Looking for addgroup - not found")
146 | endif()
147 | endif()
148 |
149 | endif()
150 |
151 | if (WSDDN_WITH_SYSTEMD STREQUAL "yes" OR WSDDN_WITH_SYSTEMD STREQUAL "auto" AND NOT DEFINED CACHE{HAVE_SYSTEMD})
152 |
153 | message(CHECK_START "Looking for systemd")
154 |
155 | find_library(LIBSYSTEMD_LIBRARY NAMES systemd systemd-daemon)
156 |
157 | if (LIBSYSTEMD_LIBRARY)
158 | if(IS_SYMLINK "${LIBSYSTEMD_LIBRARY}")
159 | file(READ_SYMLINK "${LIBSYSTEMD_LIBRARY}" LIBSYSTEMD_SO)
160 | if(NOT IS_ABSOLUTE "${LIBSYSTEMD_SO}")
161 | get_filename_component(dir "${LIBSYSTEMD_LIBRARY}" DIRECTORY)
162 | set(LIBSYSTEMD_SO "${dir}/${LIBSYSTEMD_SO}")
163 | endif()
164 | else()
165 | set(LIBSYSTEMD_SO "${LIBSYSTEMD_LIBRARY}")
166 | endif()
167 |
168 | set(LIBSYSTEMD_SO "${LIBSYSTEMD_SO}" CACHE INTERNAL "" FORCE)
169 | endif()
170 |
171 | cmake_push_check_state(RESET)
172 | set(CMAKE_REQUIRED_LIBRARIES ${LIBSYSTEMD_LIBRARY})
173 | check_include_files(systemd/sd-daemon.h HAVE_SYSTEMD_SD_DAEMON_H)
174 | check_function_exists(sd_notify HAVE_SYSTEMD_SD_NOTIFY)
175 | cmake_pop_check_state()
176 |
177 | if (HAVE_SYSTEMD_SD_DAEMON_H AND HAVE_SYSTEMD_SD_NOTIFY AND LIBSYSTEMD_SO)
178 |
179 | set(HAVE_SYSTEMD ON CACHE INTERNAL "")
180 | message(CHECK_PASS "found in ${LIBSYSTEMD_SO}")
181 |
182 | else()
183 |
184 | message(CHECK_FAIL "not found")
185 | if (WSDDN_WITH_SYSTEMD STREQUAL "yes")
186 | message(FATAL_ERROR "systemd is required but not found")
187 | endif()
188 |
189 | endif()
190 |
191 | endif()
192 |
193 |
194 | find_program(PANDOC_PATH pandoc)
195 | find_program(GROFF_PATH groff)
196 |
--------------------------------------------------------------------------------
/config/metadata/README.md:
--------------------------------------------------------------------------------
1 | # Custom metadata
2 |
3 |
4 | [wsdp]: https://specs.xmlsoap.org/ws/2006/02/devprof/devicesprofile.pdf
5 | [pnpx]: https://download.microsoft.com/download/a/f/7/af7777e5-7dcd-4800-8a0a-b18336565f5b/PnPX-spec.doc
6 | [ms-pbsd]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pbsd/a3c6b665-a44e-41d5-98ec-d70c188378e4
7 | [ms-dpwsrp]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dpwsrp/0a96c35a-4cd4-4274-9f42-f44334d4d893
8 | [cont-id]: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/container-ids-for-dpws-devices
9 | [metadata-retrieval]: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-metadata-retrieval-client
10 | [wsdd-print]: https://learn.microsoft.com/en-us/windows-hardware/drivers/print/ws-discovery-mobile-printing-support
11 |
12 |
13 | WS-Discovery protocol tells Windows what kind of device it is exposing by sending over "metadata" - essentially an XML document containing information about the computer.
14 |
15 | By default **wsdd-native** sends metadata describing the host it is running on as an SMB server. It is possible to change that by authoring your own metadata and telling **wsdd-native** to use that instead.
16 |
17 | To do so you need to use `--metadata PATH` or `-m PATH` command line switch or put `metadata="path"` in `wsddn.conf` file.
18 |
19 | ## Format
20 |
21 | Custom metadata must be a valid, well-formed, standalone XML file. All namespaces you use must be fully declared.
22 |
23 | ### General form
24 |
25 | The general form of the metadata is as follows. For more details see [this obscure spec][wsdp]
26 |
27 | ```xml
28 |
29 |
33 |
34 |
35 | ...
36 | ...
37 | ...
38 |
39 |
40 |
41 |
42 | ...
43 | ...
44 | ...
45 | ...
46 |
47 |
48 |
49 |
50 | ...
51 |
52 |
53 |
54 | ```
55 |
56 | ### Placeholders
57 |
58 | You can use _placeholders_ inside XML element values and attributes. The placeholders are keywords that start with a `$` sign.
59 |
60 | The rules for placeholders are as follows:
61 |
62 | * A single `$` not followed by a placeholder is simply dropped and ignored
63 | * `$$` is replaced with a single `$`
64 | * The following placeholders are currently defined:
65 |
66 | | Placeholder | Meaning
67 | |-----------------------|--------
68 | | $ENDPOINT_ID | Replaced with the URN in the form urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx where the UUID is the identifier of the host. It is auto-generated or supplied via `--uuid` option.
69 | | $IP_ADDR | The IP address of the interface on which the metadata is being sent. This allows you to create URLs that operate on the same network Windows that Windows "sees" your machine.
70 | | $SMB_HOST_DESCRIPTION | The description of the host as derived from SMB configuration. If no description is available simple SMB host name is used.
71 | | $SMB_FULL_HOST_NAME | A string in format `hostname/Workgroup:workgroup_name` or `hostname/Domain:domain_name` that provides full SMB name of the machine in WS-Discovery protocol.
72 |
73 | * Placeholders are _prefix-matched_ so $IP_ADDR_HELLO will be expanded to something like 192.168.1.1_HELLO
74 |
75 | ### Examples
76 |
77 | This directory contains some examples that might help you author your own metadata.
78 |
79 | The [default.xml](default.xml) file contains an equivalent of what **wsdd-native** sends by default with no custom metadata. This is the standard metadata of an SMB host.
80 |
81 | The [other.xml](other.xml) file contains an example of a simple HTTP server. When you click on it in Windows Explorer the browser would open pointing to that host. Where it points to is actually controlled by the line
82 |
83 | ```xml
84 | http://$IP_ADDR/
85 | ```
86 | which you can change to anything you want.
87 |
88 | The section and icon in Windows Explorer Network view are determined by the line
89 |
90 | ```xml
91 | Other
92 | ```
93 |
94 | In the example it is `Other` which would result in the host being in "Other devices". You can try other things here like `HomeAutomation` or `MediaDevices`. The full(?) list of what is allowed here can be divined from [this obscure spec][pnpx] (**warning: .doc file**). See the table in "PnPX Category Definitions" section.
95 |
96 |
97 | ## More information
98 |
99 | The entire area of what the actual WS-Discovery payload should be is incredibly poorly documented by Microsoft. The useful references (some already mentioned) I could find are as follows:
100 |
101 | * [Devices Profile for Web Services][wsdp] - a horribly formatted obscure spec that leaves many questions unanswered
102 | * [PnPX: Plug and Play Extensions for Windows][pnpx] - (**warning: .doc file**). A very old spec that seems to exist only as a Word document. This sheds some light on `pnpx` stuff that can be used in metadata.
103 | * [\[MS-PBSD\]: Publication Services Data Structure][ms-pbsd] - see especially "Section 3: Structure Examples" there. This describes the metadata for an SMB server.
104 | * [\[MS-DPWSRP\]: Devices Profile for Web Services (DPWS): Shared Resource Publishing Data Structure][ms-dpwsrp] - explains the bizarre "not quite base-64" data is in the "Structure Examples" above
105 | * [Container IDs for DPWS Devices][cont-id] - explains how multiple devices can be grouped into "containers"
106 | * [Device Metadata Retrieval Client][metadata-retrieval] - indirectly explains how Windows looks up more information about devices including device-specific icons etc. Unfortunately it seems that there is no way to specify an icon via WS-Discovery, you need to register extra device metadata with Microsoft and refer to it by device ID.
107 | * [WS-Discovery mobile printing support][wsdd-print] - an example of how printers should expose themselves via WS-Discovery
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/sys_socket.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2022, Eugene Gershnik
2 | // SPDX-License-Identifier: BSD-3-Clause
3 |
4 | #ifndef HEADER_SYS_SOCKET_H_INCLUDED
5 | #define HEADER_SYS_SOCKET_H_INCLUDED
6 |
7 | #include "sys_util.h"
8 |
9 | #if __has_include()
10 | #include
11 | #endif
12 |
13 | #if __has_include()
14 | #include
15 | #endif
16 |
17 | namespace ptl {
18 |
19 | template
20 | struct FileDescriptorTraits> {
21 | [[gnu::always_inline]] static int c_fd(asio::basic_datagram_socket & socket) noexcept
22 | { return socket.native_handle();}
23 | };
24 |
25 | }
26 |
27 | #define IN6_IS_SCOPE_LINKLOCAL(a) \
28 | ((IN6_IS_ADDR_LINKLOCAL(a)) || \
29 | (IN6_IS_ADDR_MC_LINKLOCAL(a)))
30 |
31 |
32 | WSDDN_DECLARE_MEMBER_DETECTOR(in6_addr, s6_addr16, in6_addr_has_s6_addr16);
33 |
34 | template T>
35 | static inline uint16_t * in6_addr_addr16(T & addr) {
36 | if constexpr (in6_addr_has_s6_addr16)
37 | return addr.s6_addr16;
38 | else
39 | return (uint16_t *)&addr.s6_addr;
40 | }
41 |
42 |
43 | inline auto makeAddress(const sockaddr_in & addr) -> ip::address_v4 {
44 | return ip::address_v4(ntohl(addr.sin_addr.s_addr));
45 | }
46 |
47 | inline auto makeAddress(const sockaddr_in6 & addr) -> ip::address_v6 {
48 | union {
49 | ip::address_v6::bytes_type asio;
50 | in6_addr raw;
51 | } clearAddr;
52 | memcpy(&clearAddr.raw, addr.sin6_addr.s6_addr, sizeof(clearAddr.raw));
53 | uint32_t scope = addr.sin6_scope_id;
54 | if (IN6_IS_SCOPE_LINKLOCAL(&clearAddr.raw)) {
55 | uint16_t * words = in6_addr_addr16(clearAddr.raw);
56 | if (uint32_t embeddedScope = htons(words[1])) {
57 | scope = embeddedScope;
58 | }
59 | words[1] = 0;
60 | }
61 | return ip::address_v6(clearAddr.asio, scope);
62 | }
63 |
64 | WSDDN_DECLARE_MEMBER_DETECTOR(struct ifreq, ifr_ifindex, ifreq_has_ifr_ifindex);
65 |
66 | template T>
67 | static inline void set_ifreq_ifindex(T & req, int ifIndex) {
68 | if constexpr (ifreq_has_ifr_ifindex)
69 | req.ifr_ifindex = ifIndex;
70 | else
71 | req.ifr_index = ifIndex;
72 | }
73 |
74 | template T>
75 | static inline int ifreq_ifindex(const T & req) {
76 | if constexpr (ifreq_has_ifr_ifindex)
77 | return req.ifr_ifindex;
78 | else
79 | return req.ifr_index;
80 | }
81 |
82 | template
83 | class SocketIOControl {
84 | public:
85 | constexpr auto name() const -> unsigned long { return Name; }
86 | auto data() -> void * { return &m_data; }
87 |
88 | protected:
89 | T m_data;
90 | };
91 |
92 | #ifdef SIOCGIFFLAGS
93 |
94 | class GetInterfaceFlags : public SocketIOControl<(unsigned long)SIOCGIFFLAGS, ifreq> {
95 |
96 | public:
97 | GetInterfaceFlags(const sys_string & name) {
98 | auto copied = name.copy_data(0, m_data.ifr_name, IFNAMSIZ);
99 | memset(m_data.ifr_name + copied, 0, IFNAMSIZ - copied);
100 | }
101 |
102 | auto result() const -> std::remove_cvref_tm_data.ifr_flags)> {
103 | return m_data.ifr_flags;
104 | }
105 | };
106 |
107 | #endif
108 |
109 | #ifdef SIOCGLIFCONF
110 |
111 | class GetLInterfaceConf : public SocketIOControl<(unsigned long)SIOCGLIFCONF, lifconf> {
112 | public:
113 | GetLInterfaceConf(sa_family_t family, lifreq * dest, size_t size) {
114 | m_data.lifc_family = family;
115 | m_data.lifc_flags = 0;
116 | m_data.lifc_len = size * sizeof(lifreq);
117 | m_data.lifc_req = dest;
118 | }
119 |
120 | auto result() const -> size_t {
121 | return m_data.lifc_len / sizeof(lifreq);
122 | }
123 | };
124 |
125 | #endif
126 |
127 | #ifdef SIOCGIFCONF
128 |
129 | class GetInterfaceConf : public SocketIOControl<(unsigned long)SIOCGIFCONF, ifconf> {
130 | public:
131 | GetInterfaceConf(ifreq * dest, size_t size) {
132 | m_data.ifc_len = size * sizeof(ifreq);
133 | m_data.ifc_req = dest;
134 | }
135 |
136 | auto result() const -> size_t {
137 | return m_data.ifc_len / sizeof(ifreq);
138 | }
139 | };
140 |
141 | #endif
142 |
143 | #ifdef SIOCGLIFINDEX
144 |
145 | class GetLInterfaceIndex : public SocketIOControl<(unsigned long)SIOCGLIFINDEX, lifreq> {
146 | public:
147 | GetLInterfaceIndex(const sys_string & name) {
148 | auto copied = name.copy_data(0, m_data.lifr_name, IFNAMSIZ);
149 | memset(m_data.lifr_name + copied, 0, IFNAMSIZ - copied);
150 | }
151 |
152 | auto result() const -> int {
153 | return m_data.lifr_index;
154 | }
155 | };
156 |
157 | #endif
158 |
159 | #ifdef SIOCGIFINDEX
160 |
161 | class GetInterfaceIndex : public SocketIOControl<(unsigned long)SIOCGIFINDEX, ifreq> {
162 | public:
163 | GetInterfaceIndex(const sys_string & name) {
164 | auto copied = name.copy_data(0, m_data.ifr_name, IFNAMSIZ);
165 | memset(m_data.ifr_name + copied, 0, IFNAMSIZ - copied);
166 | }
167 |
168 | auto result() const -> int {
169 | return ifreq_ifindex(m_data);
170 | }
171 | };
172 |
173 | #endif
174 |
175 | #ifdef SIOCGLIFFLAGS
176 |
177 | class GetLInterfaceFlags : public SocketIOControl<(unsigned long)SIOCGLIFFLAGS, lifreq> {
178 |
179 | public:
180 | GetLInterfaceFlags(const sys_string & name) {
181 | auto copied = name.copy_data(0, m_data.lifr_name, IFNAMSIZ);
182 | memset(m_data.lifr_name + copied, 0, IFNAMSIZ - copied);
183 | }
184 |
185 | auto result() const -> std::remove_cvref_t