1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
|
Design of APR
The Apache Portable Run-time libraries have been designed to provide a common
interface to low level routines across any platform. The original goal of APR
was to combine all code in Apache to one common code base. This is not the
correct approach however, so the goal of APR has changed.
There are places where common code is not a good thing. For example, how to
map requests to either threads or processes should be platform specific.
APR's place is now to combine any code that can be safely combined without
sacrificing performance.
To this end we have created a set of operations that are required for cross
platfrom development. There may be other types that are desired and those
will be implemented in the future. The first version of APR will focus on
what Apache 2.0 needs. Of course, anything that is submitted will be
considered for inclusion.
This document will discuss the structure of APR, and how best to contribute
code to the effort.
APR On Windows
APR on Windows is different from APR on all other systems, because it
doesn't use autoconf. On Unix, apr_private.h (private to APR) and apr.h
(public, used by applications that use APR) are generated by autoconf
from acconfig.h and apr.h.in respectively. On Windows, apr_private.h
and apr.h are created from apr_private.hw and apr.hw respectively.
!!!*** If you add code to acconfig.h or tests to configure.in or aclocal.m4,
please give some thought to whether or not Windows needs this addition
as well. A general rule of thumb, is that if it is a feature macro,
such as APR_HAS_THREADS, Windows needs it. If the definition is going
to be used in a public APR header file, such as apr_general.h, Windows
needs it.
The only time it is safe to add a macro or test without also adding
the macro to apr*.hw, is if the macro tells APR how to build. For
example, a test for a header file does not need to be added to Windows.
***!!!
APR Features
One of the goals of APR is to provide a common set of features across all
platforms. This is an admirable goal, it is also not realisitic. We cannot
expect to be able to implement ALL features on ALL platforms. So we are
going to do the next best thing. Provide a common interface to ALL APR
features on MOST platforms.
APR developers should create FEATURE MACROS for any feature that is not
available on ALL platforms. This should be a simple definition which has
the form:
APR_HAS_FEATURE
This macro should evaluate to true if APR has this feature on this platform.
For example, Linux and Windows have mmap'ed files, and APR is providing an
interface for mmapp'ing a file. On both Linux and Windows, APR_HAS_MMAP
should evaluate to one, and the ap_mmap_* functions should map files into
memory and return the appropriate status codes.
If your OS of choice does not have mmap'ed files, APR_HAS_MMAP should evaluate
to zero, and all ap_mmap_* functions should not be defined. The second step
is a precaution that will allow us to break at compile time if a programmer
tries to use unsupported functions.
APR types
The base types in APR
file_io File I/O, including pipes
lib A portable library originally used in Apache. This contains
memory management, tables, and arrays.
locks Mutex and reader/writer locks
misc Any APR type which doesn't have any other place to belong
network_io Network I/O
shmem Shared Memory (Not currently implemented)
signal Asynchronous Signals
threadproc Threads and Processes
time Time
Directory Structure
Each type has a base directory. Inside this base directory, are
subdirectories, which contain the actual code. These subdirectories are named
after the platforms the are compiled on. Unix is also used as a common
directory. If the code you are writing is POSIX based, you should look at the
code in the unix directory. A good rule of thumb, is that if more than half
your code needs to be ifdef'ed out, and the structures required for your code
are substantively different from the POSIX code, you should create a new
directory.
Currently, the APR code is written for Unix, BeOS, Windows, and OS/2. An
example of the directory structure is the file I/O directory:
apr
|
-> file_io
|
-> unix The Unix and common base code
|
-> win32 The Windows code
|
-> os2 The OS/2 code
Obviously, BeOS does not have a directory. This is because BeOS is currently
using the Unix directory for it's file_io. In the near future, it will be
possible to use indiviual files from the Unix directory.
There are a few special top level directories. These are test, inc, include,
and libs. Test is a directory which stores all test programs. It is expected
that if a new type is developed, there will also be a new test program, to
help people port this new type to different platforms. Inc is a directory for
internal header files. This directory is likely to go away soon. Include is
a directory which stores all required APR header files for external use. The
distinction between internal and external header files will be made soon.
Finally, libs is a generated directory. When APR finishes building, it will
store it's library files in the libs directory.
Creating an APR Type
The current design of APR requires that APR types be incomplete. It is not
possible to write flexible portable code if programs can access the internals
of APR types. This is because different platforms are likely to define
different native types.
For this reason, each platform defines a structure in their own directories.
Those structures are then typedef'ed in an external header file. For example
in file_io/unix/fileio.h:
struct ap_file_t {
apr_pool_t *cntxt;
int filedes;
FILE *filehand;
...
}
In include/apr_file_io.h:
typedef struct ap_file_t ap_file_t;
This will cause a compiler error if somebody tries to access the filedes field
in this strcture. Windows does not have a filedes field, so obviously, it is
important that programs not be able to access these.
The only exception to the incomplete type rule can be found in apr_portable.h.
This file defines the native types for each platform. Using these types, it
is possible to extract native types for any APR type.
You may notice the apr_pool_t field. Most APR types have this field. This
type is used to allocate memory within APR. Any APR type that has this
field should place this field first. If it is important to retrieve the
pool from an APR variable, it is possible to use the macro APR_GET_POOL to
accomplish this. This macro will only work on types that actually have
a pool in them as the first field. On any other type, this Macro will cause
a seg fault as soon as the pool is used.
New Function
When creating a new function, please try to adhere to these rules.
1) Result arguments should be the first arguments.
2) If a function needs a context, it should be the last argument.
3) These rules are flexible, especially if it makes the code easier
to understand because it mimics a standard function.
Documentation
Whenever a new function is added to APR, it MUST be documented. New
functions will not be committed unless there are docs to go along with them.
The documentation should be a comment block above the function in the header
file.
The format for the comment block is:
/**
* Brief description of the function
* @param parma_1_name explanation
* @param parma_2_name explanation
* @param parma_n_name explanation
* @tip Any extra information people should know.
* @deffunc function prototype if required
*/
The last line is not strictly needed. The parser in ScanDoc is not perfect
yet, and it can not parse prototypes that are in any form other than
return_type program_name(type1 param1, type2 param2, ...)
This means that any function prototype that resembles:
APR_DECLARE(ap_status_t) ap_foo(int f1, char *f2)
will need the deffunc.
For an actual example, look at any file in the include directory (ap_tables.h
hasn't been done yet).
APR Error reporting
Most APR functions should return an ap_status_t type. The only time an
APR function does not return an ap_status_t is if it absolutly CAN NOT
fail. Examples of this would be filling out an array when you know you are
not beyond the array's range. If it cannot fail on your platform, but it
could conceivably fail on another platform, it should return an ap_status_t.
Unless you are sure, return an ap_status_t. :-)
All platform return errno values unchanged. Each platform can also have
one system error type, which can be returned after an offset is added.
There are five types of error values in APR, each with it's own offset.
Name Purpose
0) This is 0 for all platforms and isn't really defined
anywhere, but it is the offset for errno values.
(This has no name because it isn't actually defined,
but completeness we are discussing it here).
1) APR_OS_START_ERROR This is platform dependant, and is the offset at which
APR errors start to be defined. (Canonical error
values are also defined in this section. [Canonical
error values are discussed later]).
2) APR_OS_START_STATUS This is platform dependant, and is the offset at which
APR status values start.
4) APR_OS_START_USEERR This is platform dependant, and is the offset at which
APR apps can begin to add their own error codes.
3) APR_OS_START_SYSERR This is platform dependant, and is the offset at which
system error values begin.
All of these definitions can be found in apr_errno.h for all platforms. When
an error occurs in an APR function, the function must return an error code.
If the error occurred in a system call and that system call uses errno to
report an error, then the code is returned unchanged. For example:
if (open(fname, oflags, 0777) < 0)
return errno;
The next place an error can occur is a system call that uses some error value
other than the primary error value on a platform. This can also be handled
by APR applications. For example:
if (CreateFile(fname, oflags, sharemod, NULL,
createflags, attributes,0) == INVALID_HANDLE_VALUE
return (GetLAstError() + APR_OS_START_SYSERR);
These two examples implement the same function for two different platforms.
Obviously even if the underlying problem is the same on both platforms, this
will result in two different error codes being returned. This is OKAY, and
is correct for APR. APR relies on the fact that most of the time an error
occurs, the program logs the error and continues, it does not try to
programatically solve the problem. This does not mean we have not provided
support for programmatically solving the problem, it just isn't the default
case. We'll get to how this problem is solved in a little while.
If the error occurs in an APR function but it is not due to a system call,
but it is actually an APR error or just a status code from APR, then the
appropriate code should be returned. These codes are defined in apr_errno.h
and are self explanatory.
No APR code should ever return a code between APR_OS_START_USEERR and
APR_OS_START_SYSERR, those codes are reserved for APR applications.
To programmatically correct an error in a running application, the error codes
need to be consistent across platforms. This should make sense. To get
consistent error codes, APR provides a function ap_canonical_error().
This function will take as input any ap_status_t value, and return a small
subset of canonical APR error codes. These codes will be equivalent to
Unix errno's. Why is it a small subset? Because we don't want to try to
convert everything in the first pass. As more programs require that more
error codes are converted, they will be added to this function.
Why did APR take this approach? There are two ways to deal with error
codes portably.
1) return the same error code across all platforms. 2) return platform
specific error codes and convert them when necessary.
The problem with option number one is that it takes time to convert error
codes to a common code, and most of the time programs want to just output
an error string. If we convert all errors to a common subset, we have four
steps to output an error string:
make syscall that fails
convert to common error code step 1
return common error code
check for success
call error output function step 2
convert back to system error step 3
output error string step 4
By keeping the errors platform specific, we can output error strings in two
steps.
make syscall that fails
return error code
check for success
call error output function step 1
output error string step 2
Less often, programs change their execution based on what error was returned.
This is no more expensive using option 2 and it is using option 1, but we
put the onus of converting the error code on the programmer themselves.
For example, using option 1:
make syscall that fails
convert to common error code
return common error code
decide execution basd on common error code
Using option 2:
make syscall that fails
return error code
convert to common error code (using ap_canonical_error)
decide execution based on common error code
Finally, there is one more operation on error codes. You can get a string
that explains in human readable form what has happened. To do this using
APR, call ap_strerror().
On all platforms ap_strerror takes the form:
char *ap_strerror(ap_status_t err)
{
if (err < APR_OS_START_ERRNO2)
return (platform dependant error string generator)
if (err < APR_OS_START_ERROR)
return (platform dependant error string generator for
supplemental error values)
if (err < APR_OS_SYSERR)
return (APR generated error or status string)
if (err == 0)
return "No error was found"
else
return "APR doesn't understand this error value"
}
Notice, this does not handle canonicalized error values well. Those will
return "APR doesn't understand this error value" on some platforms and
an actual error string on others. To deal with this, just get the
string before canonicalizing your error code.
The other problem with option 1, is that it is a lossy conversion. For
example, Windows and OS/2 have a couple hundred error codes, but POSIX errno
only defines about 50 errno values. This means that if we convert to a
canonical error value immediately, there is no way for the programmer to
get the actual system error.
|