Line data Source code
1 : // Copyright (c) 2011-2025 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (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 General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Retrieve the next unique number.
22 : *
23 : * The unique_number function reads a file for a unique number. That number
24 : * is managed by this function. The file is locked so any number of threads
25 : * and processes can call the function safely.
26 : *
27 : * The number is managed as a 128 bits number. You may cast it down to any
28 : * number of bits as required by your function.
29 : *
30 : * The function also accepts an index which gives you the ability to create
31 : * any number of counters within one file. This is particularly useful if
32 : * your service makes use of many different counter. Instead of create a
33 : * separate file for each counter, reuse the same file with varying
34 : * indexes. This is likely faster since the kernel only needs to cache one
35 : * single file.
36 : */
37 :
38 : // libexcept
39 : //
40 : #include <libexcept/exception.h>
41 :
42 :
43 : // snapdev
44 : //
45 : #include <snapdev/lockfile.h>
46 : #include <snapdev/raii_generic_deleter.h>
47 :
48 :
49 : // C
50 : //
51 : #include <sys/file.h>
52 : //#include <unistd.h>
53 : //#include <string.h>
54 :
55 :
56 :
57 : namespace snapdev
58 : {
59 :
60 202 : DECLARE_MAIN_EXCEPTION(unique_number_error);
61 :
62 1 : DECLARE_EXCEPTION(unique_number_error, io_error);
63 200 : DECLARE_EXCEPTION(unique_number_error, out_of_range);
64 1 : DECLARE_EXCEPTION(unique_number_error, path_missing);
65 :
66 :
67 :
68 : /** \brief Limit the size of one counter file to 1Mb.
69 : *
70 : * This is the limit of the index passed to the unique_number(). If you
71 : * pass a negative number of a number equal or larger to this number,
72 : * then the function raises an error.
73 : */
74 : constexpr int const COUNTER_MAXIMUM_INDEX = 65536;
75 :
76 :
77 : /** \brief Manage a unique number within a file.
78 : *
79 : * This function handles a unique number. It creates a file if it
80 : * does not exist yet and starts counting. Each time you call the
81 : * function, it returns the current counter value plus 1. The very
82 : * first time, it returns 1. The counter is 128 bits so rather
83 : * unlikely to wrap around.
84 : *
85 : * The \p counter parameter holds the path and filename to the
86 : * counter which gets saved to file.
87 : *
88 : * The \p index parameter can be set to a number larger than 0 to
89 : * reference a different counter. That way, you can make use of the
90 : * same file for many counters, which is likely going to be faster
91 : * and reduce the amount of disk space necessary to manage the counters.
92 : *
93 : * The index cannot be negative or larger or equal to COUNTER_MAXIMUM_INDEX.
94 : *
95 : * For a specific counter, the number returned is unique (assuming no
96 : * wrapping occurs) for the computer it exists on. To make it
97 : * unique in a cluster, consider adding the computer host name. Similarly,
98 : * to make it unique anywhere, also add the cluster unique identifier.
99 : *
100 : * \note
101 : * The index can be used to create counters at any index. Intermediate
102 : * counters do not need to exist. So if you use 100, it will create a
103 : * counter at offset 100 * 16 (unsigned __int128 is 16 bytes). This
104 : * means the file may end up being a sparse file.
105 : *
106 : * \exception path_missing
107 : * This exception is raised if the \p counter path is an empty string.
108 : *
109 : * \exception out_of_range
110 : * This exception is raised if the index is negative or larger or
111 : * equal to the limit, COUNTER_MAXIMUM_INDEX.
112 : *
113 : * \exception io_error
114 : * This exception is raised if an I/O error is detected. This includes
115 : * opening, seeking, reading, and writing to the file.
116 : *
117 : * \param[in] counter The path to the counter file.
118 : * \param[in] index The counter index starting at 0.
119 : *
120 : * \return The current counter after 1 was added to it.
121 : */
122 : #pragma GCC diagnostic push
123 : #pragma GCC diagnostic ignored "-Wpedantic"
124 10302 : inline unsigned __int128 unique_number(std::string const & counter, int index = 0)
125 : {
126 10302 : if(counter.empty())
127 : {
128 3 : throw path_missing("a counter filename must be specified when calling snapdev::unique_number.");
129 : }
130 10301 : if(static_cast<std::size_t>(index) >= static_cast<std::size_t>(COUNTER_MAXIMUM_INDEX))
131 : {
132 : throw out_of_range(
133 : "counter index in unique_number must be defined between 0 and "
134 400 : + std::to_string(COUNTER_MAXIMUM_INDEX - 1)
135 600 : + " inclusive.");
136 : }
137 :
138 10101 : raii_fd_t fd(::open(counter.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH));
139 10101 : if(fd == nullptr)
140 : {
141 1 : throw io_error("could not open unique_number file \"" + counter + "\".");
142 : }
143 :
144 : // make sure we are exclusive
145 : //
146 10100 : lockfd lock(fd.get(), operation_t::OPERATION_EXCLUSIVE);
147 :
148 10100 : unsigned __int128 result(0);
149 10100 : off_t const position(index * sizeof(result));
150 10100 : if(lseek(fd.get(), position, SEEK_SET) != position)
151 : {
152 : throw io_error("could not properly lseek() unique_number file \"" + counter + "\" to read the counter."); // LCOV_EXCL_LINE
153 : }
154 :
155 : // read errors are expected when calling the function for the first time
156 : //
157 10100 : if(read(fd.get(), &result, sizeof(result)) != sizeof(result))
158 : {
159 4 : result = 0;
160 : }
161 :
162 10100 : ++result;
163 :
164 10100 : if(lseek(fd.get(), position, SEEK_SET) != position)
165 : {
166 : throw io_error("could not properly lseek() unique_number file \"" + counter + "\" to write the counter."); // LCOV_EXCL_LINE
167 : }
168 10100 : if(write(fd.get(), &result, sizeof(result)) != sizeof(result))
169 : {
170 : throw io_error("could not properly save() unique_number to file \"" + counter + "\"."); // LCOV_EXCL_LINE
171 : }
172 :
173 10100 : return result;
174 10101 : }
175 : #pragma GCC diagnostic pop
176 :
177 :
178 :
179 : } // namespace snapdev
180 : // vim: ts=4 sw=4 et
|