paul@384 | 1 | = Server Library = |
paul@384 | 2 | |
paul@384 | 3 | Within the filesystem server library, a number of different abstractions and |
paul@384 | 4 | mechanisms are employed to provide access to filesystem objects. |
paul@384 | 5 | |
paul@457 | 6 | The server library is provided by [[Libraries#libfsserver|`libfsserver`]] |
paul@457 | 7 | within the `departure` package. |
paul@427 | 8 | |
paul@384 | 9 | <<TableOfContents(2,3)>> |
paul@384 | 10 | |
paul@384 | 11 | This document uses C-style interface syntax since the components operating |
paul@384 | 12 | within the described mechanisms will themselves be described using such |
paul@384 | 13 | syntax. |
paul@384 | 14 | |
paul@457 | 15 | ((Accountable)) |
paul@457 | 16 | == Accountables == |
paul@384 | 17 | |
paul@457 | 18 | The `Accountable` interface provides the following operations: |
paul@384 | 19 | |
paul@384 | 20 | {{{ |
paul@390 | 21 | void attach(); |
paul@390 | 22 | unsigned int detach(); |
paul@384 | 23 | }}} |
paul@384 | 24 | |
paul@384 | 25 | Its purpose is to provide reference counting for components, with the `attach` |
paul@384 | 26 | operation incrementing the number of users of a component, and with the |
paul@384 | 27 | `detach` operation decrementing the number of users and returning the |
paul@384 | 28 | resulting number of users. When components no longer have any attached users, |
paul@384 | 29 | they may perform operations to tidy up after themselves and permit their |
paul@384 | 30 | deallocation. |
paul@384 | 31 | |
paul@457 | 32 | The [[#Provider|`Provider`]] abstraction employs this interface to record its |
paul@457 | 33 | usage by multiple resources. |
paul@457 | 34 | |
paul@384 | 35 | == Accessors == |
paul@384 | 36 | |
paul@384 | 37 | Accessors provide the means of accessing filesystem object data and metadata. |
paul@384 | 38 | Conceptually, files and directories are both exposed by accessors, although |
paul@384 | 39 | the interfaces and mechanisms may differ between these types of objects. |
paul@384 | 40 | |
paul@384 | 41 | === Directory Accessors === |
paul@384 | 42 | |
paul@384 | 43 | Currently, directory accessors provide support for traversing directory |
paul@384 | 44 | listings, obtaining the relevant filesystem metadata using the underlying |
paul@384 | 45 | filesystem access library. |
paul@384 | 46 | |
paul@457 | 47 | The `DirectoryAccessor` interface provides the following operation: |
paul@457 | 48 | |
paul@457 | 49 | {{{ |
paul@457 | 50 | void read_directory(file_t *writer); |
paul@457 | 51 | }}} |
paul@457 | 52 | |
paul@457 | 53 | The `read_directory` operation is presented with a writer object into which |
paul@457 | 54 | directory listing data will be written. In the case of the ext2 directory |
paul@457 | 55 | accessor, the writer is presented to a directory iterator which traverses the |
paul@457 | 56 | directory data structure, invoking a callback sending directory entries via |
paul@457 | 57 | the writer to the client. |
paul@457 | 58 | |
paul@384 | 59 | === File Accessors === |
paul@384 | 60 | |
paul@457 | 61 | File content is accessed through the `Accessor` interface with the following |
paul@457 | 62 | operations: |
paul@384 | 63 | |
paul@384 | 64 | {{{ |
paul@390 | 65 | void close(); |
paul@390 | 66 | void fill(Flexpage *flexpage); |
paul@390 | 67 | void flush(Flexpage *flexpage); |
paul@390 | 68 | offset_t get_size(); |
paul@390 | 69 | void set_size(offset_t size); |
paul@384 | 70 | }}} |
paul@384 | 71 | |
paul@384 | 72 | The operations need to be supported with actual filesystem operations. For |
paul@384 | 73 | example, ext2-based filesystems employ a specific abstraction which invokes |
paul@457 | 74 | library functions provided by [[Libraries#libext2fs|`libext2fs`]]. |
paul@384 | 75 | |
paul@457 | 76 | ((Provider)) |
paul@384 | 77 | == Providers == |
paul@384 | 78 | |
paul@384 | 79 | Providers encapsulate the essential functionality for accessing filesystem |
paul@384 | 80 | objects. Implementing the `Accountable` interface, they are shared by |
paul@384 | 81 | resources and discarded when no resources are using them. |
paul@384 | 82 | |
paul@457 | 83 | The following operations are supported by providers as defined by the |
paul@457 | 84 | `Provider` interface: |
paul@384 | 85 | |
paul@384 | 86 | {{{ |
paul@390 | 87 | ProviderRegistry *registry(); |
paul@390 | 88 | long make_resource(offset_t *size, object_flags_t *object_flags, Resource **resource); |
paul@390 | 89 | bool removal_pending(); |
paul@390 | 90 | void remove_pending(bool remove); |
paul@384 | 91 | }}} |
paul@384 | 92 | |
paul@457 | 93 | === Origin and Ownership === |
paul@457 | 94 | |
paul@457 | 95 | A provider is created to represent a filesystem object when an attempt is made |
paul@457 | 96 | to open that object. It is then recorded in a provider registry so that |
paul@457 | 97 | subsequent attempts to open the object yield a common provider instance. The |
paul@457 | 98 | provider registry having ownership of each provider can be obtained using its |
paul@457 | 99 | `registry` operation. |
paul@457 | 100 | |
paul@457 | 101 | See the [[Filesystem Access#Opening Files|file opening mechanism]] for details |
paul@457 | 102 | of the creation and registration of providers. |
paul@457 | 103 | |
paul@457 | 104 | === Resource Creation === |
paul@384 | 105 | |
paul@384 | 106 | Providers also support the creation of resources through which each user of a |
paul@384 | 107 | provider exercises its use of that provider. The `make_resource` operation |
paul@384 | 108 | performs the creation and returns size, flags and resource instance details. |
paul@384 | 109 | |
paul@457 | 110 | See the [[Filesystem Access#Opening Files|file opening mechanism]] for details |
paul@457 | 111 | of resource creation. |
paul@457 | 112 | |
paul@457 | 113 | === Deallocation === |
paul@384 | 114 | |
paul@457 | 115 | Deallocation of providers is managed by the provider registry when resources |
paul@457 | 116 | are closed and detached from providers. Since the registry effectively has |
paul@457 | 117 | ownership of the provider, having registered it, the registry must therefore |
paul@457 | 118 | be involved in its deallocation, deregistering it first. |
paul@384 | 119 | |
paul@457 | 120 | ######## A graph showing the deallocation mechanism |
paul@384 | 121 | |
paul@384 | 122 | {{{#!graphviz |
paul@384 | 123 | #format svg |
paul@384 | 124 | #transform notugly |
paul@384 | 125 | digraph provider_removal { |
paul@384 | 126 | node [fontsize="12.0",fontname="sans-serif",shape=box]; |
paul@384 | 127 | edge [fontsize="12.0",fontname="sans-serif"]; |
paul@384 | 128 | rankdir=LR; |
paul@384 | 129 | |
paul@384 | 130 | ResourceServer -> Resource [label="close"]; |
paul@384 | 131 | Resource -> ProviderRegistry [label="detach"]; |
paul@457 | 132 | ProviderRegistry -> Provider [label="detach\ndelete"]; |
paul@384 | 133 | } |
paul@384 | 134 | }}} |
paul@384 | 135 | |
paul@384 | 136 | ######## |
paul@384 | 137 | |
paul@457 | 138 | === Filesystem Object Removal === |
paul@457 | 139 | |
paul@457 | 140 | The removal of filesystem objects that are being represented by providers can |
paul@457 | 141 | be directed using the `remove_pending` operation. Where `remove_pending` has |
paul@457 | 142 | been called with a true value as its parameter, the `removal_pending` |
paul@457 | 143 | operation will also return a true value, and upon the provider being |
paul@457 | 144 | discarded, a removal operation will be invoked on the underlying object being |
paul@457 | 145 | provided. |
paul@457 | 146 | |
paul@457 | 147 | See the [[Filesystem Access#Removing Files|file removal mechanism]] for more |
paul@457 | 148 | details of the invocations involved. |
paul@457 | 149 | |
paul@457 | 150 | ((Resource)) |
paul@384 | 151 | == Resources == |
paul@384 | 152 | |
paul@384 | 153 | Resources are objects accessed by clients that support a basic level of |
paul@457 | 154 | accounting and management. They act as servers and receive messages via the |
paul@457 | 155 | interprocess communication (IPC) mechanism. |
paul@384 | 156 | |
paul@457 | 157 | The `Resource` abstraction is intended to work with lower-level mechanisms |
paul@457 | 158 | provided by [[Libraries#libipc|`libipc`]] involving data structures and |
paul@457 | 159 | functions that are usable in the C programming language. This abstraction |
paul@457 | 160 | integrates the fundamental `libipc` support with C++. |
paul@457 | 161 | |
paul@457 | 162 | The generic operations of the `Resource` interface are as follows: |
paul@384 | 163 | |
paul@384 | 164 | {{{ |
paul@390 | 165 | void activate(); |
paul@390 | 166 | void close(); |
paul@384 | 167 | }}} |
paul@384 | 168 | |
paul@457 | 169 | The activation of a resource, supported by `activate`, is an optional |
paul@457 | 170 | operation that performs any initialisation before a resource is made available |
paul@457 | 171 | as a server. |
paul@457 | 172 | |
paul@457 | 173 | The `close` operation is invoked when resources are to be discarded. |
paul@384 | 174 | |
paul@457 | 175 | See the [[Filesystem Access#Closing Files|file closing mechanism]] for the |
paul@457 | 176 | context in which the `close` operation is invoked. |
paul@457 | 177 | |
paul@457 | 178 | In practice, other operations are required to make resources useful. Such |
paul@457 | 179 | other operations are provided by classes inheriting from `Resource` and thus |
paul@457 | 180 | specialising it. Such classes also inherit from IPC interface types so as to |
paul@457 | 181 | be able to support the invocation of operations exposed by such interfaces. |
paul@384 | 182 | |
paul@384 | 183 | In some cases, resources provide the mechanism by which each user of a |
paul@384 | 184 | filesystem object may access that object independently. They would then |
paul@384 | 185 | effectively provide a session in which accesses can occur. |
paul@384 | 186 | |
paul@384 | 187 | === Directory Resources === |
paul@384 | 188 | |
paul@384 | 189 | Directory resources primarily expose the contents of directories in the |
paul@384 | 190 | filesystem to a user. They employ directory accessors which concern themselves |
paul@384 | 191 | with the actual filesystem content. |
paul@384 | 192 | |
paul@384 | 193 | [[Components#Directories|Directory components]] are provided using directory |
paul@384 | 194 | resources. |
paul@384 | 195 | |
paul@384 | 196 | <<Anchor(Pager)>> |
paul@384 | 197 | === Pagers or File Resources === |
paul@384 | 198 | |
paul@384 | 199 | Pagers are resources that support dataspace access operations, thus allowing |
paul@384 | 200 | the resources to expose filesystem content in mapped memory regions to a |
paul@384 | 201 | particular user of the object providing the content. |
paul@384 | 202 | |
paul@384 | 203 | [[Components#Files|File components]] and [[Components#Pipes|pipe components]] |
paul@384 | 204 | are provided using pagers. |
paul@384 | 205 | |
paul@384 | 206 | === Filesystem Resources === |
paul@384 | 207 | |
paul@384 | 208 | Filesystem resources provide the entry point for access to a filesystem by |
paul@384 | 209 | other components or programs. Since filesystems often enforce identity-based |
paul@384 | 210 | access controls, a filesystem resource will typically support the |
paul@384 | 211 | `open_for_user` operation in various forms, with the result of this operation |
paul@384 | 212 | being the instantiation of an `OpenerResource` configured for the indicated |
paul@384 | 213 | user identity. |
paul@384 | 214 | |
paul@457 | 215 | === Server Framework Integration === |
paul@457 | 216 | |
paul@457 | 217 | Resources must also support specific operations for integration with the |
paul@457 | 218 | lower-level IPC handling implemented in `libipc`. The following operations |
paul@457 | 219 | will be invoked by the server framework in `libfsserver` to configure a server |
paul@457 | 220 | controlled by `libipc`: |
paul@457 | 221 | |
paul@457 | 222 | {{{ |
paul@460 | 223 | ipc_server_default_config_type config(); |
paul@457 | 224 | void *interface(); |
paul@457 | 225 | }}} |
paul@457 | 226 | |
paul@460 | 227 | The `config` operation returns a default configuration object containing |
paul@460 | 228 | details pertinent to the initialisation of a server for the resource, these |
paul@460 | 229 | involving aspects of incoming messages and the handler function to deal with |
paul@460 | 230 | such messages. Typically, such an object, along with the handler function, |
paul@457 | 231 | will be generated by a tool such as `idl`. |
paul@457 | 232 | |
paul@460 | 233 | The `interface` operation returns a pointer to the resource instance coerced |
paul@460 | 234 | to an appropriate type. Such a type will be that of the most specialised IPC |
paul@460 | 235 | interface type inherited by the resource implementation, as expected by the |
paul@460 | 236 | handler function for the resource. |
paul@457 | 237 | |
paul@457 | 238 | For example, consider the following: |
paul@457 | 239 | |
paul@457 | 240 | {{{ |
paul@457 | 241 | class FilePager : public Pager, public MappedFileObject |
paul@457 | 242 | }}} |
paul@457 | 243 | |
paul@457 | 244 | Here, the `interface` operation will return a pointer coerced to |
paul@460 | 245 | `MappedFileObject`, and the `config` operation will return a suitable |
paul@460 | 246 | configuration object. The `idl` tool will produce an object called |
paul@460 | 247 | `config_MappedFileObject` for this purpose along with a handler function |
paul@460 | 248 | called `handle_MappedFileObject` available via the configuration object. |
paul@457 | 249 | |
paul@384 | 250 | == Registries == |
paul@384 | 251 | |
paul@384 | 252 | The basic mechanism for obtaining a resource involves a registry, as |
paul@384 | 253 | illustrated by the following diagram. |
paul@384 | 254 | |
paul@384 | 255 | ######## A graph showing the use of a registry in obtaining resources |
paul@384 | 256 | |
paul@384 | 257 | {{{#!graphviz |
paul@384 | 258 | #format svg |
paul@384 | 259 | #transform notugly |
paul@384 | 260 | digraph registry { |
paul@384 | 261 | node [fontsize="12.0",fontname="sans-serif",shape=box]; |
paul@384 | 262 | edge [fontsize="12.0",fontname="sans-serif"]; |
paul@384 | 263 | rankdir=LR; |
paul@384 | 264 | |
paul@384 | 265 | subgraph { |
paul@384 | 266 | node [label="OpenerContextResource"]; |
paul@384 | 267 | rank=same; |
paul@384 | 268 | |
paul@384 | 269 | OpenerContextResource1 -> OpenerContextResource2 [dir=none,style=dotted]; |
paul@384 | 270 | } |
paul@384 | 271 | |
paul@384 | 272 | subgraph { |
paul@384 | 273 | node [label="OpenerResource"]; |
paul@384 | 274 | rank=same; |
paul@384 | 275 | |
paul@384 | 276 | OpenerResource1 -> OpenerResource2 [dir=none,style=dotted]; |
paul@384 | 277 | } |
paul@384 | 278 | |
paul@384 | 279 | subgraph { |
paul@384 | 280 | rank=same; |
paul@384 | 281 | |
paul@384 | 282 | ResourceRegistry -> Resource; |
paul@384 | 283 | } |
paul@384 | 284 | |
paul@384 | 285 | subgraph { |
paul@384 | 286 | rank=same; |
paul@384 | 287 | |
paul@384 | 288 | ProviderRegistry -> Provider; |
paul@384 | 289 | } |
paul@384 | 290 | |
paul@384 | 291 | OpenerContextResource1 -> OpenerResource1 [label="open"]; |
paul@384 | 292 | OpenerResource1 -> ResourceRegistry [label="get_resource"]; |
paul@384 | 293 | ResourceRegistry -> ProviderRegistry [label="get/set"]; |
paul@384 | 294 | |
paul@384 | 295 | Provider -> Resource -> OpenerResource2 -> OpenerContextResource2; |
paul@384 | 296 | } |
paul@384 | 297 | }}} |
paul@384 | 298 | |
paul@384 | 299 | ######## |
paul@384 | 300 | |
paul@466 | 301 | === Concurrency and Locking === |
paul@466 | 302 | |
paul@384 | 303 | The `ResourceRegistry` coordinates access to filesystem resources and, through |
paul@384 | 304 | synchronisation, prevents conflicting operations from occurring concurrently. |
paul@384 | 305 | For example, a removal operation on a file may not be allowed to occur while |
paul@384 | 306 | an opening operation is in progress. |
paul@384 | 307 | |
paul@384 | 308 | To achieve this, a common lock is employed to serialise access, with any |
paul@384 | 309 | underlying filesystem operations being initiated from the registry while the |
paul@384 | 310 | lock is held. An object providing the `FileOpening` interface for a filesystem |
paul@384 | 311 | provides such operations, and the following interaction pattern is thereby |
paul@384 | 312 | employed: |
paul@384 | 313 | |
paul@384 | 314 | ######## A graph showing the registry coordinating filesystem operations |
paul@384 | 315 | |
paul@384 | 316 | {{{#!graphviz |
paul@384 | 317 | #format svg |
paul@384 | 318 | #transform notugly |
paul@384 | 319 | digraph registry_operations { |
paul@384 | 320 | node [fontsize="12.0",fontname="sans-serif",shape=box]; |
paul@384 | 321 | edge [fontsize="12.0",fontname="sans-serif"]; |
paul@384 | 322 | rankdir=LR; |
paul@384 | 323 | |
paul@384 | 324 | OpenerContextResource -> OpenerResource -> ResourceRegistry -> FileOpening; |
paul@384 | 325 | } |
paul@384 | 326 | }}} |
paul@384 | 327 | |
paul@384 | 328 | ######## |
paul@384 | 329 | |
paul@466 | 330 | === Openers and Filesystem Access === |
paul@466 | 331 | |
paul@384 | 332 | Since the `ResourceRegistry` functionality is generic, it could be specialised |
paul@384 | 333 | for each filesystem or be configured with an appropriate reference to a |
paul@384 | 334 | `FileOpening` object. The `OpenerResource` would then be generic, invoking the |
paul@384 | 335 | registry which would, in turn, invoke the opening object. |
paul@384 | 336 | |
paul@384 | 337 | However, the chosen approach is to permit `OpenerResource` objects to |
paul@384 | 338 | implement the `FileOpening` interface for each filesystem, meaning that the |
paul@384 | 339 | `ResourceRegistry` will end up being called by the opener and then invoking |
paul@384 | 340 | the opener in return. This is slightly more convenient than the alternative |
paul@384 | 341 | since the opener can be configured with a given user identity, and such |
paul@384 | 342 | identity details will ultimately be employed when accessing the underlying |
paul@384 | 343 | filesystem itself. |