Skip to content

Commit cf45490

Browse files
authored
Merge pull request #226 from Sambruk/feature/197
Feature/197
2 parents 6011da2 + 16716f5 commit cf45490

12 files changed

+408
-33
lines changed

.gitattributes

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Set the default behavior, in case core.autocrlf isn't set.
2+
* text=auto
3+
4+
test/tests/**/*.txt text eol=lf

.github/workflows/cmake-multi-platform.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
4444
- name: Install dependencies on Ubuntu
4545
if: matrix.os == 'ubuntu-latest'
46-
run: sudo apt-get install -y libldap2-dev
46+
run: sudo apt-get update && sudo apt-get install -y libldap2-dev
4747

4848
- name: Configure CMake
4949
run: cmake -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} --preset=${{ matrix.cmake_preset }}

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#### Features
55
- Contents of the cache file can now be printed in JSON format (#54)
66
- New functionality for blacklisting users (#223)
7+
- Space for the cache file is now pre-allocated before SCIM operations start (#197)
78

89
## v2.18 (2024-11-19)
910
#### Features

CMakeLists.txt

+4-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ else()
3535
include_directories(${CURL_INCLUDE_DIRS})
3636
endif ()
3737

38-
find_package(Boost REQUIRED COMPONENTS program_options uuid)
38+
find_package(Boost REQUIRED COMPONENTS program_options uuid interprocess)
3939
if (NOT Boost_FOUND)
4040
message(FATAL_ERROR "please install boost")
4141
else ()
@@ -51,16 +51,16 @@ endif()
5151
link_libraries(${LDFLAGS})
5252

5353
set(srcroot src)
54-
set(SOURCES ${srcroot}/scim_json_parse.cpp ${srcroot}/utility/simplescim_error_string.cpp ${srcroot}/simplescim_scim_send.cpp ${srcroot}/cache_file.cpp ${srcroot}/model/base_object.cpp ${srcroot}/model/object_list.cpp ${srcroot}/model/object_list.hpp ${srcroot}/simplescim_ldap_attrs_parser.cpp ${srcroot}/config_file.cpp ${srcroot}/config_file_parser.cpp ${srcroot}/scim.cpp ${srcroot}/utility/utils.cpp ${srcroot}/utility/utils.hpp ${srcroot}/utility/utils.cpp ${srcroot}/ldap_wrapper.cpp ${srcroot}/ldap_wrapper.hpp ${srcroot}/data_server.cpp ${srcroot}/data_server.hpp ${srcroot}/json_data_file.cpp ${srcroot}/json_data_file.hpp ${srcroot}/json_template_parser.cpp ${srcroot}/json_template_parser.hpp ${srcroot}/simplescim_ldap.cpp ${srcroot}/generated_load.cpp ${srcroot}/generated_load.hpp ${srcroot}/generated_group_load.cpp ${srcroot}/generated_group_load.hpp ${srcroot}/csv_load.cpp ${srcroot}/csv_load.hpp ${srcroot}/csv_store.cpp ${srcroot}/csv_store.hpp ${srcroot}/csv_file.cpp ${srcroot}/csv_file.hpp ${srcroot}/sql_load.cpp ${srcroot}/sql_load.hpp ${srcroot}/scim_server_info.cpp ${srcroot}/scim_server_info.hpp ${srcroot}/load_limiter.hpp ${srcroot}/load_limiter.cpp ${srcroot}/fedtlsauth/castore_file.hpp ${srcroot}/fedtlsauth/castore_file.cpp ${srcroot}/fedtlsauth/metadata_parser.hpp ${srcroot}/fedtlsauth/metadata_parser.cpp ${srcroot}/utility/indented_logger.hpp ${srcroot}/utility/indented_logger.cpp ${srcroot}/post_processing.hpp ${srcroot}/post_processing.cpp ${srcroot}/sql.hpp ${srcroot}/sql.cpp ${srcroot}/dl.hpp ${srcroot}/dl.cpp ${srcroot}/plugin_config.hpp ${srcroot}/plugin_config.cpp ${srcroot}/load_common.hpp ${srcroot}/load_common.cpp ${srcroot}/load_limiter_impl.hpp ${srcroot}/model/rendered_object.hpp ${srcroot}/model/rendered_object.cpp ${srcroot}/model/rendered_object_list.hpp ${srcroot}/model/rendered_object_list.cpp ${srcroot}/renderer.hpp ${srcroot}/renderer.cpp ${srcroot}/rendered_cache_file.hpp ${srcroot}/rendered_cache_file.cpp ${srcroot}/utility/temporary_umask.hpp ${srcroot}/transformer.hpp ${srcroot}/transformer.cpp ${srcroot}/transformer_impl.hpp ${srcroot}/transformer_impl.cpp ${srcroot}/readable_id.hpp ${srcroot}/readable_id.cpp ${srcroot}/config.hpp ${srcroot}/config.cpp ${srcroot}/dnpactivities.cpp ${srcroot}/dnpactivities.hpp ${srcroot}/utility/simpleurl.cpp ${srcroot}/utility/simpleurl.hpp ${srcroot}/thresholds.hpp ${srcroot}/thresholds.cpp ${srcroot}/audit.hpp ${srcroot}/audit.cpp ${srcroot}/print_cache.hpp ${srcroot}/print_cache.cpp)
54+
set(SOURCES ${srcroot}/scim_json_parse.cpp ${srcroot}/utility/simplescim_error_string.cpp ${srcroot}/simplescim_scim_send.cpp ${srcroot}/cache_file.cpp ${srcroot}/model/base_object.cpp ${srcroot}/model/object_list.cpp ${srcroot}/model/object_list.hpp ${srcroot}/simplescim_ldap_attrs_parser.cpp ${srcroot}/config_file.cpp ${srcroot}/config_file_parser.cpp ${srcroot}/scim.cpp ${srcroot}/utility/utils.cpp ${srcroot}/utility/utils.hpp ${srcroot}/utility/utils.cpp ${srcroot}/ldap_wrapper.cpp ${srcroot}/ldap_wrapper.hpp ${srcroot}/data_server.cpp ${srcroot}/data_server.hpp ${srcroot}/json_data_file.cpp ${srcroot}/json_data_file.hpp ${srcroot}/json_template_parser.cpp ${srcroot}/json_template_parser.hpp ${srcroot}/simplescim_ldap.cpp ${srcroot}/generated_load.cpp ${srcroot}/generated_load.hpp ${srcroot}/generated_group_load.cpp ${srcroot}/generated_group_load.hpp ${srcroot}/csv_load.cpp ${srcroot}/csv_load.hpp ${srcroot}/csv_store.cpp ${srcroot}/csv_store.hpp ${srcroot}/csv_file.cpp ${srcroot}/csv_file.hpp ${srcroot}/sql_load.cpp ${srcroot}/sql_load.hpp ${srcroot}/scim_server_info.cpp ${srcroot}/scim_server_info.hpp ${srcroot}/load_limiter.hpp ${srcroot}/load_limiter.cpp ${srcroot}/fedtlsauth/castore_file.hpp ${srcroot}/fedtlsauth/castore_file.cpp ${srcroot}/fedtlsauth/metadata_parser.hpp ${srcroot}/fedtlsauth/metadata_parser.cpp ${srcroot}/utility/indented_logger.hpp ${srcroot}/utility/indented_logger.cpp ${srcroot}/post_processing.hpp ${srcroot}/post_processing.cpp ${srcroot}/sql.hpp ${srcroot}/sql.cpp ${srcroot}/dl.hpp ${srcroot}/dl.cpp ${srcroot}/plugin_config.hpp ${srcroot}/plugin_config.cpp ${srcroot}/load_common.hpp ${srcroot}/load_common.cpp ${srcroot}/load_limiter_impl.hpp ${srcroot}/model/rendered_object.hpp ${srcroot}/model/rendered_object.cpp ${srcroot}/model/rendered_object_list.hpp ${srcroot}/model/rendered_object_list.cpp ${srcroot}/renderer.hpp ${srcroot}/renderer.cpp ${srcroot}/rendered_cache_file.hpp ${srcroot}/rendered_cache_file.cpp ${srcroot}/utility/temporary_umask.hpp ${srcroot}/transformer.hpp ${srcroot}/transformer.cpp ${srcroot}/transformer_impl.hpp ${srcroot}/transformer_impl.cpp ${srcroot}/readable_id.hpp ${srcroot}/readable_id.cpp ${srcroot}/config.hpp ${srcroot}/config.cpp ${srcroot}/dnpactivities.cpp ${srcroot}/dnpactivities.hpp ${srcroot}/utility/simpleurl.cpp ${srcroot}/utility/simpleurl.hpp ${srcroot}/thresholds.hpp ${srcroot}/thresholds.cpp ${srcroot}/audit.hpp ${srcroot}/audit.cpp ${srcroot}/print_cache.hpp ${srcroot}/print_cache.cpp ${srcroot}/advisory_file_lock.cpp ${srcroot}/advisory_file_lock.hpp)
5555

56-
set(TEST_SOURCES ${srcroot}/tests/main.cpp ${srcroot}/tests/json_template_parser_tests.cpp ${srcroot}/tests/scim_query_tests.cpp ${srcroot}/tests/csv_tests.cpp ${srcroot}/tests/uuid_tests.cpp ${srcroot}/tests/scim_json_parse_tests.cpp ${srcroot}/tests/load_limiter_tests.cpp ${srcroot}/tests/object_list_tests.cpp ${srcroot}/tests/generated_group_tests.cpp ${srcroot}/tests/transformer_tests.cpp ${srcroot}/tests/dnpactivities_tests.cpp ${srcroot}/tests/thresholds_tests.cpp ${srcroot}/tests/audit_tests.cpp)
56+
set(TEST_SOURCES ${srcroot}/tests/main.cpp ${srcroot}/tests/json_template_parser_tests.cpp ${srcroot}/tests/scim_query_tests.cpp ${srcroot}/tests/csv_tests.cpp ${srcroot}/tests/uuid_tests.cpp ${srcroot}/tests/scim_json_parse_tests.cpp ${srcroot}/tests/load_limiter_tests.cpp ${srcroot}/tests/object_list_tests.cpp ${srcroot}/tests/generated_group_tests.cpp ${srcroot}/tests/transformer_tests.cpp ${srcroot}/tests/dnpactivities_tests.cpp ${srcroot}/tests/thresholds_tests.cpp ${srcroot}/tests/audit_tests.cpp ${srcroot}/tests/rendered_cache_file_tests.cpp)
5757

5858
add_library (Egil ${SOURCES})
5959

6060
add_executable(EgilSCIMClient ${srcroot}/main.cpp)
6161
add_executable(tests ${TEST_SOURCES})
6262

63-
target_link_libraries(EgilSCIMClient LINK_PUBLIC Egil Boost::program_options Boost::uuid CURL::libcurl)
63+
target_link_libraries(EgilSCIMClient LINK_PUBLIC Egil Boost::program_options Boost::uuid CURL::libcurl Boost::interprocess)
6464
target_link_libraries(tests LINK_PUBLIC Egil)
6565

6666
install (TARGETS EgilSCIMClient DESTINATION bin)

src/advisory_file_lock.cpp

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* This file is part of the EGIL SCIM client.
3+
*
4+
* Copyright (C) 2017-2025 Föreningen Sambruk
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "advisory_file_lock.hpp"
21+
#include "utility/utils.hpp"
22+
#include <filesystem>
23+
#include <boost/date_time/posix_time/posix_time.hpp>
24+
25+
namespace {
26+
/** Construct a name for the mutex based on the path.
27+
* We'll canonicalize the path in case it has been given differently
28+
* to different processes. We'll then generate a UUID based on that
29+
* since path names are not valid mutex names (at least on Windows).
30+
*/
31+
std::string mutex_name(const std::string& path) {
32+
std::string str;
33+
try {
34+
str = std::filesystem::weakly_canonical(std::filesystem::path(path)).string();
35+
}
36+
catch (const std::exception&) {
37+
str = path;
38+
}
39+
return uuid_util::instance().generate(str);
40+
}
41+
}
42+
43+
AdvisoryFileLock::AdvisoryFileLock(const std::string& path, int timeout)
44+
: m_timeout(timeout) {
45+
// Creating or locking the mutex might throw in exotic situations,
46+
// we don't want that to block us from writing the cache file.
47+
try {
48+
const auto mtx_name = mutex_name(path);
49+
m_pmtx = std::make_unique<boost::interprocess::named_mutex>(boost::interprocess::open_or_create_t{}, mtx_name.c_str());
50+
auto locked = m_pmtx->timed_lock(boost::posix_time::second_clock::universal_time() + boost::posix_time::seconds(m_timeout));
51+
if (!locked) {
52+
// There really shouldn't be any congestion for this mutex, if we fail to lock
53+
// the most likely explanation is a problem with the mutex (perhaps a process that held
54+
// the mutex was hard killed before it could release the mutex).
55+
m_pmtx = nullptr;
56+
boost::interprocess::named_mutex::remove(mtx_name.c_str());
57+
}
58+
}
59+
catch (std::exception&) {
60+
m_pmtx = nullptr;
61+
}
62+
63+
}
64+
65+
AdvisoryFileLock::~AdvisoryFileLock() {
66+
if (m_pmtx != nullptr) {
67+
m_pmtx->unlock();
68+
}
69+
}

src/advisory_file_lock.hpp

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* This file is part of the EGIL SCIM client.
3+
*
4+
* Copyright (C) 2017-2025 Föreningen Sambruk
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
#ifndef EGILSCIM_ADVISORY_FILE_LOCK_HPP
21+
#define EGILSCIM_ADVISORY_FILE_LOCK_HPP
22+
23+
#include <memory>
24+
#include <string>
25+
#include <boost/interprocess/sync/named_mutex.hpp>
26+
27+
/** This is an advisory file lock for the cache file.
28+
* Internally this uses boost's named_mutex to coordinate
29+
* access to the cache file, but in a pragmatic way to
30+
* suit our needs for the cache file.
31+
*
32+
* Motivation:
33+
*
34+
* 1. We really want to avoid being blocked from writing the new cache file.
35+
* 2. Under normal circumstances there should be little risk of having to
36+
* wait long for access to the file.
37+
* 3. There could be other processes accessing the file (such as a backup script)
38+
* which won't respect our advisory locking anyway, so the writing to the
39+
* cache file will anyway need to implement a retry in case the filesystem
40+
* blocks the rename when we're done writing.
41+
* 4. boost's named_mutex can throw an interprocess_error, if the interprocess
42+
* implementation isn't working for some reason we don't want that to be
43+
* enough of a reason not to write the cache file.
44+
* 5. If we fail to get the lock within a reasonable time, the most likely explanation
45+
* is not that others are accessing it, but that the named_mutex hasn't been properly
46+
* released (for instance it the process is hard killed while holding the mutex).
47+
*
48+
* All this put together leads to an implementation which tries to take the
49+
* named mutex but with a timeout. If we fail to take the mutex (either by timeout or
50+
* a problem with the interprocess system), we'll carry on as if we have the mutex
51+
* (and remove it from the system so we won't need to wait next time).
52+
*
53+
* In other words, under normal circumstances this will block multiple instances of
54+
* EgilSCIMClient from accessing the file at once, but it's not a 100% guarantee.
55+
* We're ok with that since we're anyway retrying the writes.
56+
*/
57+
class AdvisoryFileLock {
58+
public:
59+
/*
60+
* path is the file to lock.
61+
* timeout is given in seconds.
62+
*/
63+
AdvisoryFileLock(const std::string& path, int timeout);
64+
virtual ~AdvisoryFileLock();
65+
66+
private:
67+
std::unique_ptr<boost::interprocess::named_mutex> m_pmtx;
68+
int m_timeout;
69+
};
70+
71+
#endif // EGILSCIM_ADVISORY_FILE_LOCK_HPP

0 commit comments

Comments
 (0)