pax_global_header00006660000000000000000000000064132262143050014510gustar00rootroot0000000000000052 comment=293abe42862129d938b6768f342b8512d5068638 .dir-locals.el000066400000000000000000000014171322621430500135100ustar00rootroot00000000000000; Sets emacs variables based on mode. ; A list of (major-mode . ((var1 . value1) (var2 . value2))) ; Mode can be nil, which gives default values. ; NOTE: If you update this file make sure to update .vimrc and .editorconfig, ; too. ((nil . ((indent-tabs-mode . nil) (tab-width . 8) (fill-column . 79))) (c-mode . ((fill-column . 119) (c-basic-offset . 8) (eval . (c-set-offset 'substatement-open 0)) (eval . (c-set-offset 'statement-case-open 0)) (eval . (c-set-offset 'case-label 0)) (eval . (c-set-offset 'arglist-intro '++)) (eval . (c-set-offset 'arglist-close 0)))) (nxml-mode . ((nxml-child-indent . 2) (fill-column . 119))) (meson-mode . ((meson-indent-basic . 8)))) .editorconfig000066400000000000000000000007331322621430500135340ustar00rootroot00000000000000# EditorConfig configuration for systemd # http://EditorConfig.org # NOTE: If you update this file make sure to update .dir-locals.el and .vimrc, # too. # Top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file, utf-8 charset [*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 # Match config files, set indent to spaces with width of eight [*.{c,h}] indent_style = space indent_size = 8 .gitignore000066400000000000000000000002311322621430500130400ustar00rootroot00000000000000*.cache *.cache-pre-dev *.cache-pre-inst *.plist *.pyc *.stamp *.swp *~ /*.tar.bz2 /*.tar.gz /*.tar.xz /GPATH /GRTAGS /GSYMS /GTAGS /TAGS /tags /build*/ .vimrc000066400000000000000000000010711322621430500121740ustar00rootroot00000000000000" 'set exrc' in ~/.vimrc will read .vimrc from the current directory " Warning: Enabling exrc is dangerous! You can do nearly everything from a " vimrc configuration file, including write operations and shell execution. " You should consider setting 'set secure' as well, which is highly " recommended! " NOTE: If you update this file make sure to update .dir-locals.el and " .editorconfig, too. set tabstop=8 set shiftwidth=8 set expandtab set makeprg=GCC_COLORS=\ make set tw=79 au BufRead,BufNewFile *.xml set tw=119 shiftwidth=2 smarttab au FileType c set tw=119 LICENSE.LGPL2.1000066400000000000000000000636421322621430500130520ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! NEWS000066400000000000000000000067331322621430500115640ustar00rootroot00000000000000~~~ casync 2 ~~~ - casync now supports retrieving index and chunk data from sftp:// URLs. (In addition to the existing ftp://, http:// and https:// support). - casync will now honour $TMP if it is set, for placing temporary files and directories. - casync now saves/restores basic btrfs subvolume information. (Specifically it will store whether a directory is a subvolume, and whether it has the read-only bit set.) Control this metadata option with the new --with=subvolume/--without=subvolume and --with=subvolume-ro/--without=subvolume-ro switches. - casync now saves/restores SELinux label information. Control this metadata option with the new --with=selinux/--without=selinux switches. - The libgcrypt dependency has been replaced with an OpenSSL dependency, as that appears to be better supported today, and may be used to generate SHA512/256 hashes (see below). - casync now permits selecting the hash function to use with the new --digest= option. SHA512/256 is now supported in addition to the old SHA256 algorithm, which continues to be supported. The new default however is SHA512/256, as it is substantially faster at otherwise equal properties on today's 64bit processors. In specific environments SHA256 might perform better, hence both algorithms remain supported. Index files contain information about the hash algorithm used, hence automatic compatibility is retained. - casync now permits selecting the compression format to use with the new option --compression=. In addition to the originally reported xz compression, gzip and zstd compression are now supported, the latter being the new default as it provides excellent compression at very high speeds. It's OK to mix chunks compressed with different algorithms in the same store, but of course clients downloading them need to be new enough to read chunks in non-xz formats. Note that the file suffix for compressed chunks changed ".xz" → ".cacnk", as they now may contain either compression, and continuing to use the ".xz" suffix would be misleading. To retain compatibility with older casync, the environment variable $CASYNC_COMPRESSED_CHUNK_SUFFIX may be set to ".xz", to force usage of the old suffix. - When extracting archives or archive indexes a subset of the metadata stored in the archive may now be selected to be replayed, using the usual --with= and --without= options. For example, if an archive containing full metadata is extracted with --without=privileged only the unprivileged metadata fields are extracted (i.e. no file ownership, ACLs, SELinux labels, ...). - After completing an operation statistics about downloaded chunks are now shown. - When invoking "casync mkdev" the third parameter may now be an arbitrarily selected path below /dev which is then created as a symlink to the block device used, and registered with udev. This means the usual device enumeration will find the block device under the name picked. Example: # casync mkdev /somepath/tomy/index-file.caibx /dev/quux This will expose the block image /somepath/tomy/index-file.caibx as /dev/quux. Contributions from: David Guibert, enkore, Felipe Sateler, Igor Gnatenko, Jesus Rodriguez, John Paul Adrian Glaubitz, Lennart Poettering, Martin Pitt, Silvio Fricke, Zbigniew Jędrzejewski-Szmek ~~~ casync 1 ~~~ - Initial release Contributions from: Daniel Mack, Djalal Harouni, Lennart Poettering, Martin Pitt, Nikita Puzyryov, Thomas Hindoe Paaboel Andersen, Zbigniew Jędrzejewski-Szmek README.md000066400000000000000000000165441322621430500123450ustar00rootroot00000000000000# casync — Content Addressable Data Synchronizer What is this? 1. A combination of the rsync algorithm and content-addressable storage 2. An efficient way to store and retrieve multiple related versions of large file systems or directory trees 3. An efficient way to deliver and update OS, VM, IoT and container images over the Internet in an HTTP and CDN friendly way 4. An efficient backup system See the [Announcement Blog Story](http://0pointer.net/blog/casync-a-tool-for-distributing-file-system-images.html) for a comprehensive introduction. The medium length explanation goes something like this: Encoding: Let's take a large linear data stream, split it into variable-sized chunks (the size of each being a function of the chunk's contents), and store these chunks in individual, compressed files in some directory, each file named after a strong hash value of its contents, so that the hash value may be used to as key for retrieving the full chunk data. Let's call this directory a "chunk store". At the same time, generate a "chunk index" file that lists these chunk hash values plus their respective chunk sizes in a simple linear array. The chunking algorithm is supposed to create variable, but similarly sized chunks from the data stream, and do so in a way that the same data results in the same chunks even if placed at varying offsets. For more information [see this blog story](https://moinakg.wordpress.com/2013/06/22/high-performance-content-defined-chunking/). Decoding: Let's take the chunk index file, and reassemble the large linear data stream by concatenating the uncompressed chunks retrieved from the chunk store, keyed by the listed chunk hash values. As an extra twist, we introduce a well-defined, reproducible, random-access serialization format for directory trees (think: a more modern `tar`), to permit efficient, stable storage of complete directory trees in the system, simply by serializing them and then passing them into the encoding step explained above. Finally, let's put all this on the network: for each image you want to deliver, generate a chunk index file and place it on an HTTP server. Do the same with the chunk store, and share it between the various index files you intend to deliver. Why bother with all of this? Streams with similar contents will result in mostly the same chunk files in the chunk store. This means it is very efficient to store many related versions of a data stream in the same chunk store, thus minimizing disk usage. Moreover, when transferring linear data streams chunks already known on the receiving side can be made use of, thus minimizing network traffic. Why is this different from `rsync` or OSTree, or similar tools? Well, one major difference between `casync` and those tools is that we remove file boundaries before chunking things up. This means that small files are lumped together with their siblings and large files are chopped into pieces, which permits us to recognize similarities in files and directories beyond file boundaries, and makes sure our chunk sizes are pretty evenly distributed, without the file boundaries affecting them. The "chunking" algorithm is based on the buzhash rolling hash function. SHA512/256 is used as a strong hash function to generate digests of the chunks (alternatively: SHA256). zstd is used to compress the individual chunks (alternatively xz or gzip). Is this new? Conceptually, not too much. This uses well-known concepts, implemented in a variety of other projects, and puts them together in a moderately new, nice way. That's all. The primary influences are rsync and git, but there are other systems that use similar algorithms, in particular: - BorgBackup (http://www.borgbackup.org/) - bup (https://bup.github.io/) - CAFS (https://github.com/indyjo/cafs) - dedupfs (https://github.com/xolox/dedupfs) - LBFS (https://pdos.csail.mit.edu/archive/lbfs/) - restic (https://restic.github.io/) - Tahoe-LAFS (https://tahoe-lafs.org/trac/tahoe-lafs) - tarsnap (https://www.tarsnap.com/) - Venti (https://en.wikipedia.org/wiki/Venti) - zsync (http://zsync.moria.org.uk/) (ordered alphabetically, not in order of relevance) ## File Suffixes 1. .catar → archive containing a directory tree (like "tar") 2. .caidx → index file referring to a directory tree (i.e. a .catar file) 3. .caibx → index file referring to a blob (i.e. any other file) 4. .castr → chunk store directory (where we store chunks under their hashes) 5. .cacnk → a compressed chunk in a chunk store (i.e. one of the files stored below a .castr directory) ## Operations on directory trees ``` # casync list /home/lennart # casync digest /home/lennart # casync mtree /home/lennart (BSD mtree(5) compatible manifest) ``` ## Operations on archives ``` # casync make /home/lennart.catar /home/lennart # casync extract /home/lennart.catar /home/lennart # casync list /home/lennart.catar # casync digest /home/lennart.catar # casync mtree /home/lennart.catar # casync mount /home/lennart.catar /home/lennart # casync verify /home/lennart.catar /home/lennart (NOT IMPLEMENTED YET) # casync diff /home/lennart.catar /home/lennart (NOT IMPLEMENTED YET) ``` ## Operations on archive index files ``` # casync make --store=/var/lib/backup.castr /home/lennart.caidx /home/lennart # casync extract --store=/var/lib/backup.castr /home/lennart.caidx /home/lennart # casync list --store=/var/lib/backup.castr /home/lennart.caidx # casync digest --store=/var/lib/backup.castr /home/lennart.caidx # casync mtree --store=/var/lib/backup.castr /home/lennart.caidx # casync mount --store=/var/lib/backup.castr /home/lennart.caidx /home/lennart # casync verify --store=/var/lib/backup.castr /home/lennart.caidx /home/lennart (NOT IMPLEMENTED YET) # casync diff --store=/var/lib/backup.castr /home/lennart.caidx /home/lennart (NOT IMPLEMENTED YET) ``` ## Operations on blob index files ``` # casync digest --store=/var/lib/backup.castr fedora25.caibx # casync mkdev --store=/var/lib/backup.castr fedora25.caibx # casync verify --store=/var/lib/backup.castr fedora25.caibx /home/lennart/Fedora25.raw (NOT IMPLEMENTED YET) ``` ## Operations involving ssh remoting ``` # casync make foobar:/srv/backup/lennart.caidx /home/lennart # casync extract foobar:/srv/backup/lennart.caidx /home/lennart2 # casync list foobar:/srv/backup/lennart.caidx # casync digest foobar:/srv/backup/lennart.caidx # casync mtree foobar:/srv/backup/lennart.caidx # casync mount foobar:/srv/backup/lennart.caidx /home/lennart ``` ## Operations involving the web ``` # casync extract http://www.foobar.com/lennart.caidx /home/lennart # casync list http://www.foobar.com/lennart.caidx # casync digest http://www.foobar.com/lennart.caidx # casync mtree http://www.foobar.com/lennart.caidx # casync extract --seed=/home/lennart http://www.foobar.com/lennart.caidx /home/lennart2 # casync mount --seed=/home/lennart http://www.foobar.com/lennart.caidx /home/lennart2 ``` ## Maintenance ``` # casync gc /home/lennart-20170101.caidx /home/lennart-20170102.caidx /home/lennart-20170103.caidx # casync gc --backup /var/lib/backup/backup.castr /home/lennart-*.caidx # casync make /home/lennart.catab /home/lennart (NOT IMPLEMENTED) ``` ## Building casync casync uses the [Meson](http://mesonbuild.com/) build system. To build casync, install Meson (at least 0.40), as well as the necessary build dependencies (gcc, liblzma, libcurl, libacl, and optionally libfuse). Then run: ``` # meson build && ninja -C build && sudo ninja -C build install ``` TODO000066400000000000000000000045031322621430500115460ustar00rootroot00000000000000SHORT-TERM: * use btrfs reflink with contents checking * rework how default --with= and --without= are calculated, so that --except stuff doesn't leak into blob index TO MAKE IT USEFUL FOR BACKUPS: * encryption: aes256 of rotating hash function + HMAC for identifying chunks + individually encrypted chunks * speed up repeated image generation: maintain persistent cache for directory trees that permits lookups by a path location as key, returning a chunk id and "newest covering mtime" LATER: * verify * diff * save/restore hardlinks? * check fs features when restoring * exclude patterns * build seed while extracting * optionally make seeds persistent * acquire gpg signature along with caidx/caibx/catar * coalesce index frames sent over protocol * rework uploading via ssh to use seed instead of cache store for providing chunks to server * casync-http: parallel http GETs * casync-http: try all configured stores one after the other before sending MISSING * add support for compressed index files and archive files * define mime types for our files * define http-based url protocol prefix for caibx+caidx * support accessing base trees through native protocol * implicitly generate index + chunks when accessing base trees or archives through native protocol * rework caindex to read multiple chunks per read (use reallocbuffer like elsewhere) * permit 511 (or 4095?) redundant NUL bytes at the end of archive and index files, so that they could in theory stored on block devices * seed: cache GOODBYE name table data so that we can regenerate the right bits when needed * optionally import from/export to classic tar ball (and zip?) * optionally interpret aufs/union mount whiteout files? * fuse: expose acls and fcaps, selinux * fuse: possibly translate user names on access * fuse: provide "mount.casync" compat symlink so that people can list casync mounts in /etc/fstab * encoder: change seeking to be more like decoder's seeking (i.e. delay returned events until the next ca_encoder_step() call) * rename offset accessor functions (drop the "archive") * send progress information via sd_notify(), so that people can wrap casync nicely in UIs * maybe turn "recursive" mode into a numeric value specifying how far to descend? * make "casync stat" work on a directory with a subpath * save/restore xfs/ext4 projid * tweak chunker: shift cut to last "marker". coccinelle/000077500000000000000000000000001322621430500131545ustar00rootroot00000000000000coccinelle/fprintf.cocci000066400000000000000000000004061322621430500156260ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ @@ expression e; local idexpression r; expression list s; @@ - if (e) { - fprintf(stderr, s); - return r; - } + if (e) + return log_error_errno(r, s); @@ expression list s; @@ - fprintf(stderr, s); + log_error(s); doc/000077500000000000000000000000001322621430500116215ustar00rootroot00000000000000doc/casync.rst000066400000000000000000000240151322621430500136350ustar00rootroot00000000000000.. SPDX-License-Identifier: LGPL-2.1+ :orphan: casync manual page ================== Synopsis -------- | **casync** [*OPTIONS*...] make [*ARCHIVE* | *ARCHIVE_INDEX* | *BLOB_INDEX*] [*PATH*] | **casync** [*OPTIONS*...] extract [*ARCHIVE* | *ARCHIVE_INDEX* | *BLOB_INDEX*] [*PATH*] | **casync** [*OPTIONS*...] list [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] | **casync** [*OPTIONS*...] mtree [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] | **casync** [*OPTIONS*...] stat [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] [*PATH*] | **casync** [*OPTIONS*...] digest [*ARCHIVE* | *BLOB* | *ARCHIVE_INDEX* | *BLOB_INDEX* | *DIRECTORY*] | **casync** [*OPTIONS*...] mount [*ARCHIVE* | *ARCHIVE_INDEX*] *PATH* | **casync** [*OPTIONS*...] mkdev [*BLOB* | *BLOB_INDEX*] [*NODE*] | **casync** [*OPTIONS*...] gc *BLOB_INDEX* | *ARCHIVE_INDEX* ... Description ----------- Content-Addressable Data Synchronization Tool Commands -------- | **casync** **make** [*ARCHIVE* | *ARCHIVE_INDEX*] [*DIRECTORY*] | **casync** **make** [*BLOB_INDEX*] *FILE* | *DEVICE* This will create either a .catar archive or an .caidx index for for the given *DIRECTORY*, or a .caibx index for the given *FILE* or block *DEVICE*. The type of output is automatically chosen based on the file extension (this may be overridden with ``--what=``). *DIRECTORY* is optional, and the current directory will be used if not specified. When a .caidx or .caibx file is created, a .castr storage directory will be created too, by default located in the same directory, and named ``default.castr`` unless configured otherwise (see ``--store=`` option). The metadata included in the archive is controlled by the ``--with-*`` and ``--without-*`` options. | | **casync** **extract** [*ARCHIVE* | *ARCHIVE_INDEX*] [*DIRECTORY*] | **casync** **extract** *BLOB_INDEX* *FILE* | *DEVICE* This will extract the contents of a .catar archive or .caidx index into the specified *DIRECTORY*, or the contents specified by *BLOB_INDEX* to the specified *FILE* or block *DEVICE*. *DIRECTORY* may be omitted, and the current directory will be used by default. The metadata replayed from the archive is controlled by the ``--with-*`` and ``--without-*`` options. | | **casync** **list** [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] This will list all the files and directories in the specified .catar archive or .caidx index, or the directory. The argument is optional, and the current directory will be used by default. The output includes the permission mask and file names:: $ casync list /usr/share/doc/casync drwxr-xr-x -rw-r--r-- README.md -rw-r--r-- TODO | | **casync** **mtree** [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] This is similar to **list**, but includes information about each entry in the key=value format defined by BSD mtree(5):: $ casync mtree /usr/share/doc/casync . type=dir mode=0755 uid=0 gid=0 time=1500343585.721189650 README.md type=file mode=0644 size=7286 uid=0 gid=0 time=1498175562.000000000 sha256digest=af75eacac1f00abf6adaa7510a2c7fe00a4636daf9ea910d69d96f0a4ae85df4 TODO type=file mode=0644 size=2395 uid=0 gid=0 time=1498175562.000000000 sha256digest=316f11a03c08ec39f0328ab1f7446bd048507d3fbeafffe7c32fad4942244b7d | | **casync** **stat** [*ARCHIVE* | *ARCHIVE_INDEX* | *DIRECTORY*] [*PATH*] This will show detailed information about a file or directory *PATH*, as found in either *ARCHIVE* or *ARCHIVE_INDEX* or underneath *DIRECTORY*. Both arguments are optional. The first defaults to the current directory, and the second the top-level path (``.``). Example output:: $ casync stat . File: . Mode: drwxrwxr-x FileAttr: ---------- FATAttr: --- Offset: 0 Time: 2017-07-17 22:53:30.723304050 User: zbyszek (1000) Group: zbyszek (1000) | | **casync** **digest** [*ARCHIVE* | *BLOB* | *ARCHIVE_INDEX* | *BLOB_INDEX* | *DIRECTORY*] This will compute and print the checksum of the argument. The argument is optional and defaults to the current directory:: $ casync digest d1698b0c4c27163284abea5d1e369b92e89dd07cb74378638849800e0406baf7 $ casync digest . d1698b0c4c27163284abea5d1e369b92e89dd07cb74378638849800e0406baf7 | | **casync** **mount** [*ARCHIVE* | *ARCHIVE_INDEX*] *PATH* This will mount the specified .catar archive or .caidx index at the specified *PATH*, using the FUSE protocol. | | **casync** **mkdev** [*BLOB* | *BLOB_INDEX*] [*NODE*] This will create a block device *NODE* with the contents specified by the .caibx *BLOB_INDEX* or just the file or block device *BLOB*, using the NBD protocol. Example:: $ sudo casync -v mkdev README.md Attached: /dev/nbd0 (in another terminal) $ sudo head -n1 /dev/nbd0 # casync — Content Addressable Data Synchronizer When ``casync mkdev`` is killed, the device is destroyed. | | **casync** **gc** *ARCHIVE_INDEX* | *BLOB_INDEX* ... This will remove all chunks that are not used by one of the specified indices (one or more blob and archive indices can be given). If ``--store`` is not given, the default store for the first index will be used. This command can be used to prune unused chunks from a shared chunk store. Options ------- General options: --help, -h Show terse help output --verbose, -v Show terse status information during runtime --dry-run, -n Only print what would be removed with **gc** --store=PATH The primary chunk store to use --extra-store= Additional chunk store to look for chunks in --chunk-size=<[MIN:]AVG[:MAX]> The minimal/average/maximum number of bytes in a chunk --digest= Pick digest algorithm (sha512-256 or sha256) --compression= Pick compression algorithm (zstd, xz or gzip) --seed= Additional file or directory to use as seed --rate-limit-bps= Maximum bandwidth in bytes/s for remote communication --exclude-nodump=no Don't exclude files with chattr(1)'s +d **nodump** flag when creating archive --exclude-submounts=yes Exclude submounts when creating archive --reflink=no Don't create reflinks from seeds when extracting --hardlink=yes Create hardlinks from seeds when extracting --punch-holes=no Don't create sparse files when extracting --delete=no Don't delete existing files not listed in archive after extraction --undo-immutable=yes When removing existing files, undo chattr(1)'s +i 'immutable' flag when extracting --seed-output=no Don't implicitly add pre-existing output as seed when extracting --recursive=no List non-recursively --uid-shift= Shift UIDs/GIDs --uid-range= Restrict UIDs/GIDs to range Input/output selector: --what=archive Operate on archive file --what=archive-index Operate on archive index file --what=blob Operate on blob file --what=blob-index Operate on blob index file --what=directory Operate on directory --what=help Print a list of allowed values (and terminate the program) Turn on archive feature sets: --with=best Store most accurate information --with=unix Store UNIX baseline information --with=fat Store FAT information --with=chattr Store chattr(1) file attributes --with=fat-attrs Store FAT file attributes --with=privileged Store file data that requires privileges to restore --with=fuse Store file data that can exposed again via 'casync mount' To turn archive features *off*, ``--without=…`` may be used, such as ``--without=fat-attrs``, ``--without=privileged``, etc. To disable all optional features, ``--without=all`` may be used. (The positive form ``--with=all`` does not make sense, because some features are conflicting. To enable the maximum set of information, use ``--with=best``.) Individual archive features: --with=<16bit-uids> Store reduced 16bit UID/GID information --with=<32bit-uids> Store full 32bit UID/GID information --with= Store user/group names --with= Store timestamps in 1s granularity --with= Store timestamps in 1µs granularity --with= Store timestamps in 1ns granularity --with=<2sec-time> Store timestamps in 2s granularity --with= Store per-file read only flag --with= Store full per-file UNIX permissions --with= Store symbolic links --with= Store block and character device nodes --with= Store named pipe nodes --with= Store AF_UNIX file system socket nodes --with= Store FAT "hidden" file flag --with= Store FAT "system" file flag --with= Store FAT "archive" file flag --with= Store "append-only" file flag --with= Store "disable access time" file flag --with= Store "enable compression" file flag --with= Store "disable copy-on-write" file flag --with= Store "disable dumping" file flag --with= Store "synchronous" directory flag --with= Store "immutable" file flag --with= Store "synchronous" file flag --with= Store "disable compression" file flag --with= Store "project quota inheritance" flag --with= Store btrfs subvolume information --with= Store btrfs subvolume read-only property --with= Store extended file attributes --with= Store file access control lists --with= Store SElinux file labels --with= Store file capabilities (and similar: ``--without=16bit-uids``, ``--without=32bit-uids``, ...) Archive features ---------------- The various ``--with=`` and ``--without=`` parameters control the precise set of metadata to store in the archive, or restore when extracting. These flags only apply if ``casync`` operates on the file system level. doc/conf.py000066400000000000000000000067721322621430500131340ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # SPDX-License-Identifier: LGPL-2.1+ # # casync documentation build configuration file, created by # sphinx-quickstart on Tue Jun 20 16:46:39 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'casync' author = '' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1' # The full version, including alpha/beta/rc tags. release = '1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'casyncdoc' # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('casync', 'casync', 'casync Documentation', [], 1) ] doc/index.rst000066400000000000000000000005401322621430500134610ustar00rootroot00000000000000.. SPDX-License-Identifier: LGPL-2.1+ .. casync documentation master file, created by sphinx-quickstart on Tue Jun 20 16:46:39 2017. Welcome to casync's documentation! ================================== .. toctree:: :maxdepth: 2 :caption: Contents: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` doc/meson.build000066400000000000000000000012611322621430500137630ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ sphinx_sources = [ 'conf.py', 'casync.rst', 'index.rst'] man_pages = [ 'casync.1'] mandir1 = join_paths(get_option('mandir'), 'man1') if get_option('man') sphinx_build = find_program('sphinx-build-3', 'sphinx-build') custom_target( 'man', command : [sphinx_build, '-b', 'man', meson.current_source_dir(), meson.current_build_dir()], input : sphinx_sources, output : man_pages, install : true, install_dir : mandir1) endif meson.build000066400000000000000000000243631322621430500132260ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ project('casync', 'c', version : '2', license : 'LGPLv2+', default_options: [ 'c_std=gnu99', 'prefix=/usr', 'sysconfdir=/etc', 'localstatedir=/var', ], meson_version : '>= 0.40') cc = meson.get_compiler('c') c_args = ''' -Wextra -Werror=undef -Werror=format=2 -Wformat-security -Wformat-nonliteral -Wlogical-op -Wmissing-include-dirs -Werror=old-style-definition -Werror=pointer-arith -Winit-self -Wdeclaration-after-statement -Wfloat-equal -Wsuggest-attribute=noreturn -Werror=missing-prototypes -Werror=implicit-function-declaration -Werror=missing-declarations -Werror=return-type -Werror=incompatible-pointer-types -Werror=shadow -Wstrict-prototypes -Wredundant-decls -Wmissing-noreturn -Wendif-labels -Wstrict-aliasing=2 -Wwrite-strings -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-result -Werror=overflow -Werror=sign-compare -Wdate-time -Wnested-externs -ffast-math -fno-common -fdiagnostics-show-option -fno-strict-aliasing -fvisibility=hidden -fstack-protector -fstack-protector-strong -fPIE --param=ssp-buffer-size=4 '''.split() foreach arg : c_args if cc.has_argument(arg) add_project_arguments(arg, language : 'c') endif endforeach conf = configuration_data() conf.set_quoted('PACKAGE_VERSION', meson.project_version()) conf.set('_GNU_SOURCE', true) conf.set('__SANE_USERSPACE_TYPES__', true) conf.set('SIZEOF_PID_T', cc.sizeof('pid_t', prefix : '#include ')) conf.set('SIZEOF_UID_T', cc.sizeof('uid_t', prefix : '#include ')) conf.set('SIZEOF_GID_T', cc.sizeof('gid_t', prefix : '#include ')) foreach ident : [ ['renameat2', '''#include '''], ['copy_file_range', '''#include #include '''], ] have = cc.has_function(ident[0], prefix : ident[1]) conf.set10('HAVE_' + ident[0].to_upper(), have) endforeach if cc.has_function('getrandom', prefix : '''#include ''') conf.set10('USE_SYS_RANDOM_H', true) conf.set10('HAVE_GETRANDOM', true) else conf.set10('USE_SYS_RANDOM_H', false) have = cc.has_function('getrandom', prefix : '''#include ''') conf.set10('HAVE_GETRANDOM', have) endif ############################################################ prefixdir = get_option('prefix') bindir = join_paths(prefixdir, get_option('bindir')) datadir = join_paths(prefixdir, get_option('datadir')) docdir = join_paths(datadir, 'doc/casync') protocoldir = join_paths(prefixdir, 'lib/casync/protocols') conf.set_quoted('CASYNC_PROTOCOL_PATH', protocoldir) liblzma = dependency('liblzma', version : '>= 5.1.0') libcurl = dependency('libcurl', version : '>= 7.32.0') openssl = dependency('openssl', version : '>= 1.0') libz = dependency('zlib') libzstd = dependency('libzstd', version : '>= 0.8.1') libacl = cc.find_library('acl') if get_option('fuse') libfuse = dependency('fuse', version : '>= 2.6') else libfuse = [] endif conf.set10('HAVE_FUSE', get_option('fuse')) if get_option('selinux') libselinux = dependency('libselinux') else libselinux = [] endif conf.set10('HAVE_SELINUX', get_option('selinux')) if get_option('udev') libudev = dependency('libudev') else libudev = [] endif conf.set10('HAVE_UDEV', get_option('udev')) threads = dependency('threads') math = cc.find_library('m') config_h = configure_file( output : 'config.h', configuration : conf) add_project_arguments('-include', 'config.h', language : 'c') subdir('src') subdir('test') includes = include_directories('src') udevdir = dependency('udev', required : false).get_pkgconfig_variable('udevdir') udevrulesdir = join_paths(udevdir, 'rules.d') subdir('doc') ############################################################ casync = executable( 'casync', casync_sources, link_with : libshared, dependencies : [ libacl, libfuse, liblzma, libselinux, libudev, libz, libzstd, math, openssl], install : true) casync_http = executable( 'casync-http', casync_http_sources, link_with : libshared, dependencies : [ libcurl, liblzma, libz, libzstd, math, openssl], install : true, install_dir : protocoldir) meson.add_install_script('sh', '-c', 'ln -svf casync-http $DESTDIR@0@'.format( join_paths(protocoldir, 'casync-https'))) meson.add_install_script('sh', '-c', 'ln -svf casync-http $DESTDIR@0@'.format( join_paths(protocoldir, 'casync-ftp'))) meson.add_install_script('sh', '-c', 'ln -svf casync-http $DESTDIR@0@'.format( join_paths(protocoldir, 'casync-sftp'))) meson.add_postconf_script('sh', '-c', 'ln -svf casync-http $MESON_BUILD_ROOT/casync-https') meson.add_postconf_script('sh', '-c', 'ln -svf casync-http $MESON_BUILD_ROOT/casync-ftp') meson.add_postconf_script('sh', '-c', 'ln -svf casync-http $MESON_BUILD_ROOT/casync-sftp') ############################################################ executable('notify-wait', notify_wait_sources, include_directories : includes, install : false) ############################################################ test_files_sh = find_program('test-files/test-files.sh') run_target( 'test-files', command : [test_files_sh, 'create']) run_target( 'clean-test-files', command : [test_files_sh, 'clean']) substs = configuration_data() substs.set_quoted('top_builddir', meson.build_root()) substs.set_quoted('top_srcdir', meson.source_root()) substs.set('bindir_unquoted', bindir) test_script_sh = configure_file( output : 'test-script.sh', input : 'test/test-script.sh.in', configuration : substs) test_script = find_program(test_script_sh) test('test-script.sh', test_script, timeout : 30 * 60) test_script_sha256_sh = configure_file( output : 'test-script-sha256.sh', input : 'test/test-script-sha256.sh.in', configuration : substs) test_script_sha256 = find_program(test_script_sha256_sh) test('test-script-sha256.sh', test_script_sha256, timeout : 30 * 60) test_script_gzip_sh = configure_file( output : 'test-script-gzip.sh', input : 'test/test-script-gzip.sh.in', configuration : substs) test_script_gzip = find_program(test_script_gzip_sh) test('test-script-gzip.sh', test_script_gzip, timeout : 30 * 60) test_script_xz_sh = configure_file( output : 'test-script-xz.sh', input : 'test/test-script-xz.sh.in', configuration : substs) test_script_xz = find_program(test_script_xz_sh) test('test-script-xz.sh', test_script_xz, timeout : 30 * 60) test_nbd_sh = configure_file( output : 'test-nbd.sh', input : 'test/test-nbd.sh.in', configuration : substs) test_nbd = find_program(test_nbd_sh) test('test-nbd.sh', test_nbd, timeout : 30 * 60) test_fuse_sh = configure_file( output : 'test-fuse.sh', input : 'test/test-fuse.sh.in', configuration : substs) test_fuse = find_program(test_fuse_sh) test('test-fuse.sh', test_fuse, timeout : 30 * 60) udev_rule = configure_file( output : '75-casync.rules', input : 'src/75-casync.rules.in', configuration : substs) install_data(udev_rule, install_dir : udevrulesdir) ############################################################ test_sources = ''' test-cachunk test-cachunker test-cachunker-histogram test-cadigest test-caencoder test-camakebst test-caorigin test-casync test-cautil test-util '''.split() non_test_sources = ''' test-caformat test-caindex test-calc-digest '''.split() foreach test_name : test_sources + non_test_sources exe = executable( test_name, 'test/@0@.c'.format(test_name), link_with : libshared, include_directories : includes, dependencies : [ libacl, liblzma, libselinux, libz, libzstd, math, openssl, threads]) if test_sources.contains(test_name) test(test_name, exe, timeout : 3 * 60) endif endforeach ############################################################ meson_check_help = find_program('test/meson-check-help.sh') foreach exec : [casync, casync_http] name = exec.full_path().split('/')[-1] test('check-help-' + name, meson_check_help, args : [exec.full_path()]) endforeach ############################################################ git = find_program('git', required : false) if git.found() all_files = run_command( git, ['--git-dir=@0@/.git'.format(meson.source_root()), 'ls-files', ':/*.[ch]']) all_files = files(all_files.stdout().split()) run_target('tags', command : ['env', 'etags', '-o', '@0@/@1@'.format(meson.source_root(), 'TAGS')] + all_files) run_target('ctags', command : ['env', 'ctags', '-o', '@0@/@1@'.format(meson.source_root(), 'tags')] + all_files) endif meson_options.txt000066400000000000000000000010521322621430500145070ustar00rootroot00000000000000# -*- mode: meson -*- # SPDX-License-Identifier: LGPL-2.1+ option('fuse', type : 'boolean', value : true, description : 'build the FUSE integration (requires fuse-devel)') option('selinux', type : 'boolean', value : true, description : 'build the SELinux backend (requires libselinux-devel)') option('udev', type : 'boolean', value : true, description : 'build the libudev integration (requires libudev-devel)') option('man', type : 'boolean', value : true, description : 'build and install man pages (requires spinx-build') mkosi.build000077500000000000000000000002261322621430500132220ustar00rootroot00000000000000#!/bin/sh -ex # SPDX-License-Identifier: LGPL-2.1+ export LC_CTYPE=C.UTF-8 meson build ninja -C build all ninja -C build test ninja -C build install mkosi.default000066400000000000000000000006531322621430500135500ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ [Distribution] Distribution=fedora Release=27 [Output] Format=raw_gpt Bootable=yes [Partitions] RootSize=2G [Packages] BuildPackages= diffutils fuse-devel gcc libacl-devel libcurl-devel libzstd-devel openssl-devel meson /usr/bin/sphinx-build pkgconfig rsync xz-devel Packages= fuse src/000077500000000000000000000000001322621430500116435ustar00rootroot00000000000000src/75-casync.rules.in000066400000000000000000000004171322621430500150370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ ACTION=="remove", GOTO="casync_end" SUBSYSTEM=="block", KERNEL=="nbd*", IMPORT{program}="@bindir_unquoted@/casync udev %N" SUBSYSTEM=="block", KERNEL=="nbd*", ENV{CASYNC_NAME}=="?*", SYMLINK+="$env{CASYNC_NAME}" LABEL="casync_end" src/cachunk.c000066400000000000000000000613131322621430500134270ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include "cachunk.h" #include "cautil.h" #include "compressor.h" #include "def.h" #include "util.h" #define CHUNK_PATH_SIZE(prefix, suffix) \ (strlen_null(prefix) + 4 + 1 + CA_CHUNK_ID_FORMAT_MAX + strlen_null(suffix)) static char* ca_format_chunk_path( const char *prefix, const CaChunkID *chunkid, const char *suffix, char buffer[]) { size_t n; assert(chunkid); assert(buffer); if (prefix) { n = strlen(prefix); memcpy(buffer, prefix, n); } else n = 0; ca_chunk_id_format(chunkid, buffer + n + 4 + 1); memcpy(buffer + n, buffer + n + 4 + 1, 4); buffer[n + 4] = '/'; if (suffix) strcpy(buffer + n + 4 + 1 + CA_CHUNK_ID_FORMAT_MAX - 1, suffix); return buffer; } int ca_load_fd(int fd, ReallocBuffer *buffer) { uint64_t count = 0; if (fd < 0) return -EINVAL; if (!buffer) return -EINVAL; for (;;) { ssize_t l; void *p; /* Don't permit loading chunks larger than the chunk limit */ if (count >= CA_CHUNK_SIZE_LIMIT_MAX) return -EBADMSG; p = realloc_buffer_extend(buffer, BUFFER_SIZE); if (!p) return -ENOMEM; l = read(fd, p, BUFFER_SIZE); if (l < 0) return -errno; realloc_buffer_shorten(buffer, BUFFER_SIZE - l); count += l; if (l == 0) break; } /* Don't permit empty chunks */ if (count < CA_CHUNK_SIZE_LIMIT_MIN) return -EBADMSG; return 0; } int ca_load_and_decompress_fd(int fd, ReallocBuffer *buffer) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; int compression_type = _CA_COMPRESSION_TYPE_INVALID; uint8_t fd_buffer[BUFFER_SIZE]; bool got_decoder_eof = false; uint64_t ccount = 0, dcount = 0; ssize_t l; int r; if (fd < 0) return -EINVAL; if (!buffer) return -EINVAL; /* First, read enough of the file, so that we can figure out which algorithm is used */ for (;;) { assert(ccount < BUFFER_SIZE); l = read(fd, fd_buffer + ccount, sizeof(fd_buffer) - ccount); if (l < 0) return -errno; if (l == 0) return -EPIPE; ccount += l; compression_type = detect_compression(fd_buffer, ccount); if (compression_type >= 0) break; if (compression_type != -EAGAIN) /* EAGAIN means: need more data before I can decide */ return compression_type; } r = compressor_start_decode(&context, compression_type); if (r < 0) return r; l = ccount; for (;;) { r = compressor_input(&context, fd_buffer, l); if (r < 0) goto finish; for (;;) { size_t done; void *p; p = realloc_buffer_extend(buffer, BUFFER_SIZE); if (!p) { r = -ENOMEM; goto finish; } r = compressor_decode(&context, p, BUFFER_SIZE, &done); if (r < 0) goto finish; realloc_buffer_shorten(buffer, BUFFER_SIZE - done); dcount += done; if (dcount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EBADMSG; goto finish; } got_decoder_eof = r == COMPRESSOR_EOF; if (r != COMPRESSOR_MORE) break; }; l = read(fd, fd_buffer, sizeof(fd_buffer)); if (l < 0) { r = -errno; goto finish; } if (l == 0) { if (!got_decoder_eof) { /* Premature end of file */ r = -EPIPE; goto finish; } break; } if (got_decoder_eof) { /* Trailing noise */ r = -EBADMSG; goto finish; } ccount += l; if (ccount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EBADMSG; goto finish; } } if (ccount < CA_CHUNK_SIZE_LIMIT_MIN || dcount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EBADMSG; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_load_and_compress_fd(int fd, CaCompressionType compression_type, ReallocBuffer *buffer) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; uint64_t ccount = 0, dcount = 0; int r; if (fd < 0) return -EINVAL; if (!buffer) return -EINVAL; if (compression_type < 0) return -EINVAL; if (compression_type >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; r = compressor_start_encode(&context, compression_type); if (r < 0) return r; for (;;) { uint8_t fd_buffer[BUFFER_SIZE]; ssize_t l; bool eof, got_encoder_eof = false; l = read(fd, fd_buffer, sizeof(fd_buffer)); if (l < 0) { r = -errno; goto finish; } eof = (size_t) l < sizeof(fd_buffer); dcount += l; if (dcount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EBADMSG; goto finish; } r = compressor_input(&context, fd_buffer, l); if (r < 0) goto finish; for (;;) { uint8_t *p; size_t done; p = realloc_buffer_extend(buffer, BUFFER_SIZE); if (!p) { r = -ENOMEM; goto finish; } r = compressor_encode(&context, eof, p, BUFFER_SIZE, &done); if (r < 0) goto finish; realloc_buffer_shorten(buffer, BUFFER_SIZE - done); ccount += done; if (ccount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EBADMSG; goto finish; } got_encoder_eof = r == COMPRESSOR_EOF; if (r != COMPRESSOR_MORE) break; } if (got_encoder_eof) break; } if (ccount < CA_CHUNK_SIZE_LIMIT_MIN || dcount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EBADMSG; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_save_fd(int fd, const void *data, size_t size) { if (fd < 0) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!data) return -EINVAL; return loop_write(fd, data, size); } int ca_save_and_compress_fd(int fd, CaCompressionType compression_type, const void *data, size_t size) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; uint64_t ccount = 0; int r; if (fd < 0) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!data) return -EINVAL; if (compression_type < 0) return -EINVAL; if (compression_type >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; r = compressor_start_encode(&context, compression_type); if (r < 0) return r; r = compressor_input(&context, data, size); if (r < 0) return r; for (;;) { uint8_t buffer[BUFFER_SIZE]; size_t done; int k; r = compressor_encode(&context, true, buffer, sizeof(buffer), &done); if (r < 0) goto finish; k = loop_write(fd, buffer, done); if (k < 0) { r = k; goto finish; } ccount += done; if (ccount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EINVAL; goto finish; } if (r == COMPRESSOR_EOF) break; } if (ccount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EINVAL; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_save_and_decompress_fd(int fd, const void *data, size_t size) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; int compression_type; uint64_t dcount = 0; int r; if (fd < 0) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!data) return -EINVAL; compression_type = detect_compression(data, size); if (compression_type == -EAGAIN) /* If we the data isn't long enough to contain a signature, refuse */ return -EBADMSG; if (compression_type < 0) return compression_type; r = compressor_start_decode(&context, compression_type); if (r < 0) return r; r = compressor_input(&context, data, size); if (r < 0) goto finish; for (;;) { uint8_t buffer[BUFFER_SIZE]; size_t done; int k; r = compressor_decode(&context, buffer, sizeof(buffer), &done); if (r < 0) goto finish; k = loop_write(fd, buffer, done); if (k < 0) { r = k; goto finish; } dcount += done; if (dcount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EINVAL; goto finish; } if (r == COMPRESSOR_EOF) break; } if (dcount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EINVAL; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_compress(CaCompressionType compression_type, const void *data, size_t size, ReallocBuffer *buffer) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; uint64_t ccount = 0; int r; if (!buffer) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!data) return -EINVAL; r = compressor_start_encode(&context, compression_type); if (r < 0) return r; r = compressor_input(&context, data, size); if (r < 0) return r; for (;;) { size_t done; uint8_t *p; p = realloc_buffer_extend(buffer, BUFFER_SIZE); if (!p) { r = -ENOMEM; goto finish; } r = compressor_encode(&context, true, p, BUFFER_SIZE, &done); if (r < 0) goto finish; realloc_buffer_shorten(buffer, BUFFER_SIZE - done); ccount += done; if (ccount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EINVAL; goto finish; } if (r == COMPRESSOR_EOF) break; } if (ccount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EINVAL; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_decompress(const void *data, size_t size, ReallocBuffer *buffer) { CompressorContext context = COMPRESSOR_CONTEXT_INIT; uint64_t dcount = 0; int compression_type; int r; if (!buffer) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!data) return -EINVAL; compression_type = detect_compression(data, size); if (compression_type == -EAGAIN) return -EBADMSG; if (compression_type < 0) return compression_type; r = compressor_start_decode(&context, compression_type); if (r < 0) return r; r = compressor_input(&context, data, size); if (r < 0) return r; for (;;) { uint8_t *p; size_t done; p = realloc_buffer_extend(buffer, BUFFER_SIZE); if (!p) { r = -ENOMEM; goto finish; } r = compressor_decode(&context, p, BUFFER_SIZE, &done); if (r < 0) goto finish; realloc_buffer_shorten(buffer, BUFFER_SIZE - done); dcount += done; if (dcount >= CA_CHUNK_SIZE_LIMIT_MAX) { r = -EINVAL; goto finish; } if (r == COMPRESSOR_EOF) break; } if (dcount < CA_CHUNK_SIZE_LIMIT_MIN) { r = -EINVAL; goto finish; } r = 0; finish: compressor_finish(&context); return r; } int ca_chunk_file_open(int chunk_fd, const char *prefix, const CaChunkID *chunkid, const char *suffix, int flags) { char path[CHUNK_PATH_SIZE(prefix, suffix)]; bool made = false; char *slash = NULL; int r, fd; /* Opens a file below the directory identified by 'chunk_fd', built as <4ch id prefix>/. */ if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; ca_format_chunk_path(prefix, chunkid, suffix, path); if ((flags & O_CREAT) == O_CREAT) { assert_se(slash = strrchr(path, '/')); *slash = 0; if (mkdirat(chunk_fd, path, 0777) < 0) { if (errno != EEXIST) return -errno; } else made = true; *slash = '/'; } fd = openat(chunk_fd, path, flags, 0444); /* we mark the chunk files read-only, as they should be considered immutable after creation */ if (fd < 0) { r = -errno; if (made) { assert(slash); *slash = 0; (void) unlinkat(chunk_fd, path, AT_REMOVEDIR); } return r; } return fd; } static int ca_chunk_file_access(int chunk_fd, const char *prefix, const CaChunkID *chunkid, const char *suffix) { char path[CHUNK_PATH_SIZE(prefix, suffix)]; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; ca_format_chunk_path(prefix, chunkid, suffix, path); if (faccessat(chunk_fd, path, F_OK, AT_SYMLINK_NOFOLLOW) < 0) return errno == ENOENT ? 0 : -errno; return 1; } static int ca_chunk_file_unlink(int chunk_fd, const char *prefix, const CaChunkID *chunkid, const char *suffix) { char path[CHUNK_PATH_SIZE(prefix, suffix)], *slash; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; ca_format_chunk_path(prefix, chunkid, suffix, path); if (unlinkat(chunk_fd, path, 0) < 0) return -errno; slash = strrchr(path, '/'); assert(slash); *slash = 0; (void) unlinkat(chunk_fd, path, AT_REMOVEDIR); return 0; } static int ca_chunk_file_rename(int chunk_fd, const char *prefix, const CaChunkID *chunkid, const char *old_suffix, const char *new_suffix) { char old_path[CHUNK_PATH_SIZE(prefix, old_suffix)], new_path[CHUNK_PATH_SIZE(prefix, new_suffix)]; int r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; ca_format_chunk_path(prefix, chunkid, old_suffix, old_path); ca_format_chunk_path(prefix, chunkid, new_suffix, new_path); r = rename_noreplace(chunk_fd, old_path, chunk_fd, new_path); if (r < 0) return r; return 0; } int ca_chunk_file_load( int chunk_fd, const char *prefix, const CaChunkID *chunkid, CaChunkCompression desired_compression, CaCompressionType compression_type, ReallocBuffer *buffer, CaChunkCompression *ret_effective_compression) { int fd, r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; if (desired_compression < 0) return -EINVAL; if (desired_compression >= _CA_CHUNK_COMPRESSION_MAX) return -EINVAL; if (!buffer) return -EINVAL; fd = ca_chunk_file_open(chunk_fd, prefix, chunkid, NULL, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fd < 0) { if (fd == -ELOOP) /* If it's a symlink, then it's marked as "missing" */ return -EADDRNOTAVAIL; if (fd != -ENOENT) return fd; fd = ca_chunk_file_open(chunk_fd, prefix, chunkid, ca_compressed_chunk_suffix(), O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fd == -ELOOP) return -EADDRNOTAVAIL; if (fd < 0) return fd; if (desired_compression == CA_CHUNK_UNCOMPRESSED) r = ca_load_and_decompress_fd(fd, buffer); else r = ca_load_fd(fd, buffer); if (r >= 0 && ret_effective_compression) *ret_effective_compression = desired_compression == CA_CHUNK_AS_IS ? CA_CHUNK_COMPRESSED : desired_compression; } else { if (desired_compression == CA_CHUNK_COMPRESSED) r = ca_load_and_compress_fd(fd, compression_type, buffer); else r = ca_load_fd(fd, buffer); if (r >= 0 && ret_effective_compression) *ret_effective_compression = desired_compression == CA_CHUNK_AS_IS ? CA_CHUNK_UNCOMPRESSED : desired_compression; } safe_close(fd); return r; } int ca_chunk_file_save( int chunk_fd, const char *prefix, const CaChunkID *chunkid, CaChunkCompression effective_compression, CaChunkCompression desired_compression, CaCompressionType compression_type, const void *p, uint64_t l) { char *suffix; int fd, r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; if (desired_compression < 0) return -EINVAL; if (desired_compression >= _CA_CHUNK_COMPRESSION_MAX) return -EINVAL; if (effective_compression < 0) return -EINVAL; if (effective_compression >= _CA_CHUNK_COMPRESSION_MAX) return -EINVAL; if (effective_compression == CA_CHUNK_AS_IS) return -EINVAL; if (!p) return -EINVAL; if (l <= 0) return -EINVAL; r = ca_chunk_file_test(chunk_fd, prefix, chunkid); if (r < 0) return r; if (r > 0) return -EEXIST; if (asprintf(&suffix, ".%" PRIx64 ".tmp", random_u64()) < 0) return -ENOMEM; fd = ca_chunk_file_open(chunk_fd, prefix, chunkid, suffix, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC); if (fd < 0) { free(suffix); return fd; } if (desired_compression == CA_CHUNK_AS_IS) desired_compression = effective_compression; if (desired_compression == effective_compression) r = loop_write(fd, p, l); else if (desired_compression == CA_CHUNK_COMPRESSED) r = ca_save_and_compress_fd(fd, compression_type, p, l); else { assert(desired_compression == CA_CHUNK_UNCOMPRESSED); r = ca_save_and_decompress_fd(fd, p, l); } safe_close(fd); if (r < 0) goto fail; r = ca_chunk_file_rename(chunk_fd, prefix, chunkid, suffix, desired_compression == CA_CHUNK_COMPRESSED ? ca_compressed_chunk_suffix() : NULL); if (r < 0) goto fail; free(suffix); return 0; fail: (void) ca_chunk_file_unlink(chunk_fd, prefix, chunkid, suffix); free(suffix); return r; } int ca_chunk_file_mark_missing(int chunk_fd, const char *prefix, const CaChunkID *chunkid) { char path[CHUNK_PATH_SIZE(prefix, NULL)]; bool made = false; char *slash; int r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; r = ca_chunk_file_test(chunk_fd, prefix, chunkid); if (r < 0) return r; if (r > 0) return -EEXIST; ca_format_chunk_path(prefix, chunkid, NULL, path); assert_se(slash = strrchr(path, '/')); *slash = 0; if (mkdirat(chunk_fd, path, 0777) < 0) { if (errno != EEXIST) return -errno; } else made = true; *slash = '/'; if (symlinkat("/dev/null", chunk_fd, path) < 0) { r = -errno; if (made) { *slash = 0; (void) unlinkat(chunk_fd, path, AT_REMOVEDIR); } return r; } return 0; } int ca_chunk_file_test(int chunk_fd, const char *prefix, const CaChunkID *chunkid) { int r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; r = ca_chunk_file_access(chunk_fd, prefix, chunkid, NULL); if (r != 0) return r; return ca_chunk_file_access(chunk_fd, prefix, chunkid, ca_compressed_chunk_suffix()); } int ca_chunk_file_remove(int chunk_fd, const char *prefix, const CaChunkID *chunkid) { int r; if (chunk_fd < 0 && chunk_fd != AT_FDCWD) return -EINVAL; if (!chunkid) return -EINVAL; r = ca_chunk_file_unlink(chunk_fd, prefix, chunkid, NULL); if (r < 0 && r != -ENOENT) return -EINVAL; return ca_chunk_file_unlink(chunk_fd, prefix, chunkid, ca_compressed_chunk_suffix()); } src/cachunk.h000066400000000000000000000037031322621430500134330ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocachunkhfoo #define foocachunkhfoo #include "cachunkid.h" #include "cacompression.h" #include "realloc-buffer.h" /* The hardcoded, maximum chunk size, after which we refuse operation */ #define CA_CHUNK_SIZE_LIMIT_MAX ((size_t) (128U*1024U*1024U)) #define CA_CHUNK_SIZE_LIMIT_MIN ((size_t) 1) typedef enum CaChunkCompression { CA_CHUNK_UNCOMPRESSED, CA_CHUNK_COMPRESSED, CA_CHUNK_AS_IS, _CA_CHUNK_COMPRESSION_MAX, } CaChunkCompression; int ca_load_fd(int fd, ReallocBuffer *buffer); int ca_load_and_decompress_fd(int fd, ReallocBuffer *buffer); int ca_load_and_compress_fd(int fd, CaCompressionType compression_type, ReallocBuffer *buffer); int ca_save_fd(int fd, const void *data, size_t size); int ca_save_and_decompress_fd(int fd, const void *data, size_t size); int ca_save_and_compress_fd(int fd, CaCompressionType compression_type, const void *data, size_t size); int ca_decompress(const void *data, size_t size, ReallocBuffer *buffer); int ca_compress(CaCompressionType compression_type, const void *data, size_t size, ReallocBuffer *buffer); int ca_chunk_file_open(int cache_fd, const char *prefix, const CaChunkID *chunkid, const char *suffix, int flags); int ca_chunk_file_test(int cache_fd, const char *prefix, const CaChunkID *chunkid); int ca_chunk_file_load(int cache_fd, const char *prefix, const CaChunkID *chunkid, CaChunkCompression desired_compression, CaCompressionType compression_type, ReallocBuffer *buffer, CaChunkCompression *ret_effective_compression); int ca_chunk_file_save(int cache_fd, const char *prefix, const CaChunkID *chunkid, CaChunkCompression effective_compression, CaChunkCompression desired_compression, CaCompressionType compression_type, const void *p, uint64_t l); int ca_chunk_file_mark_missing(int cache_fd, const char *prefix, const CaChunkID *chunkid); int ca_chunk_file_remove(int chunk_fd, const char *prefix, const CaChunkID *chunkid); #endif src/cachunker.c000066400000000000000000000223431322621430500137560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "cachunk.h" #include "cachunker.h" #include "util.h" int ca_chunker_set_size( CaChunker *c, size_t min_size, size_t avg_size, size_t max_size) { size_t discriminator; assert(c); if (c->window_size != 0) /* Already started? */ return -EBUSY; if (avg_size == 0) { if (min_size != 0 && max_size != 0) avg_size = (size_t) pow(2, (log2(min_size) + log2(max_size)) / 2); else if (min_size != 0) avg_size = min_size * 4; else if (max_size != 0) avg_size = max_size / 4; else avg_size = CA_CHUNK_SIZE_AVG_DEFAULT; if (avg_size < CA_CHUNK_SIZE_LIMIT_MIN) avg_size = CA_CHUNK_SIZE_LIMIT_MIN; if (avg_size > CA_CHUNK_SIZE_LIMIT_MAX) avg_size = CA_CHUNK_SIZE_LIMIT_MAX; } else { if (avg_size < CA_CHUNK_SIZE_LIMIT_MIN) return -ERANGE; if (avg_size > CA_CHUNK_SIZE_LIMIT_MAX) return -ERANGE; } if (min_size == 0) { min_size = avg_size / 4; if (min_size < CA_CHUNK_SIZE_LIMIT_MIN) min_size = CA_CHUNK_SIZE_LIMIT_MIN; } else { if (min_size < CA_CHUNK_SIZE_LIMIT_MIN) return -ERANGE; if (min_size > avg_size) return -EINVAL; } if (max_size == 0) { max_size = avg_size * 4; if (max_size > CA_CHUNK_SIZE_LIMIT_MAX) max_size = CA_CHUNK_SIZE_LIMIT_MAX; } else { if (max_size > CA_CHUNK_SIZE_LIMIT_MAX) return -ERANGE; if (max_size < avg_size) return -EINVAL; } assert(CA_CHUNK_SIZE_LIMIT_MIN <= min_size); assert(min_size <= avg_size); assert(avg_size <= max_size); assert(max_size <= CA_CHUNK_SIZE_LIMIT_MAX); /* Correct the average chunk size for our cut test. In the relevant range the chunks end up being ~1.32 times * larger than the raw configured chunk size since the chunk sizes are not distributed evenly. */ discriminator = CA_CHUNKER_DISCRIMINATOR_FROM_AVG(avg_size); if (discriminator < min_size) discriminator = min_size; if (discriminator > max_size) discriminator = max_size; c->chunk_size_min = min_size; c->chunk_size_avg = avg_size; c->chunk_size_max = max_size; c->discriminator = discriminator; /* fprintf(stderr, "Setting min/avg/max chunk size: %zu/%zu/%zu.\n", */ /* c->chunk_size_min, c->chunk_size_avg, c->chunk_size_max); */ return 0; } static const uint32_t buzhash_table[] = { 0x458be752, 0xc10748cc, 0xfbbcdbb8, 0x6ded5b68, 0xb10a82b5, 0x20d75648, 0xdfc5665f, 0xa8428801, 0x7ebf5191, 0x841135c7, 0x65cc53b3, 0x280a597c, 0x16f60255, 0xc78cbc3e, 0x294415f5, 0xb938d494, 0xec85c4e6, 0xb7d33edc, 0xe549b544, 0xfdeda5aa, 0x882bf287, 0x3116737c, 0x05569956, 0xe8cc1f68, 0x0806ac5e, 0x22a14443, 0x15297e10, 0x50d090e7, 0x4ba60f6f, 0xefd9f1a7, 0x5c5c885c, 0x82482f93, 0x9bfd7c64, 0x0b3e7276, 0xf2688e77, 0x8fad8abc, 0xb0509568, 0xf1ada29f, 0xa53efdfe, 0xcb2b1d00, 0xf2a9e986, 0x6463432b, 0x95094051, 0x5a223ad2, 0x9be8401b, 0x61e579cb, 0x1a556a14, 0x5840fdc2, 0x9261ddf6, 0xcde002bb, 0x52432bb0, 0xbf17373e, 0x7b7c222f, 0x2955ed16, 0x9f10ca59, 0xe840c4c9, 0xccabd806, 0x14543f34, 0x1462417a, 0x0d4a1f9c, 0x087ed925, 0xd7f8f24c, 0x7338c425, 0xcf86c8f5, 0xb19165cd, 0x9891c393, 0x325384ac, 0x0308459d, 0x86141d7e, 0xc922116a, 0xe2ffa6b6, 0x53f52aed, 0x2cd86197, 0xf5b9f498, 0xbf319c8f, 0xe0411fae, 0x977eb18c, 0xd8770976, 0x9833466a, 0xc674df7f, 0x8c297d45, 0x8ca48d26, 0xc49ed8e2, 0x7344f874, 0x556f79c7, 0x6b25eaed, 0xa03e2b42, 0xf68f66a4, 0x8e8b09a2, 0xf2e0e62a, 0x0d3a9806, 0x9729e493, 0x8c72b0fc, 0x160b94f6, 0x450e4d3d, 0x7a320e85, 0xbef8f0e1, 0x21d73653, 0x4e3d977a, 0x1e7b3929, 0x1cc6c719, 0xbe478d53, 0x8d752809, 0xe6d8c2c6, 0x275f0892, 0xc8acc273, 0x4cc21580, 0xecc4a617, 0xf5f7be70, 0xe795248a, 0x375a2fe9, 0x425570b6, 0x8898dcf8, 0xdc2d97c4, 0x0106114b, 0x364dc22f, 0x1e0cad1f, 0xbe63803c, 0x5f69fac2, 0x4d5afa6f, 0x1bc0dfb5, 0xfb273589, 0x0ea47f7b, 0x3c1c2b50, 0x21b2a932, 0x6b1223fd, 0x2fe706a8, 0xf9bd6ce2, 0xa268e64e, 0xe987f486, 0x3eacf563, 0x1ca2018c, 0x65e18228, 0x2207360a, 0x57cf1715, 0x34c37d2b, 0x1f8f3cde, 0x93b657cf, 0x31a019fd, 0xe69eb729, 0x8bca7b9b, 0x4c9d5bed, 0x277ebeaf, 0xe0d8f8ae, 0xd150821c, 0x31381871, 0xafc3f1b0, 0x927db328, 0xe95effac, 0x305a47bd, 0x426ba35b, 0x1233af3f, 0x686a5b83, 0x50e072e5, 0xd9d3bb2a, 0x8befc475, 0x487f0de6, 0xc88dff89, 0xbd664d5e, 0x971b5d18, 0x63b14847, 0xd7d3c1ce, 0x7f583cf3, 0x72cbcb09, 0xc0d0a81c, 0x7fa3429b, 0xe9158a1b, 0x225ea19a, 0xd8ca9ea3, 0xc763b282, 0xbb0c6341, 0x020b8293, 0xd4cd299d, 0x58cfa7f8, 0x91b4ee53, 0x37e4d140, 0x95ec764c, 0x30f76b06, 0x5ee68d24, 0x679c8661, 0xa41979c2, 0xf2b61284, 0x4fac1475, 0x0adb49f9, 0x19727a23, 0x15a7e374, 0xc43a18d5, 0x3fb1aa73, 0x342fc615, 0x924c0793, 0xbee2d7f0, 0x8a279de9, 0x4aa2d70c, 0xe24dd37f, 0xbe862c0b, 0x177c22c2, 0x5388e5ee, 0xcd8a7510, 0xf901b4fd, 0xdbc13dbc, 0x6c0bae5b, 0x64efe8c7, 0x48b02079, 0x80331a49, 0xca3d8ae6, 0xf3546190, 0xfed7108b, 0xc49b941b, 0x32baf4a9, 0xeb833a4a, 0x88a3f1a5, 0x3a91ce0a, 0x3cc27da1, 0x7112e684, 0x4a3096b1, 0x3794574c, 0xa3c8b6f3, 0x1d213941, 0x6e0a2e00, 0x233479f1, 0x0f4cd82f, 0x6093edd2, 0x5d7d209e, 0x464fe319, 0xd4dcac9e, 0x0db845cb, 0xfb5e4bc3, 0xe0256ce1, 0x09fb4ed1, 0x0914be1e, 0xa5bdb2c3, 0xc6eb57bb, 0x30320350, 0x3f397e91, 0xa67791bc, 0x86bc0e2c, 0xefa0a7e2, 0xe9ff7543, 0xe733612c, 0xd185897b, 0x329e5388, 0x91dd236b, 0x2ecb0d93, 0xf4d82a3d, 0x35b5c03f, 0xe4e606f0, 0x05b21843, 0x37b45964, 0x5eff22f4, 0x6027f4cc, 0x77178b3c, 0xae507131, 0x7bf7cabc, 0xf9c18d66, 0x593ade65, 0xd95ddf11, }; uint32_t ca_chunker_start(CaChunker *c, const void *p, size_t n) { const uint8_t *q = p; size_t i; assert(c); assert(CA_CHUNK_SIZE_LIMIT_MIN <= c->chunk_size_min); assert(c->chunk_size_min <= c->chunk_size_avg); assert(c->chunk_size_avg <= c->chunk_size_max); assert(c->chunk_size_max <= CA_CHUNK_SIZE_LIMIT_MAX); c->window_size = n; for (i = 1; i < n; i++, q++) c->h ^= rol32(buzhash_table[*q], n - i); c->h ^= buzhash_table[*q]; return c->h; } uint32_t ca_chunker_roll(CaChunker *c, uint8_t leave, uint8_t enter) { c->h = rol32(c->h, 1) ^ rol32(buzhash_table[leave], c->window_size) ^ buzhash_table[enter]; return c->h; } static bool shall_break(CaChunker *c, uint32_t v) { assert(c); if (c->chunk_size >= c->chunk_size_max) return true; if (c->chunk_size < c->chunk_size_min) return false; return (v % c->discriminator) == (c->discriminator - 1); } size_t ca_chunker_scan(CaChunker *c, const void* p, size_t n) { const uint8_t *q = p; uint32_t v; size_t k = 0, idx; assert(c); assert(p); /* Scans the specified bytes for chunk borders. Returns (size_t) -1 if no border was discovered, otherwise the * chunk size. */ if (c->window_size < CA_CHUNKER_WINDOW_SIZE) { size_t m; /* Append to window to make it full */ m = MIN(CA_CHUNKER_WINDOW_SIZE - c->window_size, n); memcpy(c->window + c->window_size, q, m); q += m, n -= m; c->window_size += m; c->chunk_size += m; /* If the window isn't full still, return early */ if (c->window_size < CA_CHUNKER_WINDOW_SIZE) return (size_t) -1; /* Window is full, we are now ready to go. */ v = ca_chunker_start(c, c->window, c->window_size); k = m; if (shall_break(c, v)) goto now; } idx = c->chunk_size % CA_CHUNKER_WINDOW_SIZE; while (n > 0) { v = ca_chunker_roll(c, c->window[idx], *q); c->chunk_size++; k++; if (shall_break(c, v)) goto now; c->window[idx++] = *q; if (idx == CA_CHUNKER_WINDOW_SIZE) idx = 0; q++, n--; } return (size_t) -1; now: c->h = 0; c->chunk_size = 0; c->window_size = 0; return k; } src/cachunker.h000066400000000000000000000047441322621430500137700ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foochunkerhfoo #define foochunkerhfoo #include #include #include /* The default average chunk size */ #define CA_CHUNK_SIZE_AVG_DEFAULT ((size_t) (64U*1024U)) /* Our checksum window size */ #define CA_CHUNKER_WINDOW_SIZE 48 /* The chunk cut discriminator. In order to get an average chunk size of avg, we cut whenever for a hash value "h" at * byte "i" given the descriminator "d(avg)": h(i) mod d(avg) == d(avg) - 1. Note that the discriminator * calculated like this only yields correct results as long as the minimal chunk size is picked as avg/4, and the * maximum chunk size as avg*4. If they are picked differently the result might be skewed into either direction. */ #define CA_CHUNKER_DISCRIMINATOR_FROM_AVG(avg) ((size_t) (avg / (-1.42888852e-7 * avg + 1.33237515))) typedef struct CaChunker { uint32_t h; size_t window_size; size_t chunk_size; size_t chunk_size_min; size_t chunk_size_max; size_t chunk_size_avg; size_t discriminator; uint8_t window[CA_CHUNKER_WINDOW_SIZE]; } CaChunker; /* The default initializer for the chunker. We pick an average chunk size equivalent to 64K */ #define CA_CHUNKER_INIT \ { \ .chunk_size_min = CA_CHUNK_SIZE_AVG_DEFAULT/4, \ .chunk_size_avg = CA_CHUNK_SIZE_AVG_DEFAULT, \ .chunk_size_max = CA_CHUNK_SIZE_AVG_DEFAULT*4, \ .discriminator = CA_CHUNKER_DISCRIMINATOR_FROM_AVG(CA_CHUNK_SIZE_AVG_DEFAULT), \ } /* Set the min/avg/max chunk size. Each parameter may be 0, in which case a default is used. */ int ca_chunker_set_size(CaChunker *c, size_t min_size, size_t avg_size, size_t max_size); /* Scans the specified data for a chunk border. Returns (size_t) -1 if none was found (and the function should be * called with more data later on), or another value indicating the position of a border. */ size_t ca_chunker_scan(CaChunker *c, const void* p, size_t n); /* Low-level buzhash functions. Only exported for testing purposes. */ uint32_t ca_chunker_start(CaChunker *c, const void *p, size_t n); uint32_t ca_chunker_roll(CaChunker *c, uint8_t pop_byte, uint8_t push_byte); #endif src/cachunkid.c000066400000000000000000000040101322621430500137330ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "cachunk.h" #include "cachunkid.h" static char encode_char(uint8_t x) { x &= 0xF; return (x < 10 ? '0' : 'a' - 10) + x; } static int decode_char(char x) { if (x >= '0' && x <= '9') return x - '0'; if (x >= 'a' && x <= 'f') return x - 'a' + 10; return -EINVAL; } CaChunkID* ca_chunk_id_parse(const char *v, CaChunkID *ret) { CaChunkID id; size_t i; assert(v); assert(ret); for (i = 0; i < sizeof(CaChunkID); i++) { int x, y; x = decode_char(v[i*2]); if (x < 0) return NULL; y = decode_char(v[i*2+1]); if (y < 0) return NULL; id.bytes[i] = (uint8_t) x << 4 | (uint8_t) y; } if (v[sizeof(CaChunkID)*2] != 0) return NULL; *ret = id; return ret; } char* ca_chunk_id_format(const CaChunkID *id, char v[CA_CHUNK_ID_FORMAT_MAX]) { size_t i; assert(id); assert(v); for (i = 0; i < sizeof(CaChunkID); i++) { v[i*2] = encode_char(id->bytes[i] >> 4); v[i*2+1] = encode_char(id->bytes[i] & 0xF); } v[sizeof(CaChunkID) * 2] = 0; return v; } int ca_chunk_id_make(CaDigest *digest, const void *p, size_t l, CaChunkID *ret) { if (!digest) return -EINVAL; if (!p) return -EINVAL; if (l < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (l > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!ret) return -EINVAL; if (ca_digest_get_size(digest) != sizeof(CaChunkID)) return -EINVAL; ca_digest_reset(digest); ca_digest_write(digest, p, l); memcpy(ret, ca_digest_read(digest), sizeof(CaChunkID)); return 0; } src/cachunkid.h000066400000000000000000000023511322621430500137460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaobjectidhfoo #define foocaobjectidhfoo #include #include #include #include #include "cadigest.h" #define CA_CHUNK_ID_SIZE 32 #define CA_CHUNK_ID_FORMAT_MAX (CA_CHUNK_ID_SIZE*2+1) typedef union CaChunkID { /* Depending on context either a SHA256 or SHA512/256 sum */ uint8_t bytes[CA_CHUNK_ID_SIZE]; uint64_t u64[CA_CHUNK_ID_SIZE / sizeof(uint64_t)]; } CaChunkID; CaChunkID* ca_chunk_id_parse(const char *v, CaChunkID *ret); char *ca_chunk_id_format(const CaChunkID *id, char v[CA_CHUNK_ID_FORMAT_MAX]); static inline bool ca_chunk_id_equal(const CaChunkID *a, const CaChunkID *b) { if (a == b) return true; if (!a || !b) return false; return memcmp(a, b, sizeof(CaChunkID)) == 0; } static inline bool ca_chunk_id_is_null(const CaChunkID *a) { size_t i; if (!a) return true; for (i = 0; i < CA_CHUNK_ID_SIZE / sizeof(uint64_t); i++) if (a->u64[0] != 0) return false; return true; } int ca_chunk_id_make(CaDigest *digest, const void *p, size_t l, CaChunkID *ret); #endif src/cacommon.h000066400000000000000000000005031322621430500136060ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef cacommonhfoo #define cacommonhfoo typedef enum CaIterate { CA_ITERATE_CURRENT, CA_ITERATE_FIRST, CA_ITERATE_LAST, CA_ITERATE_NEXT, CA_ITERATE_PREVIOUS, _CA_ITERATE_MAX, _CA_ITERATE_INVALID = -1, } CaIterate; #endif src/cacompression.c000066400000000000000000000016411322621430500146560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "util.h" #include "cacompression.h" static const char* const table[_CA_COMPRESSION_TYPE_MAX] = { [CA_COMPRESSION_XZ] = "xz", [CA_COMPRESSION_GZIP] = "gzip", [CA_COMPRESSION_ZSTD] = "zstd", }; const char* ca_compression_type_to_string(CaCompressionType c) { if (c < 0) return NULL; if (c >= _CA_COMPRESSION_TYPE_MAX) return NULL; return table[c]; } CaCompressionType ca_compression_type_from_string(const char *s) { CaCompressionType i; if (isempty(s)) return _CA_COMPRESSION_TYPE_INVALID; if (streq(s, "default")) return CA_COMPRESSION_DEFAULT; for (i = 0; i < _CA_COMPRESSION_TYPE_MAX; i++) { if (streq(table[i], s)) return i; } return _CA_COMPRESSION_TYPE_INVALID; } src/cacompression.h000066400000000000000000000007741322621430500146710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocacompressorhfoo #define foocacompressorhfoo typedef enum CaCompressionType { CA_COMPRESSION_XZ, CA_COMPRESSION_GZIP, CA_COMPRESSION_ZSTD, _CA_COMPRESSION_TYPE_MAX, CA_COMPRESSION_DEFAULT = CA_COMPRESSION_ZSTD, _CA_COMPRESSION_TYPE_INVALID = -1, } CaCompressionType; const char* ca_compression_type_to_string(CaCompressionType c); CaCompressionType ca_compression_type_from_string(const char *s); #endif src/cadecoder.c000066400000000000000000005221301322621430500137230ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SELINUX # include #endif #include "cadecoder.h" #include "caformat-util.h" #include "caformat.h" #include "cautil.h" #include "chattr.h" #include "def.h" #include "realloc-buffer.h" #include "reflink.h" #include "rm-rf.h" #include "siphash24.h" #include "util.h" /* #undef EINVAL */ /* #define EINVAL __LINE__ */ /* #undef EBADMSG */ /* #define EBADMSG __LINE__ */ /* #undef EBUSY */ /* #define EBUSY __LINE__ */ /* #undef ENOENT */ /* #define ENOENT __LINE__ */ /* #undef EUNATCH */ /* #define EUNATCH __LINE__ */ #define APPLY_EARLY_FS_FL \ (FS_NOATIME_FL| \ FS_COMPR_FL| \ FS_NOCOW_FL| \ FS_NOCOMP_FL| \ FS_PROJINHERIT_FL) typedef struct CaDecoderExtendedAttribute { struct CaDecoderExtendedAttribute *next; struct CaDecoderExtendedAttribute *previous; CaFormatXAttr format; } CaDecoderExtendedAttribute; typedef struct CaDecoderACLEntry { struct CaDecoderACLEntry *next; union { CaFormatACLUser user; CaFormatACLGroup group; }; } CaDecoderACLEntry; typedef struct CaDecoderNode { int fd; uint64_t entry_offset; uint64_t payload_offset; uint64_t goodbye_offset; uint64_t end_offset; /* offset of the byte behind the goodbye marker */ char *name; char *temporary_name; CaFormatEntry *entry; CaFormatGoodbye *goodbye; mode_t mode; /* Only relevant if entry == NULL */ uint64_t size; /* Only for S_ISREG() */ char *user_name; char *group_name; char *symlink_target; /* Only for S_ISLNK() */ dev_t rdev; /* Only for S_ISCHR() and S_ISBLK() */ char *selinux_label; CaDecoderExtendedAttribute *xattrs_first; CaDecoderExtendedAttribute *xattrs_last; CaDecoderExtendedAttribute *xattrs_current; bool have_fcaps; void *fcaps; size_t fcaps_size; bool have_acl; CaDecoderACLEntry *acl_user; CaDecoderACLEntry *acl_group; CaDecoderACLEntry *acl_default_user; CaDecoderACLEntry *acl_default_group; uint64_t acl_group_obj_permissions; uint64_t acl_default_user_obj_permissions; uint64_t acl_default_group_obj_permissions; uint64_t acl_default_other_permissions; uint64_t acl_default_mask_permissions; /* Only for S_ISREG(), the origin if we know it */ CaOrigin *payload_origin; /* Only for S_ISDIR(), so that we can remove files that aren't there anymore */ char **dirents; size_t n_dirents; size_t n_dirents_allocated; bool dirents_invalid; bool hardlinked; } CaDecoderNode; typedef enum CaDecoderState { CA_DECODER_INIT, CA_DECODER_ENTERED, CA_DECODER_ENTERED_FOR_SEEK, CA_DECODER_ENTRY, CA_DECODER_IN_PAYLOAD, CA_DECODER_IN_DIRECTORY, CA_DECODER_GOODBYE, CA_DECODER_FINALIZE, CA_DECODER_EOF, /* As the result of ca_decoder_seek_offset() we'll traverse through these two states ... */ CA_DECODER_PREPARING_SEEK_TO_OFFSET, CA_DECODER_SEEKING_TO_OFFSET, /* As the result of ca_decoder_seek_path() we'll traverse through these eight states ... */ CA_DECODER_PREPARING_SEEK_TO_FILENAME, CA_DECODER_SEEKING_TO_FILENAME, CA_DECODER_PREPARING_SEEK_TO_NEXT_SIBLING, CA_DECODER_SEEKING_TO_NEXT_SIBLING, CA_DECODER_PREPARING_SEEK_TO_ENTRY, CA_DECODER_SEEKING_TO_ENTRY, CA_DECODER_PREPARING_SEEK_TO_PAYLOAD, CA_DECODER_SEEKING_TO_PAYLOAD, CA_DECODER_PREPARING_SEEK_TO_GOODBYE, CA_DECODER_SEEKING_TO_GOODBYE, CA_DECODER_PREPARING_SEEK_TO_GOODBYE_TAIL, CA_DECODER_SEEKING_TO_GOODBYE_TAIL, CA_DECODER_NOWHERE, CA_DECODER_SKIPPING, } CaDecoderState; struct CaDecoder { CaDecoderState state; uint64_t feature_flags; /* The actual feature flags in the archive */ uint64_t replay_feature_flags; /* The feature flags we shall restore and which are available in the archive */ uint64_t expected_feature_flags; /* The feature flags we expect to be stored in the file, given what we learnt from the index file */ uint64_t feature_flags_mask; /* The mask of feature flags that the user asked for restoring */ CaDecoderNode nodes[NODES_MAX]; size_t n_nodes; size_t node_idx; size_t boundary_node_idx; /* Never go further up than this node. We set this in order to stop iteration above the point we seeked to */ /* A buffer that automatically resizes, containing what we read most recently */ ReallocBuffer buffer; CaOrigin *buffer_origin; /* An EOF was signalled to us */ bool eof; /* Where we are from the stream start */ uint64_t archive_offset; /* Only in CA_DECODER_IN_PAYLOAD: Where we are from the start of the current payload we are looking at */ uint64_t payload_offset; /* How far cadecoder_step() will jump ahead */ uint64_t step_size; /* If we are seeking, the path we are seeking to */ char *seek_path; /* full */ const char *seek_subpath; /* the subpath left to seek */ bool seek_next_sibling; /* if true then we'll seek to the entry one after the specified path */ uint64_t seek_idx; /* Current counter of filenames with the same hash value */ uint64_t seek_offset; /* Where to seek to, if we already know */ uint64_t seek_end_offset; /* If we are seeking somewhere and know the end of the object we seek into, we store it here */ uint64_t seek_payload; /* Payload we shall seek to */ uint64_t skip_bytes; /* How many bytes to skip if we are in CA_DECODER_SKIPPING state */ /* Cached name → UID/GID translation */ uid_t cached_uid; gid_t cached_gid; char *cached_user_name; char *cached_group_name; /* A cached pair of st_dev and magic, so that we don't have to call statfs() for each file */ dev_t cached_st_dev; statfs_f_type_t cached_magic; int boundary_fd; bool punch_holes:1; bool reflink:1; bool hardlink:1; bool delete:1; bool payload:1; bool undo_immutable:1; uint64_t n_punch_holes_bytes; uint64_t n_reflink_bytes; uint64_t n_hardlink_bytes; uid_t uid_shift; uid_t uid_range; /* uid_range == 0 means "full range" */ CaDigest *archive_digest; CaDigest *payload_digest; CaDigest *hardlink_digest; bool want_archive_digest:1; bool want_payload_digest:1; bool want_hardlink_digest:1; bool payload_digest_invalid:1; bool hardlink_digest_invalid:1; }; static inline bool CA_DECODER_IS_SEEKING(CaDecoder *d) { return IN_SET(d->state, CA_DECODER_ENTERED_FOR_SEEK, CA_DECODER_PREPARING_SEEK_TO_OFFSET, CA_DECODER_SEEKING_TO_OFFSET, CA_DECODER_PREPARING_SEEK_TO_FILENAME, CA_DECODER_SEEKING_TO_FILENAME, CA_DECODER_PREPARING_SEEK_TO_NEXT_SIBLING, CA_DECODER_SEEKING_TO_NEXT_SIBLING, CA_DECODER_PREPARING_SEEK_TO_PAYLOAD, CA_DECODER_SEEKING_TO_PAYLOAD, CA_DECODER_PREPARING_SEEK_TO_ENTRY, CA_DECODER_SEEKING_TO_ENTRY, CA_DECODER_PREPARING_SEEK_TO_GOODBYE, CA_DECODER_SEEKING_TO_GOODBYE, CA_DECODER_PREPARING_SEEK_TO_GOODBYE_TAIL, CA_DECODER_SEEKING_TO_GOODBYE_TAIL); } #define CA_DECODER_AT_ROOT(d) ((d)->node_idx == 0) static inline bool CA_DECODER_IS_NAKED(CaDecoder *d) { assert(d); /* Returns true if we are decoding a naked blob, i.e. a top-level payload, in contrast to a directory tree */ return d->n_nodes == 1 && !d->nodes[0].entry && (S_ISREG(d->nodes[0].mode) || S_ISBLK(d->nodes[0].mode)); } static mode_t ca_decoder_node_mode(CaDecoderNode *n) { assert(n); if (n->entry) return (mode_t) read_le64(&n->entry->mode); return n->mode; } CaDecoder *ca_decoder_new(void) { CaDecoder *d = NULL; d = new0(CaDecoder, 1); if (!d) return NULL; d->feature_flags = UINT64_MAX; d->replay_feature_flags = UINT64_MAX; d->expected_feature_flags = UINT64_MAX; d->feature_flags_mask = UINT64_MAX; d->seek_idx = UINT64_MAX; d->seek_offset = UINT64_MAX; d->seek_end_offset = UINT64_MAX; d->cached_uid = UID_INVALID; d->cached_gid = GID_INVALID; d->boundary_fd = -1; d->punch_holes = true; d->reflink = true; d->delete = true; d->payload = true; return d; } static void ca_decoder_node_free_xattrs(CaDecoderNode *n) { CaDecoderExtendedAttribute *i; assert(n); i = n->xattrs_first; while (i) { CaDecoderExtendedAttribute *next; next = i->next; free(i); i = next; } n->xattrs_first = n->xattrs_last = n->xattrs_current = NULL; } static void ca_decoder_node_free_acl_entries(CaDecoderACLEntry **e) { while (*e) { CaDecoderACLEntry *next; next = (*e)->next; free(*e); *e = next; } } static void ca_decoder_node_flush_entry(CaDecoderNode *n) { assert(n); n->entry = mfree(n->entry); n->user_name = mfree(n->user_name); n->group_name = mfree(n->group_name); n->symlink_target = mfree(n->symlink_target); n->size = UINT64_MAX; n->mode = (mode_t) -1; n->rdev = 0; n->fcaps = mfree(n->fcaps); n->fcaps_size = 0; n->have_fcaps = false; ca_decoder_node_free_xattrs(n); ca_decoder_node_free_acl_entries(&n->acl_user); ca_decoder_node_free_acl_entries(&n->acl_group); ca_decoder_node_free_acl_entries(&n->acl_default_user); ca_decoder_node_free_acl_entries(&n->acl_default_group); n->acl_group_obj_permissions = n->acl_default_user_obj_permissions = n->acl_default_group_obj_permissions = n->acl_default_other_permissions = n->acl_default_mask_permissions = UINT64_MAX; n->have_acl = false; n->selinux_label = mfree(n->selinux_label); } static void ca_decoder_node_free(CaDecoderNode *n) { assert(n); if (n->fd >= 3) n->fd = safe_close(n->fd); else n->fd = -1; n->name = mfree(n->name); n->temporary_name = mfree(n->temporary_name); ca_decoder_node_flush_entry(n); n->goodbye = mfree(n->goodbye); n->entry_offset = UINT64_MAX; n->payload_offset = UINT64_MAX; n->goodbye_offset = UINT64_MAX; n->end_offset = UINT64_MAX; n->payload_origin = ca_origin_unref(n->payload_origin); n->dirents = strv_free(n->dirents); n->n_dirents = n->n_dirents_allocated = 0; n->dirents_invalid = false; n->hardlinked = false; } static void ca_decoder_flush_nodes(CaDecoder *d, size_t leave) { size_t i; assert(d); for (i = leave; i < d->n_nodes; i++) ca_decoder_node_free(d->nodes + i); if (d->n_nodes > leave) d->n_nodes = leave; } CaDecoder *ca_decoder_unref(CaDecoder *d) { if (!d) return NULL; ca_decoder_flush_nodes(d, 0); realloc_buffer_free(&d->buffer); ca_origin_unref(d->buffer_origin); free(d->cached_user_name); free(d->cached_group_name); free(d->seek_path); safe_close(d->boundary_fd); ca_digest_free(d->archive_digest); ca_digest_free(d->payload_digest); ca_digest_free(d->hardlink_digest); free(d); return NULL; } int ca_decoder_set_expected_feature_flags(CaDecoder *d, uint64_t flags) { if (!d) return -EINVAL; d->expected_feature_flags = flags; return 0; } int ca_decoder_set_feature_flags_mask(CaDecoder *d, uint64_t mask) { if (!d) return -EINVAL; if (d->replay_feature_flags != UINT64_MAX) return -EBUSY; return ca_feature_flags_normalize_mask(mask, &d->feature_flags_mask); } int ca_decoder_get_feature_flags(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (d->feature_flags == UINT64_MAX) return -ENODATA; *ret = d->feature_flags; return 0; } int ca_decoder_set_base_fd(CaDecoder *d, int fd) { struct stat st; struct statfs sfs; if (!d) return -EINVAL; if (fd < 0) return -EINVAL; if (d->n_nodes > 0) return -EBUSY; if (d->boundary_fd >= 0) return -EBUSY; if (fstat(fd, &st) < 0) return -errno; if (fstatfs(fd, &sfs) < 0) return -errno; if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) return -ENOTTY; d->nodes[0] = (CaDecoderNode) { .fd = fd, .entry_offset = S_ISDIR(st.st_mode) ? 0 : UINT64_MAX, .payload_offset = S_ISREG(st.st_mode) || S_ISBLK(st.st_mode) ? 0 : UINT64_MAX, .goodbye_offset = UINT64_MAX, .end_offset = UINT64_MAX, .mode = st.st_mode, .size = UINT64_MAX, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, }; d->n_nodes = 1; d->cached_magic = sfs.f_type; d->cached_st_dev = st.st_dev; return 0; } int ca_decoder_set_boundary_fd(CaDecoder *d, int fd) { struct stat st; struct statfs sfs; if (!d) return -EINVAL; if (fd < 0) return -EINVAL; if (d->boundary_fd >= 0) return -EBUSY; if (d->n_nodes > 0 && d->nodes[0].fd >= 0) return -EBUSY; if (fstat(fd, &st) < 0) return -errno; if (fstatfs(fd, &sfs) < 0) return -errno; if (!S_ISDIR(st.st_mode)) return -ENOTDIR; d->boundary_fd = fd; d->nodes[0] = (CaDecoderNode) { .fd = -1, .entry_offset = 0, .payload_offset = UINT64_MAX, .goodbye_offset = UINT64_MAX, .end_offset = UINT64_MAX, .mode = st.st_mode, .size = UINT64_MAX, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, }; d->n_nodes = 1; d->cached_magic = sfs.f_type; d->cached_st_dev = st.st_dev; return 0; } int ca_decoder_set_base_mode(CaDecoder *d, mode_t m) { if (!d) return -EINVAL; if (m & ~(07777 | S_IFMT)) return -EINVAL; if (!S_ISREG(m) && !S_ISDIR(m) && !S_ISBLK(m)) return -ENOTTY; if (d->n_nodes > 0) return -EBUSY; d->nodes[0] = (CaDecoderNode) { .fd = -1, .entry_offset = S_ISDIR(m) ? 0 : UINT64_MAX, .payload_offset = S_ISREG(m) || S_ISBLK(m) ? 0 : UINT64_MAX, .goodbye_offset = UINT64_MAX, .end_offset = UINT64_MAX, .mode = m, .size = UINT64_MAX, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, }; d->n_nodes = 1; return 0; } static CaDecoderNode* ca_decoder_current_node(CaDecoder *d) { assert(d); if (d->node_idx >= d->n_nodes) return NULL; return d->nodes + d->node_idx; } static CaDecoderNode* ca_decoder_current_parent_node(CaDecoder *d) { assert(d); if (d->node_idx == 0) return NULL; return d->nodes + d->node_idx - 1; } static void ca_decoder_forget_children(CaDecoder *d) { assert(d); while (d->n_nodes-1 > d->node_idx) ca_decoder_node_free(d->nodes + --d->n_nodes); } static CaDecoderNode* ca_decoder_init_child(CaDecoder *d) { CaDecoderNode *n; assert(d); ca_decoder_forget_children(d); if (d->n_nodes >= NODES_MAX) return NULL; n = d->nodes + d->n_nodes++; *n = (CaDecoderNode) { .fd = -1, .entry_offset = UINT64_MAX, .payload_offset = UINT64_MAX, .goodbye_offset = UINT64_MAX, .end_offset = UINT64_MAX, .mode = (mode_t) -1, .size = UINT64_MAX, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, }; return n; } static int ca_decoder_enter_child(CaDecoder *d) { assert(d); if (d->node_idx+1 >= d->n_nodes) return -EINVAL; if (!d->nodes[d->node_idx+1].name) return -EINVAL; d->node_idx++; return 0; } static void ca_decoder_enter_state(CaDecoder *d, CaDecoderState state) { assert(d); d->state = state; d->payload_offset = 0; d->step_size = 0; } static int ca_decoder_leave_child(CaDecoder *d) { assert(d); if (d->node_idx <= d->boundary_node_idx) return 0; d->node_idx--; return 1; } static int ca_decoder_object_is_complete(const void *p, size_t size) { const CaFormatHeader *h; uint64_t k; if (size < sizeof(CaFormatHeader)) return false; assert(p); h = p; k = read_le64(&h->size); if (k < sizeof(CaFormatHeader)) return -EBADMSG; if (k == UINT64_MAX) return -EBADMSG; return size >= k; } static int ca_decoder_determine_replay_feature_flags(CaDecoder *d) { uint64_t t; int r; assert(d); /* First, let's extend the stream's feature flags so that all redundant bits are set */ r = ca_feature_flags_normalize_mask(d->feature_flags, &t); if (r < 0) return r; /* Then, mask away everything the user's feature flag mask (that got extended like this too) doesn't allow */ t &= d->feature_flags_mask; /* Finally, let's normalize this to drop all redundant bits again */ return ca_feature_flags_normalize(t, &d->replay_feature_flags); } static bool validate_filename(const char *name, size_t n) { const char *p; assert(name); if (n < 2) return false; if (name[n-1] != 0) return false; for (p = name; p < name + n-1; p++) if (*p == 0 || *p == '/') return false; if (dot_or_dot_dot(name)) return false; return true; } static bool validate_mode(CaDecoder *d, uint64_t m, uint64_t flags) { assert(d); if ((m & ~(S_IFMT | UINT64_C(07777))) != 0) return false; switch (m & S_IFMT) { case S_IFREG: case S_IFDIR: break; case S_IFSOCK: if (!(d->feature_flags & CA_FORMAT_WITH_SOCKETS)) return false; break; case S_IFIFO: if (!(d->feature_flags & CA_FORMAT_WITH_FIFOS)) return false; break; case S_IFBLK: case S_IFCHR: if (!(d->feature_flags & CA_FORMAT_WITH_DEVICE_NODES)) return false; break; case S_IFLNK: if (!(d->feature_flags & CA_FORMAT_WITH_SYMLINKS)) return false; break; default: return false; } if (!S_ISDIR(m) && (flags & CA_FORMAT_WITH_SUBVOLUME)) return false; if (S_ISLNK(m)) return (m & 07777) == 0777; if (d->feature_flags & (CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_ACL)) return true; if ((m & 07777) == (S_ISDIR(m) ? 0777 : 0666)) return true; if (d->feature_flags & CA_FORMAT_WITH_READ_ONLY) { if ((m & 07777) == (S_ISDIR(m) ? 0555 : 0444)) return true; } return false; } static bool validate_uid_gid(CaDecoder *d, uint64_t u) { /* Don't permit either (uid_t) -1 for 16bit nor 32bit uid_t. */ assert(d); if (d->feature_flags & CA_FORMAT_WITH_16BIT_UIDS) return u < UINT16_MAX; if (d->feature_flags & CA_FORMAT_WITH_32BIT_UIDS) return u < UINT32_MAX && u != UINT16_MAX; return u == 0; } static bool validate_entry_flags(CaDecoder *d, uint64_t f) { assert(d); return (f & ~(d->feature_flags & (CA_FORMAT_WITH_FAT_ATTRS|CA_FORMAT_WITH_CHATTR|CA_FORMAT_WITH_SUBVOLUME|CA_FORMAT_WITH_SUBVOLUME_RO))) == 0; } static bool validate_major(uint64_t m) { /* On Linux major numbers are 12bit */ return m < (UINT64_C(1) << 12); } static bool validate_minor(uint64_t m) { /* On Linux minor numbers are 20bit */ return m < (UINT64_C(1) << 20); } static bool validate_nsec(CaDecoder *d, uint64_t t) { assert(d); if (t == UINT64_MAX) return false; if (d->feature_flags & CA_FORMAT_WITH_NSEC_TIME) return true; if (d->feature_flags & CA_FORMAT_WITH_USEC_TIME) return (t % UINT64_C(1000000)) == 0; if (d->feature_flags & CA_FORMAT_WITH_SEC_TIME) return (t % UINT64_C(1000000000)) == 0; if (d->feature_flags & CA_FORMAT_WITH_2SEC_TIME) return (t % UINT64_C(2000000000)) == 0; return t == 0; } static bool validate_user_group_name(const char *name, size_t n) { const char *p; assert(name || n == 0); if (n < 2) return false; if (name[n-1] != 0) return false; if (!(name[0] >= 'a' && name[0] <= 'z') && !(name[0] >= 'A' && name[0] <= 'Z') && name[0] != '_') return false; for (p = name + 1; p < name + n-1; p++) if (!(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && !(*p >= '0' && *p <= '9') && *p != '_' && *p != '-') return false; if (n > 256) /* sysconf(_SC_LOGIN_NAME_MAX) on Linux is 256 */ return false; /* If the user/group name is root, then it should be suppressed. Don't accept otherwise */ if (n == 5 && memcmp(name, "root", 5) == 0) return false; if (n == 2 && memcmp(name, "0", 2) == 0) return false; return true; } static bool validate_symlink_target(const char *target, size_t n) { assert(target || n == 0); if (n < 2) return false; if (target[n-1] != 0) return false; if (memchr(target, 0, n - 1)) return false; if (n > 4096) /* PATH_MAX is 4K on Linux */ return false; return true; } static bool validate_feature_flags(CaDecoder *d, uint64_t flags) { int r; assert(d); /* We use all bits on in the flags field as a special value, don't permit this in files */ if (flags == UINT64_MAX) return false; if (d->expected_feature_flags != UINT64_MAX && flags != d->expected_feature_flags) return false; r = ca_feature_flags_are_normalized(flags); if (r <= 0 && r != -EOPNOTSUPP) /* we let unsupported flags pass here, and let the caller decide what he wants to do with that */ return false; if (d->feature_flags == UINT64_MAX) { /* The first ENTRY record decides the flags for the whole archive */ d->feature_flags = flags; r = ca_decoder_determine_replay_feature_flags(d); if (r < 0) return false; } else if (d->feature_flags != flags) return false; return true; } static const CaFormatEntry* validate_format_entry(CaDecoder *d, const void *p) { const CaFormatEntry *e = p; assert(d); assert(e); if (read_le64(&e->header.size) < sizeof(CaFormatEntry)) return NULL; if (read_le64(&e->header.type) != CA_FORMAT_ENTRY) return NULL; if (!validate_feature_flags(d, read_le64(&e->feature_flags))) return NULL; if (!validate_mode(d, read_le64(&e->mode), read_le64(&e->flags))) return NULL; if (!validate_entry_flags(d, read_le64(&e->flags))) return NULL; if (!validate_uid_gid(d, read_le64(&e->uid))) return NULL; if (!validate_uid_gid(d, read_le64(&e->gid))) return NULL; if (!validate_nsec(d, read_le64(&e->mtime))) return NULL; return e; } static const CaFormatUser* validate_format_user(CaDecoder *d, const void *p) { const CaFormatUser *u = p; assert(d); assert(u); if (read_le64(&u->header.size) < offsetof(CaFormatUser, name) + 1) return NULL; if (read_le64(&u->header.type) != CA_FORMAT_USER) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_USER_NAMES)) return NULL; if (!validate_user_group_name(u->name, read_le64(&u->header.size) - offsetof(CaFormatUser, name))) return NULL; return u; } static const CaFormatGroup* validate_format_group(CaDecoder *d, const void *p) { const CaFormatGroup *g = p; assert(d); assert(g); if (read_le64(&g->header.size) < offsetof(CaFormatGroup, name) + 1) return NULL; if (read_le64(&g->header.type) != CA_FORMAT_GROUP) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_USER_NAMES)) return NULL; if (!validate_user_group_name(g->name, read_le64(&g->header.size) - offsetof(CaFormatGroup, name))) return NULL; return g; } static const CaFormatXAttr* validate_format_xattr(CaDecoder *d, const void *p) { const CaFormatXAttr *x = p; char *n; assert(d); assert(x); if (read_le64(&x->header.size) < offsetof(CaFormatXAttr, name_and_value) + 4) /* namespace + "." + name + 0 */ return NULL; if (read_le64(&x->header.type) != CA_FORMAT_XATTR) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_XATTRS)) return NULL; /* Make sure there's a NUL byte in the first 256 bytes, so that we have a properly bounded name */ n = memchr(x->name_and_value, 0, MIN(256U, read_le64(&x->header.size) - offsetof(CaFormatXAttr, name_and_value))); if (!n) return NULL; if (!ca_xattr_name_is_valid((char*) x->name_and_value)) return NULL; return x; } static bool validate_acl_permissions(uint64_t p) { return ((p & ~(CA_FORMAT_ACL_PERMISSION_READ|CA_FORMAT_ACL_PERMISSION_WRITE|CA_FORMAT_ACL_PERMISSION_EXECUTE)) == 0); } static const CaFormatACLUser* validate_format_acl_user(CaDecoder *d, const void *p) { const CaFormatACLUser *a = p; assert(d); assert(a); if (read_le64(&a->header.size) < offsetof(CaFormatACLUser, name)) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_ACL)) return NULL; if (read_le64(&a->header.size) > offsetof(CaFormatACLUser, name)) { if ((d->feature_flags & CA_FORMAT_WITH_USER_NAMES) == 0) return NULL; if (!validate_user_group_name(a->name, read_le64(&a->header.size) - offsetof(CaFormatACLUser, name))) return NULL; } if (!validate_uid_gid(d, read_le64(&a->uid))) return NULL; if (!validate_acl_permissions(read_le64(&a->permissions))) return NULL; return a; } static const CaFormatACLGroup* validate_format_acl_group(CaDecoder *d, const void *p) { const CaFormatACLGroup *a = p; assert(d); assert(a); if (read_le64(&a->header.size) < offsetof(CaFormatACLGroup, name)) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_ACL)) return NULL; if (read_le64(&a->header.size) > offsetof(CaFormatACLGroup, name)) { if ((d->feature_flags & CA_FORMAT_WITH_USER_NAMES) == 0) return NULL; if (!validate_user_group_name(a->name, read_le64(&a->header.size) - offsetof(CaFormatACLGroup, name))) return NULL; } if (!validate_uid_gid(d, read_le64(&a->gid))) return NULL; if (!validate_acl_permissions(read_le64(&a->permissions))) return NULL; return a; } static const CaFormatACLGroupObj* validate_format_acl_group_obj(CaDecoder *d, const void *p) { const CaFormatACLGroupObj *a = p; assert(d); assert(a); if (read_le64(&a->header.size) != sizeof(CaFormatACLGroupObj)) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_ACL)) return NULL; if (!validate_acl_permissions(read_le64(&a->permissions))) return NULL; return a; } static const CaFormatACLDefault* validate_format_acl_default(CaDecoder *d, const void *p) { const CaFormatACLDefault *a = p; assert(d); assert(a); if (read_le64(&a->header.size) != sizeof(CaFormatACLDefault)) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_ACL)) return NULL; /* An ACL must have USER_OBJ + GROUP_OBJ + OTHER to be valid, but MASK is optional. See acl(5), section "VALID * ACLS" for details */ if (!validate_acl_permissions(read_le64(&a->user_obj_permissions))) return NULL; if (!validate_acl_permissions(read_le64(&a->group_obj_permissions))) return NULL; if (!validate_acl_permissions(read_le64(&a->other_permissions))) return NULL; if (read_le64(&a->mask_permissions) != UINT64_MAX && !validate_acl_permissions(read_le64(&a->mask_permissions))) return NULL; return a; } static const CaFormatSELinux *validate_format_selinux(CaDecoder *d, const void *p) { const CaFormatSELinux *l = p; size_t n; if (read_le64(&l->header.size) < offsetof(CaFormatSELinux, label) + 2) return NULL; if (read_le64(&l->header.type) != CA_FORMAT_SELINUX) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_SELINUX)) return NULL; n = read_le64(&l->header.size) - offsetof(CaFormatSELinux, label) - 1; if (l->label[n] != 0) return NULL; if (memchr(l->label, 0, n)) return NULL; return l; } static const CaFormatFCaps* validate_format_fcaps(CaDecoder *d, const void *p) { const CaFormatFCaps *f = p; assert(d); assert(f); if (read_le64(&f->header.size) < offsetof(CaFormatFCaps, data)) return NULL; if (read_le64(&f->header.type) != CA_FORMAT_FCAPS) return NULL; return f; } static const CaFormatSymlink* validate_format_symlink(CaDecoder *d, const void *p) { const CaFormatSymlink *s = p; assert(d); assert(s); if (read_le64(&s->header.size) < offsetof(CaFormatSymlink, target) + 1) return NULL; if (read_le64(&s->header.type) != CA_FORMAT_SYMLINK) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_SYMLINKS)) return NULL; if (!validate_symlink_target(s->target, read_le64(&s->header.size) - offsetof(CaFormatSymlink, target))) return NULL; return s; } static const CaFormatDevice *validate_format_device(CaDecoder *d, const void *p) { const CaFormatDevice *dd = p; if (read_le64(&dd->header.size) != sizeof(CaFormatDevice)) return NULL; if (read_le64(&dd->header.type) != CA_FORMAT_DEVICE) return NULL; if (!(d->feature_flags & CA_FORMAT_WITH_DEVICE_NODES)) return NULL; if (!validate_major(read_le64(&dd->major))) return NULL; if (!validate_minor(read_le64(&dd->minor))) return NULL; return dd; } static const CaFormatPayload* validate_format_payload(CaDecoder *d, const void *q) { const CaFormatPayload *p = q; if (read_le64(&p->header.size) < offsetof(CaFormatPayload, data)) return NULL; if (read_le64(&p->header.type) != CA_FORMAT_PAYLOAD) return NULL; return p; } static const CaFormatFilename* validate_format_filename(CaDecoder *d, const void *p) { const CaFormatFilename *f = p; if (read_le64(&f->header.size) < offsetof(CaFormatFilename, name) + 1) return NULL; if (read_le64(&f->header.type) != CA_FORMAT_FILENAME) return NULL; if (!validate_filename(f->name, read_le64(&f->header.size) - offsetof(CaFormatFilename, name))) return NULL; return f; } static inline const CaFormatGoodbyeTail* CA_FORMAT_GOODBYE_TO_TAIL(const CaFormatGoodbye *g) { return (const CaFormatGoodbyeTail*) ((uint8_t*) g + read_le64(&g->header.size) - sizeof(CaFormatGoodbyeTail)); } static const CaFormatGoodbye *validate_format_goodbye(CaDecoder *d, const void *p) { const CaFormatGoodbye *g = p; const CaFormatGoodbyeTail *t; uint64_t l; assert(sizeof(CaFormatGoodbyeTail) == sizeof(CaFormatGoodbyeItem)); if (read_le64(&g->header.size) < offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeTail)) return NULL; if (read_le64(&g->header.type) != CA_FORMAT_GOODBYE) return NULL; l = read_le64(&g->header.size) - offsetof(CaFormatGoodbye, items) - sizeof(CaFormatGoodbyeTail); if (l % sizeof(CaFormatGoodbyeItem) != 0) return NULL; t = CA_FORMAT_GOODBYE_TO_TAIL(g); if (read_le64(&t->marker) != CA_FORMAT_GOODBYE_TAIL_MARKER) return NULL; if (read_le64(&t->size) != read_le64(&g->header.size)) return NULL; /* Here we only very superficially validate the validity of the entry offset, the caller has to add a more precise test */ if (read_le64(&t->entry_offset) < read_le64(&g->header.size)) return NULL; if (read_le64(&t->entry_offset) == UINT64_MAX) return NULL; return g; } static int compare_format_acl_user(const CaFormatACLUser *a, const CaFormatACLUser *b) { bool a_has_name, b_has_name; int r; assert(a); assert(b); if (read_le64(&a->uid) < read_le64(&b->uid)) return -1; if (read_le64(&a->uid) > read_le64(&b->uid)) return 1; a_has_name = read_le64(&a->header.size) > offsetof(CaFormatACLUser, name); b_has_name = read_le64(&b->header.size) > offsetof(CaFormatACLUser, name); if (!a_has_name && b_has_name) return -1; if (a_has_name && !b_has_name) return 1; if (a_has_name && b_has_name) { r = strcmp(a->name, b->name); if (r != 0) return r; } if (read_le64(&a->permissions) < read_le64(&b->permissions)) return -1; if (read_le64(&a->permissions) > read_le64(&b->permissions)) return 1; return 0; } static int compare_format_acl_group(const CaFormatACLGroup *a, const CaFormatACLGroup *b) { bool a_has_name, b_has_name; int r; assert(a); assert(b); if (read_le64(&a->gid) < read_le64(&b->gid)) return -1; if (read_le64(&a->gid) > read_le64(&b->gid)) return 1; a_has_name = read_le64(&a->header.size) > offsetof(CaFormatACLGroup, name); b_has_name = read_le64(&b->header.size) > offsetof(CaFormatACLGroup, name); if (!a_has_name && b_has_name) return -1; if (a_has_name && !b_has_name) return 1; if (a_has_name && b_has_name) { r = strcmp(a->name, b->name); if (r != 0) return r; } if (read_le64(&a->permissions) < read_le64(&b->permissions)) return -1; if (read_le64(&a->permissions) > read_le64(&b->permissions)) return 1; return 0; } static const CaFormatGoodbyeItem* format_goodbye_search_inner( const CaFormatGoodbyeItem *table, uint64_t n, uint64_t h, uint64_t i, uint64_t *idx) { const CaFormatGoodbyeItem *f; uint64_t p; assert(table); if (i >= n) return NULL; p = read_le64(&table[i].hash); if (p == h) { /* There might be multiple entries with the same hash value in the table. We'll skip *idx of those */ if (*idx == 0) return table + i; (*idx) --; } if (h <= p) { f = format_goodbye_search_inner(table, n, h, 2*i+1, idx); if (f) return f; } if (h >= p) { f = format_goodbye_search_inner(table, n, h, 2*i+2, idx); if (f) return f; } return NULL; } static uint64_t format_goodbye_items(const CaFormatGoodbye *g) { uint64_t n; assert(g); if (g->header.size < offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeTail)) return UINT64_MAX; n = g->header.size - offsetof(CaFormatGoodbye, items) - sizeof(CaFormatGoodbyeTail); if (n % sizeof(CaFormatGoodbyeItem) != 0) return UINT64_MAX; return n / sizeof(CaFormatGoodbyeItem); } static const CaFormatGoodbyeItem* format_goodbye_search( const CaFormatGoodbye *g, const char *name, uint64_t idx) { uint64_t n, hash; assert(g); assert(name); hash = siphash24(name, strlen(name), (const uint8_t[16]) CA_FORMAT_GOODBYE_HASH_KEY); n = format_goodbye_items(g); if (n == UINT64_MAX) return NULL; return format_goodbye_search_inner(g->items, n, hash, 0, &idx); } static int path_get_component(const char **p, char **ret) { const char *q; char *copy; size_t n; assert(p); assert(*p); /* Skip initial slashes */ q = *p + strspn(*p, "/"); /* Figure out length of next component */ n = strcspn(q, "/"); if (n == 0) { /* There is no more component */ *p = q; *ret = NULL; return 0; } if (ret) { copy = strndup(q, n); if (!copy) return -ENOMEM; *ret = copy; } q += n; *p = q + strspn(q, "/"); return 1; } enum { PATH_MATCH_NO = -1, PATH_MATCH_FINAL = 0, PATH_MATCH_MORE = 1, }; static int path_match_component(const char *p, const char *component) { const char *q; size_t n; assert(p); assert(component); /* Matches a path component against the specified path. * * Returns < 0 if no match * Returns 0 if match and last component of path * Returns > 0 if match and more components are coming * */ p += strspn(p, "/"); n = strlen(component); if (strncmp(p, component, n) != 0) return PATH_MATCH_NO; /* No match */ q = p + n; if (!IN_SET(*q, 0, '/')) return PATH_MATCH_NO; /* more text coming after the component */ q += strspn(q, "/"); if (*q == 0) return PATH_MATCH_FINAL; /* last component of path */ return PATH_MATCH_MORE; /* more coming */ } static int ca_decoder_do_seek(CaDecoder *d, CaDecoderNode *n) { char *child_name; const char *p; mode_t mode; int r; if (!d) return -EINVAL; if (!n) return -EINVAL; if (!d->seek_subpath) return -EUNATCH; if (n->entry_offset == UINT64_MAX) return -EUNATCH; /* Seeking works like this: depending on how much information we have: * * - If we already are at the right place, return to the entry object * - If we know the goodbye object already, we use it and jump to the filename object * - If we know the offset of the goodbye object, we jump to it * - If we know the end offset, we jump to the GOODBYE tail structure before it to read the GOODBYE start offset * - Otherwise we fail, as we don't have enough information to execute the seek operation * * Each time, if we haven't reached the goal yet, we'll be invoked again with the relevant step executed. * */ p = d->seek_subpath; r = path_get_component(&p, &child_name); if (r < 0) return r; if (r == 0) { if (d->seek_next_sibling) { /* We are at the entry whose next sibling we shall seek to? If so, do so */ if (n->end_offset == UINT64_MAX) return -EUNATCH; r = ca_decoder_leave_child(d); if (r == 0) { /* We are at the top already? */ ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } d->seek_offset = n->end_offset; d->seek_end_offset = UINT64_MAX; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_NEXT_SIBLING); } else if (d->seek_payload != UINT64_MAX) { /* We are at the entry we wanted to go to, but shall now proceed directly to an offset in the * payload of it */ if (n->payload_offset == UINT64_MAX) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISREG(mode)) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } if (d->seek_payload > n->size) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } d->seek_offset = n->payload_offset + d->seek_payload; d->seek_end_offset = n->end_offset; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_PAYLOAD); } else { /* We are already at the goal? If so, let's seek to the beginning of the entry */ d->seek_offset = n->entry_offset; d->seek_end_offset = n->end_offset; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_ENTRY); } return 1; } else { /* If we are supposed to descend further, but this is not actually a directory, then complain immediately */ mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISDIR(mode)) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } n->dirents_invalid = true; } if (n->goodbye) { const CaFormatGoodbyeItem *item; uint64_t so; /* We already loaded the goodbye object for this entry. Use it for searching */ if (n->goodbye_offset == UINT64_MAX || n->entry_offset == UINT64_MAX) return -EUNATCH; item = format_goodbye_search(n->goodbye, child_name, d->seek_idx); free(child_name); if (!item) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } if (read_le64(&item->size) > read_le64(&item->offset)) return -EBADMSG; if (read_le64(&item->offset) > n->goodbye_offset) return -EBADMSG; so = n->goodbye_offset - read_le64(&item->offset); if (so < n->entry_offset) return -EBADMSG; d->seek_offset = so; d->seek_end_offset = so + read_le64(&item->size); ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_FILENAME); return 1; } free(child_name); if (n->goodbye_offset != UINT64_MAX) { /* we know the offset of the GOODBYE object, but haven't loaded it yet. Do so now */ d->seek_offset = n->goodbye_offset; d->seek_end_offset = UINT64_MAX; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_GOODBYE); return 1; } if (n->end_offset != UINT64_MAX) { /* We know the end of the whole shebang, jump to its last le64_t to read the goodbye object offset */ if (n->end_offset < sizeof(CaFormatGoodbyeTail)) return -EBADMSG; d->seek_offset = n->end_offset - sizeof(CaFormatGoodbyeTail); d->seek_end_offset = n->end_offset; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_GOODBYE_TAIL); return 1; } return -ESPIPE; } static int ca_decoder_write_digest(CaDecoder *d, CaDigest **digest, const void *p, size_t l) { int r; if (!d) return -EINVAL; if (!digest) return -EINVAL; r = ca_digest_ensure_allocated(digest, ca_feature_flags_to_digest_type(d->feature_flags)); if (r < 0) return r; ca_digest_write(*digest, p, l); return 0; } static int ca_decoder_parse_entry(CaDecoder *d, CaDecoderNode *n) { const CaFormatEntry *entry = NULL; const CaFormatUser *user = NULL; const CaFormatGroup *group = NULL; const CaFormatSymlink *symlink = NULL; const CaFormatPayload *payload = NULL; const CaFormatDevice *device = NULL; const CaFormatFilename *filename = NULL; const CaFormatGoodbye *goodbye = NULL; const CaFormatACLGroupObj *acl_group_obj = NULL; const CaFormatACLDefault *acl_default = NULL; const CaFormatSELinux *selinux = NULL; const CaFormatFCaps *fcaps = NULL; uint64_t offset = 0; bool done = false; mode_t mode; size_t sz; void *p; int r; assert(d); assert(n); assert(IN_SET(d->state, CA_DECODER_INIT, CA_DECODER_ENTERED, CA_DECODER_ENTERED_FOR_SEEK)); /* Make sure we flush out anything we might already have parsed */ ca_decoder_node_flush_entry(n); p = realloc_buffer_data(&d->buffer); sz = realloc_buffer_size(&d->buffer); for (;;) { const CaFormatHeader *h; uint64_t t, l, flags; if (sz < sizeof(CaFormatHeader)) /* Not read enough yet */ return CA_DECODER_REQUEST; h = p; l = read_le64(&h->size); if (l < sizeof(CaFormatHeader)) return -EBADMSG; if (l == UINT64_MAX) return -EBADMSG; t = read_le64(&h->type); /* fprintf(stderr, "Got object: %016" PRIx64 " (%s) @%" PRIu64 "\n", */ /* t, */ /* strna(ca_format_type_name(t)), */ /* d->archive_offset + offset); */ switch (t) { case CA_FORMAT_ENTRY: if (entry) return -EBADMSG; if (l != sizeof(CaFormatEntry)) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; entry = validate_format_entry(d, p); if (!entry) return -EBADMSG; /* Is this file too new for us? */ flags = read_le64(&entry->feature_flags); if ((flags & ~CA_FORMAT_FEATURE_FLAGS_MAX) != 0) return -EPROTONOSUPPORT; offset += l; break; case CA_FORMAT_USER: if (!entry) return -EBADMSG; if (user) return -EBADMSG; if (group) return -EBADMSG; if (n->xattrs_first) return -EBADMSG; if (n->have_acl) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_USER_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; user = validate_format_user(d, p); if (!user) return -EBADMSG; if ((d->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) && read_le64(&entry->uid) == 0) return -EBADMSG; offset += l; break; case CA_FORMAT_GROUP: if (!entry) return -EBADMSG; if (group) return -EBADMSG; if (n->xattrs_first) return -EBADMSG; if (n->have_acl) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_GROUP_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; group = validate_format_group(d, p); if (!group) return -EBADMSG; if ((d->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) && read_le64(&entry->gid) == 0) return -EBADMSG; offset += l; break; case CA_FORMAT_XATTR: { const struct CaFormatXAttr *x; CaDecoderExtendedAttribute *u; if (!entry) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (n->have_acl) return -EBADMSG; if (l > CA_FORMAT_XATTR_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; x = validate_format_xattr(d, p); if (!x) return -EBADMSG; /* Check whether things are properly ordered */ if (n->xattrs_last && strcmp((char*) x->name_and_value, (char*) n->xattrs_last->format.name_and_value) <= 0) return -EBADMSG; /* Add to list of extended attributes */ u = malloc(offsetof(CaDecoderExtendedAttribute, format) + l); if (!u) return -ENOMEM; memcpy(&u->format, x, l); u->next = NULL; u->previous = n->xattrs_last; if (n->xattrs_last) n->xattrs_last->next = u; else n->xattrs_first = u; n->xattrs_last = u; offset += l; break; } case CA_FORMAT_ACL_USER: if (n->acl_group) return -EBADMSG; if (acl_group_obj) return -EBADMSG; if (acl_default) return -EBADMSG; if (n->acl_default_user) return -EBADMSG; /* fall through */ case CA_FORMAT_ACL_DEFAULT_USER: { const struct CaFormatACLUser *u; CaDecoderACLEntry *a; if (!entry) return -EBADMSG; if (n->acl_default_group) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_ACL_USER_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; u = validate_format_acl_user(d, p); if (!u) return -EBADMSG; if (t == CA_FORMAT_ACL_USER) { if (n->acl_user && compare_format_acl_user(u, &n->acl_user->user) <= 0) return -EBADMSG; } else { if (n->acl_default_user && compare_format_acl_user(u, &n->acl_default_user->user) <= 0) return -EBADMSG; } a = malloc(offsetof(CaDecoderACLEntry, user) + l); if (!a) return -ENOMEM; memcpy(&a->user, u, l); if (t == CA_FORMAT_ACL_USER) { a->next = n->acl_user; n->acl_user = a; } else { a->next = n->acl_default_user; n->acl_default_user = a; } n->have_acl = true; offset += l; break; } case CA_FORMAT_ACL_GROUP: if (acl_group_obj) return -EBADMSG; if (acl_default) return -EBADMSG; if (n->acl_default_user || n->acl_default_group) return -EBADMSG; /* fall through */ case CA_FORMAT_ACL_DEFAULT_GROUP: { const struct CaFormatACLGroup *u; CaDecoderACLEntry *a; if (!entry) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_ACL_GROUP_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; u = validate_format_acl_group(d, p); if (!u) return -EBADMSG; if (t == CA_FORMAT_ACL_GROUP) { if (n->acl_group && compare_format_acl_group(u, &n->acl_group->group) <= 0) return -EBADMSG; } else { if (n->acl_default_group && compare_format_acl_group(u, &n->acl_default_group->group) <= 0) return -EBADMSG; } a = malloc(offsetof(CaDecoderACLEntry, group) + l); if (!a) return -ENOMEM; memcpy(&a->group, u, l); if (t == CA_FORMAT_ACL_GROUP) { a->next = n->acl_group; n->acl_group = a; } else { a->next = n->acl_default_group; n->acl_default_group = a; } n->have_acl = true; offset += l; break; } case CA_FORMAT_ACL_GROUP_OBJ: if (!entry) return -EBADMSG; if (acl_group_obj) return -EBADMSG; if (acl_default) return -EBADMSG; if (n->acl_default_user || n->acl_default_group) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l != sizeof(CaFormatACLGroupObj)) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; acl_group_obj = validate_format_acl_group_obj(d, p); if (!acl_group_obj) return -EBADMSG; n->have_acl = true; offset += l; break; case CA_FORMAT_ACL_DEFAULT: if (!entry) return -EBADMSG; if (acl_default) return -EBADMSG; if (n->acl_default_user || n->acl_default_group) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l != sizeof(CaFormatACLDefault)) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; acl_default = validate_format_acl_default(d, p); if (!acl_default) return -EBADMSG; n->have_acl = true; offset += l; break; case CA_FORMAT_SELINUX: if (!entry) return -EBADMSG; if (selinux) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_SELINUX_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; selinux = validate_format_selinux(d, p); if (!selinux) return -EBADMSG; offset += l; break; case CA_FORMAT_FCAPS: if (!entry) return -EBADMSG; if (fcaps) return -EBADMSG; if (l > CA_FORMAT_FCAPS_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; fcaps = validate_format_fcaps(d, p); if (!fcaps) return -EBADMSG; offset += l; break; case CA_FORMAT_SYMLINK: if (!entry) return -EBADMSG; if (!S_ISLNK(read_le64(&entry->mode))) return -EBADMSG; if (l > CA_FORMAT_SYMLINK_SIZE_MAX) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; symlink = validate_format_symlink(d, p); if (!symlink) return -EBADMSG; offset += l; done = true; break; case CA_FORMAT_DEVICE: if (!entry) return -EBADMSG; if (!S_ISCHR(read_le64(&entry->mode)) && !S_ISBLK(read_le64(&entry->mode))) return -EBADMSG; if (l != sizeof(CaFormatDevice)) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; device = validate_format_device(d, p); if (!device) return -EBADMSG; offset += l; done = true; break; case CA_FORMAT_PAYLOAD: if (!entry) return -EBADMSG; if (!S_ISREG(read_le64(&entry->mode))) return -EBADMSG; if (l < offsetof(CaFormatPayload, data)) return CA_DECODER_REQUEST; payload = validate_format_payload(d, p); if (!payload) return -EBADMSG; offset += offsetof(CaFormatPayload, data); /* only skip over the payload header, not the payload itself */ done = true; break; case CA_FORMAT_FILENAME: if (!entry) return -EBADMSG; if (l < offsetof(CaFormatFilename, name) + 1) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; filename = validate_format_filename(d, p); if (!filename) return -EBADMSG; /* Note that we don't increase "offset" here, as we want to process it as part of the next * state. */ done = true; break; case CA_FORMAT_GOODBYE: if (!entry) return -EBADMSG; if (!S_ISDIR(read_le64(&entry->mode))) return -EBADMSG; if (l < offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeTail)) return -EBADMSG; r = ca_decoder_object_is_complete(p, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; goodbye = validate_format_goodbye(d, p); if (!goodbye) return -EBADMSG; /* Note that we don't increase "offset" here, as we want to process it as part of the next * state */ done = true; break; default: log_error("Got unexpected object: %016" PRIx64, t); return -EBADMSG; } if (done) break; p = (uint8_t*) p + l; sz -= l; } if (!entry) return -EBADMSG; if (user && !(d->feature_flags & CA_FORMAT_WITH_USER_NAMES)) return -EBADMSG; if (group && !(d->feature_flags & CA_FORMAT_WITH_USER_NAMES)) return -EBADMSG; if ((d->feature_flags & CA_FORMAT_WITH_USER_NAMES) && !(d->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) && (!user || !group)) return -EBADMSG; mode = (mode_t) read_le64(&entry->mode); if (S_ISREG(mode) && !payload) return -EBADMSG; if (S_ISDIR(mode) && !(filename || goodbye)) return -EBADMSG; if (S_ISLNK(mode) && !symlink) return -EBADMSG; if ((S_ISBLK(mode) || S_ISCHR(mode)) && !device) return -EBADMSG; if (!S_ISREG(mode) && fcaps) return -EBADMSG; /* Both FAT and chattr(1) flags are only defined for regular files and directories */ if (read_le64(&entry->flags) != 0 && !S_ISREG(mode) && !S_ISDIR(mode)) return -EBADMSG; /* The top-level node must be a directory */ if (CA_DECODER_AT_ROOT(d) && !S_ISDIR(mode)) return -EBADMSG; /* xattrs/ALCs are not defined for symlinks */ if (S_ISLNK(mode) && (n->xattrs_first || n->have_acl)) return -EBADMSG; /* If there's at least one USER or GROUP entry in the access ACL, we also mus have a MASK entry (which is * stored in the stat() gid data) and hence a separate GROUP_OBJ */ if ((n->acl_user || n->acl_group) && !acl_group_obj) return -EBADMSG; /* If there's at least one USER or GROUP entry in the default ACL, we also must have a default MASK entry */ if ((n->acl_default_user || n->acl_default_group) && (!acl_default || read_le64(&acl_default->mask_permissions) == UINT64_MAX)) return -EBADMSG; /* Default ACLs are only defined for directories */ if ((n->acl_default_user || n->acl_default_group || acl_default) && !S_ISDIR(mode)) return -EBADMSG; assert(!n->entry); assert(!n->user_name); assert(!n->group_name); assert(!n->fcaps); assert(!n->symlink_target); assert(!n->selinux_label); n->entry = memdup(entry, sizeof(CaFormatEntry)); if (!n->entry) return -ENOMEM; if (user) { n->user_name = strdup(user->name); if (!n->user_name) return -ENOMEM; } if (group) { n->group_name = strdup(group->name); if (!n->group_name) return -ENOMEM; } if (acl_group_obj) n->acl_group_obj_permissions = read_le64(&acl_group_obj->permissions); if (acl_default) { n->acl_default_user_obj_permissions = read_le64(&acl_default->user_obj_permissions); n->acl_default_group_obj_permissions = read_le64(&acl_default->group_obj_permissions); n->acl_default_other_permissions = read_le64(&acl_default->other_permissions); n->acl_default_mask_permissions = read_le64(&acl_default->mask_permissions); } if (selinux) { n->selinux_label = strdup(selinux->label); if (!n->selinux_label) return -ENOMEM; } if (fcaps) { n->fcaps = memdup(fcaps->data, read_le64(&fcaps->header.size) - offsetof(CaFormatFCaps, data)); if (!n->fcaps) return -ENOMEM; n->fcaps_size = read_le64(&fcaps->header.size) - offsetof(CaFormatFCaps, data); n->have_fcaps = true; } if (symlink) { n->symlink_target = strdup(symlink->target); if (!n->symlink_target) return -ENOMEM; } if (device) n->rdev = makedev(read_le64(&device->major), read_le64(&device->minor)); if (payload) n->size = read_le64(&payload->header.size) - offsetof(CaFormatPayload, data); if (S_ISREG(mode)) n->payload_offset = d->archive_offset + offset; if (d->state == CA_DECODER_ENTERED_FOR_SEEK) { r = ca_decoder_do_seek(d, n); if (r < 0) return r; if (r == 0) return CA_DECODER_NOT_FOUND; return CA_DECODER_STEP; } ca_decoder_enter_state(d, CA_DECODER_ENTRY); d->step_size = offset; ca_decoder_write_digest(d, &d->archive_digest, realloc_buffer_data(&d->buffer), d->step_size); if (d->want_payload_digest) { ca_digest_reset(d->payload_digest); d->payload_digest_invalid = false; } if (d->want_hardlink_digest) { ca_digest_reset(d->hardlink_digest); ca_decoder_write_digest(d, &d->hardlink_digest, realloc_buffer_data(&d->buffer), d->step_size); d->hardlink_digest_invalid = false; } return CA_DECODER_NEXT_FILE; } static void ca_decoder_reset_seek(CaDecoder *d) { assert(d); d->seek_path = mfree(d->seek_path); d->seek_subpath = NULL; d->seek_idx = 0; d->seek_offset = UINT64_MAX; d->seek_end_offset = UINT64_MAX; d->seek_next_sibling = false; d->seek_payload = UINT64_MAX; } static int ca_decoder_parse_filename(CaDecoder *d, CaDecoderNode *n) { const CaFormatFilename *filename = NULL; const CaFormatGoodbye *goodbye = NULL; const CaFormatHeader *h; uint64_t l, t; size_t sz; int r; assert(d); assert(IN_SET(d->state, CA_DECODER_IN_DIRECTORY, CA_DECODER_SEEKING_TO_FILENAME, CA_DECODER_SEEKING_TO_NEXT_SIBLING, CA_DECODER_SEEKING_TO_GOODBYE)); sz = realloc_buffer_size(&d->buffer); if (sz < sizeof(CaFormatHeader)) return CA_DECODER_REQUEST; h = realloc_buffer_data(&d->buffer); l = read_le64(&h->size); if (l < sizeof(CaFormatHeader)) return -EBADMSG; if (l == UINT64_MAX) return -EBADMSG; t = read_le64(&h->type); switch (t) { case CA_FORMAT_FILENAME: { CaDecoderNode *child; bool seek_continues = false, arrived = false; uint64_t end_offset = UINT64_MAX; if (!IN_SET(d->state, CA_DECODER_IN_DIRECTORY, CA_DECODER_SEEKING_TO_FILENAME, CA_DECODER_SEEKING_TO_NEXT_SIBLING)) return -EBADMSG; if (l < offsetof(CaFormatFilename, name) + 1) return -EBADMSG; r = ca_decoder_object_is_complete(h, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; filename = validate_format_filename(d, h); if (!filename) return -EBADMSG; switch (d->state) { case CA_DECODER_IN_DIRECTORY: if (d->delete && !n->dirents_invalid) { _cleanup_free_ char *nd = NULL; nd = strdup(filename->name); if (!nd) return -ENOMEM; if (!GREEDY_REALLOC(n->dirents, n->n_dirents_allocated, n->n_dirents + 2)) return -ENOMEM; n->dirents[n->n_dirents++] = nd; nd = NULL; n->dirents[n->n_dirents] = NULL; } break; case CA_DECODER_SEEKING_TO_FILENAME: { int match; assert(d->seek_path); match = path_match_component(d->seek_subpath, filename->name); if (match == PATH_MATCH_NO) { /* We seeked to an incorrect entry. In that case we most likely had hash value * collision. Let's pick the next entry with the same hash value. */ d->seek_idx++; r = ca_decoder_do_seek(d, n); if (r < 0) return r; if (r == 0) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return CA_DECODER_NOT_FOUND; } return CA_DECODER_STEP; } end_offset = d->seek_end_offset; d->seek_idx = 0; if (match == PATH_MATCH_MORE || d->seek_next_sibling || d->seek_payload != UINT64_MAX) { /* This entry lies within our path, but the seek is not complete yet */ seek_continues = true; /* Move subpath ptr ahead to component we need to process next. */ r = path_get_component(&d->seek_subpath, NULL); if (r < 0) return r; assert(r > 0); } else { assert(match == PATH_MATCH_FINAL); /* We reached our goal, yay! */ ca_decoder_reset_seek(d); /* Make sure that a later iteration won't go up from this */ d->boundary_node_idx = d->node_idx+1; /* We arrived at the destination of the seek, report that */ arrived = true; } break; } case CA_DECODER_SEEKING_TO_NEXT_SIBLING: ca_decoder_reset_seek(d); arrived = true; break; default: break; } child = ca_decoder_init_child(d); if (!child) return -EFBIG; child->entry_offset = d->archive_offset + l; if (end_offset != UINT64_MAX) child->end_offset = end_offset; child->name = strdup(filename->name); if (!child->name) return -ENOMEM; r = ca_decoder_enter_child(d); if (r < 0) return r; if (seek_continues) ca_decoder_enter_state(d, CA_DECODER_ENTERED_FOR_SEEK); else ca_decoder_enter_state(d, CA_DECODER_ENTERED); d->step_size = l; if (d->want_archive_digest) { if (arrived) ca_digest_reset(d->archive_digest); else if (!seek_continues) ca_decoder_write_digest(d, &d->archive_digest, realloc_buffer_data(&d->buffer), d->step_size); } return arrived ? CA_DECODER_FOUND : CA_DECODER_STEP; } case CA_FORMAT_GOODBYE: if (!IN_SET(d->state, CA_DECODER_IN_DIRECTORY, CA_DECODER_SEEKING_TO_GOODBYE, CA_DECODER_SEEKING_TO_NEXT_SIBLING)) return -EBADMSG; if (l < offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeTail)) return -EBADMSG; r = ca_decoder_object_is_complete(h, sz); if (r < 0) return r; if (r == 0) return CA_DECODER_REQUEST; goodbye = validate_format_goodbye(d, h); if (!goodbye) return -EBADMSG; if (n->entry_offset != UINT64_MAX && d->archive_offset != UINT64_MAX) { const CaFormatGoodbyeTail *tail; tail = CA_FORMAT_GOODBYE_TO_TAIL(goodbye); if (read_le64(&tail->entry_offset) != d->archive_offset - n->entry_offset) return -EBADMSG; } free(n->goodbye); n->goodbye = memdup(goodbye, sz); if (!n->goodbye) return -ENOMEM; if (d->state == CA_DECODER_SEEKING_TO_GOODBYE) { r = ca_decoder_do_seek(d, n); if (r < 0) return r; if (r == 0) return CA_DECODER_NOT_FOUND; return CA_DECODER_STEP; } ca_decoder_enter_state(d, CA_DECODER_GOODBYE); d->step_size = l; ca_decoder_write_digest(d, &d->archive_digest, realloc_buffer_data(&d->buffer), d->step_size); return CA_DECODER_STEP; default: log_error("Got unexpected object: %016" PRIx64, t); return -EBADMSG; } } static int ca_decoder_parse_goodbye_tail(CaDecoder *d, CaDecoderNode *n) { const CaFormatGoodbyeTail *tail; uint64_t l, q; size_t sz; int r; assert(d); assert(d->state == CA_DECODER_SEEKING_TO_GOODBYE_TAIL); assert(n); if (d->archive_offset == UINT64_MAX) return -ESPIPE; sz = realloc_buffer_size(&d->buffer); if (sz < sizeof(CaFormatGoodbyeTail)) return CA_DECODER_REQUEST; tail = realloc_buffer_data(&d->buffer); if (read_le64(&tail->marker) != CA_FORMAT_GOODBYE_TAIL_MARKER) return -EBADMSG; l = read_le64(&tail->size); if (l < offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeTail)) return -EBADMSG; if ((l - offsetof(CaFormatGoodbye, items) - sizeof(CaFormatGoodbyeTail)) % sizeof(CaFormatGoodbyeItem) != 0) return -EBADMSG; if (l > d->archive_offset + sizeof(CaFormatGoodbyeTail)) return -EBADMSG; q = read_le64(&tail->entry_offset); if (q < sizeof(CaFormatEntry)) return -EBADMSG; if (q > d->archive_offset + sizeof(CaFormatGoodbyeTail) - l) return -EBADMSG; /* The top-level ENTRY must be starting at offset 0 */ if (d->nodes == n && d->archive_offset + sizeof(CaFormatGoodbyeTail) != l + q) return -EBADMSG; n->goodbye_offset = d->archive_offset + sizeof(CaFormatGoodbyeTail) - l; /* With the new information we acquired, try to seek to the right place now */ r = ca_decoder_do_seek(d, n); if (r < 0) return r; if (r == 0) return CA_DECODER_NOT_FOUND; return CA_DECODER_STEP; } static int ca_decoder_node_get_fd(CaDecoder *d, CaDecoderNode *n) { assert(d); assert(n); /* Returns the fd of a node, if there's one set. If not, will return -1 except for the boundary node, if one is * configured, where the boundary fd is returned */ if (n->fd >= 0) return n->fd; assert(n >= d->nodes && n < d->nodes + d->n_nodes); if ((size_t) (n - d->nodes) + 1 == d->boundary_node_idx) return d->boundary_fd; return -1; } static int mkdir_or_mksubvol(CaDecoder *d, int dir_fd, CaDecoderNode *n, const char *name) { int r; assert(dir_fd >= 0); assert(n); assert(name); if (d->replay_feature_flags & read_le64(&n->entry->flags) & CA_FORMAT_WITH_SUBVOLUME ) { struct btrfs_ioctl_vol_args args = {}; mode_t saved; size_t l; l = strlen(name); if (l > sizeof(args.name)) return -EINVAL; memcpy(args.name, name, l); saved = umask(0077); r = ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0 ? -errno : 0; umask(saved); } else r = mkdirat(dir_fd, name, 0700) < 0 ? -errno : 0; return r; } static int ca_decoder_realize_child(CaDecoder *d, CaDecoderNode *n, CaDecoderNode *child) { mode_t mode; int dir_fd, r; assert(d); assert(n); assert(child); if (CA_DECODER_IS_SEEKING(d)) return 0; if (child->fd >= 0) return 0; dir_fd = ca_decoder_node_get_fd(d, n); if (dir_fd < 0) return 0; assert(child->entry); assert(child->name); assert(!child->temporary_name); mode = read_le64(&child->entry->mode); switch (mode & S_IFMT) { case S_IFDIR: r = mkdir_or_mksubvol(d, dir_fd, child, child->name); if (r < 0 && r != -EEXIST) return r; child->fd = openat(dir_fd, child->name, O_CLOEXEC|O_NOCTTY|O_RDONLY|O_DIRECTORY|O_NOFOLLOW); if (child->fd < 0) { /* If there's something else already in place, then let's create a temporary directory first */ if (!IN_SET(errno, ENOTDIR, ELOOP)) return -errno; r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; r = mkdir_or_mksubvol(d, dir_fd, child, child->temporary_name); if (r < 0) return r; child->fd = openat(dir_fd, child->temporary_name, O_CLOEXEC|O_NOCTTY|O_RDONLY|O_DIRECTORY|O_NOFOLLOW); if (child->fd < 0) { r = -errno; (void) unlinkat(dir_fd, child->temporary_name, AT_REMOVEDIR); return r; } } break; case S_IFREG: r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; child->fd = openat(dir_fd, child->temporary_name, O_CLOEXEC|O_NOCTTY|O_WRONLY|O_NOFOLLOW|O_CREAT|O_EXCL, 0600 | mode); if (child->fd < 0) return -errno; break; case S_IFLNK: if ((d->replay_feature_flags & CA_FORMAT_WITH_SYMLINKS) == 0) return 0; r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; if (symlinkat(child->symlink_target, dir_fd, child->temporary_name) < 0) return -errno; break; case S_IFIFO: if ((d->replay_feature_flags & CA_FORMAT_WITH_FIFOS) == 0) return 0; r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; if (mkfifoat(dir_fd, child->temporary_name, mode) < 0) return -errno; break; case S_IFBLK: case S_IFCHR: if ((d->replay_feature_flags & CA_FORMAT_WITH_DEVICE_NODES) == 0) return 0; r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; if (mknodat(dir_fd, child->temporary_name, mode, child->rdev) < 0) return -errno; break; case S_IFSOCK: if ((d->replay_feature_flags & CA_FORMAT_WITH_SOCKETS) == 0) return 0; r = tempfn_random(child->name, &child->temporary_name); if (r < 0) return r; if (mknodat(dir_fd, child->temporary_name, mode, 0) < 0) return -errno; break; default: assert(false); } if (child->fd >= 0) { /* A select few chattr() attributes need to be applied (or are better applied) on empty * files/directories instead of the final result, do so here. */ r = mask_attr_fd(child->fd, ca_feature_flags_to_chattr(read_le64(&child->entry->flags)), ca_feature_flags_to_chattr(d->replay_feature_flags) & APPLY_EARLY_FS_FL); if (r < 0) return r; } return 0; } static uid_t ca_decoder_shift_uid(CaDecoder *d, uid_t uid) { uid_t result; assert(d); if (!uid_is_valid(uid)) return UID_INVALID; if (d->uid_range != 0) result = uid % d->uid_range; else result = uid; if (d->uid_shift + result < d->uid_shift) return UID_INVALID; result += d->uid_shift; return result; } static gid_t ca_decoder_shift_gid(CaDecoder *d, gid_t gid) { return (gid_t) ca_decoder_shift_uid(d, (uid_t) gid); } static int name_to_uid(CaDecoder *d, const char *name, uid_t *ret) { uid_t parsed_uid; long bufsize; int r; assert(d); assert(name); assert(ret); if (streq_ptr(name, d->cached_user_name)) { *ret = d->cached_uid; return 1; } if (parse_uid(name, &parsed_uid) >= 0) { uid_t shifted_uid; shifted_uid = ca_decoder_shift_uid(d, parsed_uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; *ret = shifted_uid; return 1; } bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); if (bufsize <= 0) bufsize = 4096; for (;;) { struct passwd pwbuf, *pw = NULL; _cleanup_free_ char *buf = NULL; buf = malloc(bufsize); if (!buf) return -ENOMEM; r = getpwnam_r(name, &pwbuf, buf, (size_t) bufsize, &pw); if (r == 0 && pw) { free(d->cached_user_name); d->cached_user_name = strdup(pw->pw_name); d->cached_uid = pw->pw_uid; *ret = pw->pw_uid; return 1; } if (r != ERANGE) return r > 0 ? -r : -ESRCH; bufsize *= 2; } } static int name_to_gid(CaDecoder *d, const char *name, gid_t *ret) { gid_t parsed_gid; long bufsize; int r; assert(d); assert(name); assert(ret); if (streq_ptr(name, d->cached_group_name)) { *ret = d->cached_gid; return 1; } if (parse_gid(name, &parsed_gid) >= 0) { uid_t shifted_gid; shifted_gid = ca_decoder_shift_gid(d, parsed_gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; *ret = shifted_gid; return 1; } bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); if (bufsize <= 0) bufsize = 4096; for (;;) { struct group grbuf, *gr = NULL; _cleanup_free_ char *buf = NULL; buf = malloc(bufsize); if (!buf) return -ENOMEM; r = getgrnam_r(name, &grbuf, buf, (size_t) bufsize, &gr); if (r == 0 && gr) { free(d->cached_group_name); d->cached_group_name = strdup(gr->gr_name); d->cached_gid = gr->gr_gid; *ret = gr->gr_gid; return 1; } if (r != ERANGE) return r > 0 ? -r : -ESRCH; bufsize *= 2; } } static int acl_add_entry_full(acl_t *acl, acl_tag_t tag, const void *qualifier, uint64_t permissions) { acl_permset_t permset; acl_entry_t entry; assert(acl); if (acl_create_entry(acl, &entry) < 0) return -errno; if (acl_set_tag_type(entry, tag) < 0) return -errno; if (qualifier) if (acl_set_qualifier(entry, qualifier) < 0) return -errno; if (acl_get_permset(entry, &permset) < 0) return -errno; if (permissions & CA_FORMAT_ACL_PERMISSION_READ) if (acl_add_perm(permset, ACL_READ) < 0) return -errno; if (permissions & CA_FORMAT_ACL_PERMISSION_WRITE) if (acl_add_perm(permset, ACL_WRITE) < 0) return -errno; if (permissions & CA_FORMAT_ACL_PERMISSION_EXECUTE) if (acl_add_perm(permset, ACL_EXECUTE) < 0) return -errno; return 0; } static int ca_decoder_acl_add_user_entries(CaDecoder *d, acl_t *acl, CaDecoderACLEntry *entries) { CaDecoderACLEntry *i; int r; assert(d); assert(acl); for (i = entries; i; i = i->next) { uid_t uid; if (read_le64(&i->user.header.size) > offsetof(CaFormatACLUser, name)) { r = name_to_uid(d, i->user.name, &uid); if (r < 0) return r; } else { uid = (uid_t) read_le64(&i->user.uid); uid = ca_decoder_shift_uid(d, uid); if (!uid_is_valid(uid)) return -EINVAL; } r = acl_add_entry_full(acl, ACL_USER, &uid, read_le64(&i->user.permissions)); if (r < 0) return r; } return 0; } static int ca_decoder_acl_add_group_entries(CaDecoder *d, acl_t* acl, CaDecoderACLEntry *entries) { CaDecoderACLEntry *i; int r; assert(d); assert(acl); for (i = entries; i; i = i->next) { gid_t gid; if (read_le64(&i->group.header.size) > offsetof(CaFormatACLGroup, name)) { r = name_to_gid(d, i->group.name, &gid); if (r < 0) return r; } else { gid = (gid_t) read_le64(&i->group.gid); gid = ca_decoder_shift_gid(d, gid); if (!gid_is_valid(gid)) return -EINVAL; } r = acl_add_entry_full(acl, ACL_GROUP, &gid, read_le64(&i->group.permissions)); if (r < 0) return r; } return 0; } static inline uint64_t mode_user_to_acl_permissions(mode_t m) { return (((uint64_t) m) >> 6) & 7; } static inline uint64_t mode_group_to_acl_permissions(mode_t m) { return (((uint64_t) m) >> 3) & 7; } static inline uint64_t mode_other_to_acl_permissions(mode_t m) { return ((uint64_t) m) & 7; } static int ca_decoder_child_to_acl(CaDecoder *d, CaDecoderNode *n, acl_t *ret) { mode_t mode; acl_t acl; int r; assert(d); assert(n); assert(ret); mode = read_le64(&n->entry->mode); acl = acl_init(5); if (!acl) return -ENOMEM; r = acl_add_entry_full(&acl, ACL_USER_OBJ, NULL, mode_user_to_acl_permissions(mode)); if (r < 0) goto fail; r = acl_add_entry_full(&acl, ACL_OTHER, NULL, mode_other_to_acl_permissions(mode)); if (r < 0) goto fail; if (n->acl_group_obj_permissions != UINT64_MAX) { /* The mask field is optional under certain conditions. If it is defined then the stat() data will * report it instead of the group mask. */ r = acl_add_entry_full(&acl, ACL_MASK, NULL, mode_group_to_acl_permissions(mode)); if (r < 0) goto fail; r = acl_add_entry_full(&acl, ACL_GROUP_OBJ, NULL, n->acl_group_obj_permissions); } else r = acl_add_entry_full(&acl, ACL_GROUP_OBJ, NULL, mode_group_to_acl_permissions(mode)); if (r < 0) goto fail; r = ca_decoder_acl_add_user_entries(d, &acl, n->acl_user); if (r < 0) goto fail; r = ca_decoder_acl_add_group_entries(d, &acl, n->acl_group); if (r < 0) goto fail; *ret = acl; return 0; fail: acl_free(acl); return r; } static int ca_decoder_child_to_default_acl(CaDecoder *d, CaDecoderNode *n, acl_t *ret) { acl_t acl; int r; assert(d); assert(n); assert(ret); acl = acl_init(5); if (!acl) return -ENOMEM; if (n->acl_default_user_obj_permissions != UINT64_MAX) { r = acl_add_entry_full(&acl, ACL_USER_OBJ, NULL, n->acl_default_user_obj_permissions); if (r < 0) goto fail; r = acl_add_entry_full(&acl, ACL_GROUP_OBJ, NULL, n->acl_default_group_obj_permissions); if (r < 0) goto fail; r = acl_add_entry_full(&acl, ACL_OTHER, NULL, n->acl_default_other_permissions); if (r < 0) goto fail; if (n->acl_default_mask_permissions != UINT64_MAX) { r = acl_add_entry_full(&acl, ACL_MASK, NULL, n->acl_default_mask_permissions); if (r < 0) goto fail; } r = ca_decoder_acl_add_user_entries(d, &acl, n->acl_default_user); if (r < 0) goto fail; r = ca_decoder_acl_add_group_entries(d, &acl, n->acl_default_group); if (r < 0) goto fail; } *ret = acl; return 0; fail: acl_free(acl); return r; } static int ca_decoder_node_reflink(CaDecoder *d, CaDecoderNode *n) { uint64_t offset = 0; mode_t mode; size_t i; int r; assert(d); assert(n); if (!d->reflink) return 0; if (n->fd < 0) return 0; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISREG(mode)) return 0; for (i = 0; i < ca_origin_items(n->payload_origin); i++) { CaLocation *l; l = ca_origin_get(n->payload_origin, i); assert(l); if (l->designator == CA_LOCATION_PAYLOAD) { uint64_t reflinked; int source_fd; source_fd = ca_location_open(l); if (source_fd == -ENOENT) { log_error_errno(source_fd, "Can't open reflink source %s: %m", ca_location_format(l)); goto next; } if (source_fd < 0) return source_fd; r = reflink_fd(source_fd, l->offset, n->fd, offset, l->size, &reflinked); safe_close(source_fd); if (r == -EBADR) /* the offsets are not multiples of 512 */ goto next; if (r == -EXDEV) /* cross-device reflinks aren't supported */ goto next; if (IN_SET(r, -ENOTTY, -EOPNOTSUPP)) /* reflinks not supported */ break; if (r < 0) return r; d->n_reflink_bytes += reflinked; } next: offset += l->size; } return 0; } static int comparison_fn_strcmpp(const void *x, const void *y) { const char* const *a = x, * const* b = y; return strcmp(*a, *b); } static inline void* safe_bsearch( const void *key, const void *base, size_t nmemb, size_t size, comparison_fn_t fn) { /* A wrapper that makes sure we can bsearch and don't have to pass a non-NULL base for nmemb == 0 */ if (nmemb == 0) return NULL; return bsearch(key, base, nmemb, size, fn); } static int ca_decoder_node_delete(CaDecoder *d, CaDecoderNode *n) { int r, fd_copy; mode_t mode; DIR *dd; assert(d); assert(n); /* If enabled, delete all files and directories below the selected directory that weren't listed in our * archive. */ if (!d->delete) return 0; if (n->fd < 0) return 0; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISDIR(mode)) return 0; if (n->dirents_invalid) return -ENODATA; fd_copy = fcntl(n->fd, F_DUPFD_CLOEXEC, 3); if (fd_copy < 0) return -errno; dd = fdopendir(fd_copy); if (!dd) { safe_close(fd_copy); return -errno; } for (;;) { struct dirent *de; const char *key; errno = 0; de = readdir(dd); if (!de) { if (errno != 0) { r = -errno; goto finish; } break; } if (dot_or_dot_dot(de->d_name)) continue; key = de->d_name; if (safe_bsearch(&key, n->dirents, n->n_dirents, sizeof(char*), comparison_fn_strcmpp)) continue; r = rm_rf_at(n->fd, de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|(d->undo_immutable ? REMOVE_UNDO_IMMUTABLE : 0)); if (r < 0) goto finish; } r = 0; finish: closedir(dd); return r; } static int drop_immutable(int dir_fd, const char *name) { _cleanup_(safe_closep) int fd = -1; struct stat st; if (dir_fd < 0) return -EBADF; if (!name) return -EINVAL; fd = openat(dir_fd, name, O_CLOEXEC|O_RDONLY|O_NOFOLLOW|O_NOCTTY|O_NONBLOCK); if (fd < 0) { if (errno == ELOOP) /* symlinks don't have an immutable bit */ return 0; return -errno; } if (fstat(fd, &st) < 0) return -errno; /* Only regular files and directories have an immutable bit */ if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return 0; return mask_attr_fd(fd, 0, FS_IMMUTABLE_FL); } static int ca_decoder_install_file(CaDecoder *d, int dir_fd, const char *temporary_name, const char *name) { int r; assert(d); assert(dir_fd >= 0); assert(temporary_name); assert(name); r = renameat(dir_fd, temporary_name, dir_fd, name) < 0 ? -errno : 0; if (r == -EPERM && d->undo_immutable) { /* Renaming the file failed. This could be because the "immutable" flag was set on the destination. Let's see if we can drop that */ r = drop_immutable(dir_fd, name); if (r < 0) return r; if (r == 0) /* Couldn't change? Then propagate the EPERM */ r = -EPERM; else /* try again... */ r = renameat(dir_fd, temporary_name, dir_fd, name) < 0 ? -errno : 0; } if (r < 0) { if (!IN_SET(r, -ENOTDIR, -EISDIR)) return r; /* The destination exists already, and we couldn't rename the file overriding it. This most likely * happened because we tried to move a directory over a regular file or vice versa. Let's now try to * swap the temporary file and the destination. If that works, we can remove the temporary file, and * expose atomic behaviour to the outside. */ r = renameat2(dir_fd, temporary_name, dir_fd, name, RENAME_EXCHANGE) < 0 ? -errno : 0; if (r == -EPERM && d->undo_immutable) { /* EPERM could mean the immutable flag was set on the destination, let's see if we can drop that. */ r = drop_immutable(dir_fd, name); if (r < 0) return r; if (r == 0) /* Couldn't change? */ r = -EPERM; else r = renameat2(dir_fd, temporary_name, dir_fd, name, RENAME_EXCHANGE) < 0 ? -errno : 0; } if (r < 0) { /* If that didn't work (kernel too old?), then let's remove the destination first, and * then try again. Of course in this mode we lose atomicity, but it's the best we can * do */ (void) rm_rf_at(dir_fd, name, REMOVE_ROOT|REMOVE_PHYSICAL|(d->undo_immutable ? REMOVE_UNDO_IMMUTABLE : 0)); if (renameat(dir_fd, temporary_name, dir_fd, name) < 0) return -errno; } else { /* The exchange worked! In that case the temporary name is now the old version, let's remove it */ r = rm_rf_at(dir_fd, temporary_name, REMOVE_ROOT|REMOVE_PHYSICAL|(d->undo_immutable ? REMOVE_UNDO_IMMUTABLE : 0)); if (r < 0) return r; } } return 0; } static int ca_decoder_finalize_child(CaDecoder *d, CaDecoderNode *n, CaDecoderNode *child) { statfs_f_type_t magic = 0; const char *name; struct stat st; mode_t mode; int r, dir_fd; assert(d); assert(child); /* If the child got replaced by a hardlink to a seed file we don't need to finalize it. */ if (child->hardlinked) return 0; /* Finalizes the file attributes on the specified child node. 'n' specifies it's parent, except for the special * case where we are processing the root direction of the serialization, where it is NULL. */ if (n) dir_fd = ca_decoder_node_get_fd(d, n); else dir_fd = -1; if (dir_fd < 0 && child->fd < 0) return 0; /* Nothing to do if no fds are opened */ mode = ca_decoder_node_mode(child); if (mode == (mode_t) -1) return -EUNATCH; /* If this is a regular file, try to reflink everything. Note we do this both for naked files (unlike the rest * of the bits here) as well as for files in directory trees. */ if (S_ISREG(mode)) { r = ca_decoder_node_reflink(d, child); if (r < 0) return r; } /* If this is a naked file, then exit early, as we don't need to adjust metadata */ if (CA_DECODER_IS_NAKED(d)) return 0; /* Ignore entries we are not supposed to replay */ if (S_ISLNK(mode) && (d->replay_feature_flags & CA_FORMAT_WITH_SYMLINKS) == 0) return 0; if (S_ISFIFO(mode) && (d->replay_feature_flags & CA_FORMAT_WITH_FIFOS) == 0) return 0; if (S_ISSOCK(mode) && (d->replay_feature_flags & CA_FORMAT_WITH_SOCKETS) == 0) return 0; if ((S_ISBLK(mode) || S_ISCHR(mode)) && (d->replay_feature_flags & CA_FORMAT_WITH_DEVICE_NODES) == 0) return 0; name = child->temporary_name ?: child->name; if (child->fd >= 0) r = fstat(child->fd, &st); else { if (!n) return -EINVAL; r = fstatat(dir_fd, name, &st, AT_SYMLINK_NOFOLLOW); } if (r < 0) return -errno; if (st.st_dev == d->cached_st_dev) magic = d->cached_st_dev; else if (child->fd >= 0) { struct statfs sfs; if (fstatfs(child->fd, &sfs) < 0) return -errno; magic = d->cached_magic = sfs.f_type; d->cached_st_dev = st.st_dev; } if (((read_le64(&child->entry->mode) ^ st.st_mode) & S_IFMT) != 0) return -EEXIST; if ((S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) && st.st_rdev != child->rdev) return -EEXIST; if (S_ISLNK(st.st_mode)) { size_t l; ssize_t z; char *buf; if (!n) return -EINVAL; l = strlen(child->symlink_target); buf = newa(char, l+2); z = readlinkat(dir_fd, name, buf, l+1); if (z < 0) return -errno; if ((size_t) z != l) return -EEXIST; if (memcmp(child->symlink_target, buf, l) != 0) return -EEXIST; } if (S_ISDIR(st.st_mode)) { r = ca_decoder_node_delete(d, child); if (r < 0) return r; } if (d->replay_feature_flags & (CA_FORMAT_WITH_32BIT_UIDS|CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_USER_NAMES)) { uid_t uid; gid_t gid; if ((d->replay_feature_flags & CA_FORMAT_WITH_USER_NAMES) && child->user_name) { r = name_to_uid(d, child->user_name, &uid); if (r < 0) return r; } else { uid = (uid_t) read_le64(&child->entry->uid); uid = ca_decoder_shift_uid(d, uid); if (!uid_is_valid(uid)) return -EINVAL; } if ((d->replay_feature_flags & CA_FORMAT_WITH_USER_NAMES) && child->group_name) { r = name_to_gid(d, child->group_name, &gid); if (r < 0) return r; } else { gid = (gid_t) read_le64(&child->entry->gid); gid = ca_decoder_shift_gid(d, gid); if (!gid_is_valid(gid)) return -EINVAL; } if (st.st_uid != uid || st.st_gid != gid) { if (child->fd >= 0) r = fchown(child->fd, uid, gid); else { if (!n) return -EINVAL; r = fchownat(dir_fd, name, uid, gid, AT_SYMLINK_NOFOLLOW); } if (r < 0) return -errno; } } if (d->replay_feature_flags & CA_FORMAT_WITH_READ_ONLY) { if ((st.st_mode & 0400) == 0 || /* not readable? */ (S_ISDIR(st.st_mode) && (st.st_mode & 0100) == 0) || /* a dir, but not executable? */ !(read_le64(&child->entry->mode) & 0222) != !(st.st_mode & 0200)) { /* writable bit doesn't match what it should be? */ mode_t new_mode; new_mode = (st.st_mode & 0444) | 0400; if (S_ISDIR(st.st_mode)) new_mode |= 0100; if (read_le64(&child->entry->mode) & 0222) new_mode |= 0200 | ((new_mode & 0040) ? 0020 : 0000) | ((new_mode & 0004) ? 0002 : 0000); else new_mode &= ~0444; if (child->fd >= 0) r = fchmod(child->fd, new_mode); else { if (!n) return -EINVAL; r = fchmodat(dir_fd, name, new_mode, AT_SYMLINK_NOFOLLOW); } if (r < 0) return -errno; } } else if (d->replay_feature_flags & (CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_ACL)) { if ((st.st_mode & 07777) != (read_le64(&child->entry->mode) & 07777)) { if (child->fd >= 0) r = fchmod(child->fd, read_le64(&child->entry->mode) & 07777); else { if (!n) return -EINVAL; r = fchmodat(dir_fd, name, read_le64(&child->entry->mode) & 07777, AT_SYMLINK_NOFOLLOW); } if (r < 0) return -errno; } } if (d->replay_feature_flags & CA_FORMAT_WITH_ACL) { char proc_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; int path_fd = -1; acl_t new_acl; if (child->fd < 0) { if (!n) return -EINVAL; path_fd = openat(dir_fd, name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_PATH); if (path_fd < 0) return -errno; } sprintf(proc_path, "/proc/self/fd/%i", child->fd < 0 ? path_fd : child->fd); r = ca_decoder_child_to_acl(d, child, &new_acl); if (r < 0) { safe_close(path_fd); return r; } if (acl_set_file(proc_path, ACL_TYPE_ACCESS, new_acl) < 0 && (!IN_SET(errno, EOPNOTSUPP, EBADF) || child->have_acl)) { r = -errno; safe_close(path_fd); acl_free(new_acl); return r; } acl_free(new_acl); if (S_ISDIR(st.st_mode)) { acl_t new_default_acl; r = ca_decoder_child_to_default_acl(d, child, &new_default_acl); if (r < 0) { safe_close(path_fd); return r; } if (acl_set_file(proc_path, ACL_TYPE_DEFAULT, new_default_acl) < 0 && (!IN_SET(errno, EOPNOTSUPP, EBADF) || child->have_acl)) { r = -errno; safe_close(path_fd); acl_free(new_default_acl); return r; } acl_free(new_default_acl); } safe_close(path_fd); } if ((d->replay_feature_flags & CA_FORMAT_WITH_XATTRS) && !S_ISLNK(st.st_mode) && child->fd >= 0) { CaDecoderExtendedAttribute *x; size_t space = 256; ssize_t l; _cleanup_free_ char *p = NULL; char *q; p = new(char, space); if (!p) return -ENOMEM; for (;;) { l = flistxattr(child->fd, p, space); if (l < 0) { if (IN_SET(errno, EOPNOTSUPP, EBADF)) { l = 0; break; } if (errno != ERANGE) return -errno; } else break; if (space*2 <= space) return -ENOMEM; space *= 2; q = realloc(p, space); if (!q) return -ENOMEM; p = q; } /* Remove xattrs set that don't appear in our list */ q = p; for (;;) { bool found = false; size_t z; if (l == 0) break; z = strlen(q); assert(z + 1 <= (size_t) l); /* Don't bother with xattrs we can't store in our archives anyway */ if (!ca_xattr_name_store(q)) goto next; for (x = child->xattrs_first; x; x = x->next) if (streq((char*) x->format.name_and_value, q)) { found = true; break; } if (found) goto next; if (fremovexattr(child->fd, q) < 0) return -errno; next: q += z + 1; l -= z + 1; } for (x = child->xattrs_first; x; x = x->next) { size_t k; k = strlen((char*) x->format.name_and_value); if (fsetxattr(child->fd, (char*) x->format.name_and_value, x->format.name_and_value + k + 1, read_le64(&x->format.header.size) - offsetof(CaFormatXAttr, name_and_value) - k - 1, 0) < 0) return -errno; } } if ((d->replay_feature_flags & CA_FORMAT_WITH_FCAPS) && S_ISREG(st.st_mode) && child->fd >= 0) { if (child->have_fcaps) { if (fsetxattr(child->fd, "security.capability", child->fcaps, child->fcaps_size, 0) < 0) return -errno; } else { char v; /* Before removing the caps we'll check if they aren't set anyway. That has the benefit that we * don't run into EPERM here if we lack perms but the xattr isn't set anyway. */ if (fgetxattr(child->fd, "security.capability", &v, sizeof(v)) < 0) { /* If the underlying file system doesn't do xattrs or caps, that's OK, if we shall set * them as empty anyway. */ if (!IN_SET(errno, EOPNOTSUPP, ENODATA)) return -errno; } else { if (fremovexattr(child->fd, "security.capability") < 0) return -errno; } } } if (d->replay_feature_flags & CA_FORMAT_WITH_SELINUX) { #if HAVE_SELINUX _cleanup_free_ char *subpath = NULL; char *label = NULL; bool update = false; if (child->fd >= 0) r = fgetfilecon(child->fd, &label) < 0 ? -errno : 0; else { if (asprintf(&subpath, "/proc/self/fd/%i/%s", dir_fd, name) < 0) return -ENOMEM; r = lgetfilecon(subpath, &label) < 0 ? -errno : 0; } if (r == -EOPNOTSUPP) { if (child->selinux_label) return -EOPNOTSUPP; /* If the backing file system doesn't support labels, and we are not supposed to set any, then that's fine */ } else if (r == -ENODATA) /* If there has been no label assigned so far, then update if we need to set one now */ update = !!child->selinux_label; else if (r < 0) /* In all other error cases propagate the error */ return r; else { update = !streq_ptr(child->selinux_label, label); freecon(label); } if (update) { if (child->selinux_label) { if (child->fd >= 0) r = fsetfilecon(child->fd, child->selinux_label) < 0 ? -errno : 0; else { assert(subpath); r = lsetfilecon(subpath, child->selinux_label) < 0 ? -errno : 0; } } else { if (child->fd >= 0) r = fremovexattr(child->fd, "security.selinux") < 0 && errno != ENODATA ? -errno : 0; else { assert(subpath); r = lremovexattr(subpath, "security.selinux") < 0 && errno != -ENODATA ? -errno : 0; } } } else r = 0; if (r < 0) return r; #else if (child->selinux_label) return -EOPNOTSUPP; #endif } if (d->replay_feature_flags & (CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_NSEC_TIME|CA_FORMAT_WITH_2SEC_TIME)) { struct timespec ts[2] = { { .tv_nsec = UTIME_OMIT }, nsec_to_timespec(read_le64(&child->entry->mtime)), }; if (child->fd >= 0) r = futimens(child->fd, ts); else { if (!n) return -EINVAL; r = utimensat(dir_fd, name, ts, AT_SYMLINK_NOFOLLOW); } if (r < 0) return -errno; } if (child->temporary_name && child->name) { /* Move the final result into place. Note that the chattr() file attributes we only apply after this, * as they might make prohibit us from renaming the file (consider the "immutable" flag) */ r = ca_decoder_install_file(d, dir_fd, child->temporary_name, child->name); if (r < 0) return r; child->temporary_name = mfree(child->temporary_name); name = child->name; } if ((d->replay_feature_flags & CA_FORMAT_WITH_CHATTR) != 0 && child->fd >= 0) { unsigned value, mask; value = ca_feature_flags_to_chattr(read_le64(&child->entry->flags)); mask = ca_feature_flags_to_chattr(d->replay_feature_flags); r = mask_attr_fd(child->fd, value, mask); if (r < 0) return r; } if ((d->replay_feature_flags & CA_FORMAT_WITH_FAT_ATTRS) != 0 && child->fd >= 0) { unsigned value, mask; value = ca_feature_flags_to_fat_attrs(read_le64(&child->entry->flags)); mask = ca_feature_flags_to_fat_attrs(d->replay_feature_flags); if (IN_SET(magic, MSDOS_SUPER_MAGIC, FUSE_SUPER_MAGIC)) { r = mask_fat_attr_fd(child->fd, value, mask); if (r < 0) return r; } else if ((value & mask) != 0) return -EOPNOTSUPP; } if (d->replay_feature_flags & CA_FORMAT_WITH_SUBVOLUME) { bool is_subvol; is_subvol = F_TYPE_EQUAL(magic, BTRFS_SUPER_MAGIC) && st.st_ino == 256; if (!!(read_le64(&child->entry->flags) & CA_FORMAT_WITH_SUBVOLUME) != is_subvol) return -EEXIST; if ((d->replay_feature_flags & CA_FORMAT_WITH_SUBVOLUME_RO) && is_subvol && child->fd >= 0) { uint64_t bflags, nflags; if (ioctl(child->fd, BTRFS_IOC_SUBVOL_GETFLAGS, &bflags) < 0) return -errno; if (read_le64(&child->entry->flags) & CA_FORMAT_WITH_SUBVOLUME_RO) nflags = bflags | BTRFS_SUBVOL_RDONLY; else nflags = bflags & ~BTRFS_SUBVOL_RDONLY; if (nflags != bflags) { if (ioctl(child->fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) return -errno; } } } return 0; } static void ca_decoder_apply_seek_offset(CaDecoder *d) { assert(d); d->archive_offset = d->seek_offset; d->step_size = 0; d->eof = false; realloc_buffer_empty(&d->buffer); ca_origin_flush(d->buffer_origin); } static int ca_decoder_step_node(CaDecoder *d, CaDecoderNode *n) { mode_t mode; int r; assert(d); assert(n); mode = ca_decoder_node_mode(n); switch (d->state) { case CA_DECODER_INIT: if (CA_DECODER_IS_NAKED(d)) { assert(CA_DECODER_AT_ROOT(d)); /* A regular file or block device and we are at the top level, process this as payload */ ca_decoder_enter_state(d, CA_DECODER_IN_PAYLOAD); return ca_decoder_step_node(d, n); } /* fall through */ case CA_DECODER_ENTERED: case CA_DECODER_ENTERED_FOR_SEEK: return ca_decoder_parse_entry(d, n); case CA_DECODER_SEEKING_TO_ENTRY: /* If we enter the entry we reached our goal, and can flush out all seek info */ ca_decoder_reset_seek(d); /* Make sure we never leave this node again through iteration */ d->boundary_node_idx = d->node_idx; ca_decoder_enter_state(d, CA_DECODER_ENTERED); ca_digest_reset(d->archive_digest); return CA_DECODER_FOUND; case CA_DECODER_ENTRY: { CaDecoderNode *parent; parent = ca_decoder_current_parent_node(d); if (parent) { r = ca_decoder_realize_child(d, parent, n); if (r < 0) return r; } if (mode == (mode_t) -1) return -EUNATCH; if (S_ISREG(mode)) { ca_decoder_enter_state(d, CA_DECODER_IN_PAYLOAD); return ca_decoder_step_node(d, n); } if (S_ISDIR(mode)) { ca_decoder_enter_state(d, CA_DECODER_IN_DIRECTORY); return ca_decoder_step_node(d, n); } ca_decoder_enter_state(d, CA_DECODER_FINALIZE); return CA_DECODER_DONE_FILE; } case CA_DECODER_IN_PAYLOAD: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISREG(mode) || S_ISBLK(mode)); /* If the size of this payload is known, and we reached it, we are done */ if (n->size != UINT64_MAX) { assert(d->payload_offset <= n->size); if (d->payload_offset == n->size) { ca_decoder_enter_state(d, CA_DECODER_FINALIZE); return CA_DECODER_DONE_FILE; } } if (realloc_buffer_size(&d->buffer) > 0) { if (n->size == UINT64_MAX) d->step_size = realloc_buffer_size(&d->buffer); else d->step_size = MIN(realloc_buffer_size(&d->buffer), n->size - d->payload_offset); if (d->want_archive_digest) ca_decoder_write_digest(d, &d->archive_digest, realloc_buffer_data(&d->buffer), d->step_size); if (d->want_payload_digest && !d->payload_digest_invalid) ca_decoder_write_digest(d, &d->payload_digest, realloc_buffer_data(&d->buffer), d->step_size); if (d->want_hardlink_digest && !d->hardlink_digest_invalid) ca_decoder_write_digest(d, &d->hardlink_digest, realloc_buffer_data(&d->buffer), d->step_size); return CA_DECODER_PAYLOAD; } if (d->eof) { /* EOF before the object was supposed to end? */ if (n->size != UINT64_MAX) return -EPIPE; /* There are still parent nodes around that wait for the GOODBYE object, and we got EOF inside this * file? */ if (!CA_DECODER_AT_ROOT(d)) return -EPIPE; /* If we don't know the length and get an EOF, we are happy and just consider this the end of the payload */ ca_decoder_enter_state(d, CA_DECODER_FINALIZE); /* If this is a top-level regular file, then do not generate CA_DECODER_DONE_FILE, as there is no file to speak of realy */ if (CA_DECODER_IS_NAKED(d)) { assert(n == d->nodes); return ca_decoder_step_node(d, n); } return CA_DECODER_DONE_FILE; } /* If the caller doesn't want the payload, and we don't need it either, but know how large it is, then let's skip over it */ if (!d->payload && !d->want_payload_digest && n->fd < 0 && n->size != UINT64_MAX) { d->skip_bytes = n->size - d->payload_offset; ca_decoder_enter_state(d, CA_DECODER_SKIPPING); return CA_DECODER_SKIP; } return CA_DECODER_REQUEST; case CA_DECODER_SKIPPING: d->archive_offset += d->skip_bytes; d->skip_bytes = 0; ca_decoder_enter_state(d, CA_DECODER_FINALIZE); return CA_DECODER_DONE_FILE; case CA_DECODER_IN_DIRECTORY: case CA_DECODER_SEEKING_TO_FILENAME: case CA_DECODER_SEEKING_TO_NEXT_SIBLING: case CA_DECODER_SEEKING_TO_GOODBYE: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); return ca_decoder_parse_filename(d, n); case CA_DECODER_GOODBYE: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); ca_decoder_enter_state(d, CA_DECODER_FINALIZE); return CA_DECODER_DONE_FILE; case CA_DECODER_FINALIZE: { CaDecoderNode *saved_child = n; r = ca_decoder_leave_child(d); if (r < 0) return r; if (r > 0) { /* We managed to one level up */ n = ca_decoder_current_node(d); if (!n) return -EUNATCH; r = ca_decoder_finalize_child(d, n, saved_child); if (r < 0) return r; ca_decoder_enter_state(d, CA_DECODER_IN_DIRECTORY); return CA_DECODER_STEP; } /* We already are at the top level. Now also fix up the top-level entry */ r = ca_decoder_finalize_child(d, ca_decoder_current_parent_node(d), n); if (r < 0) return r; ca_decoder_enter_state(d, CA_DECODER_EOF); return CA_DECODER_FINISHED; } case CA_DECODER_PREPARING_SEEK_TO_OFFSET: assert(CA_DECODER_IS_NAKED(d)); if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISREG(mode) || S_ISBLK(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_OFFSET); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_SEEKING_TO_OFFSET: assert(CA_DECODER_IS_NAKED(d)); ca_decoder_enter_state(d, CA_DECODER_IN_PAYLOAD); d->payload_offset = d->seek_offset; ca_decoder_reset_seek(d); ca_digest_reset(d->archive_digest); d->payload_digest_invalid = d->hardlink_digest_invalid = true; return ca_decoder_step_node(d, n); case CA_DECODER_PREPARING_SEEK_TO_FILENAME: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_FILENAME); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_PREPARING_SEEK_TO_NEXT_SIBLING: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_NEXT_SIBLING); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_SEEKING_TO_PAYLOAD: ca_decoder_enter_state(d, CA_DECODER_IN_PAYLOAD); d->payload_offset = d->seek_payload; ca_decoder_reset_seek(d); ca_digest_reset(d->archive_digest); if (d->want_payload_digest) { d->payload_digest_invalid = d->payload_offset > 0; if (!d->payload_digest_invalid) ca_digest_reset(d->payload_digest); } d->hardlink_digest_invalid = true; return ca_decoder_step_node(d, n); case CA_DECODER_PREPARING_SEEK_TO_PAYLOAD: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISREG(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_PAYLOAD); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_PREPARING_SEEK_TO_ENTRY: ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_ENTRY); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_PREPARING_SEEK_TO_GOODBYE: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_GOODBYE); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_PREPARING_SEEK_TO_GOODBYE_TAIL: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); ca_decoder_enter_state(d, CA_DECODER_SEEKING_TO_GOODBYE_TAIL); ca_decoder_apply_seek_offset(d); return CA_DECODER_SEEK; case CA_DECODER_SEEKING_TO_GOODBYE_TAIL: if (mode == (mode_t) -1) return -EUNATCH; assert(S_ISDIR(mode)); return ca_decoder_parse_goodbye_tail(d, n); case CA_DECODER_NOWHERE: return CA_DECODER_NOT_FOUND; default: assert(false); } return 0; } static int ca_decoder_advance_buffer(CaDecoder *d, CaDecoderNode *n) { int r; assert(d); assert(n); if (d->step_size <= 0) return 0; assert(d->step_size <= realloc_buffer_size(&d->buffer)); if (d->state == CA_DECODER_IN_PAYLOAD) { if (n->fd >= 0) { mode_t mode; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; /* If hole punching is supported and we are writing to a regular file, use it */ if (d->punch_holes && S_ISREG(mode)) { uint64_t n_punched; r = loop_write_with_holes(n->fd, realloc_buffer_data(&d->buffer), d->step_size, &n_punched); if (r < 0) return r; d->n_punch_holes_bytes += n_punched; } else { r = loop_write(n->fd, realloc_buffer_data(&d->buffer), d->step_size); if (r < 0) return r; } } if (d->reflink) { if (!n->payload_origin) { r = ca_origin_new(&n->payload_origin); if (r < 0) return r; } r = ca_origin_concat(n->payload_origin, d->buffer_origin, d->step_size); if (r < 0) return r; } d->payload_offset += d->step_size; } r = realloc_buffer_advance(&d->buffer, d->step_size); if (r < 0) return r; if (d->reflink) { r = ca_origin_advance_bytes(d->buffer_origin, d->step_size); if (r < 0) return r; } d->archive_offset += d->step_size; d->step_size = 0; return 0; } int ca_decoder_step(CaDecoder *d) { CaDecoderNode *n; int r; if (!d) return -EINVAL; if (d->state == CA_DECODER_EOF) return CA_DECODER_FINISHED; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; r = ca_decoder_advance_buffer(d, n); if (r < 0) return r; return ca_decoder_step_node(d, n); } int ca_decoder_get_request_offset(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; *ret = d->archive_offset + realloc_buffer_size(&d->buffer); return 0; } int ca_decoder_put_data(CaDecoder *d, const void *p, size_t size, CaOrigin *origin) { int r; if (!d) return -EINVAL; if (size == 0) return 0; if (!p) return -EINVAL; if (d->eof) return -EBUSY; if (size == 0) return 0; if (!realloc_buffer_append(&d->buffer, p, size)) return -ENOMEM; if (d->reflink) { if (!d->buffer_origin) { r = ca_origin_new(&d->buffer_origin); if (r < 0) return r; } if (!origin) r = ca_origin_put_void(d->buffer_origin, size); else r = ca_origin_concat(d->buffer_origin, origin, UINT64_MAX); if (r < 0) return r; } return 0; } int ca_decoder_put_eof(CaDecoder *d) { if (!d) return -EINVAL; d->eof = true; return 0; } int ca_decoder_get_payload(CaDecoder *d, const void **ret, size_t *ret_size) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISREG(mode) && !S_ISBLK(mode)) return -ENOTTY; if (d->state != CA_DECODER_IN_PAYLOAD) return -ENODATA; if (realloc_buffer_size(&d->buffer) == 0) return -ENODATA; if (d->step_size == 0) return -ENODATA; assert(d->step_size <= realloc_buffer_size(&d->buffer)); *ret = realloc_buffer_data(&d->buffer); *ret_size = d->step_size; return 0; } int ca_decoder_current_path(CaDecoder *d, char **ret) { _cleanup_free_ char *p = NULL; size_t n = 0, i; if (!d) return -EINVAL; if (!ret) return -EINVAL; if (d->n_nodes <= 0) return -EUNATCH; for (i = 1; i <= d->node_idx; i++) { CaDecoderNode *node; size_t k, nn; char *np, *q; node = d->nodes + i; assert(node->entry); k = strlen(node->name); nn = n + (n > 0) + k; np = realloc(p, nn+1); if (!np) return -ENOMEM; q = np + n; if (n > 0) *(q++) = '/'; strcpy(q, node->name); p = np; n = nn; } if (!p) { p = strdup(""); if (!p) return -ENOMEM; } *ret = p; p = NULL; return 0; } int ca_decoder_current_mode(CaDecoder *d, mode_t *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; *ret = mode; return 0; } int ca_decoder_current_target(CaDecoder *d, const char **ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISLNK(mode)) return -ENODATA; if (!n->symlink_target) return -ENODATA; *ret = n->symlink_target; return 0; } int ca_decoder_current_mtime(CaDecoder *d, uint64_t *ret) { CaDecoderNode *n; if (!d) return -EINVAL; if (!ret) return -EINVAL; if ((d->replay_feature_flags & (CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_2SEC_TIME)) == 0) return -ENODATA; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; if (!n->entry) return -ENODATA; *ret = read_le64(&n->entry->mtime); return 0; } int ca_decoder_current_size(CaDecoder *d, uint64_t *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISREG(mode)) return -ENODATA; *ret = n->size; return 0; } int ca_decoder_current_uid(CaDecoder *d, uid_t *ret) { uid_t uid, shifted_uid; CaDecoderNode *n; if (!d) return -EINVAL; if (!ret) return -EINVAL; if ((d->replay_feature_flags & (CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS)) == 0) return -ENODATA; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; if (!n->entry) return -ENODATA; uid = (uid_t) read_le64(&n->entry->uid); shifted_uid = ca_decoder_shift_uid(d, uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; *ret = shifted_uid; return 0; } int ca_decoder_current_gid(CaDecoder *d, gid_t *ret) { gid_t gid, shifted_gid; CaDecoderNode *n; if (!d) return -EINVAL; if (!ret) return -EINVAL; if ((d->replay_feature_flags & (CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS)) == 0) return -ENODATA; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; if (!n->entry) return -ENODATA; gid = (gid_t) read_le64(&n->entry->gid); shifted_gid = ca_decoder_shift_gid(d, gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; *ret = shifted_gid; return 0; } int ca_decoder_current_user(CaDecoder *d, const char **ret) { CaDecoderNode *n; if (!d) return -EINVAL; if (!ret) return -EINVAL; if ((d->replay_feature_flags & CA_FORMAT_WITH_USER_NAMES) == 0) return -ENODATA; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; if (!n->user_name) return -ENODATA; *ret = n->user_name; return 0; } int ca_decoder_current_group(CaDecoder *d, const char **ret) { CaDecoderNode *n; if (!d) return -EINVAL; if (!ret) return -EINVAL; if ((d->replay_feature_flags & CA_FORMAT_WITH_USER_NAMES) == 0) return -ENODATA; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; if (!n->group_name) return -ENODATA; *ret = n->group_name; return 0; } int ca_decoder_current_rdev(CaDecoder *d, dev_t *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISCHR(mode) && !S_ISBLK(mode)) return -ENODATA; *ret = n->rdev; return 0; } int ca_decoder_current_chattr(CaDecoder *d, unsigned *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISREG(mode) && !S_ISDIR(mode)) return -ENODATA; if (!n->entry) return -ENODATA; *ret = ca_feature_flags_to_chattr(read_le64(&n->entry->flags) & d->replay_feature_flags); return 0; } int ca_decoder_current_fat_attrs(CaDecoder *d, uint32_t *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISREG(mode) && !S_ISDIR(mode)) return -ENODATA; if (!n->entry) return -ENODATA; *ret = ca_feature_flags_to_fat_attrs(read_le64(&n->entry->flags) & d->replay_feature_flags); return 0; } int ca_decoder_current_xattr(CaDecoder *d, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size) { CaDecoderNode *n; CaDecoderExtendedAttribute *p = NULL; if (!d) return -EINVAL; if (!ret_name) return -EINVAL; if (where < 0) return -EINVAL; if (where >= _CA_ITERATE_MAX) return -EINVAL; if (!ret_name) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; switch (where) { case CA_ITERATE_NEXT: if (n->xattrs_current) p = n->xattrs_current->next; else p = NULL; break; case CA_ITERATE_PREVIOUS: if (n->xattrs_current) p = n->xattrs_current->previous; else p = NULL; break; case CA_ITERATE_FIRST: p = n->xattrs_first; break; case CA_ITERATE_LAST: p = n->xattrs_last; break; case CA_ITERATE_CURRENT: p = n->xattrs_current; break; case _CA_ITERATE_MAX: case _CA_ITERATE_INVALID: assert(false); } if (!p) goto eof; n->xattrs_current = p; *ret_name = (const char*) p->format.name_and_value; if (ret_value || ret_size) { const void *v; v = memchr(p->format.name_and_value, 0, read_le64(&p->format.header.size) - offsetof(CaFormatXAttr, name_and_value)); assert(v); v = (const uint8_t*) v + 1; if (ret_value) *ret_value = v; if (ret_size) *ret_size = read_le64(&p->format.header.size) - offsetof(CaFormatXAttr, name_and_value) - ((const uint8_t*) v - (const uint8_t*) p->format.name_and_value); } return 1; eof: *ret_name = NULL; if (ret_value) *ret_value = NULL; if (ret_size) *ret_size = 0; return 0; } int ca_decoder_current_offset(CaDecoder *d, uint64_t *ret) { CaDecoderNode *n; mode_t mode; if (!d) return -EINVAL; if (!ret) return -EINVAL; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISREG(mode) && !S_ISBLK(mode)) return -EISDIR; *ret = d->payload_offset; return 0; } int ca_decoder_seek_offset(CaDecoder *d, uint64_t offset) { /* Seek to the specified offset in the archive. Only supported when we decode a naked file, i.e. not a * directory tree serialization */ if (!d) return -EINVAL; if (offset == UINT64_MAX) return -EINVAL; if (d->n_nodes <= 0) return -EUNATCH; if (!CA_DECODER_IS_NAKED(d)) return -EISDIR; if (d->nodes[0].end_offset == UINT64_MAX) /* The top node must have a size set to be considered seekable */ return -ESPIPE; if (offset > d->nodes[0].end_offset) { ca_decoder_enter_state(d, CA_DECODER_NOWHERE); return 0; } if (offset == d->nodes[0].end_offset) { ca_decoder_enter_state(d, CA_DECODER_EOF); return 0; } ca_decoder_reset_seek(d); d->seek_offset = offset; ca_decoder_enter_state(d, CA_DECODER_PREPARING_SEEK_TO_OFFSET); d->payload_digest_invalid = d->hardlink_digest_invalid = true; return 0; } static int ca_decoder_seek_path_internal( CaDecoder *d, const char *path, bool next_sibling, uint64_t offset) { char *path_copy = NULL; const char *p; size_t new_idx; mode_t mode; int r; if (!d) return -EINVAL; if (!path) return -EINVAL; if (d->n_nodes <= 0) return -EUNATCH; if (d->nodes[0].end_offset == UINT64_MAX) /* The root directory must have a size set to be considered seekable */ return -ESPIPE; mode = ca_decoder_node_mode(d->nodes); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISDIR(mode)) return -ESPIPE; p = path + strspn(path, "/"); new_idx = 0; for (;;) { char *child_name = NULL; CaDecoderNode *child; const char *q; bool match; /* Determine the name of the immediate child we are supposed to enter */ q = p; r = path_get_component(&q, &child_name); if (r < 0) return r; if (r == 0) /* Yay, we already found where we were supposed to go */ break; if (new_idx + 1 >= d->n_nodes) { free(child_name); break; /* We can't descend any further because we haven't opened anything more so far. */ } child = d->nodes + new_idx + 1; match = streq_ptr(child_name, child->name); free(child_name); if (!match) break; /* The previously selected child doesn't match where we are supposed to go, let's stop here */ if (child->end_offset == UINT64_MAX) break; /* We don't know how large this child node is, probably because we are reading this * iteratively so far. In that case, don't make use of this, and instead check the name * table, so that we figure out the size. */ /* The name we are looking for matches the child already selected. In that case reuse it. */ p = q; new_idx ++; } path_copy = strdup(path); if (!path_copy) return -ENOMEM; ca_decoder_reset_seek(d); d->seek_path = path_copy; d->seek_subpath = path_copy + (p - path); d->seek_idx = 0; d->seek_next_sibling = next_sibling; d->seek_payload = offset; d->node_idx = new_idx; ca_decoder_forget_children(d); return ca_decoder_do_seek(d, d->nodes + new_idx); } int ca_decoder_seek_path(CaDecoder *d, const char *path) { return ca_decoder_seek_path_internal(d, path, false, UINT64_MAX); } int ca_decoder_seek_path_offset(CaDecoder *d, const char *path, uint64_t offset) { if (offset == UINT64_MAX) return -EINVAL; return ca_decoder_seek_path_internal(d, path, false, offset); } int ca_decoder_seek_next_sibling(CaDecoder *d) { _cleanup_free_ char *p = NULL; int r; if (!d) return -EINVAL; r = ca_decoder_current_path(d, &p); if (r < 0) return r; return ca_decoder_seek_path_internal(d, p, true, UINT64_MAX); } int ca_decoder_get_seek_offset(CaDecoder *d, uint64_t *ret) { /* Called by the consumer whenever we issued a CA_DECODER_SEEK event, and informs the caller to which absolute * byte index to seek to. */ if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!IN_SET(d->state, CA_DECODER_SEEKING_TO_OFFSET, CA_DECODER_SEEKING_TO_FILENAME, CA_DECODER_SEEKING_TO_NEXT_SIBLING, CA_DECODER_SEEKING_TO_ENTRY, CA_DECODER_SEEKING_TO_PAYLOAD, CA_DECODER_SEEKING_TO_GOODBYE, CA_DECODER_SEEKING_TO_GOODBYE_TAIL)) return -ENODATA; if (d->seek_offset == UINT64_MAX) return -ENODATA; *ret = d->seek_offset; return 0; } int ca_decoder_get_skip_size(CaDecoder *d, uint64_t *ret) { /* Called by the consumer whenever we issued a CA_DECODER_SKIP event, and informs the caller how many bytes to * skip in the input stream. */ if (!d) return -EINVAL; if (!ret) return -EINVAL; if (d->state != CA_DECODER_SKIPPING) return -ENODATA; if (d->skip_bytes == 0) return -ENODATA; *ret = d->skip_bytes; return 0; } int ca_decoder_set_archive_size(CaDecoder *d, uint64_t size) { if (!d) return -EINVAL; if (d->n_nodes <= 0) return -EUNATCH; d->nodes[0].end_offset = size; return 0; } int ca_decoder_set_punch_holes(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->punch_holes = enabled; return 0; } int ca_decoder_set_reflink(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->reflink = enabled; return 0; } int ca_decoder_set_hardlink(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->hardlink = enabled; return 0; } int ca_decoder_set_delete(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->delete = enabled; return 0; } int ca_decoder_set_payload(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->payload = enabled; return 0; } int ca_decoder_set_undo_immutable(CaDecoder *d, bool enabled) { if (!d) return -EINVAL; d->undo_immutable = enabled; return 0; } int ca_decoder_get_punch_holes_bytes(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->punch_holes) return -ENODATA; *ret = d->n_punch_holes_bytes; return 0; } int ca_decoder_get_reflink_bytes(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->reflink) return -ENODATA; *ret = d->n_reflink_bytes; return 0; } int ca_decoder_get_hardlink_bytes(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->hardlink) return -ENODATA; *ret = d->n_hardlink_bytes; return 0; } int ca_decoder_set_uid_shift(CaDecoder *d, uid_t u) { if (!d) return -EINVAL; d->uid_shift = u; return 0; } int ca_decoder_set_uid_range(CaDecoder *d, uid_t u) { if (!d) return -EINVAL; d->uid_range = u; return 0; } int ca_decoder_current_archive_offset(CaDecoder *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; *ret = d->archive_offset; return 0; } int ca_decoder_enable_archive_digest(CaDecoder *d, bool b) { if (!d) return -EINVAL; d->want_archive_digest = b; return 0; } int ca_decoder_enable_payload_digest(CaDecoder *d, bool b) { if (!d) return -EINVAL; d->want_payload_digest = b; return 0; } int ca_decoder_enable_hardlink_digest(CaDecoder *d, bool b) { if (!d) return -EINVAL; d->want_hardlink_digest = b; return 0; } int ca_decoder_get_archive_digest(CaDecoder *d, CaChunkID *ret) { const void *q; int r; if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->want_archive_digest) return -ENOMEDIUM; if (d->state != CA_DECODER_EOF) return -EBUSY; r = ca_digest_ensure_allocated(&d->archive_digest, ca_feature_flags_to_digest_type(d->feature_flags)); if (r < 0) return r; q = ca_digest_read(d->archive_digest); if (!q) return -EIO; assert(ca_digest_get_size(d->archive_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } int ca_decoder_get_payload_digest(CaDecoder *d, CaChunkID *ret) { CaDecoderNode *n; const void *q; mode_t mode; int r; if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->want_payload_digest) return -ENOMEDIUM; if (d->state != CA_DECODER_FINALIZE) return -EBUSY; if (d->payload_digest_invalid) return -ESTALE; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (!S_ISREG(mode) && !S_ISBLK(mode)) return -ENOTTY; r = ca_digest_ensure_allocated(&d->payload_digest, ca_feature_flags_to_digest_type(d->feature_flags)); if (r < 0) return r; q = ca_digest_read(d->payload_digest); if (!q) return -EIO; assert(ca_digest_get_size(d->payload_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } int ca_decoder_get_hardlink_digest(CaDecoder *d, CaChunkID *ret) { CaDecoderNode *n; const void *q; mode_t mode; int r; if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->want_hardlink_digest) return -ENOMEDIUM; if (d->state != CA_DECODER_FINALIZE) return -EBUSY; if (d->hardlink_digest_invalid) return -ESTALE; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -ENODATA; if (S_ISDIR(mode)) return -EISDIR; r = ca_digest_ensure_allocated(&d->hardlink_digest, ca_feature_flags_to_digest_type(d->feature_flags)); if (r < 0) return r; q = ca_digest_read(d->hardlink_digest); if (!q) return -EIO; assert(ca_digest_get_size(d->hardlink_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } static mode_t ca_decoder_mode_mask(CaDecoder *d) { assert(d); if (d->feature_flags & CA_FORMAT_WITH_PERMISSIONS) return 07777; if (d->feature_flags & CA_FORMAT_WITH_READ_ONLY) return 00200; return 0; } int ca_decoder_try_hardlink(CaDecoder *d, CaFileRoot *root, const char *path) { CaDecoderNode *n, *parent; struct stat st; mode_t mode; int r, dir_fd; if (!d) return -EINVAL; if (!root) return -EINVAL; if (!path) return -EINVAL; if (d->state != CA_DECODER_FINALIZE) return -EBUSY; if (!d->hardlink) return 0; parent = ca_decoder_current_parent_node(d); if (!parent) return -EUNATCH; if (parent->fd < 0) return 0; n = ca_decoder_current_node(d); if (!n) return -EUNATCH; mode = ca_decoder_node_mode(n); if (mode == (mode_t) -1) return -EUNATCH; if (!S_ISREG(mode)) return -ENOTTY; if (fstatat(root->fd, path, &st, AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW) < 0) { if (errno == ENOENT) /* vanished by now? */ return 0; return -errno; } /* Before we put the symlink in place, make some superficial checks if the node is still the same as when we * generated the seed for it. */ if (!S_ISREG(st.st_mode)) /* Not a regular file anymore? */ return 0; if ((uint64_t) st.st_size != n->size) return 0; if (((st.st_mode ^ mode) & ca_decoder_mode_mask(d)) != 0) return 0; assert(n->entry); if ((d->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) && (st.st_uid != read_le64(&n->entry->uid) || st.st_gid != read_le64(&n->entry->gid))) return 0; if (d->feature_flags & (CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_NSEC_TIME|CA_FORMAT_WITH_2SEC_TIME)) { uint64_t granularity; r = ca_feature_flags_time_granularity_nsec(d->feature_flags, &granularity); if (r < 0) return r; if (timespec_to_nsec(st.st_mtim) / granularity != read_le64(&n->entry->mtime) / granularity) return 0; } dir_fd = ca_decoder_node_get_fd(d, parent); if (dir_fd < 0) return dir_fd; if (linkat(root->fd, path, dir_fd, n->name, 0) < 0) { _cleanup_free_ char *t = NULL; /* NB: If a file system doesn't support hardlinks, it will return EPERM, yuck! */ if (IN_SET(errno, EXDEV, EMLINK, EPERM)) return 0; if (errno != EEXIST) return -errno; /* The file exists already. In that case, let's link it as temporary file first, and then rename it * (which makes the replacement nicely atomic) */ r = tempfn_random(n->name, &t); if (r < 0) return r; if (linkat(root->fd, path, dir_fd, t, 0) < 0) { if (IN_SET(errno, EXDEV, EMLINK, EPERM)) return 0; return -errno; } r = ca_decoder_install_file(d, dir_fd, t, n->name); if (r < 0) { (void) unlinkat(dir_fd, t, 0); return r; } } /* All good. Let's remove the file we just wrote, we don't need it if we managed to create the hard link */ assert(n->temporary_name); if (unlinkat(dir_fd, n->temporary_name, 0) < 0) return -errno; n->temporary_name = mfree(n->temporary_name); n->fd = safe_close(n->fd); n->hardlinked = true; d->n_hardlink_bytes += st.st_size; return 1; } src/cadecoder.h000066400000000000000000000125531322621430500137330ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocadecoderhfoo #define foocadecoderhfoo #include #include #include #include "cachunkid.h" #include "cacommon.h" #include "calocation.h" #include "caorigin.h" typedef struct CaDecoder CaDecoder; enum { /* Stream events */ CA_DECODER_FINISHED, /* The end of the stream has been reached */ CA_DECODER_STEP, /* We processed a bit, call us again */ CA_DECODER_NEXT_FILE, /* We started processing a new file */ CA_DECODER_DONE_FILE, /* We finished processing a file */ CA_DECODER_PAYLOAD, /* We have some payload data for you */ /* Requests to the caller */ CA_DECODER_REQUEST, /* We need more data */ CA_DECODER_SEEK, /* Please seek */ CA_DECODER_SKIP, /* Please skip some bytes */ /* Response to seeks */ CA_DECODER_FOUND, /* Seek completed successfully */ CA_DECODER_NOT_FOUND, /* Seek to file was requested, but file didn't exist */ }; CaDecoder *ca_decoder_new(void); CaDecoder *ca_decoder_unref(CaDecoder *d); /* The actual feature flags in effect if known */ int ca_decoder_get_feature_flags(CaDecoder *d, uint64_t *ret); /* The feature flags to expect. When the actual feature flags don't match this, this is considered an error */ int ca_decoder_set_expected_feature_flags(CaDecoder *d, uint64_t flags); /* A mask configuring which feature flags to actually honour when recreating files */ int ca_decoder_set_feature_flags_mask(CaDecoder *d, uint64_t flags); /* Various booleans to configure the mode of operation */ int ca_decoder_set_punch_holes(CaDecoder *d, bool enabled); int ca_decoder_set_reflink(CaDecoder *d, bool enabled); int ca_decoder_set_hardlink(CaDecoder *d, bool enabled); int ca_decoder_set_delete(CaDecoder *d, bool enabled); int ca_decoder_set_payload(CaDecoder *d, bool enabled); int ca_decoder_set_undo_immutable(CaDecoder *d, bool enabled); /* Apply UID shifting */ int ca_decoder_set_uid_shift(CaDecoder *e, uid_t u); int ca_decoder_set_uid_range(CaDecoder *e, uid_t u); /* Output: a file descriptor to a directory tree, block device node, or regular file */ int ca_decoder_set_base_fd(CaDecoder *d, int fd); int ca_decoder_set_boundary_fd(CaDecoder *d, int fd); /* Output: if no output to the file system is desired: specify instead what kind of object is to be read */ int ca_decoder_set_base_mode(CaDecoder *d, mode_t mode); /* Input: set the archive size, to make this seekable */ int ca_decoder_set_archive_size(CaDecoder *d, uint64_t size); /* The core of loop, returns one of the CA_DECODER_XYZ events defined above */ int ca_decoder_step(CaDecoder *d); /* If ca_decoder_step() returned CA_DECODER_REQUEST, which offset we are at now */ int ca_decoder_get_request_offset(CaDecoder *d, uint64_t *offset); /* If ca_decoder_step() returned CA_DECODER_SEEK, where are we supposed to seek now? (returns absolute position) */ int ca_decoder_get_seek_offset(CaDecoder *d, uint64_t *ret); /* If ca_decoder_step() returned CA_DECODER_SKIP, how many bytes are we supposed to skip? (returns relative number of bytes) */ int ca_decoder_get_skip_size(CaDecoder *d, uint64_t *ret); /* Input: archive stream data */ int ca_decoder_put_data(CaDecoder *d, const void *p, size_t size, CaOrigin *origin); int ca_decoder_put_eof(CaDecoder *d); /* Output: payload data */ int ca_decoder_get_payload(CaDecoder *d, const void **ret, size_t *ret_size); /* Retrieve information about where we currently are */ int ca_decoder_current_path(CaDecoder *d, char **ret); int ca_decoder_current_mode(CaDecoder *d, mode_t *ret); int ca_decoder_current_target(CaDecoder *d, const char **ret); int ca_decoder_current_mtime(CaDecoder *d, uint64_t *nsec); int ca_decoder_current_size(CaDecoder *d, uint64_t *size); int ca_decoder_current_uid(CaDecoder *d, uid_t *uid); int ca_decoder_current_gid(CaDecoder *d, gid_t *gid); int ca_decoder_current_user(CaDecoder *d, const char **user); int ca_decoder_current_group(CaDecoder *d, const char **user); int ca_decoder_current_rdev(CaDecoder *d, dev_t *ret); int ca_decoder_current_offset(CaDecoder *d, uint64_t *ret); int ca_decoder_current_chattr(CaDecoder *d, unsigned *ret); int ca_decoder_current_fat_attrs(CaDecoder *d, uint32_t *ret); int ca_decoder_current_xattr(CaDecoder *d, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size); /* Seeking to positions */ int ca_decoder_seek_offset(CaDecoder *d, uint64_t offset); int ca_decoder_seek_path(CaDecoder *d, const char *path); int ca_decoder_seek_path_offset(CaDecoder *d, const char *path, uint64_t offset); int ca_decoder_seek_next_sibling(CaDecoder *d); /* Statistics */ int ca_decoder_get_punch_holes_bytes(CaDecoder *d, uint64_t *ret); int ca_decoder_get_reflink_bytes(CaDecoder *d, uint64_t *ret); int ca_decoder_get_hardlink_bytes(CaDecoder *d, uint64_t *ret); int ca_decoder_current_archive_offset(CaDecoder *d, uint64_t *ret); int ca_decoder_enable_archive_digest(CaDecoder *d, bool b); int ca_decoder_enable_payload_digest(CaDecoder *d, bool b); int ca_decoder_enable_hardlink_digest(CaDecoder *d, bool b); int ca_decoder_get_archive_digest(CaDecoder *d, CaChunkID *ret); int ca_decoder_get_hardlink_digest(CaDecoder *d, CaChunkID *ret); int ca_decoder_get_payload_digest(CaDecoder *d, CaChunkID *ret); int ca_decoder_try_hardlink(CaDecoder *d, CaFileRoot *root, const char *path); #endif src/cadigest.c000066400000000000000000000120261322621430500135730ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "cadigest.h" #include "util.h" struct CaDigest { CaDigestType type; uint8_t result[CONST_MAX(SHA256_DIGEST_LENGTH, SHA512_DIGEST_LENGTH)]; union { SHA256_CTX sha256; SHA512_CTX sha512; }; }; void ca_digest_reset(CaDigest *d) { if (!d) return; switch (d->type) { case CA_DIGEST_SHA256: SHA256_Init(&d->sha256); break; case CA_DIGEST_SHA512_256: /* SHA512/256 is identical to SHA512 truncated to 256 bit, except that the start values are slightly * different. Since OpenSSL doesn't support them natively, let's hack them in here. As soon as OpenSSL * learns SHA512/256 natively, let's switch over to that. */ SHA512_Init(&d->sha512); d->sha512.h[0] = UINT64_C(0x22312194fc2bf72c); d->sha512.h[1] = UINT64_C(0x9f555fa3c84c64c2); d->sha512.h[2] = UINT64_C(0x2393b86b6f53b151); d->sha512.h[3] = UINT64_C(0x963877195940eabd); d->sha512.h[4] = UINT64_C(0x96283ee2a88effe3); d->sha512.h[5] = UINT64_C(0xbe5e1e2553863992); d->sha512.h[6] = UINT64_C(0x2b0199fc2c85b8aa); d->sha512.h[7] = UINT64_C(0x0eb72ddc81c52ca2); break; default: assert_not_reached("Unknown hash function"); } } int ca_digest_new(CaDigestType t, CaDigest **ret) { CaDigest *d; if (t < 0) return -EINVAL; if (t >= _CA_DIGEST_TYPE_MAX) return -EOPNOTSUPP; if (!ret) return -EINVAL; d = new0(CaDigest, 1); if (!d) return -ENOMEM; d->type = t; ca_digest_reset(d); *ret = d; return 0; } CaDigest *ca_digest_free(CaDigest *d) { return mfree(d); } void ca_digest_write(CaDigest *d, const void *p, size_t l) { if (!d) return; if (l <= 0) return; assert(p); switch (d->type) { case CA_DIGEST_SHA256: SHA256_Update(&d->sha256, p, l); break; case CA_DIGEST_SHA512_256: SHA512_Update(&d->sha512, p, l); break; default: assert_not_reached("Unknown hash function"); } } const void* ca_digest_read(CaDigest *d) { if (!d) return NULL; switch (d->type) { case CA_DIGEST_SHA256: assert(sizeof(d->result) >= SHA256_DIGEST_LENGTH); SHA256_Final(d->result, &d->sha256); break; case CA_DIGEST_SHA512_256: assert(sizeof(d->result) >= SHA512_DIGEST_LENGTH); SHA512_Final(d->result, &d->sha512); break; default: assert_not_reached("Unknown hash function"); } return d->result; } size_t ca_digest_get_size(CaDigest *d) { if (!d) return (size_t) -1; return ca_digest_type_size(d->type); } CaDigestType ca_digest_get_type(CaDigest *d) { if (!d) return _CA_DIGEST_TYPE_INVALID; return d->type; } const char *ca_digest_get_name(CaDigest *d) { if (!d) return NULL; return ca_digest_type_to_string(d->type); } size_t ca_digest_type_size(CaDigestType t) { switch (t) { case CA_DIGEST_SHA256: case CA_DIGEST_SHA512_256: assert(SHA256_DIGEST_LENGTH == SHA512_DIGEST_LENGTH/2); return SHA256_DIGEST_LENGTH; default: return (size_t) -1; } } static const char *const table[_CA_DIGEST_TYPE_MAX] = { [CA_DIGEST_SHA256] = "sha256", [CA_DIGEST_SHA512_256] = "sha512-256", }; const char *ca_digest_type_to_string(CaDigestType t) { if (t < 0 || t >= _CA_DIGEST_TYPE_MAX) return NULL; return table[t]; } CaDigestType ca_digest_type_from_string(const char *name) { CaDigestType t; if (!name) return _CA_DIGEST_TYPE_INVALID; if (streq(name, "default")) return CA_DIGEST_DEFAULT; for (t = 0; t < _CA_DIGEST_TYPE_MAX; t++) if (streq(table[t], name)) return t; return _CA_DIGEST_TYPE_INVALID; } int ca_digest_ensure_allocated(CaDigest **d, CaDigestType t) { int r; if (!d) return -EINVAL; if (*d) return 0; r = ca_digest_new(t, d); if (r < 0) return r; return 1; } int ca_digest_set_type(CaDigest *d, CaDigestType t) { if (!d) return -EINVAL; if (t < 0) return -EINVAL; if (t >= _CA_DIGEST_TYPE_MAX) return -EOPNOTSUPP; d->type = t; ca_digest_reset(d); return 0; } src/cadigest.h000066400000000000000000000020121322621430500135720ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocadigesthfoo #define foocadigesthfoo #include #include typedef struct CaDigest CaDigest; typedef enum CaDigestType { CA_DIGEST_SHA256, CA_DIGEST_SHA512_256, _CA_DIGEST_TYPE_MAX, CA_DIGEST_DEFAULT = CA_DIGEST_SHA512_256, _CA_DIGEST_TYPE_INVALID = -1, } CaDigestType; int ca_digest_new(CaDigestType t, CaDigest **ret); CaDigest *ca_digest_free(CaDigest *d); int ca_digest_ensure_allocated(CaDigest **d, CaDigestType t); void ca_digest_write(CaDigest *d, const void *p, size_t l); const void* ca_digest_read(CaDigest *d); void ca_digest_reset(CaDigest *d); size_t ca_digest_get_size(CaDigest *d); CaDigestType ca_digest_get_type(CaDigest *d); const char *ca_digest_get_name(CaDigest *d); size_t ca_digest_type_size(CaDigestType t); const char *ca_digest_type_to_string(CaDigestType t); CaDigestType ca_digest_type_from_string(const char *name); int ca_digest_set_type(CaDigest *d, CaDigestType t); #endif src/caencoder.c000066400000000000000000003212061322621430500137360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_SELINUX # include #endif #include "caencoder.h" #include "caformat-util.h" #include "caformat.h" #include "camakebst.h" #include "cautil.h" #include "chattr.h" #include "def.h" #include "fssize.h" #include "realloc-buffer.h" #include "siphash24.h" #include "util.h" /* #undef EINVAL */ /* #define EINVAL __LINE__ */ /* #undef ENXIO */ /* #define ENXIO __LINE__ */ typedef struct CaEncoderExtendedAttribute { char *name; void *data; size_t data_size; } CaEncoderExtendedAttribute; typedef struct CaEncoderACLEntry { char *name; uint64_t permissions; union { uid_t uid; gid_t gid; uint32_t generic_id; }; } CaEncoderACLEntry; typedef struct CaEncoderNameTable { uint64_t hash; uint64_t start_offset; uint64_t end_offset; } CaEncoderNameTable; typedef struct CaEncoderNode { int fd; struct stat stat; statfs_f_type_t magic; /* For S_ISDIR */ struct dirent **dirents; size_t n_dirents; size_t dirent_idx; /* For S_ISLNK */ char *symlink_target; /* For S_ISBLK */ uint64_t device_size; /* chattr(1) flags */ unsigned chattr_flags; bool chattr_flags_valid; /* FAT_IOCTL_GET_ATTRIBUTES flags */ uint32_t fat_attrs; bool fat_attrs_valid; /* xattrs */ CaEncoderExtendedAttribute *xattrs; size_t n_xattrs; bool xattrs_valid; size_t xattrs_idx; /* ACLs */ CaEncoderACLEntry *acl_user; size_t n_acl_user; CaEncoderACLEntry *acl_group; size_t n_acl_group; CaEncoderACLEntry *acl_default_user; size_t n_acl_default_user; CaEncoderACLEntry *acl_default_group; size_t n_acl_default_group; uint64_t acl_group_obj_permissions; uint64_t acl_default_user_obj_permissions; uint64_t acl_default_group_obj_permissions; uint64_t acl_default_other_permissions; uint64_t acl_default_mask_permissions; bool acl_valid; /* File system capabilities */ void *fcaps; size_t fcaps_size; /* btrfs subvolume flags */ bool is_subvolume:1; bool is_subvolume_ro:1; bool subvolume_valid:1; /* SELinux label */ bool selinux_label_valid:1; char *selinux_label; /* The offset in the archive */ uint64_t entry_offset; /* If this is a directory: file name lookup data */ CaEncoderNameTable *name_table; size_t n_name_table; size_t n_name_table_allocated; uint64_t previous_name_table_offset; bool name_table_incomplete; /* For detecting mount boundaries */ int mount_id; } CaEncoderNode; typedef enum CaEncoderState { CA_ENCODER_INIT, CA_ENCODER_ENTERED, CA_ENCODER_ENTRY, CA_ENCODER_IN_PAYLOAD, CA_ENCODER_FIRST_DIRENT, CA_ENCODER_NEXT_DIRENT, CA_ENCODER_FILENAME, CA_ENCODER_GOODBYE, CA_ENCODER_FINALIZE, CA_ENCODER_EOF, } CaEncoderState; struct CaEncoder { CaEncoderState state; uint64_t feature_flags; uint64_t covering_feature_flags; /* feature flags the used file systems actually support */ uint64_t time_granularity; CaEncoderNode nodes[NODES_MAX]; size_t n_nodes; size_t node_idx; ReallocBuffer buffer; ReallocBuffer xattr_list_buffer; ReallocBuffer xattr_value_buffer; uint64_t archive_offset; uint64_t payload_offset; uid_t cached_uid; gid_t cached_gid; char *cached_user_name; char *cached_group_name; uid_t uid_shift; uid_t uid_range; /* uid_range == 0 means "full range" */ CaDigest *archive_digest; CaDigest *payload_digest; CaDigest *hardlink_digest; bool payload_digest_invalid:1; bool hardlink_digest_invalid:1; bool want_archive_digest:1; bool want_payload_digest:1; bool want_hardlink_digest:1; }; #define CA_ENCODER_AT_ROOT(e) ((e)->node_idx == 0) static inline bool CA_ENCODER_IS_NAKED(CaEncoder *e) { assert(e); /* Returns true if we are encoding a naked blob, i.e. a top-level payload, in contrast to a directory tree */ return e && e->n_nodes == 1 && e->nodes[0].stat.st_mode != 0 && (S_ISREG(e->nodes[0].stat.st_mode) || S_ISBLK(e->nodes[0].stat.st_mode)); } CaEncoder *ca_encoder_new(void) { CaEncoder *e; e = new0(CaEncoder, 1); if (!e) return NULL; assert_se(ca_feature_flags_normalize(CA_FORMAT_DEFAULT, &e->feature_flags) >= 0); e->time_granularity = 1; return e; } static CaEncoderACLEntry* ca_encoder_acl_entry_free(CaEncoderACLEntry *l, size_t n) { size_t i; for (i = 0; i < n; i++) free(l[i].name); return mfree(l); } static void ca_encoder_node_free(CaEncoderNode *n) { size_t i; assert(n); if (n->fd >= 3) n->fd = safe_close(n->fd); else n->fd = -1; for (i = 0; i < n->n_dirents; i++) free(n->dirents[i]); n->dirents = mfree(n->dirents); n->n_dirents = 0; n->symlink_target = mfree(n->symlink_target); for (i = 0; i < n->n_xattrs; i++) { free(n->xattrs[i].name); free(n->xattrs[i].data); } n->xattrs = mfree(n->xattrs); n->n_xattrs = 0; n->xattrs_idx = (size_t) -1; n->acl_user = ca_encoder_acl_entry_free(n->acl_user, n->n_acl_user); n->acl_group = ca_encoder_acl_entry_free(n->acl_group, n->n_acl_group); n->acl_default_user = ca_encoder_acl_entry_free(n->acl_default_user, n->n_acl_default_user); n->acl_default_group = ca_encoder_acl_entry_free(n->acl_default_group, n->n_acl_default_group); n->n_acl_user = n->n_acl_group = n->n_acl_default_user = n->n_acl_default_group = 0; n->acl_group_obj_permissions = UINT64_MAX; n->acl_default_user_obj_permissions = UINT64_MAX; n->acl_default_group_obj_permissions = UINT64_MAX; n->acl_default_other_permissions = UINT64_MAX; n->acl_default_mask_permissions = UINT64_MAX; n->fcaps = mfree(n->fcaps); #if HAVE_SELINUX if (n->selinux_label) { freecon(n->selinux_label); n->selinux_label = NULL; } #endif n->device_size = UINT64_MAX; n->stat.st_mode = 0; n->name_table = mfree(n->name_table); n->n_name_table = n->n_name_table_allocated = 0; n->previous_name_table_offset = UINT64_MAX; n->name_table_incomplete = false; n->entry_offset = UINT64_MAX; n->mount_id = -1; } CaEncoder *ca_encoder_unref(CaEncoder *e) { size_t i; if (!e) return NULL; for (i = 0; i < e->n_nodes; i++) ca_encoder_node_free(e->nodes + i); free(e->cached_user_name); free(e->cached_group_name); realloc_buffer_free(&e->buffer); realloc_buffer_free(&e->xattr_list_buffer); realloc_buffer_free(&e->xattr_value_buffer); ca_digest_free(e->archive_digest); ca_digest_free(e->payload_digest); ca_digest_free(e->hardlink_digest); return mfree(e); } int ca_encoder_set_feature_flags(CaEncoder *e, uint64_t flags) { int r; if (!e) return -EINVAL; r = ca_feature_flags_normalize(flags, &flags); if (r < 0) return r; r = ca_feature_flags_time_granularity_nsec(flags, &e->time_granularity); if (r == -ENODATA) e->time_granularity = UINT64_MAX; else if (r < 0) return r; e->feature_flags = flags; return 0; } int ca_encoder_get_feature_flags(CaEncoder *e, uint64_t *ret) { if (!e) return -EINVAL; if (!ret) return -EINVAL; *ret = e->feature_flags; return 0; } int ca_encoder_get_covering_feature_flags(CaEncoder *e, uint64_t *ret) { if (!e) return -EINVAL; if (!ret) return -EINVAL; *ret = e->covering_feature_flags; return 0; } int ca_encoder_set_base_fd(CaEncoder *e, int fd) { struct stat st; struct statfs sfs; if (!e) return -EINVAL; if (fd < 0) return -EINVAL; if (e->n_nodes > 0) return -EBUSY; if (fstat(fd, &st) < 0) return -errno; if (fstatfs(fd, &sfs) < 0) return -errno; if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) return -ENOTTY; e->nodes[0] = (struct CaEncoderNode) { .fd = fd, .stat = st, .device_size = UINT64_MAX, .magic = sfs.f_type, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, .previous_name_table_offset = UINT64_MAX, .entry_offset = UINT64_MAX, .mount_id = -1, }; e->n_nodes = 1; return 0; } int ca_encoder_get_base_fd(CaEncoder *e) { if (!e) return -EINVAL; if (e->n_nodes == 0) return -EUNATCH; if (e->nodes[0].fd < 0) return -EUNATCH; return e->nodes[0].fd; } static CaEncoderNode* ca_encoder_current_node(CaEncoder *e) { assert(e); if (e->node_idx >= e->n_nodes) return NULL; return e->nodes + e->node_idx; } static CaEncoderNode* ca_encoder_current_child_node(CaEncoder *e) { assert(e); if (e->node_idx+1 >= e->n_nodes) return NULL; return e->nodes + e->node_idx + 1; } static CaEncoderNode *ca_encoder_node_child_of(CaEncoder *e, CaEncoderNode *n) { size_t idx; assert(n >= e->nodes); idx = n - e->nodes; assert(idx < e->n_nodes); if (idx+1 >= e->n_nodes) return NULL; return e->nodes + idx + 1; } static CaEncoderNode *ca_encoder_node_parent_of(CaEncoder *e, CaEncoderNode *n) { size_t idx; assert(n >= e->nodes); idx = n - e->nodes; assert(idx < e->n_nodes); if (idx == 0) return NULL; return e->nodes + idx - 1; } static const struct dirent *ca_encoder_node_current_dirent(CaEncoderNode *n) { assert(n); if (n->n_dirents <= 0) return NULL; if (n->dirent_idx >= n->n_dirents) return NULL; return n->dirents[n->dirent_idx]; } static int scandir_filter(const struct dirent *de) { assert(de); /* Filter out "." and ".." */ return !dot_or_dot_dot(de->d_name); } static int scandir_compare(const struct dirent **a, const struct dirent **b) { assert(a); assert(b); /* We don't use alphasort() here, as we want locale-independent ordering */ return strcmp((*a)->d_name, (*b)->d_name); } static int ca_encoder_node_read_dirents(CaEncoderNode *n) { int r; assert(n); if (n->dirents) return 0; if (!S_ISDIR(n->stat.st_mode)) return -ENOTDIR; if (n->fd < 0) return -EBADFD; r = scandirat(n->fd, ".", &n->dirents, scandir_filter, scandir_compare); if (r < 0) return -errno; n->n_dirents = (size_t) r; n->dirent_idx = 0; return 1; } static int ca_encoder_node_read_device_size(CaEncoderNode *n) { unsigned long u = 0; uint64_t fs_size; int r; assert(n); if (n->device_size != (uint64_t) -1) return 0; if (!S_ISBLK(n->stat.st_mode)) return -ENOTTY; if (n->fd < 0) return -EBADFD; if (ioctl(n->fd, BLKGETSIZE, &u) < 0) return -errno; n->device_size = (uint64_t) u * 512; r = read_file_system_size(n->fd, &fs_size); if (r < 0) return r; if (r > 0) { /* The actual superblock claims a smaller size, let's fix this up. */ if (n->device_size > fs_size) n->device_size = fs_size; } return 1; } static int ca_encoder_node_read_symlink( CaEncoderNode *n, const struct dirent *de, CaEncoderNode *symlink) { int r; assert(n); assert(de); assert(symlink); if (!S_ISDIR(n->stat.st_mode)) return -ENOTDIR; if (n->fd < 0) return -EBADFD; if (!S_ISLNK(symlink->stat.st_mode)) return 0; if (symlink->symlink_target) return 0; r = readlinkat_malloc(n->fd, de->d_name, &symlink->symlink_target); if (r < 0) return r; return 1; } static int ca_encoder_node_read_chattr( CaEncoder *e, CaEncoderNode *n) { int r; assert(e); assert(n); if (!S_ISDIR(n->stat.st_mode) && !S_ISREG(n->stat.st_mode)) return 0; if (n->fd < 0) return -EBADFD; if (n->chattr_flags_valid) return 0; if ((e->feature_flags & (CA_FORMAT_WITH_CHATTR|CA_FORMAT_EXCLUDE_NODUMP)) == 0) return 0; r = read_attr_fd(n->fd, &n->chattr_flags); if (r < 0) return r; n->chattr_flags_valid = true; return 0; } static int ca_encoder_node_read_fat_attrs( CaEncoder *e, CaEncoderNode *n) { int r; assert(e); assert(n); if (!S_ISDIR(n->stat.st_mode) && !S_ISREG(n->stat.st_mode)) return 0; if (n->fd < 0) return -EBADFD; if (n->fat_attrs_valid) return 0; if ((e->feature_flags & CA_FORMAT_WITH_FAT_ATTRS) == 0) return 0; if (IN_SET(n->magic, MSDOS_SUPER_MAGIC, FUSE_SUPER_MAGIC)) { /* FUSE and true FAT file systems might implement this ioctl(), otherwise don't bother */ r = read_fat_attr_fd(n->fd, &n->fat_attrs); if (r < 0) return r; } else n->fat_attrs = 0; n->fat_attrs_valid = true; return 0; } static int ca_encoder_node_read_btrfs( CaEncoder *e, CaEncoderNode *n) { assert(e); assert(n); if (!S_ISDIR(n->stat.st_mode)) return 0; if (n->fd < 0) return -EBADFD; if ((e->feature_flags & CA_FORMAT_WITH_SUBVOLUME) == 0) return 0; if (n->subvolume_valid) return 0; if (F_TYPE_EQUAL(n->magic, BTRFS_SUPER_MAGIC) && n->stat.st_ino == 256) { uint64_t bflags; if (ioctl(n->fd, BTRFS_IOC_SUBVOL_GETFLAGS, &bflags) < 0) return -errno; n->is_subvolume = true; n->is_subvolume_ro = !!(bflags & BTRFS_SUBVOL_RDONLY); } else { n->is_subvolume = false; n->is_subvolume_ro = false; } n->subvolume_valid = true; return 0; } static int ca_encoder_node_read_selinux_label( CaEncoder *e, CaEncoderNode *n) { #if HAVE_SELINUX char *label; int r; #endif assert(e); assert(n); if ((e->feature_flags & CA_FORMAT_WITH_SELINUX) == 0) return 0; #if HAVE_SELINUX if (n->selinux_label_valid) return 0; if (n->fd >= 0) r = fgetfilecon(n->fd, &label) < 0 ? -errno : 0; else { const struct dirent *de; CaEncoderNode *parent; _cleanup_free_ char *subpath = NULL; parent = ca_encoder_node_parent_of(e, n); if (!parent) return -EUNATCH; de = ca_encoder_node_current_dirent(parent); if (!de) return -EUNATCH; if (asprintf(&subpath, "/proc/self/fd/%i/%s", parent->fd, de->d_name) < 0) return -ENOMEM; r = lgetfilecon(subpath, &label) < 0 ? -errno : 0; } if (r < 0) { if (!IN_SET(-r, ENODATA, EOPNOTSUPP)) return r; if (n->selinux_label) { freecon(n->selinux_label); n->selinux_label = NULL; } } else { if (n->selinux_label) freecon(n->selinux_label); n->selinux_label = label; } n->selinux_label_valid = true; return 0; #else return -EOPNOTSUPP; #endif } static int compare_xattr(const void *a, const void *b) { const CaEncoderExtendedAttribute *x = a, *y = b; assert(x); assert(y); assert(x->name); assert(y->name); return strcmp(x->name, y->name); } static int ca_encoder_node_read_xattrs( CaEncoder *e, CaEncoderNode *n) { size_t space = 256, left, count = 0; bool has_fcaps = false; int path_fd = -1, r; char *q; assert(e); assert(n); if ((e->feature_flags & (CA_FORMAT_WITH_XATTRS|CA_FORMAT_WITH_FCAPS)) == 0) return 0; if (n->xattrs_valid) return 0; if (S_ISLNK(n->stat.st_mode)) return 0; assert(!n->xattrs); assert(n->n_xattrs == 0); assert(!n->fcaps); assert(n->fcaps_size == 0); if (n->fd < 0) { const struct dirent *de; CaEncoderNode *parent; parent = ca_encoder_node_parent_of(e, n); if (!parent) { r = -EUNATCH; goto finish; } de = ca_encoder_node_current_dirent(parent); if (!de) { r = -EUNATCH; goto finish; } /* There's no listxattrat() unfortunately, we fake it via openat() with O_PATH */ path_fd = openat(parent->fd, de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_PATH); if (path_fd < 0) { r = -errno; goto finish; } } for (;;) { ssize_t l; char *p; p = realloc_buffer_acquire(&e->xattr_list_buffer, space); if (!p) { r = -ENOMEM; goto finish; } l = flistxattr(n->fd < 0 ? path_fd : n->fd, p, space); if (l < 0) { /* If xattrs aren't supported there are none. EOPNOTSUPP is returned by file systems that do * not support xattrs, and EBADF is returned on nodes that cannot carry xattrs (such as * symlinks). */ if (IN_SET(errno, EOPNOTSUPP, EBADF)) { n->xattrs_valid = true; r = 0; goto finish; } if (errno != ERANGE) { r = -errno; goto finish; } } else { realloc_buffer_truncate(&e->xattr_list_buffer, l); break; } if (space*2 <= space) { r = -ENOMEM; goto finish; } space *= 2; } q = realloc_buffer_data(&e->xattr_list_buffer); left = realloc_buffer_size(&e->xattr_list_buffer); /* Count the number of relevant extended attributes */ while (left > 1) { size_t k; k = strlen(q); assert(left >= k + 1); if (ca_xattr_name_store(q)) count ++; else if (streq(q, "security.capability") && S_ISREG(n->stat.st_mode)) has_fcaps = true; q += k + 1; left -= k + 1; } if (count == 0 && !has_fcaps) { /* None set */ n->xattrs_valid = true; r = 0; goto finish; } if (count > 0) { n->xattrs = new0(CaEncoderExtendedAttribute, count); if (!n->xattrs) { r = -ENOMEM; goto finish; } } q = realloc_buffer_data(&e->xattr_list_buffer); left = realloc_buffer_size(&e->xattr_list_buffer); while (left > 1) { size_t k; k = strlen(q); assert(left >= k + 1); if (ca_xattr_name_store(q) || (streq(q, "security.capability") && S_ISREG(n->stat.st_mode))) { bool good = false; space = 256; for (;;) { ssize_t l; char *p; p = realloc_buffer_acquire(&e->xattr_value_buffer, space); if (!p) { r = -ENOMEM; goto finish; } l = fgetxattr(n->fd < 0 ? path_fd : n->fd, q, p, space); if (l < 0) { if (errno == ENODATA) /* Went missing? That's fine */ break; if (errno != ERANGE) { r = -errno; goto finish; } } else { realloc_buffer_truncate(&e->xattr_value_buffer, l); good = true; break; } if (space*2 <= space) { r = -ENOMEM; goto finish; } space *= 2; } if (good) { if (streq(q, "security.capability") && S_ISREG(n->stat.st_mode)) { size_t z; assert(!n->fcaps); assert(n->fcaps_size == 0); z = realloc_buffer_size(&e->xattr_value_buffer); n->fcaps = realloc_buffer_steal(&e->xattr_value_buffer); if (!n->fcaps) { r = -ENOMEM; goto finish; } n->fcaps_size = z; } else { _cleanup_free_ char *name = NULL; size_t z; void *d; assert(n->xattrs); assert(n->n_xattrs < count); name = strdup(q); if (!name) { r = -ENOMEM; goto finish; } z = realloc_buffer_size(&e->xattr_value_buffer); d = realloc_buffer_steal(&e->xattr_value_buffer); if (!d) { r = -ENOMEM; goto finish; } n->xattrs[n->n_xattrs++] = (CaEncoderExtendedAttribute) { .name = name, .data = d, .data_size = z, }; name = NULL; } } } q += k + 1; left -= k + 1; } /* Bring extended attributes in a defined order */ if (n->n_xattrs > 1) qsort(n->xattrs, n->n_xattrs, sizeof(CaEncoderExtendedAttribute), compare_xattr); n->xattrs_valid = true; r = 0; finish: safe_close(path_fd); return r; } static int ca_encoder_node_read_mount_id( CaEncoder *e, CaEncoderNode *n) { size_t line_allocated = 0; _cleanup_free_ char *line = NULL, *p = NULL; FILE *f; int r; assert(e); assert(n); if (!(e->feature_flags & CA_FORMAT_EXCLUDE_SUBMOUNTS)) return 0; if (n->mount_id >= 0) return 0; if (n->fd < 0) return 0; if (asprintf(&p, "/proc/self/fdinfo/%i", n->fd) < 0) return -ENOMEM; f = fopen(p, "re"); if (!f) return -errno; for (;;) { int mnt_id; ssize_t k; char *z; errno = 0; k = getline(&line, &line_allocated, f); if (k < 0) { if (errno == 0) /* EOF? */ break; r = -errno; goto finish; } z = startswith(line, "mnt_id:"); if (!z) continue; z += strspn(z, WHITESPACE); truncate_nl(z); r = safe_atoi(z, &mnt_id); if (r < 0) goto finish; if (mnt_id < 0) { r = -EINVAL; goto finish; } n->mount_id = mnt_id; break; } r = 0; finish: fclose(f); return r; } static uid_t ca_encoder_shift_uid(CaEncoder *e, uid_t uid) { uid_t result; assert(e); if (!uid_is_valid(uid)) return UID_INVALID; if (uid < e->uid_shift) return UID_INVALID; result = uid - e->uid_shift; if (e->uid_range != 0) result %= e->uid_range; return result; } static gid_t ca_encoder_shift_gid(CaEncoder *e, gid_t gid) { /* Let's rely on the fact that UIDs and GIDs have identical numeric behaviour */ return (gid_t) ca_encoder_shift_uid(e, (uid_t) gid); } static int uid_to_name(CaEncoder *e, uid_t uid, char **ret) { long bufsize; int r; assert(e); assert(ret); if (!uid_is_valid(uid)) return -EINVAL; if (uid == 0) { /* Don't store name for root, it's clear anyway */ *ret = NULL; return 0; } bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); if (bufsize <= 0) bufsize = 4096; for (;;) { struct passwd pwbuf, *pw = NULL; _cleanup_free_ char *buf = NULL; buf = malloc(bufsize); if (!buf) return -ENOMEM; r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); if (r == 0 && pw) { char *n; n = strdup(pw->pw_name); if (!n) return -ENOMEM; *ret = n; return 1; } if (r != ERANGE) { uid_t shifted_uid; /* User name cannot be retrieved */ if (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) { *ret = NULL; return 0; } shifted_uid = ca_encoder_shift_uid(e, uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; if (asprintf(ret, UID_FMT, shifted_uid) < 0) return -ENOMEM; return 1; } bufsize *= 2; } } static int gid_to_name(CaEncoder *e, gid_t gid, char **ret) { long bufsize; int r; assert(e); assert(ret); if (gid == 0) { *ret = NULL; return 0; } bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); if (bufsize <= 0) bufsize = 4096; for (;;) { struct group grbuf, *gr = NULL; _cleanup_free_ char *buf = NULL; buf = malloc(bufsize); if (!buf) return -ENOMEM; r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); if (r == 0 && gr) { char *n; n = strdup(gr->gr_name); if (!n) return -ENOMEM; *ret = n; return 1; } if (r != ERANGE) { gid_t shifted_gid; if (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) { *ret = NULL; return 0; } shifted_gid = ca_encoder_shift_gid(e, gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; if (asprintf(ret, GID_FMT, shifted_gid) < 0) return -ENOMEM; return 1; } bufsize *= 2; } } static int compare_acl_entry(const void *a, const void *b) { const CaEncoderACLEntry *x = a, *y = b; int r; assert(x); assert(y); if (x->generic_id < y->generic_id) return -1; if (x->generic_id > y->generic_id) return 1; if (!x->name && y->name) return -1; if (x->name && !y->name) return 1; if (x->name && y->name) { r = strcmp(x->name, y->name); if (r != 0) return r; } if (x->permissions < y->permissions) return -1; if (x->permissions > y->permissions) return 1; return 0; } static int acl_entry_permissions(acl_entry_t entry, uint64_t *ret) { uint64_t permissions = 0; acl_permset_t permset; int r; assert(entry); if (acl_get_permset(entry, &permset) < 0) return -errno; r = acl_get_perm(permset, ACL_READ); if (r < 0) return -errno; if (r > 0) permissions |= CA_FORMAT_ACL_PERMISSION_READ; r = acl_get_perm(permset, ACL_WRITE); if (r < 0) return -errno; if (r > 0) permissions |= CA_FORMAT_ACL_PERMISSION_WRITE; r = acl_get_perm(permset, ACL_EXECUTE); if (r < 0) return -errno; if (r > 0) permissions |= CA_FORMAT_ACL_PERMISSION_EXECUTE; *ret = permissions; return 0; } static int ca_encoder_node_process_acl( CaEncoder *e, CaEncoderNode *n, acl_type_t type, acl_t acl) { size_t n_allocated_user = 0, n_allocated_group = 0; uint64_t user_obj_permissions = UINT64_MAX, group_obj_permissions = UINT64_MAX, other_permissions = UINT64_MAX, mask_permissions = UINT64_MAX; CaEncoderACLEntry **user_entries, **group_entries; size_t *n_user_entries, *n_group_entries; acl_entry_t entry; int r; switch (type) { case ACL_TYPE_ACCESS: user_entries = &n->acl_user; n_user_entries = &n->n_acl_user; group_entries = &n->acl_group; n_group_entries = &n->n_acl_group; break; case ACL_TYPE_DEFAULT: user_entries = &n->acl_default_user; n_user_entries = &n->n_acl_default_user; group_entries = &n->acl_default_group; n_group_entries = &n->n_acl_default_group; break; default: assert(false); } for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); r > 0; r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { uint64_t permissions; void *q; acl_tag_t tag; if (acl_get_tag_type(entry, &tag) < 0) return -errno; r = acl_entry_permissions(entry, &permissions); if (r < 0) return r; switch (tag) { case ACL_USER_OBJ: user_obj_permissions = permissions; break; case ACL_GROUP_OBJ: group_obj_permissions = permissions; break; case ACL_OTHER: other_permissions = permissions; break; case ACL_MASK: mask_permissions = permissions; break; case ACL_USER: { uid_t uid, shifted_uid; _cleanup_free_ char *name = NULL; q = acl_get_qualifier(entry); if (!q) return -errno; uid = *(uid_t*) q; acl_free(q); if (!uid_is_valid(uid)) return -EINVAL; shifted_uid = ca_encoder_shift_uid(e, uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; if ((e->feature_flags & CA_FORMAT_WITH_16BIT_UIDS) && shifted_uid > UINT16_MAX) return -EPROTONOSUPPORT; if (e->feature_flags & CA_FORMAT_WITH_USER_NAMES) { r = uid_to_name(e, uid, &name); if (r < 0) return r; } if (!GREEDY_REALLOC(*user_entries, n_allocated_user, *n_user_entries+1)) return -ENOMEM; (*user_entries)[(*n_user_entries)++] = (CaEncoderACLEntry) { .name = name, .permissions = permissions, .uid = (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) ? shifted_uid : 0, }; name = NULL; break; } case ACL_GROUP: { gid_t gid, shifted_gid; _cleanup_free_ char *name = NULL; q = acl_get_qualifier(entry); if (!q) return -errno; gid = *(gid_t*) q; acl_free(q); if (!gid_is_valid(gid)) return -EINVAL; shifted_gid = ca_encoder_shift_gid(e, gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; if ((e->feature_flags & CA_FORMAT_WITH_16BIT_UIDS) && shifted_gid > UINT16_MAX) return -EPROTONOSUPPORT; if (e->feature_flags & CA_FORMAT_WITH_USER_NAMES) { r = gid_to_name(e, gid, &name); if (r < 0) return r; } if (!GREEDY_REALLOC(*group_entries, n_allocated_group, *n_group_entries+1)) return -ENOMEM; (*group_entries)[(*n_group_entries)++] = (CaEncoderACLEntry) { .name = name, .permissions = permissions, .gid = (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) ? shifted_gid : 0, }; name = NULL; break; } default: assert(false); } } if (r < 0) return -errno; if (*n_user_entries > 1) qsort(*user_entries, *n_user_entries, sizeof(CaEncoderACLEntry), compare_acl_entry); if (*n_group_entries > 1) qsort(*group_entries, *n_group_entries, sizeof(CaEncoderACLEntry), compare_acl_entry); switch (type) { case ACL_TYPE_ACCESS: /* We only store the group object if there's also a mask set. This is because on Linux the stat() * reported group permissions map to the ACL mask if one is set and the group permissions otherwise. */ if (group_obj_permissions != UINT64_MAX && mask_permissions != UINT64_MAX) n->acl_group_obj_permissions = group_obj_permissions; break; case ACL_TYPE_DEFAULT: n->acl_default_user_obj_permissions = user_obj_permissions; n->acl_default_group_obj_permissions = group_obj_permissions; n->acl_default_other_permissions = other_permissions; n->acl_default_mask_permissions = mask_permissions; break; default: assert(false); } return 0; } static int ca_encoder_node_read_acl( CaEncoder *e, CaEncoderNode *n) { char proc_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; int path_fd = -1, r; acl_t acl = NULL; if ((e->feature_flags & CA_FORMAT_WITH_ACL) == 0) return 0; if (n->acl_valid) return 0; assert(n->n_acl_user == 0); assert(!n->acl_user); assert(n->n_acl_group == 0); assert(!n->acl_group); assert(n->n_acl_default_user == 0); assert(!n->acl_default_user); assert(n->n_acl_default_group == 0); assert(!n->acl_default_group); assert(n->acl_group_obj_permissions == UINT64_MAX); assert(n->acl_default_user_obj_permissions == UINT64_MAX); assert(n->acl_default_group_obj_permissions == UINT64_MAX); assert(n->acl_default_other_permissions == UINT64_MAX); assert(n->acl_default_mask_permissions == UINT64_MAX); if (S_ISLNK(n->stat.st_mode)) return 0; if (n->fd < 0) { CaEncoderNode *parent; const struct dirent *de; parent = ca_encoder_node_parent_of(e, n); if (!parent) { r = -EUNATCH; goto finish; } de = ca_encoder_node_current_dirent(parent); if (!de) { r = -EUNATCH; goto finish; } /* There's no acl_get_fdat() unfortunately, we fake it via openat() with O_PATH */ path_fd = openat(parent->fd, de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_PATH); if (path_fd < 0) { r = -errno; goto finish; } } sprintf(proc_path, "/proc/self/fd/%i", n->fd < 0 ? path_fd : n->fd); acl = acl_get_file(proc_path, ACL_TYPE_ACCESS); if (acl) { r = ca_encoder_node_process_acl(e, n, ACL_TYPE_ACCESS, acl); if (r < 0) goto finish; acl_free(acl); acl = NULL; } else if (!IN_SET(errno, EOPNOTSUPP, EBADF, ENODATA)) { r = -errno; goto finish; } if (S_ISDIR(n->stat.st_mode)) { acl = acl_get_file(proc_path, ACL_TYPE_DEFAULT); if (acl) { r = ca_encoder_node_process_acl(e, n, ACL_TYPE_DEFAULT, acl); if (r < 0) goto finish; acl_free(acl); acl = NULL; } else if (!IN_SET(errno, EOPNOTSUPP, EBADF, ENODATA)) { r = -errno; goto finish; } } n->acl_valid = true; r = 0; finish: safe_close(path_fd); if (acl) acl_free(acl); return r; } static int ca_encoder_node_read_user_group_names( CaEncoder *e, CaEncoderNode *n) { int r; assert(e); assert(n); if (!(e->feature_flags & CA_FORMAT_WITH_USER_NAMES)) return 0; if (n->stat.st_mode == 0) return -EINVAL; /* We store the user/group name in a per-encoder variable instead of per-node, under the assumption that * there's a good chance we can reuse the once retrieved data between subsequent files. */ if (!e->cached_user_name || e->cached_uid != n->stat.st_uid) { e->cached_user_name = mfree(e->cached_user_name); r = uid_to_name(e, n->stat.st_uid, &e->cached_user_name); if (r < 0) return r; e->cached_uid = n->stat.st_uid; } if (!e->cached_group_name || e->cached_gid != n->stat.st_gid) { e->cached_group_name = mfree(e->cached_group_name); r = gid_to_name(e, n->stat.st_gid, &e->cached_group_name); if (r < 0) return r; e->cached_gid = n->stat.st_gid; } return 0; } static void ca_encoder_forget_children(CaEncoder *e) { assert(e); while (e->n_nodes > e->node_idx+1) ca_encoder_node_free(e->nodes + --e->n_nodes); } static CaEncoderNode* ca_encoder_init_child(CaEncoder *e) { CaEncoderNode *n; assert(e); ca_encoder_forget_children(e); if (e->n_nodes >= NODES_MAX) return NULL; n = e->nodes + e->n_nodes++; *n = (CaEncoderNode) { .fd = -1, .device_size = UINT64_MAX, .acl_group_obj_permissions = UINT64_MAX, .acl_default_user_obj_permissions = UINT64_MAX, .acl_default_group_obj_permissions = UINT64_MAX, .acl_default_other_permissions = UINT64_MAX, .acl_default_mask_permissions = UINT64_MAX, .entry_offset = UINT64_MAX, .mount_id = -1, }; return n; } static int ca_encoder_open_child(CaEncoder *e, CaEncoderNode *n, const struct dirent *de) { int r, open_flags = O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW; bool shall_open, have_stat; CaEncoderNode *child; assert(e); assert(n); assert(de); if (!S_ISDIR(n->stat.st_mode)) return -ENOTDIR; if (n->fd < 0) return -EBADFD; child = ca_encoder_init_child(e); if (!child) return -E2BIG; if (IN_SET(de->d_type, DT_DIR, DT_REG)) { shall_open = true; have_stat = false; if (de->d_type == DT_DIR) open_flags |= O_DIRECTORY; } else { if (fstatat(n->fd, de->d_name, &child->stat, AT_SYMLINK_NOFOLLOW) < 0) return -errno; have_stat = true; shall_open = S_ISREG(child->stat.st_mode) || S_ISDIR(child->stat.st_mode); if (S_ISDIR(child->stat.st_mode)) open_flags |= O_DIRECTORY; } if (shall_open) { child->fd = openat(n->fd, de->d_name, open_flags); if (child->fd < 0) return -errno; if (!have_stat) { if (fstat(child->fd, &child->stat) < 0) return -errno; } } if (child->stat.st_dev == n->stat.st_dev || child->fd < 0) child->magic = n->magic; else { struct statfs sfs; if (fstatfs(child->fd, &sfs) < 0) return -errno; child->magic = sfs.f_type; } r = ca_encoder_node_read_symlink(n, de, child); if (r < 0) return r; return 0; } static int ca_encoder_enter_child(CaEncoder *e) { assert(e); if (e->node_idx+1 >= e->n_nodes) return -EINVAL; if (e->nodes[e->node_idx+1].stat.st_mode == 0) return -EINVAL; e->node_idx++; return 0; } static int ca_encoder_leave_child(CaEncoder *e) { assert(e); if (e->node_idx <= 0) return 0; e->node_idx--; return 1; } static int ca_encoder_shall_store_child_node(CaEncoder *e, CaEncoderNode *n) { CaEncoderNode *child; int r; assert(e); assert(n); /* Check whether this node is one we should care for or skip */ child = ca_encoder_current_child_node(e); if (!child) return -EUNATCH; if (!(e->feature_flags & CA_FORMAT_WITH_SYMLINKS) && S_ISLNK(child->stat.st_mode)) return false; if (!(e->feature_flags & CA_FORMAT_WITH_DEVICE_NODES) && (S_ISBLK(child->stat.st_mode) || S_ISCHR(child->stat.st_mode))) return false; if (!(e->feature_flags & CA_FORMAT_WITH_FIFOS) && S_ISFIFO(child->stat.st_mode)) return false; if (!(e->feature_flags & CA_FORMAT_WITH_SOCKETS) && S_ISSOCK(child->stat.st_mode)) return false; /* Check if the NODUMP flag is set */ r = ca_encoder_node_read_chattr(e, child); if (r < 0) return r; if ((e->feature_flags & CA_FORMAT_EXCLUDE_NODUMP) && (child->chattr_flags & FS_NODUMP_FL)) return false; /* Check if we are crossing a mount point boundary */ r = ca_encoder_node_read_mount_id(e, n); if (r < 0) return r; r = ca_encoder_node_read_mount_id(e, child); if (r < 0) return r; if ((e->feature_flags & CA_FORMAT_EXCLUDE_SUBMOUNTS) && child->mount_id >= 0 && n->mount_id >= 0 && child->mount_id != n->mount_id) return false; return true; } static int ca_encoder_node_shall_enumerate(CaEncoder *e, CaEncoderNode *n) { assert(e); assert(n); /* Checks whether we shall enumerate the dirents inside the current node (or in case of a regular file, include * the file contents */ if (!S_ISDIR(n->stat.st_mode) && !S_ISREG(n->stat.st_mode)) /* We only care for files and directories here */ return false; /* Exclude all virtual API file systems */ if (IN_SET(n->magic, BINFMTFS_MAGIC, CGROUP2_SUPER_MAGIC, CGROUP_SUPER_MAGIC, CONFIGFS_MAGIC, DEBUGFS_MAGIC, DEVPTS_SUPER_MAGIC, EFIVARFS_MAGIC, FUSE_CTL_SUPER_MAGIC, HUGETLBFS_MAGIC, MQUEUE_MAGIC, NFSD_MAGIC, PROC_SUPER_MAGIC, PSTOREFS_MAGIC, RPCAUTH_GSSMAGIC, SECURITYFS_MAGIC, SELINUX_MAGIC, SMACK_MAGIC, SYSFS_MAGIC)) return false; return true; } static int ca_encoder_collect_covering_feature_flags(CaEncoder *e, CaEncoderNode *n) { assert(e); assert(n); /* Collect all feature flags that cover the complete feature set of the underlying file systems */ e->covering_feature_flags |= ca_feature_flags_from_magic(n->magic); return 0; } static int ca_encoder_node_get_payload_size(CaEncoderNode *n, uint64_t *ret) { int r; assert(n); assert(ret); if (S_ISREG(n->stat.st_mode)) { *ret = n->stat.st_size; return 0; } if (S_ISBLK(n->stat.st_mode)) { r = ca_encoder_node_read_device_size(n); if (r < 0) return r; *ret = n->device_size; return 0; } return -ENOTTY; } static void ca_encoder_enter_state(CaEncoder *e, CaEncoderState state) { assert(e); e->state = state; e->payload_offset = 0; } static int ca_encoder_step_node(CaEncoder *e, CaEncoderNode *n) { int r; assert(e); assert(n); switch (e->state) { case CA_ENCODER_INIT: if (CA_ENCODER_IS_NAKED(e)) { assert(CA_ENCODER_AT_ROOT(e)); /* If we are just initializing and looking at a regular file/block device, then our top-level * node is serialized as its contents, hence continue in payload mode. */ ca_encoder_enter_state(e, CA_ENCODER_IN_PAYLOAD); } else /* Otherwise, if we are initializing and looking at anything else, then start with an ENTRY * record. */ ca_encoder_enter_state(e, CA_ENCODER_ENTERED); return ca_encoder_step_node(e, n); case CA_ENCODER_ENTERED: /* We just entered this node. In this case, generate the ENTRY record for it */ if (e->want_payload_digest) { ca_digest_reset(e->payload_digest); e->payload_digest_invalid = false; } if (e->want_hardlink_digest) { ca_digest_reset(e->hardlink_digest); e->hardlink_digest_invalid = false; } r = ca_encoder_collect_covering_feature_flags(e, n); if (r < 0) return r; ca_encoder_enter_state(e, CA_ENCODER_ENTRY); return CA_ENCODER_NEXT_FILE; case CA_ENCODER_ENTRY: /* The ENTRY record has been generated now, let's see if we now shall serialize the conents of the * node, or go to the next one right-away */ r = ca_encoder_node_shall_enumerate(e, n); if (r < 0) return r; if (r > 0) { if (S_ISREG(n->stat.st_mode)) { /* The ENTRY record has been generated, now go for the payload. */ ca_encoder_enter_state(e, CA_ENCODER_IN_PAYLOAD); return ca_encoder_step_node(e, n); } if (S_ISDIR(n->stat.st_mode)) { /* The ENTRY record has been generated, now go for the first directory entry. */ ca_encoder_enter_state(e, CA_ENCODER_FIRST_DIRENT); return ca_encoder_step_node(e, n); } } else { if (S_ISDIR(n->stat.st_mode)) { /* We shall not enumerate the entry, hence go directly for the GOODBYE record. */ ca_encoder_enter_state(e, CA_ENCODER_GOODBYE); return CA_ENCODER_DATA; } } ca_encoder_enter_state(e, CA_ENCODER_FINALIZE); return CA_ENCODER_DONE_FILE; case CA_ENCODER_IN_PAYLOAD: { uint64_t size; assert(S_ISREG(n->stat.st_mode) || S_ISBLK(n->stat.st_mode)); r = ca_encoder_node_get_payload_size(n, &size); if (r < 0) return r; if (e->payload_offset >= size) { ca_encoder_enter_state(e, CA_ENCODER_FINALIZE); /* If this is a blob archive (i.e. a top-level payload), then let's not generate the DONE_FILE * event (because there is no entry) but let's shortcut to FINISHED. */ if (CA_ENCODER_IS_NAKED(e)) { assert(CA_ENCODER_AT_ROOT(e)); return ca_encoder_step(e); } return CA_ENCODER_DONE_FILE; } return CA_ENCODER_PAYLOAD; } case CA_ENCODER_NEXT_DIRENT: if (!n->name_table_incomplete) { assert(n->n_name_table > 0); if (e->archive_offset == UINT64_MAX) n->name_table_incomplete = true; else n->name_table[n->n_name_table-1].end_offset = e->archive_offset; } n->dirent_idx++; /* Fall through */ case CA_ENCODER_FIRST_DIRENT: { const struct dirent *de; assert(S_ISDIR(n->stat.st_mode)); r = ca_encoder_node_read_dirents(n); if (r < 0) return r; for (;;) { de = ca_encoder_node_current_dirent(n); if (!de) { if (!n->name_table_incomplete && n->n_name_table > 0) { if (e->archive_offset == UINT64_MAX) n->name_table_incomplete = true; else n->name_table[n->n_name_table-1].end_offset = e->archive_offset; } ca_encoder_enter_state(e, CA_ENCODER_GOODBYE); return CA_ENCODER_DATA; } r = ca_encoder_open_child(e, n, de); if (r < 0) return r; /* Check if this child is relevant to us */ r = ca_encoder_shall_store_child_node(e, n); if (r < 0) return r; if (r > 0) /* Yay, this one's relevant */ break; /* Nope, not relevant to us, let's try the next one */ n->dirent_idx++; } if (!n->name_table_incomplete) { if (e->archive_offset == UINT64_MAX) n->name_table_incomplete = true; else { if (!GREEDY_REALLOC(n->name_table, n->n_name_table_allocated, n->n_name_table+1)) return -ENOMEM; n->name_table[n->n_name_table++] = (CaEncoderNameTable) { .start_offset = e->archive_offset, .hash = siphash24(de->d_name, strlen(de->d_name), (const uint8_t[16]) CA_FORMAT_GOODBYE_HASH_KEY), }; } } ca_encoder_enter_state(e, CA_ENCODER_FILENAME); return CA_ENCODER_DATA; } case CA_ENCODER_FILENAME: { CaEncoderNode *child; /* The FILENAME record was written, now enter the child */ r = ca_encoder_enter_child(e); if (r < 0) return r; child = ca_encoder_current_node(e); if (!child) return -EUNATCH; ca_encoder_enter_state(e, CA_ENCODER_ENTERED); return ca_encoder_step_node(e, child); } case CA_ENCODER_GOODBYE: ca_encoder_enter_state(e, CA_ENCODER_FINALIZE); return CA_ENCODER_DONE_FILE; case CA_ENCODER_FINALIZE: r = ca_encoder_leave_child(e); if (r < 0) return r; if (r > 0) { CaEncoderNode *parent; parent = ca_encoder_current_node(e); if (!parent) return -EUNATCH; ca_encoder_enter_state(e, CA_ENCODER_NEXT_DIRENT); return ca_encoder_step_node(e, parent); } ca_encoder_enter_state(e, CA_ENCODER_EOF); return CA_ENCODER_FINISHED; default: assert(false); } assert(false); } static void ca_encoder_advance_buffer(CaEncoder *e) { size_t sz; assert(e); sz = realloc_buffer_size(&e->buffer); e->payload_offset += sz; if (e->archive_offset != UINT64_MAX) e->archive_offset += sz; realloc_buffer_empty(&e->buffer); } int ca_encoder_step(CaEncoder *e) { CaEncoderNode *n; if (!e) return -EINVAL; if (e->state == CA_ENCODER_EOF) return CA_ENCODER_FINISHED; ca_encoder_advance_buffer(e); n = ca_encoder_current_node(e); if (!n) return -EUNATCH; return ca_encoder_step_node(e, n); } static int ca_encoder_get_payload_data(CaEncoder *e, CaEncoderNode *n) { uint64_t size; ssize_t m; size_t k; void *p; int r; assert(e); assert(n); assert(S_ISREG(n->stat.st_mode) || S_ISBLK(n->stat.st_mode)); assert(e->state == CA_ENCODER_IN_PAYLOAD); r = ca_encoder_node_get_payload_size(n, &size); if (r < 0) return r; if (e->payload_offset >= size) /* at EOF? */ return 0; if (realloc_buffer_size(&e->buffer) > 0) /* already in buffer? */ return 1; k = (size_t) MIN(BUFFER_SIZE, size - e->payload_offset); p = realloc_buffer_acquire(&e->buffer, k); if (!p) return -ENOMEM; m = read(n->fd, p, k); if (m < 0) { r = -errno; goto fail; } if ((size_t) m != k) { r = -EIO; goto fail; } return 1; fail: realloc_buffer_empty(&e->buffer); return r; } static int ca_encoder_get_filename_data(CaEncoder *e, const struct dirent *de) { CaFormatFilename *filename; size_t size; assert(e); assert(de); if (realloc_buffer_size(&e->buffer) > 0) return 1; size = offsetof(CaFormatFilename, name) + strlen(de->d_name) + 1; filename = realloc_buffer_acquire(&e->buffer, size); if (!filename) return -ENOMEM; filename->header = (CaFormatHeader) { .type = htole64(CA_FORMAT_FILENAME), .size = htole64(size), }; strcpy(filename->name, de->d_name); return 1; } static uint64_t ca_encoder_fixup_mtime(CaEncoder *e, CaEncoderNode *n) { uint64_t mtime; assert(e); assert(n); if (e->time_granularity == UINT64_MAX) mtime = 0; else { mtime = timespec_to_nsec(n->stat.st_mtim); mtime = (mtime / e->time_granularity) * e->time_granularity; } return mtime; } static mode_t ca_encoder_fixup_mode(CaEncoder *e, CaEncoderNode *n) { mode_t mode; assert(e); assert(n); mode = n->stat.st_mode; if (S_ISLNK(mode)) mode = S_IFLNK | 0777; else if (e->feature_flags & (CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_ACL)) mode = mode & (S_IFMT|07777); else if (e->feature_flags & CA_FORMAT_WITH_READ_ONLY) mode = (mode & S_IFMT) | ((mode & 0222) ? (S_ISDIR(mode) ? 0777 : 0666) : (S_ISDIR(mode) ? 0555 : 0444)); else mode = (mode & S_IFMT) | (S_ISDIR(mode) ? 0777 : 0666); return mode; } static size_t ca_encoder_format_acl_user_size(CaEncoderACLEntry *l, size_t n) { size_t i, size = 0; for (i = 0; i < n; i++) size += offsetof(CaFormatACLUser, name) + (l[i].name ? strlen(l[i].name) + 1 : 0); return size; } static size_t ca_encoder_format_acl_group_size(CaEncoderACLEntry *l, size_t n) { size_t i, size = 0; for (i = 0; i < n; i++) size += offsetof(CaFormatACLGroup, name) + (l[i].name ? strlen(l[i].name) + 1 : 0); return size; } static void *ca_encoder_format_acl_user_append(CaEncoder *e, void *p, uint64_t type, CaEncoderACLEntry *l, size_t n) { CaFormatACLUser *acl_user; size_t i; assert(e); assert(p); acl_user = alloca0(offsetof(CaFormatACLUser, name)); for (i = 0; i < n; i++) { acl_user->header = (CaFormatHeader) { .type = htole64(type), .size = htole64(offsetof(CaFormatACLUser, name) + (l[i].name ? strlen(l[i].name) + 1 : 0)), }; acl_user->uid = (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) ? htole64(l[i].uid) : htole64(0); acl_user->permissions = htole64(l[i].permissions); p = mempcpy(p, acl_user, offsetof(CaFormatACLUser, name)); if (l[i].name) p = stpcpy(p, l[i].name) + 1; } return p; } static void *ca_encoder_format_acl_group_append(CaEncoder *e, void *p, uint64_t type, CaEncoderACLEntry *l, size_t n) { CaFormatACLGroup *acl_group; size_t i; assert(e); assert(p); acl_group = alloca0(offsetof(CaFormatACLGroup, name)); for (i = 0; i < n; i++) { acl_group->header = (CaFormatHeader) { .type = htole64(type), .size = htole64(offsetof(CaFormatACLGroup, name) + (l[i].name ? strlen(l[i].name) + 1 : 0)), }; acl_group->gid = (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) ? htole64(l[i].gid) : htole64(0); acl_group->permissions = htole64(l[i].permissions); p = mempcpy(p, acl_group, offsetof(CaFormatACLGroup, name)); if (l[i].name) p = stpcpy(p, l[i].name) + 1; } return p; } static int ca_encoder_get_entry_data(CaEncoder *e, CaEncoderNode *n) { uint64_t mtime, mode, uid, gid, flags = 0, fsize; CaFormatEntry *entry; size_t size, i; void *p; int r; assert(e); assert(n); assert(e->state == CA_ENCODER_ENTRY); if (realloc_buffer_size(&e->buffer) > 0) /* Already generated */ return 1; r = ca_encoder_node_read_chattr(e, n); if (r < 0) return r; r = ca_encoder_node_read_fat_attrs(e, n); if (r < 0) return r; r = ca_encoder_node_read_btrfs(e, n); if (r < 0) return r; r = ca_encoder_node_read_selinux_label(e, n); if (r < 0) return r; r = ca_encoder_node_read_xattrs(e, n); if (r < 0) return r; r = ca_encoder_node_read_acl(e, n); if (r < 0) return r; r = ca_encoder_node_read_user_group_names(e, n); if (r < 0) return r; if (e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) { uid_t shifted_uid; gid_t shifted_gid; shifted_uid = ca_encoder_shift_uid(e, n->stat.st_uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; shifted_gid = ca_encoder_shift_gid(e, n->stat.st_gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; if ((e->feature_flags & CA_FORMAT_WITH_16BIT_UIDS) && (shifted_uid > UINT16_MAX || shifted_gid > UINT16_MAX)) return -EPROTONOSUPPORT; uid = shifted_uid; gid = shifted_gid; } else uid = gid = 0; if ((e->feature_flags & CA_FORMAT_WITH_SYMLINKS) == 0 && S_ISLNK(n->stat.st_mode)) return -EPROTONOSUPPORT; if ((e->feature_flags & CA_FORMAT_WITH_DEVICE_NODES) == 0 && (S_ISBLK(n->stat.st_mode) || S_ISCHR(n->stat.st_mode))) return -EPROTONOSUPPORT; if ((e->feature_flags & CA_FORMAT_WITH_FIFOS) == 0 && S_ISFIFO(n->stat.st_mode)) return -EPROTONOSUPPORT; if ((e->feature_flags & CA_FORMAT_WITH_SOCKETS) == 0 && S_ISSOCK(n->stat.st_mode)) return -EPROTONOSUPPORT; mtime = ca_encoder_fixup_mtime(e, n); mode = ca_encoder_fixup_mode(e, n); if (S_ISDIR(n->stat.st_mode) || S_ISREG(n->stat.st_mode)) { /* chattr(1) flags and FAT file flags are only defined for regular files and directories */ if ((e->feature_flags & CA_FORMAT_WITH_CHATTR) != 0) { assert(n->chattr_flags_valid); flags |= ca_feature_flags_from_chattr(n->chattr_flags) & e->feature_flags; } if ((e->feature_flags & CA_FORMAT_WITH_FAT_ATTRS) != 0) { assert(n->fat_attrs_valid); flags |= ca_feature_flags_from_fat_attrs(n->fat_attrs) & e->feature_flags; } } if (S_ISDIR(n->stat.st_mode) && (e->feature_flags & CA_FORMAT_WITH_SUBVOLUME)) { assert(n->subvolume_valid); flags |= ((n->is_subvolume ? CA_FORMAT_WITH_SUBVOLUME : 0) | (n->is_subvolume_ro ? CA_FORMAT_WITH_SUBVOLUME_RO : 0)) & e->feature_flags; } r = ca_encoder_node_shall_enumerate(e, n); if (r < 0) return r; fsize = r > 0 ? n->stat.st_size : 0; size = sizeof(CaFormatEntry); if (n->stat.st_uid == e->cached_uid && e->cached_user_name) size += offsetof(CaFormatUser, name) + strlen(e->cached_user_name) + 1; if (n->stat.st_gid == e->cached_gid && e->cached_group_name) size += offsetof(CaFormatGroup, name) + strlen(e->cached_group_name) + 1; for (i = 0; i < n->n_xattrs; i++) size += offsetof(CaFormatXAttr, name_and_value) + strlen(n->xattrs[i].name) + 1 + n->xattrs[i].data_size; size += ca_encoder_format_acl_user_size(n->acl_user, n->n_acl_user); size += ca_encoder_format_acl_group_size(n->acl_group, n->n_acl_group); if (n->acl_group_obj_permissions != UINT64_MAX) size += sizeof(CaFormatACLGroupObj); if (n->acl_default_user_obj_permissions != UINT64_MAX || n->acl_default_group_obj_permissions != UINT64_MAX || n->acl_default_other_permissions != UINT64_MAX || n->acl_default_mask_permissions != UINT64_MAX) size += sizeof(CaFormatACLDefault); size += ca_encoder_format_acl_user_size(n->acl_default_user, n->n_acl_default_user); size += ca_encoder_format_acl_group_size(n->acl_default_group, n->n_acl_default_group); if (n->selinux_label_valid && n->selinux_label) size += offsetof(CaFormatSELinux, label) + strlen(n->selinux_label) + 1; if (n->fcaps) size += offsetof(CaFormatFCaps, data) + n->fcaps_size; if (S_ISREG(n->stat.st_mode)) size += offsetof(CaFormatPayload, data); else if (S_ISLNK(n->stat.st_mode)) size += offsetof(CaFormatSymlink, target) + strlen(n->symlink_target) + 1; else if (S_ISBLK(n->stat.st_mode) || S_ISCHR(n->stat.st_mode)) size += sizeof(CaFormatDevice); entry = realloc_buffer_acquire(&e->buffer, size); if (!entry) return -ENOMEM; *entry = (CaFormatEntry) { .header.type = htole64(CA_FORMAT_ENTRY), .header.size = htole64(sizeof(CaFormatEntry)), .feature_flags = htole64(e->feature_flags), .mode = htole64(mode), .flags = htole64(flags), .uid = htole64(uid), .gid = htole64(gid), .mtime = htole64(mtime), }; p = (uint8_t*) entry + sizeof(CaFormatEntry); /* Note that any follow-up structures from here are unaligned in memory! */ if (n->stat.st_uid == e->cached_uid && e->cached_user_name) { CaFormatHeader header = { .type = htole64(CA_FORMAT_USER), .size = htole64(offsetof(CaFormatUser, name) + strlen(e->cached_user_name) + 1), }; p = mempcpy(p, &header, sizeof(header)); p = stpcpy(p, e->cached_user_name) + 1; } if (n->stat.st_gid == e->cached_gid && e->cached_group_name) { CaFormatHeader header = { .type = htole64(CA_FORMAT_GROUP), .size = htole64(offsetof(CaFormatGroup, name) + strlen(e->cached_group_name) + 1), }; p = mempcpy(p, &header, sizeof(header)); p = stpcpy(p, e->cached_group_name) + 1; } for (i = 0; i < n->n_xattrs; i++) { CaFormatHeader header = { .type = htole64(CA_FORMAT_XATTR), .size = htole64(offsetof(CaFormatXAttr, name_and_value) + strlen(n->xattrs[i].name) + 1 + n->xattrs[i].data_size), }; p = mempcpy(p, &header, sizeof(header)); p = stpcpy(p, n->xattrs[i].name) + 1; p = mempcpy(p, n->xattrs[i].data, n->xattrs[i].data_size); } p = ca_encoder_format_acl_user_append(e, p, CA_FORMAT_ACL_USER, n->acl_user, n->n_acl_user); p = ca_encoder_format_acl_group_append(e, p, CA_FORMAT_ACL_GROUP, n->acl_group, n->n_acl_group); if (n->acl_group_obj_permissions != UINT64_MAX) { CaFormatACLGroupObj acl_group_obj = { .header.type = htole64(CA_FORMAT_ACL_GROUP_OBJ), .header.size = htole64(sizeof(CaFormatACLGroupObj)), .permissions = htole64(n->acl_group_obj_permissions), }; p = mempcpy(p, &acl_group_obj, sizeof(acl_group_obj)); } if (n->acl_default_user_obj_permissions != UINT64_MAX || n->acl_default_group_obj_permissions != UINT64_MAX || n->acl_default_other_permissions != UINT64_MAX || n->acl_default_mask_permissions != UINT64_MAX) { CaFormatACLDefault acl_default = { .header.type = htole64(CA_FORMAT_ACL_DEFAULT), .header.size = htole64(sizeof(CaFormatACLDefault)), .user_obj_permissions = htole64(n->acl_default_user_obj_permissions), .group_obj_permissions = htole64(n->acl_default_group_obj_permissions), .other_permissions = htole64(n->acl_default_other_permissions), .mask_permissions = htole64(n->acl_default_mask_permissions), }; p = mempcpy(p, &acl_default, sizeof(acl_default)); } p = ca_encoder_format_acl_user_append(e, p, CA_FORMAT_ACL_DEFAULT_USER, n->acl_default_user, n->n_acl_default_user); p = ca_encoder_format_acl_group_append(e, p, CA_FORMAT_ACL_DEFAULT_GROUP, n->acl_default_group, n->n_acl_default_group); if (n->selinux_label_valid && n->selinux_label) { CaFormatHeader header = { .type = htole64(CA_FORMAT_SELINUX), .size = htole64(offsetof(CaFormatSELinux, label) + strlen(n->selinux_label) + 1), }; p = mempcpy(p, &header, sizeof(header)); p = stpcpy(p, n->selinux_label) + 1; } if (n->fcaps) { CaFormatHeader header = { .type = htole64(CA_FORMAT_FCAPS), .size = htole64(offsetof(CaFormatFCaps, data) + n->fcaps_size), }; p = mempcpy(p, &header, sizeof(header)); p = mempcpy(p, n->fcaps, n->fcaps_size); } if (S_ISREG(n->stat.st_mode)) { CaFormatHeader header = { .type = htole64(CA_FORMAT_PAYLOAD), .size = htole64(offsetof(CaFormatPayload, data) + fsize), }; memcpy(p, &header, sizeof(header)); } else if (S_ISLNK(n->stat.st_mode)) { CaFormatHeader header = { .type = htole64(CA_FORMAT_SYMLINK), .size = htole64(offsetof(CaFormatSymlink, target) + strlen(n->symlink_target) + 1), }; p = mempcpy(p, &header, sizeof(header)); strcpy(p, n->symlink_target); } else if (S_ISBLK(n->stat.st_mode) || S_ISCHR(n->stat.st_mode)) { CaFormatDevice device = { .header.type = htole64(CA_FORMAT_DEVICE), .header.size = htole64(sizeof(CaFormatDevice)), .major = htole64(major(n->stat.st_rdev)), .minor = htole64(minor(n->stat.st_rdev)), }; memcpy(p, &device, sizeof(device)); } /* fprintf(stderr, "entry at %" PRIu64 " (%s)\n", e->archive_offset, entry->name); */ n->entry_offset = e->archive_offset; return 1; } static int name_table_compare(const void *a, const void *b) { const CaEncoderNameTable *x = a, *y = b; if (x->hash < y->hash) return -1; if (x->hash > y->hash) return 1; if (x->start_offset < y->start_offset) return -1; if (x->start_offset > y->start_offset) return 1; return 0; } static int ca_encoder_get_goodbye_data(CaEncoder *e, CaEncoderNode *n) { const CaEncoderNameTable *table; _cleanup_free_ CaEncoderNameTable *bst = NULL; CaFormatGoodbye *g; CaFormatGoodbyeTail *tail; size_t size, i; assert(e); assert(n); assert(S_ISDIR(n->stat.st_mode)); assert(e->state == CA_ENCODER_GOODBYE); assert(sizeof(CaFormatGoodbyeTail) == sizeof(CaFormatGoodbyeItem)); if (realloc_buffer_size(&e->buffer) > 0) /* Already generated */ return 1; /* If we got here through a seek we don't know the correct addresses of the directory entries, hence can't * correctly generate the name GOODBYE record. */ if (n->name_table_incomplete) return -ENOLINK; /* Similar, if we don't know the entry offset */ if (n->entry_offset == UINT64_MAX) return -ENOLINK; size = offsetof(CaFormatGoodbye, items) + sizeof(CaFormatGoodbyeItem) * n->n_name_table + sizeof(CaFormatGoodbyeTail); g = realloc_buffer_acquire(&e->buffer, size); if (!g) return -ENOMEM; g->header = (CaFormatHeader) { .type = htole64(CA_FORMAT_GOODBYE), .size = htole64(size), }; if (n->n_name_table <= 1) table = n->name_table; else { qsort(n->name_table, n->n_name_table, sizeof(CaEncoderNameTable), name_table_compare); bst = new(CaEncoderNameTable, n->n_name_table); if (!bst) return -ENOMEM; ca_make_bst(n->name_table, n->n_name_table, sizeof(CaEncoderNameTable), bst); table = bst; } for (i = 0; i < n->n_name_table; i++) { assert(table[i].start_offset < e->archive_offset); assert(table[i].end_offset <= e->archive_offset); assert(table[i].start_offset < table[i].end_offset); g->items[i] = (CaFormatGoodbyeItem) { .offset = htole64(e->archive_offset - table[i].start_offset), .size = htole64(table[i].end_offset - table[i].start_offset), .hash = htole64(table[i].hash), }; } assert(n->entry_offset != UINT64_MAX); assert(n->entry_offset < e->archive_offset); /* Write the tail */ tail = (CaFormatGoodbyeTail*) (g->items + n->n_name_table); write_le64(&tail->entry_offset, e->archive_offset - n->entry_offset); write_le64(&tail->size, size); write_le64(&tail->marker, CA_FORMAT_GOODBYE_TAIL_MARKER); return 1; } static int ca_encoder_write_digest(CaEncoder *e, CaDigest **digest, const void *p, size_t l) { int r; if (!e) return -EINVAL; if (!digest) return -EINVAL; r = ca_digest_ensure_allocated(digest, ca_feature_flags_to_digest_type(e->feature_flags)); if (r < 0) return r; ca_digest_write(*digest, p, l); return 0; } int ca_encoder_get_data(CaEncoder *e, const void **ret, size_t *ret_size) { bool skip_applied = false; CaEncoderNode *n; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; switch (e->state) { case CA_ENCODER_ENTRY: r = ca_encoder_get_entry_data(e, n); if (r < 0) return r; break; case CA_ENCODER_IN_PAYLOAD: assert(S_ISREG(n->stat.st_mode) || S_ISBLK(n->stat.st_mode)); skip_applied = true; r = ca_encoder_get_payload_data(e, n); break; case CA_ENCODER_FILENAME: { const struct dirent *de; assert(S_ISDIR(n->stat.st_mode)); de = ca_encoder_node_current_dirent(n); if (!de) return -EUNATCH; r = ca_encoder_get_filename_data(e, de); break; } case CA_ENCODER_GOODBYE: assert(S_ISDIR(n->stat.st_mode)); r = ca_encoder_get_goodbye_data(e, n); break; default: return -ENODATA; } if (r < 0) return r; if (!skip_applied && r > 0) { /* When we got here due to a seek, there might be an additional offset set, simply drop it form our generated buffer. */ r = realloc_buffer_advance(&e->buffer, e->payload_offset); if (r < 0) return r; r = 1; } if (r == 0) { /* EOF */ *ret = NULL; *ret_size = 0; return 0; } if (e->want_archive_digest) ca_encoder_write_digest(e, &e->archive_digest, realloc_buffer_data(&e->buffer), realloc_buffer_size(&e->buffer)); if (e->want_hardlink_digest && !e->hardlink_digest_invalid && IN_SET(e->state, CA_ENCODER_ENTRY, CA_ENCODER_IN_PAYLOAD)) ca_encoder_write_digest(e, &e->hardlink_digest, realloc_buffer_data(&e->buffer), realloc_buffer_size(&e->buffer)); if (e->want_payload_digest && !e->payload_digest_invalid && e->state == CA_ENCODER_IN_PAYLOAD) ca_encoder_write_digest(e, &e->payload_digest, realloc_buffer_data(&e->buffer), realloc_buffer_size(&e->buffer)); *ret = realloc_buffer_data(&e->buffer); *ret_size = realloc_buffer_size(&e->buffer); return 1; } static int ca_encoder_node_path(CaEncoder *e, CaEncoderNode *node, char **ret) { _cleanup_free_ char *p = NULL; size_t n = 0, i; bool found = false; if (!e) return -EINVAL; if (!node) return -EINVAL; if (!ret) return -EINVAL; for (i = 0; i <= e->n_nodes; i++) { CaEncoderNode *in; const struct dirent *de; char *np, *q; size_t k, nn; in = e->nodes + i; if (in == node) { found = true; break; } de = ca_encoder_node_current_dirent(in); if (!de) break; k = strlen(de->d_name); nn = n + (n > 0) + k; np = realloc(p, nn+1); if (!np) return -ENOMEM; q = np + n; if (n > 0) *(q++) = '/'; strcpy(q, de->d_name); p = np; n = nn; } if (!found) return -EINVAL; if (!p) { p = strdup(""); if (!p) return -ENOMEM; } *ret = p; p = NULL; return 0; } int ca_encoder_current_path(CaEncoder *e, char **ret) { CaEncoderNode *node; if (!e) return -EINVAL; if (!ret) return -EINVAL; node = ca_encoder_current_node(e); if (!node) return -EUNATCH; return ca_encoder_node_path(e, node, ret); } int ca_encoder_current_mode(CaEncoder *e, mode_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; *ret = ca_encoder_fixup_mode(e, n); return 0; } int ca_encoder_current_target(CaEncoder *e, const char **ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISLNK(n->stat.st_mode)) return -ENOLINK; if (!n->symlink_target) return -ENODATA; *ret = n->symlink_target; return 0; } int ca_encoder_current_mtime(CaEncoder *e, uint64_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (e->time_granularity == UINT64_MAX) return -ENODATA; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; *ret = ca_encoder_fixup_mtime(e, n); return 0; } int ca_encoder_current_size(CaEncoder *e, uint64_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISREG(n->stat.st_mode)) return -ENODATA; *ret = n->stat.st_size; return 0; } int ca_encoder_current_uid(CaEncoder *e, uid_t *ret) { CaEncoderNode *n; uid_t shifted_uid; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!(e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS))) return -ENODATA; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; shifted_uid = ca_encoder_shift_uid(e, n->stat.st_uid); if (!uid_is_valid(shifted_uid)) return -EINVAL; *ret = shifted_uid; return 0; } int ca_encoder_current_gid(CaEncoder *e, gid_t *ret) { CaEncoderNode *n; gid_t shifted_gid; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!(e->feature_flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS))) return -ENODATA; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; shifted_gid = ca_encoder_shift_gid(e, n->stat.st_gid); if (!gid_is_valid(shifted_gid)) return -EINVAL; *ret = shifted_gid; return 0; } int ca_encoder_current_user(CaEncoder *e, const char **ret) { CaEncoderNode *n; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!(e->feature_flags & (CA_FORMAT_WITH_USER_NAMES))) return -ENODATA; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; r = ca_encoder_node_read_user_group_names(e, n); if (r < 0) return r; if (!e->cached_user_name || e->cached_uid != n->stat.st_uid) return -ENODATA; *ret = e->cached_user_name; return 0; } int ca_encoder_current_group(CaEncoder *e, const char **ret) { CaEncoderNode *n; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!(e->feature_flags & (CA_FORMAT_WITH_USER_NAMES))) return -ENODATA; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; r = ca_encoder_node_read_user_group_names(e, n); if (r < 0) return r; if (!e->cached_group_name || e->cached_gid != n->stat.st_gid) return -ENODATA; *ret = e->cached_group_name; return 0; } int ca_encoder_current_rdev(CaEncoder *e, dev_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISBLK(n->stat.st_mode) && !S_ISCHR(n->stat.st_mode)) return -ENODATA; *ret = n->stat.st_rdev; return 0; } int ca_encoder_current_chattr(CaEncoder *e, unsigned *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISREG(n->stat.st_mode) && !S_ISDIR(n->stat.st_mode)) return -ENODATA; if (!n->chattr_flags_valid) return -ENODATA; *ret = ca_feature_flags_to_chattr((ca_feature_flags_from_chattr(n->chattr_flags) & e->feature_flags)); return 0; } int ca_encoder_current_fat_attrs(CaEncoder *e, uint32_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISREG(n->stat.st_mode) && !S_ISDIR(n->stat.st_mode)) return -ENODATA; if (!n->fat_attrs_valid) return -ENODATA; *ret = ca_feature_flags_to_fat_attrs((ca_feature_flags_from_fat_attrs(n->fat_attrs) & e->feature_flags)); return 0; } int ca_encoder_current_xattr(CaEncoder *e, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size) { CaEncoderNode *n; size_t p; if (!e) return -EINVAL; if (!ret_name) return -EINVAL; if (where < 0) return -EINVAL; if (where >= _CA_ITERATE_MAX) return -EINVAL; if (!ret_name) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!n->xattrs_valid) return -ENODATA; switch (where) { case CA_ITERATE_NEXT: if (n->xattrs_idx == (size_t) -1) goto eof; p = n->xattrs_idx + 1; break; case CA_ITERATE_PREVIOUS: if (n->xattrs_idx == (size_t) -1 || n->xattrs_idx == 0) goto eof; p = n->xattrs_idx - 1; break; case CA_ITERATE_FIRST: p = 0; break; case CA_ITERATE_LAST: if (n->n_xattrs == 0) goto eof; p = n->n_xattrs - 1; break; case CA_ITERATE_CURRENT: p = n->xattrs_idx; break; case _CA_ITERATE_MAX: case _CA_ITERATE_INVALID: default: assert(false); } if (p == (size_t) -1) goto eof; if (p >= n->n_xattrs) goto eof; n->xattrs_idx = p; *ret_name = n->xattrs[p].name; if (ret_value) *ret_value = n->xattrs[p].data; if (ret_size) *ret_size = n->xattrs[p].data_size; return 1; eof: *ret_name = NULL; if (ret_value) *ret_value = NULL; if (ret_size) *ret_size = 0; return 0; } int ca_encoder_current_payload_offset(CaEncoder *e, uint64_t *ret) { CaEncoderNode *n; if (!e) return -EINVAL; if (!ret) return -EINVAL; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISREG(n->stat.st_mode) && !S_ISBLK(n->stat.st_mode)) return -EISDIR; *ret = e->payload_offset; return 0; } int ca_encoder_current_archive_offset(CaEncoder *e, uint64_t *ret) { if (!e) return -EINVAL; if (!ret) return -EINVAL; if (e->archive_offset == UINT64_MAX) return -ENODATA; *ret = e->archive_offset; return 0; } int ca_encoder_current_location(CaEncoder *e, uint64_t add, CaLocation **ret) { CaLocationDesignator designator; CaEncoderNode *node; _cleanup_free_ char *path = NULL; CaLocation *l; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; node = ca_encoder_current_node(e); if (!node) return -EUNATCH; switch (e->state) { case CA_ENCODER_ENTRY: designator = CA_LOCATION_ENTRY; break; case CA_ENCODER_IN_PAYLOAD: assert(S_ISREG(node->stat.st_mode) || S_ISBLK(node->stat.st_mode)); designator = CA_LOCATION_PAYLOAD; break; case CA_ENCODER_FILENAME: assert(S_ISDIR(node->stat.st_mode)); node = ca_encoder_current_child_node(e); if (!node) return -EUNATCH; designator = CA_LOCATION_FILENAME; break; case CA_ENCODER_GOODBYE: assert(S_ISDIR(node->stat.st_mode)); designator = CA_LOCATION_GOODBYE; break; default: return -ENOTTY; } r = ca_encoder_node_path(e, node, &path); if (r < 0 && r != -ENOTDIR) return r; r = ca_location_new(path, designator, e->payload_offset + add, UINT64_MAX, &l); if (r < 0) return r; *ret = l; path = NULL; return 0; } static int dirent_bsearch_func(const void *key, const void *member) { const char *k = key; const struct dirent ** const m = (const struct dirent ** const) member; return strcmp(k, (*m)->d_name); } static int ca_encoder_node_seek_child(CaEncoder *e, CaEncoderNode *n, const char *name) { const struct dirent *de; int r; assert(n); if (!S_ISDIR(n->stat.st_mode)) return -ENOTDIR; r = ca_encoder_node_read_dirents(n); if (r < 0) return r; n->name_table_incomplete = true; de = ca_encoder_node_current_dirent(n); if (de && streq(name, de->d_name)) { CaEncoderNode *child; child = ca_encoder_node_child_of(e, n); if (child && child->stat.st_mode != 0) return 0; } else { struct dirent **found; found = bsearch(name, n->dirents, n->n_dirents, sizeof(struct dirent*), dirent_bsearch_func); if (!found) return -ENOENT; assert(found >= n->dirents); assert((size_t) (found - n->dirents) < n->n_dirents); n->dirent_idx = found - n->dirents; de = *found; } return ca_encoder_open_child(e, n, de); } static int ca_encoder_seek_path(CaEncoder *e, const char *path) { CaEncoderNode *node; int r; assert(e); assert(path); /* fprintf(stderr, "seeking to: %s\n", path); */ node = ca_encoder_current_node(e); if (!node) return -EUNATCH; for (;;) { size_t l = strcspn(path, "/"); char name[l + 1]; if (l <= 0) return -EINVAL; if (!S_ISDIR(node->stat.st_mode)) return -ENOTDIR; memcpy(name, path, l); name[l] = 0; r = ca_encoder_node_seek_child(e, node, name); if (r < 0) return r; path += l; if (*path == 0) break; path++; r = ca_encoder_enter_child(e); if (r < 0) return r; node = ca_encoder_current_node(e); assert(node); } return 0; } static int ca_encoder_seek_path_and_enter(CaEncoder *e, const char *path) { int r; assert(e); if (isempty(path)) return 0; r = ca_encoder_seek_path(e, path); if (r < 0) return r; return ca_encoder_enter_child(e); } int ca_encoder_seek_location(CaEncoder *e, CaLocation *location) { CaEncoderNode *node; int r; if (!e) return -EINVAL; if (!location) return -EINVAL; if (location->size == 0) return -EINVAL; if (!CA_LOCATION_DESIGNATOR_VALID(location->designator)) return -EINVAL; if (e->n_nodes == 0) return -EUNATCH; e->node_idx = 0; switch (location->designator) { case CA_LOCATION_ENTRY: r = ca_encoder_seek_path_and_enter(e, location->path); if (r < 0) return r; node = ca_encoder_current_node(e); assert(node); node->dirent_idx = 0; ca_encoder_enter_state(e, CA_ENCODER_ENTRY); e->payload_offset = location->offset; e->archive_offset = UINT64_MAX; realloc_buffer_empty(&e->buffer); ca_digest_reset(e->archive_digest); if (e->want_payload_digest) { ca_digest_reset(e->payload_digest); e->payload_digest_invalid = false; } e->hardlink_digest_invalid = location->offset > 0; if (e->want_hardlink_digest && !e->hardlink_digest_invalid) ca_digest_reset(e->hardlink_digest); return CA_ENCODER_DATA; case CA_LOCATION_PAYLOAD: { uint64_t size; r = ca_encoder_seek_path_and_enter(e, location->path); if (r < 0) return r; node = ca_encoder_current_node(e); assert(node); if (!S_ISREG(node->stat.st_mode) && !S_ISBLK(node->stat.st_mode)) return -EISDIR; r = ca_encoder_node_get_payload_size(node, &size); if (r < 0) return r; if (location->offset >= size) return -ENXIO; if (lseek(node->fd, location->offset, SEEK_SET) == (off_t) -1) return -errno; ca_encoder_enter_state(e, CA_ENCODER_IN_PAYLOAD); e->payload_offset = location->offset; if (CA_ENCODER_AT_ROOT(e)) e->archive_offset = location->offset; else e->archive_offset = UINT64_MAX; realloc_buffer_empty(&e->buffer); ca_digest_reset(e->archive_digest); e->payload_digest_invalid = location->offset > 0; if (e->want_payload_digest && !e->payload_digest_invalid) ca_digest_reset(e->payload_digest); e->hardlink_digest_invalid = true; return CA_ENCODER_PAYLOAD; } case CA_LOCATION_FILENAME: r = ca_encoder_seek_path(e, location->path); if (r < 0) return r; node = ca_encoder_current_node(e); assert(node); if (!S_ISDIR(node->stat.st_mode)) return -ENOTDIR; ca_encoder_enter_state(e, CA_ENCODER_FILENAME); e->payload_offset = location->offset; e->archive_offset = UINT64_MAX; realloc_buffer_empty(&e->buffer); ca_digest_reset(e->archive_digest); return CA_ENCODER_DATA; case CA_LOCATION_GOODBYE: r = ca_encoder_seek_path_and_enter(e, location->path); if (r < 0) return r; node = ca_encoder_current_node(e); assert(node); if (!S_ISDIR(node->stat.st_mode)) return -ENOTDIR; r = ca_encoder_node_read_dirents(node); if (r < 0) return r; node->dirent_idx = node->n_dirents; node->name_table_incomplete = node->n_dirents > 0; ca_encoder_enter_state(e, CA_ENCODER_GOODBYE); e->payload_offset = location->offset; e->archive_offset = UINT64_MAX; realloc_buffer_empty(&e->buffer); ca_digest_reset(e->archive_digest); return CA_ENCODER_DATA; default: return -EINVAL; } return 0; } int ca_encoder_set_uid_shift(CaEncoder *e, uid_t u) { if (!e) return -EINVAL; e->uid_shift = u; return 0; } int ca_encoder_set_uid_range(CaEncoder *e, uid_t u) { if (!e) return -EINVAL; e->uid_range = u; return 0; } int ca_encoder_enable_archive_digest(CaEncoder *e, bool b) { if (!e) return -EINVAL; e->want_archive_digest = b; return 0; } int ca_encoder_enable_payload_digest(CaEncoder *e, bool b) { if (!e) return -EINVAL; e->want_payload_digest = b; return 0; } int ca_encoder_enable_hardlink_digest(CaEncoder *e, bool b) { if (!e) return -EINVAL; e->want_hardlink_digest = b; return 0; } int ca_encoder_get_archive_digest(CaEncoder *e, CaChunkID *ret) { const void *q; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!e->want_archive_digest) return -ENOMEDIUM; if (e->state != CA_ENCODER_EOF) return -EBUSY; r = ca_digest_ensure_allocated(&e->archive_digest, ca_feature_flags_to_digest_type(e->feature_flags)); if (r < 0) return r; q = ca_digest_read(e->archive_digest); if (!q) return -EIO; assert(ca_digest_get_size(e->archive_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } int ca_encoder_get_payload_digest(CaEncoder *e, CaChunkID *ret) { CaEncoderNode *n; const void *q; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!e->want_payload_digest) return -ENOMEDIUM; if (e->state != CA_ENCODER_FINALIZE) return -EBUSY; if (e->payload_digest_invalid) return -ESTALE; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (!S_ISREG(n->stat.st_mode) && !S_ISBLK(n->stat.st_mode)) return -ENOTTY; r = ca_digest_ensure_allocated(&e->payload_digest, ca_feature_flags_to_digest_type(e->feature_flags)); if (r < 0) return r; q = ca_digest_read(e->payload_digest); if (!q) return -EIO; assert(ca_digest_get_size(e->payload_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } int ca_encoder_get_hardlink_digest(CaEncoder *e, CaChunkID *ret) { CaEncoderNode *n; const void *q; int r; if (!e) return -EINVAL; if (!ret) return -EINVAL; if (!e->want_hardlink_digest) return -ENOMEDIUM; if (e->state != CA_ENCODER_FINALIZE) return -EBUSY; if (e->hardlink_digest_invalid) return -ESTALE; n = ca_encoder_current_node(e); if (!n) return -EUNATCH; if (S_ISDIR(n->stat.st_mode)) return -EISDIR; r = ca_digest_ensure_allocated(&e->hardlink_digest, ca_feature_flags_to_digest_type(e->feature_flags)); if (r < 0) return r; q = ca_digest_read(e->hardlink_digest); if (!q) return -EIO; assert(ca_digest_get_size(e->hardlink_digest) == sizeof(CaChunkID)); memcpy(ret, q, sizeof(CaChunkID)); return 0; } src/caencoder.h000066400000000000000000000055061322621430500137450ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaencoderhfoo #define foocaencoderhfoo #include #include #include #include "cachunkid.h" #include "cacommon.h" #include "calocation.h" typedef struct CaEncoder CaEncoder; enum { CA_ENCODER_FINISHED, /* The end of the stream has been reached */ CA_ENCODER_NEXT_FILE, /* We started with the next file, and we generated some data */ CA_ENCODER_DONE_FILE, /* We finished with some file, and possibly generated some data */ CA_ENCODER_PAYLOAD, /* We just read some payload data, and we generated some data */ CA_ENCODER_DATA, /* We generated some data */ }; CaEncoder *ca_encoder_new(void); CaEncoder *ca_encoder_unref(CaEncoder *e); int ca_encoder_set_feature_flags(CaEncoder *e, uint64_t flags); int ca_encoder_get_feature_flags(CaEncoder *e, uint64_t *ret); int ca_encoder_get_covering_feature_flags(CaEncoder *e, uint64_t *ret); int ca_encoder_set_uid_shift(CaEncoder *e, uid_t u); int ca_encoder_set_uid_range(CaEncoder *e, uid_t u); /* Input: a directory tree, block device node or regular file */ int ca_encoder_set_base_fd(CaEncoder *e, int fd); int ca_encoder_get_base_fd(CaEncoder *e); int ca_encoder_step(CaEncoder *e); /* Output: archive stream data */ int ca_encoder_get_data(CaEncoder *e, const void **ret, size_t *ret_size); int ca_encoder_current_path(CaEncoder *e, char **ret); int ca_encoder_current_mode(CaEncoder *d, mode_t *ret); int ca_encoder_current_target(CaEncoder *e, const char **ret); int ca_encoder_current_mtime(CaEncoder *e, uint64_t *nsec); int ca_encoder_current_size(CaEncoder *e, uint64_t *size); int ca_encoder_current_uid(CaEncoder *e, uid_t *ret); int ca_encoder_current_gid(CaEncoder *e, gid_t *ret); int ca_encoder_current_user(CaEncoder *e, const char **ret); int ca_encoder_current_group(CaEncoder *e, const char **ret); int ca_encoder_current_rdev(CaEncoder *e, dev_t *ret); int ca_encoder_current_chattr(CaEncoder *e, unsigned *ret); int ca_encoder_current_fat_attrs(CaEncoder *e, uint32_t *ret); int ca_encoder_current_xattr(CaEncoder *e, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size); int ca_encoder_current_payload_offset(CaEncoder *e, uint64_t *ret); int ca_encoder_current_archive_offset(CaEncoder *e, uint64_t *ret); int ca_encoder_current_location(CaEncoder *e, uint64_t add, CaLocation **ret); int ca_encoder_seek_location(CaEncoder *e, CaLocation *location); int ca_encoder_enable_archive_digest(CaEncoder *e, bool b); int ca_encoder_enable_payload_digest(CaEncoder *e, bool b); int ca_encoder_enable_hardlink_digest(CaEncoder *e, bool b); int ca_encoder_get_archive_digest(CaEncoder *e, CaChunkID *ret); int ca_encoder_get_hardlink_digest(CaEncoder *e, CaChunkID *ret); int ca_encoder_get_payload_digest(CaEncoder *e, CaChunkID *ret); #endif src/cafileroot.c000066400000000000000000000025051322621430500141400ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "cafileroot.h" #include "util.h" int ca_file_root_new(const char *path, int fd, CaFileRoot **ret) { CaFileRoot *root; if (!path && fd < 0) return -EINVAL; if (!ret) return -EINVAL; root = new0(CaFileRoot, 1); if (!root) return -ENOMEM; root->n_ref = 1; root->fd = fd; if (path) { root->path = strdup(path); if (!root->path) { free(root); return -ENOMEM; } } *ret = root; return 0; } CaFileRoot* ca_file_root_ref(CaFileRoot *root) { if (!root) return NULL; assert_se(root->n_ref > 0); root->n_ref++; return root; } CaFileRoot* ca_file_root_unref(CaFileRoot *root) { if (!root) return NULL; assert_se(root->n_ref > 0); root->n_ref--; if (root->n_ref > 0) return NULL; root->fd = -1; free(root->path); return mfree(root); } void ca_file_root_invalidate(CaFileRoot *root) { if (!root) return; root->fd = -1; root->path = mfree(root->path); root->invalidated = true; } src/cafileroot.h000066400000000000000000000007241322621430500141460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocafileroothfoo #define foocafileroothfoo #include typedef struct CaFileRoot { unsigned n_ref; char *path; int fd; bool invalidated; } CaFileRoot; int ca_file_root_new(const char *path, int fd, CaFileRoot **ret); CaFileRoot* ca_file_root_ref(CaFileRoot *root); CaFileRoot* ca_file_root_unref(CaFileRoot *root); void ca_file_root_invalidate(CaFileRoot *root); #endif src/caformat-util.c000066400000000000000000000460331322621430500145640ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "caformat.h" #include "caformat-util.h" const char *ca_format_type_name(uint64_t u) { switch (u) { case CA_FORMAT_ENTRY: return "entry"; case CA_FORMAT_USER: return "user"; case CA_FORMAT_GROUP: return "group"; case CA_FORMAT_XATTR: return "xattr"; case CA_FORMAT_FCAPS: return "fcaps"; case CA_FORMAT_ACL_USER: return "acl-user"; case CA_FORMAT_ACL_GROUP: return "acl-group"; case CA_FORMAT_ACL_GROUP_OBJ: return "acl-group-obj"; case CA_FORMAT_ACL_DEFAULT: return "acl-default"; case CA_FORMAT_ACL_DEFAULT_USER: return "acl-default-user"; case CA_FORMAT_ACL_DEFAULT_GROUP: return "acl-default-group"; case CA_FORMAT_SELINUX: return "selinux"; case CA_FORMAT_SYMLINK: return "symlink"; case CA_FORMAT_DEVICE: return "device"; case CA_FORMAT_PAYLOAD: return "payload"; case CA_FORMAT_FILENAME: return "filename"; case CA_FORMAT_GOODBYE: return "goodbye"; case CA_FORMAT_INDEX: return "index"; case CA_FORMAT_TABLE: return "table"; } return NULL; } static const struct { const char *name; uint64_t feature; } with_feature_map[] = { { "16bit-uids", CA_FORMAT_WITH_16BIT_UIDS }, { "32bit-uids", CA_FORMAT_WITH_32BIT_UIDS }, { "user-names", CA_FORMAT_WITH_USER_NAMES }, { "sec-time", CA_FORMAT_WITH_SEC_TIME }, { "usec-time", CA_FORMAT_WITH_USEC_TIME }, { "nsec-time", CA_FORMAT_WITH_NSEC_TIME }, { "2sec-time", CA_FORMAT_WITH_2SEC_TIME }, { "read-only", CA_FORMAT_WITH_READ_ONLY }, { "permissions", CA_FORMAT_WITH_PERMISSIONS }, { "symlinks", CA_FORMAT_WITH_SYMLINKS }, { "device-nodes", CA_FORMAT_WITH_DEVICE_NODES }, { "fifos", CA_FORMAT_WITH_FIFOS }, { "sockets", CA_FORMAT_WITH_SOCKETS }, { "flag-hidden", CA_FORMAT_WITH_FLAG_HIDDEN }, { "flag-system", CA_FORMAT_WITH_FLAG_SYSTEM }, { "flag-archive", CA_FORMAT_WITH_FLAG_ARCHIVE }, { "flag-append", CA_FORMAT_WITH_FLAG_APPEND }, { "flag-noatime", CA_FORMAT_WITH_FLAG_NOATIME }, { "flag-compr", CA_FORMAT_WITH_FLAG_COMPR }, { "flag-nocow", CA_FORMAT_WITH_FLAG_NOCOW }, { "flag-nodump", CA_FORMAT_WITH_FLAG_NODUMP }, { "flag-dirsync", CA_FORMAT_WITH_FLAG_DIRSYNC }, { "flag-immutable", CA_FORMAT_WITH_FLAG_IMMUTABLE }, { "flag-sync", CA_FORMAT_WITH_FLAG_SYNC }, { "flag-nocomp", CA_FORMAT_WITH_FLAG_NOCOMP }, { "flag-projinherit", CA_FORMAT_WITH_FLAG_PROJINHERIT }, { "flag-subvolume", CA_FORMAT_WITH_SUBVOLUME }, { "flag-subvolume-ro",CA_FORMAT_WITH_SUBVOLUME_RO }, { "xattrs", CA_FORMAT_WITH_XATTRS }, { "acl", CA_FORMAT_WITH_ACL }, { "selinux", CA_FORMAT_WITH_SELINUX }, { "fcaps", CA_FORMAT_WITH_FCAPS }, { "best", CA_FORMAT_WITH_BEST }, { "unix", CA_FORMAT_WITH_UNIX }, { "fat", CA_FORMAT_WITH_FAT }, { "chattr", CA_FORMAT_WITH_CHATTR }, { "fat-attrs", CA_FORMAT_WITH_FAT_ATTRS }, { "privileged", CA_FORMAT_WITH_PRIVILEGED }, { "fuse", CA_FORMAT_WITH_FUSE }, { "all", CA_FORMAT_WITH_MASK }, }; int ca_with_feature_flags_parse_one(const char *name, uint64_t *ret) { size_t i; for (i = 0; i < ELEMENTSOF(with_feature_map); i++) if (streq(with_feature_map[i].name, name)) { *ret = with_feature_map[i].feature; return 0; } return -ENXIO; } int ca_with_feature_flags_format(uint64_t features, char **ret) { char *s = NULL; size_t i; for (i = 0; i < ELEMENTSOF(with_feature_map); i++) { uint64_t f; if (features == 0) break; f = with_feature_map[i].feature; if ((features & f) != f) continue; if (!strextend(&s, s ? " " : "", with_feature_map[i].name, NULL)) { free(s); return -ENOMEM; } features &= ~f; } if ((features & ~(CA_FORMAT_EXCLUDE_NODUMP|CA_FORMAT_EXCLUDE_SUBMOUNTS|CA_FORMAT_SHA512_256)) != 0) { free(s); return -EINVAL; } *ret = s; return 0; } int ca_feature_flags_normalize(uint64_t flags, uint64_t *ret) { if (!ret) return -EINVAL; /* This normalizes the specified flags value, i.e. drops redundant bits, so that the resulting flags field has * the minimum number of bits that express the feature set set. */ if (flags == UINT64_MAX) return -EINVAL; if ((flags & ~CA_FORMAT_FEATURE_FLAGS_MAX) != 0) return -EOPNOTSUPP; if ((flags & CA_FORMAT_WITH_ACL) && (flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS|CA_FORMAT_WITH_USER_NAMES)) == 0) flags |= CA_FORMAT_WITH_32BIT_UIDS|CA_FORMAT_WITH_USER_NAMES; if (flags & CA_FORMAT_WITH_32BIT_UIDS) flags &= ~CA_FORMAT_WITH_16BIT_UIDS; if (flags & CA_FORMAT_WITH_NSEC_TIME) flags &= ~(CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_2SEC_TIME); if (flags & CA_FORMAT_WITH_USEC_TIME) flags &= ~(CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_2SEC_TIME); if (flags & CA_FORMAT_WITH_SEC_TIME) flags &= ~CA_FORMAT_WITH_2SEC_TIME; if (flags & CA_FORMAT_WITH_ACL) flags &= ~(CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_READ_ONLY); if (flags & CA_FORMAT_WITH_PERMISSIONS) flags &= ~CA_FORMAT_WITH_READ_ONLY; if (flags & CA_FORMAT_EXCLUDE_NODUMP) flags &= ~CA_FORMAT_WITH_FLAG_NODUMP; if (flags & CA_FORMAT_WITH_SUBVOLUME_RO) flags |= CA_FORMAT_WITH_SUBVOLUME; *ret = flags; return 0; } int ca_feature_flags_are_normalized(uint64_t flags) { if (flags == UINT64_MAX) return -EINVAL; if ((flags & ~CA_FORMAT_FEATURE_FLAGS_MAX) != 0) return -EOPNOTSUPP; if ((flags & CA_FORMAT_WITH_NSEC_TIME) && (flags & (CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_2SEC_TIME))) return false; if ((flags & CA_FORMAT_WITH_USEC_TIME) && (flags & (CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_2SEC_TIME))) return false; if ((flags & CA_FORMAT_WITH_SEC_TIME) && (flags & CA_FORMAT_WITH_2SEC_TIME)) return false; if ((flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) == (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) return false; if ((flags & CA_FORMAT_WITH_PERMISSIONS) && (flags & CA_FORMAT_WITH_READ_ONLY)) return false; if ((flags & CA_FORMAT_WITH_ACL) && (flags & (CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_READ_ONLY))) return false; if ((flags & CA_FORMAT_WITH_ACL) && (flags & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS|CA_FORMAT_WITH_USER_NAMES)) == 0) return false; if ((flags & CA_FORMAT_EXCLUDE_NODUMP) && (flags & CA_FORMAT_WITH_FLAG_NODUMP)) return false; if ((flags & CA_FORMAT_WITH_SUBVOLUME_RO) && !(flags & CA_FORMAT_WITH_SUBVOLUME)) return false; return true; } int ca_feature_flags_normalize_mask(uint64_t mask, uint64_t *ret) { if (!ret) return -EINVAL; /* This normalizes the specified flags parameter, so that all redundant bits that could be set are set. */ if (mask == UINT64_MAX) { *ret = UINT64_MAX; return 0; } mask &= CA_FORMAT_FEATURE_FLAGS_MAX; if (mask & (CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS)) mask |= CA_FORMAT_WITH_16BIT_UIDS|CA_FORMAT_WITH_32BIT_UIDS; if (mask & (CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_NSEC_TIME|CA_FORMAT_WITH_2SEC_TIME)) mask |= CA_FORMAT_WITH_SEC_TIME|CA_FORMAT_WITH_USEC_TIME|CA_FORMAT_WITH_NSEC_TIME|CA_FORMAT_WITH_2SEC_TIME; if (mask & CA_FORMAT_WITH_PERMISSIONS) mask |= CA_FORMAT_WITH_READ_ONLY; if (mask & CA_FORMAT_WITH_ACL) mask |= CA_FORMAT_WITH_PERMISSIONS|CA_FORMAT_WITH_READ_ONLY; if (mask & CA_FORMAT_WITH_SUBVOLUME_RO) mask |= CA_FORMAT_WITH_SUBVOLUME; *ret = mask; return 0; } int ca_feature_flags_time_granularity_nsec(uint64_t flags, uint64_t *ret) { uint64_t granularity; if ((flags & ~CA_FORMAT_FEATURE_FLAGS_MAX) != 0) return -EOPNOTSUPP; if (!ret) return -EINVAL; if (flags & CA_FORMAT_WITH_NSEC_TIME) granularity = 1; else if (flags & CA_FORMAT_WITH_USEC_TIME) granularity = 1000; else if (flags & CA_FORMAT_WITH_SEC_TIME) granularity = 1000000000; else if (flags & CA_FORMAT_WITH_2SEC_TIME) granularity = 2000000000; else return -ENODATA; *ret = granularity; return 0; } static const struct { uint64_t feature_flag; unsigned chattr_flag; } chattr_map[] = { { CA_FORMAT_WITH_FLAG_APPEND, FS_APPEND_FL }, { CA_FORMAT_WITH_FLAG_NOATIME, FS_NOATIME_FL }, { CA_FORMAT_WITH_FLAG_COMPR, FS_COMPR_FL }, { CA_FORMAT_WITH_FLAG_NOCOW, FS_NOCOW_FL }, { CA_FORMAT_WITH_FLAG_NODUMP, FS_NODUMP_FL }, { CA_FORMAT_WITH_FLAG_DIRSYNC, FS_DIRSYNC_FL }, { CA_FORMAT_WITH_FLAG_IMMUTABLE, FS_IMMUTABLE_FL }, { CA_FORMAT_WITH_FLAG_SYNC, FS_SYNC_FL }, { CA_FORMAT_WITH_FLAG_NOCOMP, FS_NOCOMP_FL }, { CA_FORMAT_WITH_FLAG_PROJINHERIT, FS_PROJINHERIT_FL }, }; uint64_t ca_feature_flags_from_chattr(unsigned flags) { uint64_t f = 0; size_t i; for (i = 0; i < ELEMENTSOF(chattr_map); i++) if (flags & chattr_map[i].chattr_flag) f |= chattr_map[i].feature_flag; return f; } unsigned ca_feature_flags_to_chattr(uint64_t flags) { unsigned f = 0; size_t i; for (i = 0; i < ELEMENTSOF(chattr_map); i++) { if (flags & chattr_map[i].feature_flag) f |= chattr_map[i].chattr_flag; } return f; } static const struct { uint64_t feature_flag; uint32_t fat_flag; } fat_attrs_map[] = { { CA_FORMAT_WITH_FLAG_HIDDEN, ATTR_HIDDEN }, { CA_FORMAT_WITH_FLAG_SYSTEM, ATTR_SYS }, { CA_FORMAT_WITH_FLAG_ARCHIVE, ATTR_ARCH }, }; uint64_t ca_feature_flags_from_fat_attrs(uint32_t flags) { uint64_t f = 0; size_t i; for (i = 0; i < ELEMENTSOF(fat_attrs_map); i++) if (flags & fat_attrs_map[i].fat_flag) f |= fat_attrs_map[i].feature_flag; return f; } uint32_t ca_feature_flags_to_fat_attrs(uint64_t flags) { uint32_t f = 0; size_t i; for (i = 0; i < ELEMENTSOF(fat_attrs_map); i++) { if (flags & fat_attrs_map[i].feature_flag) f |= fat_attrs_map[i].fat_flag; } return f; } uint64_t ca_feature_flags_from_magic(statfs_f_type_t magic) { /* Returns the set of features we know a specific file system type provides. Ideally the kernel would let us * know this, but this is Linux and hence we have crappy interfaces. */ switch (magic) { case (statfs_f_type_t) MSDOS_SUPER_MAGIC: return CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_FLAG_HIDDEN| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_ARCHIVE; case (statfs_f_type_t) EXT2_SUPER_MAGIC: return CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_DIRSYNC| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_XATTRS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_FCAPS; case (statfs_f_type_t) XFS_SUPER_MAGIC: return CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_XATTRS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_FCAPS; case (statfs_f_type_t) BTRFS_SUPER_MAGIC: return CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_COMPR| CA_FORMAT_WITH_FLAG_NOCOW| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_DIRSYNC| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_FLAG_NOCOMP| CA_FORMAT_WITH_XATTRS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_SUBVOLUME| CA_FORMAT_WITH_SUBVOLUME_RO| CA_FORMAT_WITH_FCAPS; case (statfs_f_type_t) TMPFS_MAGIC: return CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX; case (statfs_f_type_t) FUSE_SUPER_MAGIC: /* We don't actually know what the backing FUSE file system supports, but it's likely more limited than * what we support ourselves, hence use that.*/ return CA_FORMAT_WITH_FUSE; default: return CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS; } } uint64_t ca_feature_flags_from_digest_type(CaDigestType type) { switch (type) { case CA_DIGEST_SHA256: return 0; case CA_DIGEST_SHA512_256: return CA_FORMAT_SHA512_256; default: return UINT64_MAX; } } CaDigestType ca_feature_flags_to_digest_type(uint64_t flags) { if (flags & CA_FORMAT_SHA512_256) return CA_DIGEST_SHA512_256; else return CA_DIGEST_SHA256; } src/caformat-util.h000066400000000000000000000017731322621430500145730ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaformatutilhfoo #define foocaformatutilhfoo #include #include "cadigest.h" #include "util.h" const char *ca_format_type_name(uint64_t u); int ca_with_feature_flags_parse_one(const char *name, uint64_t *ret); int ca_with_feature_flags_format(uint64_t features, char **ret); int ca_feature_flags_normalize(uint64_t flags, uint64_t *ret); int ca_feature_flags_normalize_mask(uint64_t mask, uint64_t *ret); int ca_feature_flags_time_granularity_nsec(uint64_t flags, uint64_t *ret); uint64_t ca_feature_flags_from_chattr(unsigned flags); unsigned ca_feature_flags_to_chattr(uint64_t flags); uint64_t ca_feature_flags_from_fat_attrs(uint32_t flags); uint32_t ca_feature_flags_to_fat_attrs(uint64_t flags); uint64_t ca_feature_flags_from_magic(statfs_f_type_t type); int ca_feature_flags_are_normalized(uint64_t f); uint64_t ca_feature_flags_from_digest_type(CaDigestType type); CaDigestType ca_feature_flags_to_digest_type(uint64_t flags); #endif src/caformat.h000066400000000000000000000401261322621430500136130ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaformathfoo #define foocaformathfoo #include #include #include "cachunkid.h" #include "util.h" /* The format is like this: every archive begins items in the following order: * * ENTRY -- containing general stat() data and related bits * USER -- user name as text, if enabled * GROUP -- group name as text, if enabled * XATTR -- one extended attribute * .. -- more of these when there are multiple defined * ACL_USER -- one USER ACL entry * ... -- more of these when there are multiple defined * ACL_GROUP -- one GROUP ACL entry * ... -- more of these when there are multiple defined * ACL_GROUP_OBJ -- The ACL_GROUP_OBJ * ACL_DEFAULT -- The various default ACL fields if there's one defined * ACL_DEFAULT_USER -- one USER ACL entry * ... -- more of these when multiple are defined * ACL_DEFAULT_GROUP -- one GROUP ACL entry * ... -- more of these when multiple are defined * FCAPS -- file capability in Linux disk format * PAYLOAD -- file contents, if it is one * SYMLINK -- symlink target, if it is one * DEVICE -- device major/minor, if it is a block/char device * * If we are serializing a directory, then this is followed by: * * FILENAME -- name of the first directory entry (strictly ordered!) * -- serialization of the first directory entry's metadata and contents, * following the exact same archive format * FILENAME -- name of the second directory entry (strictly ordered!) * -- serialization of the second directory entry * … * GOODBYE -- lookup table at the end of a list of directory entries * * And that's already it. * */ enum { /* The archive file format */ CA_FORMAT_ENTRY = UINT64_C(0x1396fabcea5bbb51), CA_FORMAT_USER = UINT64_C(0xf453131aaeeaccb3), CA_FORMAT_GROUP = UINT64_C(0x25eb6ac969396a52), CA_FORMAT_XATTR = UINT64_C(0xb8157091f80bc486), CA_FORMAT_ACL_USER = UINT64_C(0x297dc88b2ef12faf), CA_FORMAT_ACL_GROUP = UINT64_C(0x36f2acb56cb3dd0b), CA_FORMAT_ACL_GROUP_OBJ = UINT64_C(0x23047110441f38f3), CA_FORMAT_ACL_DEFAULT = UINT64_C(0xfe3eeda6823c8cd0), CA_FORMAT_ACL_DEFAULT_USER = UINT64_C(0xbdf03df9bd010a91), CA_FORMAT_ACL_DEFAULT_GROUP = UINT64_C(0xa0cb1168782d1f51), CA_FORMAT_FCAPS = UINT64_C(0xf7267db0afed0629), CA_FORMAT_SELINUX = UINT64_C(0x46faf0602fd26c59), CA_FORMAT_SYMLINK = UINT64_C(0x664a6fb6830e0d6c), CA_FORMAT_DEVICE = UINT64_C(0xac3dace369dfe643), CA_FORMAT_PAYLOAD = UINT64_C(0x8b9e1d93d6dcffc9), CA_FORMAT_FILENAME = UINT64_C(0x6dbb6ebcb3161f0b), CA_FORMAT_GOODBYE = UINT64_C(0xdfd35c5e8327c403), /* The end marker used in the GOODBYE object */ CA_FORMAT_GOODBYE_TAIL_MARKER = UINT64_C(0x57446fa533702943), /* The index file format */ CA_FORMAT_INDEX = UINT64_C(0x96824d9c7b129ff9), CA_FORMAT_TABLE = UINT64_C(0xe75b9e112f17417d), /* The end marker used in the TABLE object */ CA_FORMAT_TABLE_TAIL_MARKER = UINT64_C(0x4b4f050e5549ecd1), }; /* Feature flags */ enum { CA_FORMAT_WITH_16BIT_UIDS = 0x1, CA_FORMAT_WITH_32BIT_UIDS = 0x2, CA_FORMAT_WITH_USER_NAMES = 0x4, CA_FORMAT_WITH_SEC_TIME = 0x8, CA_FORMAT_WITH_USEC_TIME = 0x10, CA_FORMAT_WITH_NSEC_TIME = 0x20, CA_FORMAT_WITH_2SEC_TIME = 0x40, /* FAT-style 2s time granularity */ CA_FORMAT_WITH_READ_ONLY = 0x80, CA_FORMAT_WITH_PERMISSIONS = 0x100, CA_FORMAT_WITH_SYMLINKS = 0x200, CA_FORMAT_WITH_DEVICE_NODES = 0x400, CA_FORMAT_WITH_FIFOS = 0x800, CA_FORMAT_WITH_SOCKETS = 0x1000, /* DOS file flags */ CA_FORMAT_WITH_FLAG_HIDDEN = 0x2000, CA_FORMAT_WITH_FLAG_SYSTEM = 0x4000, CA_FORMAT_WITH_FLAG_ARCHIVE = 0x8000, /* chattr() flags */ CA_FORMAT_WITH_FLAG_APPEND = 0x10000, CA_FORMAT_WITH_FLAG_NOATIME = 0x20000, CA_FORMAT_WITH_FLAG_COMPR = 0x40000, CA_FORMAT_WITH_FLAG_NOCOW = 0x80000, CA_FORMAT_WITH_FLAG_NODUMP = 0x100000, CA_FORMAT_WITH_FLAG_DIRSYNC = 0x200000, CA_FORMAT_WITH_FLAG_IMMUTABLE = 0x400000, CA_FORMAT_WITH_FLAG_SYNC = 0x800000, CA_FORMAT_WITH_FLAG_NOCOMP = 0x1000000, CA_FORMAT_WITH_FLAG_PROJINHERIT = 0x2000000, /* btrfs magic */ CA_FORMAT_WITH_SUBVOLUME = 0x4000000, CA_FORMAT_WITH_SUBVOLUME_RO = 0x8000000, /* Extended Attribute metadata */ CA_FORMAT_WITH_XATTRS = 0x10000000, CA_FORMAT_WITH_ACL = 0x20000000, CA_FORMAT_WITH_SELINUX = 0x40000000, CA_FORMAT_WITH_FCAPS = 0x80000000, CA_FORMAT_SHA512_256 = UINT64_C(0x2000000000000000), CA_FORMAT_EXCLUDE_SUBMOUNTS = UINT64_C(0x4000000000000000), CA_FORMAT_EXCLUDE_NODUMP = UINT64_C(0x8000000000000000), CA_FORMAT_WITH_BEST = CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FLAG_HIDDEN| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_ARCHIVE| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_COMPR| CA_FORMAT_WITH_FLAG_NOCOW| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_DIRSYNC| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_FLAG_NOCOMP| CA_FORMAT_WITH_FLAG_PROJINHERIT| CA_FORMAT_WITH_SUBVOLUME| CA_FORMAT_WITH_SUBVOLUME_RO| CA_FORMAT_WITH_XATTRS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_FCAPS, CA_FORMAT_WITH_UNIX = /* Conservative UNIX file properties */ CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS, CA_FORMAT_WITH_FAT = /* FAT file properties */ CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_FLAG_HIDDEN| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_ARCHIVE, CA_FORMAT_WITH_CHATTR = /* All chattr file attributes */ CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_COMPR| CA_FORMAT_WITH_FLAG_NOCOW| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_DIRSYNC| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_FLAG_NOCOMP| CA_FORMAT_WITH_FLAG_PROJINHERIT, CA_FORMAT_WITH_FAT_ATTRS = /* All FAT file attributes */ CA_FORMAT_WITH_FLAG_HIDDEN| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_ARCHIVE, CA_FORMAT_WITH_PRIVILEGED = /* All bits that may only be restored with privileges */ CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_SUBVOLUME| CA_FORMAT_WITH_SUBVOLUME_RO| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_FCAPS, CA_FORMAT_WITH_FUSE = /* All bits that may also be exposed via fuse */ CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FAT_ATTRS| CA_FORMAT_WITH_CHATTR| CA_FORMAT_WITH_XATTRS, CA_FORMAT_WITH_MASK = /* All with bits */ CA_FORMAT_WITH_16BIT_UIDS| CA_FORMAT_WITH_32BIT_UIDS| CA_FORMAT_WITH_USER_NAMES| CA_FORMAT_WITH_SEC_TIME| CA_FORMAT_WITH_USEC_TIME| CA_FORMAT_WITH_NSEC_TIME| CA_FORMAT_WITH_2SEC_TIME| CA_FORMAT_WITH_READ_ONLY| CA_FORMAT_WITH_PERMISSIONS| CA_FORMAT_WITH_SYMLINKS| CA_FORMAT_WITH_DEVICE_NODES| CA_FORMAT_WITH_FIFOS| CA_FORMAT_WITH_SOCKETS| CA_FORMAT_WITH_FLAG_HIDDEN| CA_FORMAT_WITH_FLAG_SYSTEM| CA_FORMAT_WITH_FLAG_ARCHIVE| CA_FORMAT_WITH_FLAG_APPEND| CA_FORMAT_WITH_FLAG_NOATIME| CA_FORMAT_WITH_FLAG_COMPR| CA_FORMAT_WITH_FLAG_NOCOW| CA_FORMAT_WITH_FLAG_NODUMP| CA_FORMAT_WITH_FLAG_DIRSYNC| CA_FORMAT_WITH_FLAG_IMMUTABLE| CA_FORMAT_WITH_FLAG_SYNC| CA_FORMAT_WITH_FLAG_NOCOMP| CA_FORMAT_WITH_FLAG_PROJINHERIT| CA_FORMAT_WITH_SUBVOLUME| CA_FORMAT_WITH_SUBVOLUME_RO| CA_FORMAT_WITH_XATTRS| CA_FORMAT_WITH_ACL| CA_FORMAT_WITH_SELINUX| CA_FORMAT_WITH_FCAPS, CA_FORMAT_DEFAULT = /* The default set of flags */ CA_FORMAT_WITH_BEST| CA_FORMAT_EXCLUDE_NODUMP| CA_FORMAT_SHA512_256, CA_FORMAT_FEATURE_FLAGS_MAX = /* All known bits turned on */ CA_FORMAT_WITH_MASK| CA_FORMAT_EXCLUDE_NODUMP| CA_FORMAT_EXCLUDE_SUBMOUNTS| CA_FORMAT_SHA512_256, }; typedef struct CaFormatHeader { le64_t size; le64_t type; } CaFormatHeader; typedef struct CaFormatEntry { CaFormatHeader header; le64_t feature_flags; le64_t mode; le64_t flags; le64_t uid; le64_t gid; le64_t mtime; /* nsec */ } CaFormatEntry; typedef struct CaFormatUser { CaFormatHeader header; char name[]; } CaFormatUser; /* LOGIN_NAME_MAX on Linux is 256 (NUL byte already included) */ #define CA_FORMAT_USER_SIZE_MAX (offsetof(CaFormatUser, name) + 256) typedef struct CaFormatGroup { CaFormatHeader header; char name[]; } CaFormatGroup; #define CA_FORMAT_GROUP_SIZE_MAX (offsetof(CaFormatGroup, name) + 256) typedef struct CaFormatXAttr { CaFormatHeader header; uint8_t name_and_value[]; /* a 0 char terminates the name, the value begins after that */ } CaFormatXAttr; #define CA_FORMAT_XATTR_SIZE_MAX (offsetof(CaFormatXAttr, name_and_value) + 255 + 1 + (64 * 1024)) typedef struct CaFormatFCaps { CaFormatHeader header; uint8_t data[]; /* struct vfs_cap_data, in any of the supported sizes */ } CaFormatFCaps; #define CA_FORMAT_FCAPS_SIZE_MAX (offsetof(CaFormatFCaps, data) + (64*1024)) #define CA_FORMAT_ACL_PERMISSION_READ 4 #define CA_FORMAT_ACL_PERMISSION_WRITE 2 #define CA_FORMAT_ACL_PERMISSION_EXECUTE 1 typedef struct CaFormatACLUser { CaFormatHeader header; le64_t uid; le64_t permissions; char name[]; } CaFormatACLUser; #define CA_FORMAT_ACL_USER_SIZE_MAX (offsetof(CaFormatACLUser, name) + 256) typedef struct CaFormatACLGroup { CaFormatHeader header; le64_t gid; le64_t permissions; char name[]; } CaFormatACLGroup; #define CA_FORMAT_ACL_GROUP_SIZE_MAX (offsetof(CaFormatACLGroup, name) + 256) typedef struct CaFormatACLGroupObj { CaFormatHeader header; le64_t permissions; } CaFormatACLGroupObj; typedef struct CaFormatACLDefault { CaFormatHeader header; le64_t user_obj_permissions; le64_t group_obj_permissions; le64_t other_permissions; le64_t mask_permissions; } CaFormatACLDefault; typedef struct CaFormatSELinux { CaFormatHeader header; char label[]; } CaFormatSELinux; /* The kernel appears to permit one page max */ #define CA_FORMAT_SELINUX_SIZE_MAX (offsetof(CaFormatSELinux, label) + 4096) typedef struct CaFormatSymlink { CaFormatHeader header; char target[]; } CaFormatSymlink; /* PATH_MAX on Linux is 4096 (NUL byte already included) */ #define CA_FORMAT_SYMLINK_SIZE_MAX (offsetof(CaFormatSymlink, target) + 4096) typedef struct CaFormatPayload { CaFormatHeader header; uint8_t data[]; } CaFormatPayload; typedef struct CaFormatDevice { CaFormatHeader header; uint64_t major; uint64_t minor; } CaFormatDevice; typedef struct CaFormatFilename { CaFormatHeader header; char name[]; } CaFormatFilename; /* NAME_MAX on Linux is 255 + NUL byte */ #define CA_FORMAT_FILENAME_SIZE_MAX (offsetof(CaFormatFilename, name) + 256) typedef struct CaFormatGoodbyeItem { le64_t offset; le64_t size; le64_t hash; } CaFormatGoodbyeItem; typedef struct CaFormatGoodbyeTail { le64_t entry_offset; /* The offset from the start of the GOODBYE object to the start of the matching ENTRY object */ le64_t size; /* Size of GOODBYE object, a second time */ le64_t marker; /* CA_FORMAT_GOODBYE_TAIL_MARKER */ } CaFormatGoodbyeTail; typedef struct CaFormatGoodbye { CaFormatHeader header; CaFormatGoodbyeItem items[]; /* Followed by one CaFormatGoodbyeTail */ } CaFormatGoodbye; #define CA_FORMAT_GOODBYE_HASH_KEY \ { \ 0xb3U, 0x84U, 0x1dU, 0x0fU, \ 0x2bU, 0x44U, 0x74U, 0x85U, \ 0xc1U, 0x2eU, 0xc2U, 0xd1U, \ 0x30U, 0xedU, 0x36U, 0x27U, \ } /*** The following structures are used by the index files. ***/ typedef struct CaFormatIndex { CaFormatHeader header; le64_t feature_flags; le64_t chunk_size_min; le64_t chunk_size_avg; le64_t chunk_size_max; } CaFormatIndex; typedef struct CaFormatTableItem { le64_t offset; uint8_t chunk[CA_CHUNK_ID_SIZE]; } CaFormatTableItem; typedef struct CaFormatTableTail { le64_t _zero_fill1; /* Some extra space, to make sure CaFormatTableItem and CaFormatTableTail have the same size */ le64_t _zero_fill2; le64_t index_offset; /* the offset from the start of the TABLE object to the start of the matching INDEX object */ le64_t size; /* the TABLE object size, a second time */ le64_t marker; /* CA_FORMAT_TABLE_TAIL_MARKER */ } CaFormatTableTail; typedef struct CaFormatTable { CaFormatHeader header; /* size is set to UINT64_MAX, so that we can put this together incrementally */ CaFormatTableItem items[]; /* Followed by one CaFormatTableTail */ } CaFormatTable; #endif src/cafuse.c000066400000000000000000000517611322621430500132670ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #define FUSE_USE_VERSION 26 #include #include #include #include "caformat-util.h" #include "caformat.h" #include "cafuse.h" #include "notify.h" #include "signal-handler.h" #include "util.h" static CaSync *instance = NULL; static struct fuse *fuse = NULL; static void fuse_exit_signal_handler(int signo) { /* Call our own generic handler */ exit_signal_handler(signo); /* Let FUSE know we are supposed to quit */ if (fuse) fuse_exit(fuse); } static int iterate_until_file(CaSync *s) { int r; assert(s); for (;;) { int step; step = ca_sync_step(s); if (step < 0) return log_error_errno(step, "Failed to run synchronizer: %m"); switch (step) { case CA_SYNC_FINISHED: log_error("Premature end of file."); return -EIO; case CA_SYNC_NEXT_FILE: return 0; /* Gotcha! */ case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_DONE_FILE: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_FOUND: break; case CA_SYNC_POLL: r = sync_poll_sigset(s); if (r == -ESHUTDOWN) /* Quit */ return r; if (r < 0) return log_error_errno(r, "Failed to poll: %m"); break; case CA_SYNC_NOT_FOUND: /* fprintf(stderr, "Not found.\n"); */ return -ENOENT; } } } static int seek_to_path(CaSync *s, const char *path) { int r; assert(s); assert(path); r = ca_sync_seek_path(s, path); if (r < 0) return log_error_errno(r, "Failed to seek for stat to %s: %m", path); return iterate_until_file(s); } static int fill_stat(CaSync *s, struct stat *stbuf) { mode_t mode; uid_t uid = 0; gid_t gid = 0; uint64_t mtime = 0; uint64_t size = 0; dev_t rdev = 0; int r; assert(s); assert(stbuf); r = ca_sync_current_mode(s, &mode); if (r < 0) return log_error_errno(r, "Failed to get current mode: %m"); (void) ca_sync_current_uid(s, &uid); (void) ca_sync_current_gid(s, &gid); (void) ca_sync_current_mtime(s, &mtime); if (S_ISREG(mode)) (void) ca_sync_current_size(s, &size); if (S_ISBLK(mode) || S_ISCHR(mode)) (void) ca_sync_current_rdev(s, &rdev); *stbuf = (struct stat) { .st_mode = mode, .st_nlink = S_ISDIR(mode) ? 2 : 1, .st_size = size, .st_rdev = rdev, .st_uid = uid, .st_gid = gid, .st_mtim = NSEC_TO_TIMESPEC_INIT(mtime), .st_ctim = NSEC_TO_TIMESPEC_INIT(mtime), .st_atim = NSEC_TO_TIMESPEC_INIT(mtime), }; return 0; } static void *casync_init(struct fuse_conn_info *conn) { return NULL; } static int casync_getattr( const char *path, struct stat *stbuf) { int r; assert(path); assert(stbuf); assert(instance); /* fprintf(stderr, "Got request for stat(%s).\n", path); */ r = seek_to_path(instance, path); if (r < 0) return r; r = fill_stat(instance, stbuf); if (r < 0) return r; /* fprintf(stderr, "stat(%s) successful!\n", path); */ return 0; } static int casync_readlink( const char *path, char *ret, size_t size) { const char *target; int r; assert(path); assert(ret); assert(size); /* fprintf(stderr, "Got request for readlink(%s).\n", path); */ r = seek_to_path(instance, path); if (r < 0) return r; r = ca_sync_current_target(instance, &target); if (r < 0) return log_error_errno(r, "Failed to get symlink target: %m"); strncpy(ret, target, size); /* fprintf(stderr, "readlink(%s) successful!\n", path); */ return 0; } static int casync_readdir( const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *info) { bool seen_toplevel = false; int r; /* fprintf(stderr, "Got request for readdir(%s).\n", path); */ if (filler(buf, ".", NULL, 0) != 0) return -ENOBUFS; if (filler(buf, "..", NULL, 0) != 0) return -ENOBUFS; r = ca_sync_set_payload(instance, false); if (r < 0) return log_error_errno(r, "Failed to turn off payload: %m"); r = ca_sync_seek_path(instance, path); if (r < 0) return log_error_errno(r, "Failed to seek to path %s: %m", path); for (;;) { int step; step = ca_sync_step(instance); if (step < 0) return log_error_errno(step, "Failed to run synchronizer: %m"); switch (step) { case CA_SYNC_FINISHED: return 0; case CA_SYNC_NEXT_FILE: { struct stat stbuf; char *name; if (!seen_toplevel) { seen_toplevel = true; break; } r = ca_sync_current_path(instance, &name); if (r < 0) return log_error_errno(r, "Failed to get current path: %m"); r = fill_stat(instance, &stbuf); if (r < 0) { free(name); return r; } if (filler(buf, basename(name), &stbuf, 0) != 0) { free(name); return -ENOBUFS; } free(name); r = ca_sync_seek_next_sibling(instance); if (r < 0) return log_error_errno(r, "Failed to seek to next sibling: %m"); break; } case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_DONE_FILE: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_FOUND: break; case CA_SYNC_POLL: r = sync_poll_sigset(instance); if (r == -ESHUTDOWN) /* Quit */ return r; if (r < 0) return log_error_errno(r, "Failed to poll: %m"); break; case CA_SYNC_NOT_FOUND: /* fprintf(stderr, "Not found: %s\n", path); */ return -ENOENT; } } /* fprintf(stderr, "readdir(%s) successful!\n", path); */ return 0; } static int casync_open(const char *path, struct fuse_file_info *fi) { int r; assert(path); assert(fi); /* fprintf(stderr, "Got request for open(%s).\n", path); */ r = seek_to_path(instance, path); if (r < 0) return r; if ((fi->flags & O_ACCMODE) != O_RDONLY) return -EACCES; fi->keep_cache = 1; /* fprintf(stderr, "open(%s) successful!\n", path); */ return 0; } static int casync_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { int r, sum = 0; assert(path); assert(buf); assert(size > 0); assert(fi); /* fprintf(stderr, "Got request for read(%s@%" PRIu64 ").\n", path, (uint64_t) offset); */ r = ca_sync_set_payload(instance, true); if (r < 0) return log_error_errno(r, "Failed to turn on payload: %m"); r = ca_sync_seek_path_offset(instance, path, offset); if (r < 0) return log_error_errno(r, "Failed to seek to path %s@%" PRIu64 ": %m", path, (uint64_t)offset); for (;;) { bool eof = false; int step; if (size == 0) break; step = ca_sync_step(instance); if (step < 0) return log_error_errno(step, "Failed to run synchronizer: %m"); switch (step) { case CA_SYNC_FINISHED: eof = true; break; case CA_SYNC_PAYLOAD: { const void *p; size_t n; r = ca_sync_get_payload(instance, &p, &n); if (r < 0) return log_error_errno(r, "Failed to acquire payload: %m"); if (n > size) n = size; memcpy(buf, p, n); buf += n; size -= n; sum += n; break; } case CA_SYNC_STEP: case CA_SYNC_NEXT_FILE: case CA_SYNC_DONE_FILE: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_FOUND: break; case CA_SYNC_POLL: r = sync_poll_sigset(instance); if (r == -ESHUTDOWN) /* Quit */ return r; if (r < 0) return log_error_errno(r, "Failed to poll: %m"); break; case CA_SYNC_NOT_FOUND: /* fprintf(stderr, "Not found: %s@%" PRIu64 "\n", path, (uint64_t) offset); */ return -ENOENT; } if (eof) break; } /* fprintf(stderr, "read(%s@%" PRIu64 ") successful!\n", path, (uint64_t) offset); */ return sum; } static int casync_statfs(const char *path, struct statvfs *sfs) { uint64_t size = UINT64_MAX; int r; /* fprintf(stderr, "Got request for stats().\n"); */ for (;;) { int step; r = ca_sync_get_archive_size(instance, &size); if (r >= 0) break; if (r != -EAGAIN) return log_error_errno(r, "Failed to acquire archive size: %m"); step = ca_sync_step(instance); if (step < 0) return log_error_errno(step, "Failed to run synchronizer: %m"); switch (step) { case CA_SYNC_FINISHED: log_error("Premature end of file."); return -EIO; case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_NEXT_FILE: case CA_SYNC_DONE_FILE: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: break; case CA_SYNC_POLL: r = sync_poll_sigset(instance); if (r == -ESHUTDOWN) /* Quit */ return r; if (r < 0) return log_error_errno(r, "Failed to poll: %m"); break; default: assert(false); } } *sfs = (struct statvfs) { .f_namemax = CA_FORMAT_FILENAME_SIZE_MAX - offsetof(CaFormatFilename, name) - 1, .f_bsize = 4096, .f_blocks = (size + 4095) / 4096, }; /* fprintf(stderr, "statfs() successful!\n"); */ return 0; } static int casync_ioctl( const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { int r; if (flags & FUSE_IOCTL_COMPAT) return -ENOSYS; if (!IN_SET(cmd, FS_IOC_GETFLAGS, FAT_IOCTL_GET_ATTRIBUTES)) return -ENOTTY; r = seek_to_path(instance, path); if (r < 0) return r; switch (cmd) { case FS_IOC_GETFLAGS: { unsigned chattr; r = ca_sync_current_chattr(instance, &chattr); if (r < 0) return r; *(unsigned long*) data = chattr; break; } case FAT_IOCTL_GET_ATTRIBUTES: { uint32_t fat_attrs; r = ca_sync_current_fat_attrs(instance, &fat_attrs); if (r < 0) return r; *(uint32_t*) data = fat_attrs; break; } default: assert(false); } return 0; } static int casync_getxattr(const char *path, const char *name, char *buffer, size_t size) { const char *n; const void *v; size_t l; int r; assert(path); assert(name); assert(buffer || size == 0); r = seek_to_path(instance, path); if (r < 0) return r; r = ca_sync_current_xattr(instance, CA_ITERATE_FIRST, &n, &v, &l); for (;;) { if (r < 0) return r; if (r == 0) break; if (streq(name, n)) { if (size == 0) return (int) l; if (size < l) return -ERANGE; memcpy(buffer, v, l); return (int) l; } r = ca_sync_current_xattr(instance, CA_ITERATE_NEXT, &n, &v, &l); } return -ENODATA; } static int casync_listxattr(const char *path, char *list, size_t size) { const char *n; size_t k = 0; char *p; int r; assert(path); assert(list || size == 0); r = seek_to_path(instance, path); if (r < 0) return r; r = ca_sync_current_xattr(instance, CA_ITERATE_FIRST, &n, NULL, NULL); for (;;) { if (r < 0) return r; if (r == 0) break; k += strlen(n) + 1; r = ca_sync_current_xattr(instance, CA_ITERATE_NEXT, &n, NULL, NULL); } if (size == 0) return (int) k; if (size < k) return -ERANGE; p = list; r = ca_sync_current_xattr(instance, CA_ITERATE_FIRST, &n, NULL, NULL); for (;;) { if (r < 0) return r; if (r == 0) break; p = stpcpy(p, n) + 1; r = ca_sync_current_xattr(instance, CA_ITERATE_NEXT, &n, NULL, NULL); } return (int) k; } static const struct fuse_operations ops = { .init = casync_init, .getattr = casync_getattr, .readlink = casync_readlink, .readdir = casync_readdir, .open = casync_open, .read = casync_read, .statfs = casync_statfs, .ioctl = casync_ioctl, .getxattr = casync_getxattr, .listxattr = casync_listxattr, }; static int feature_flags_warning(CaSync *s) { uint64_t ff, unsupported; char *t; int r; for (;;) { int step; r = ca_sync_get_feature_flags(s, &ff); if (r >= 0) break; if (r != -ENODATA) return log_error_errno(r, "Failed to retrieve feature flags: %m"); step = ca_sync_step(instance); if (step < 0) return log_error_errno(step, "Failed to run synchronizer: %m"); switch (step) { case CA_SYNC_FINISHED: log_error("Premature end of file."); return -EIO; case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_NEXT_FILE: case CA_SYNC_DONE_FILE: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: break; case CA_SYNC_POLL: r = sync_poll_sigset(s); if (r == -ESHUTDOWN) /* Quit */ return r; if (r < 0) return log_error_errno(r, "Failed to poll: %m"); break; default: assert(false); } } unsupported = ff & ~(CA_FORMAT_WITH_FUSE|CA_FORMAT_SHA512_256|CA_FORMAT_EXCLUDE_SUBMOUNTS|CA_FORMAT_EXCLUDE_NODUMP); if (unsupported == 0) return 0; r = ca_with_feature_flags_format(unsupported, &t); if (r < 0) return log_error_errno(r, "Failed to format feature flags: %m"); log_error("The following feature flags are not exposed in the mounted file system: %s", t); free(t); return 0; } int ca_fuse_run(CaSync *s, const char *what, const char *where, bool do_mkdir) { struct fuse_chan *fc = NULL; const char * arguments[] = { "casync", NULL, /* -o ... */ NULL }; const char *opts; struct fuse_args args = { .argc = 2, .argv = (char **) arguments, }; bool updated_signal_handlers = false; int r; assert(s); assert(where); assert(!fuse); assert(!instance); opts = "-oro,default_permissions,kernel_cache,subtype=casync"; if (geteuid() == 0) opts = strjoina(opts, ",allow_other"); if (what) opts = strjoina(opts, ",fsname=", what); /* FIXME: needs escaping */ arguments[1] = opts; instance = s; errno = 0; fc = fuse_mount(where, &args); if (!fc) { r = errno != 0 ? -abs(errno) : -EIO; if (r == -ENOENT && do_mkdir) { if (mkdir(where, 0777) < 0) { r = -errno; log_error("Failed to create mount directory %s: %m", where); goto finish; } errno = 0; fc = fuse_mount(where, &args); r = fc ? 0 : (errno != 0 ? -abs(errno) : -EIO); } if (r < 0) { log_error_errno(r, "Failed to establish FUSE mount: %m"); goto finish; } } errno = 0; fuse = fuse_new(fc, NULL, &ops, sizeof(ops), s); if (!fuse) { r = errno != 0 ? -abs(errno) : -ENOMEM; log_error_errno(r, "Failed to allocate FUSE object: %m"); goto finish; } /* Update signal handler: in addition to our generic logic, we now also need to tell FUSE to quit */ install_exit_handler(fuse_exit_signal_handler); updated_signal_handlers = true; printf("Mounted: %s\n", where); r = feature_flags_warning(s); if (r < 0) goto finish; if (quit) { r = 0; goto finish; } (void) send_notify("READY=1"); r = fuse_loop(fuse); if (IN_SET(r, -ESHUTDOWN, -EINTR) && quit) r = 0; if (r < 0) { log_error_errno(r, "Failed to run FUSE loop: %m"); goto finish; } finish: if (updated_signal_handlers) install_exit_handler(NULL); if (fc) fuse_unmount(where, fc); if (fuse) { fuse_destroy(fuse); fuse = NULL; } instance = NULL; return r; } src/cafuse.h000066400000000000000000000002761322621430500132670ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef cafusehfoo #define cafusehfoo #include "casync.h" int ca_fuse_run(CaSync *s, const char *what, const char *where, bool do_mkdir); #endif src/caindex.c000066400000000000000000000715051322621430500134320ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "cachunk.h" #include "caformat-util.h" #include "caformat.h" #include "caindex.h" #include "def.h" #include "util.h" /* #undef EBADMSG */ /* #define EBADMSG __LINE__ */ /* #undef EINVAL */ /* #define EINVAL __LINE__ */ typedef enum CaIndexMode { CA_INDEX_WRITE, /* only cooked writing */ CA_INDEX_READ, /* only cooked reading */ CA_INDEX_INCREMENTAL_WRITE, /* cooked writing + incremental raw reading back */ CA_INDEX_INCREMENTAL_READ, /* incremental raw writing + cooked reading back */ } CaIndexMode; struct CaIndex { CaIndexMode mode; int open_flags; int fd; mode_t make_mode; char *path; char *temporary_path; bool wrote_eof; uint64_t start_offset, cooked_offset, raw_offset; uint64_t item_position; uint64_t previous_chunk_offset; uint64_t chunk_size_min; uint64_t chunk_size_max; uint64_t chunk_size_avg; uint64_t feature_flags; uint64_t file_size; /* The size of the index file */ uint64_t blob_size; /* The size of the blob this index file describes */ }; static inline uint64_t CA_INDEX_METADATA_SIZE(CaIndex *i) { assert(i); return i->start_offset + sizeof(CaFormatTableTail); } static CaIndex* ca_index_new(void) { CaIndex *i; i = new0(CaIndex, 1); if (!i) return NULL; i->fd = -1; i->make_mode = (mode_t) -1; i->file_size = UINT64_MAX; i->blob_size = UINT64_MAX; i->feature_flags = UINT64_MAX; return i; } CaIndex *ca_index_new_write(void) { CaIndex *i; i = ca_index_new(); if (!i) return NULL; i->open_flags = O_CLOEXEC|O_NOCTTY|O_WRONLY|O_CREAT|O_EXCL; i->mode = CA_INDEX_WRITE; i->feature_flags = 0; return i; } CaIndex *ca_index_new_read(void) { CaIndex *i; i = ca_index_new(); if (!i) return NULL; i->open_flags = O_CLOEXEC|O_NOCTTY|O_RDONLY; i->mode = CA_INDEX_READ; i->feature_flags = UINT64_MAX; return i; } CaIndex *ca_index_new_incremental_write(void) { CaIndex *i; i = ca_index_new(); if (!i) return NULL; i->open_flags = O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL; i->mode = CA_INDEX_INCREMENTAL_WRITE; i->feature_flags = 0; return i; } CaIndex *ca_index_new_incremental_read(void) { CaIndex *i; i = ca_index_new(); if (!i) return NULL; i->open_flags = O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL; i->mode = CA_INDEX_INCREMENTAL_READ; i->feature_flags = UINT64_MAX; return i; } CaIndex *ca_index_unref(CaIndex *i) { if (!i) return NULL; free(i->path); if (i->temporary_path) { (void) unlink(i->temporary_path); free(i->temporary_path); } if (i->fd >= 2) safe_close(i->fd); return mfree(i); } int ca_index_set_make_mode(CaIndex *i, mode_t m) { if (!i) return -EINVAL; if (m & ~0666) return -EINVAL; if (i->mode == CA_INDEX_READ) return -ENOTTY; if (i->make_mode != (mode_t) -1) return -EBUSY; i->make_mode = m; return 0; } int ca_index_set_fd(CaIndex *i, int fd) { if (!i) return -EINVAL; if (i->fd >= 0) return -EBUSY; if (i->path) return -EBUSY; i->fd = fd; return 0; } int ca_index_set_path(CaIndex *i, const char *path) { if (!i) return -EINVAL; if (i->fd >= 0) return -EBUSY; if (i->path) return -EBUSY; i->path = strdup(path); if (!i->path) return -ENOMEM; return 0; } static int ca_index_open_fd(CaIndex *i) { const char *p; int r; assert(i); if (i->fd >= 0) return 0; switch (i->open_flags & O_ACCMODE) { case O_RDONLY: if (!i->path) return -EUNATCH; p = i->path; break; case O_WRONLY: case O_RDWR: if (!i->temporary_path) { if (i->path) { r = tempfn_random(i->path, &i->temporary_path); if (r < 0) return r; } else { const char *d; r = var_tmp_dir(&d); if (r < 0) return r; if (asprintf(&i->temporary_path, "%s/%" PRIx64 ".caidx", d, random_u64()) < 0) return -ENOMEM; } } p = i->temporary_path; break; default: assert(false); } i->fd = open(p, i->open_flags, 0666 & i->make_mode); if (i->fd < 0) return -errno; return 1; } static int ca_index_write_head(CaIndex *i) { struct { CaFormatIndex index; CaFormatHeader table; } head = { .index.header.size = htole64(sizeof(CaFormatIndex)), .index.header.type = htole64(CA_FORMAT_INDEX), .table.size = htole64(UINT64_MAX), .table.type = htole64(CA_FORMAT_TABLE), }; int r; assert(i); if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return 0; if (i->start_offset != 0) return 0; if (i->feature_flags == UINT64_MAX) return -EINVAL; if (i->chunk_size_min == 0 || i->chunk_size_avg == 0 || i->chunk_size_max == 0) return -EUNATCH; if (!(i->chunk_size_min <= i->chunk_size_avg && i->chunk_size_avg <= i->chunk_size_max)) return -EINVAL; head.index.feature_flags = htole64(i->feature_flags); head.index.chunk_size_min = htole64(i->chunk_size_min); head.index.chunk_size_avg = htole64(i->chunk_size_avg); head.index.chunk_size_max = htole64(i->chunk_size_max); assert(i->cooked_offset == 0); r = loop_write(i->fd, &head, sizeof(head)); if (r < 0) return r; i->start_offset = i->cooked_offset = sizeof(head); return 0; } static int ca_index_enough_data(CaIndex *i, size_t n) { size_t end; assert(i); if (i->mode == CA_INDEX_READ) return 1; if (i->mode != CA_INDEX_INCREMENTAL_READ) return -ENOTTY; if (i->wrote_eof) return 1; end = i->cooked_offset + n; if (end < i->cooked_offset) /* Overflow? */ return -E2BIG; if (end > i->raw_offset) return 0; return 1; } static int ca_index_read_head(CaIndex *i) { struct { CaFormatIndex index; CaFormatHeader table; } head; ssize_t n; int r; assert(i); if (!IN_SET(i->mode, CA_INDEX_READ, CA_INDEX_INCREMENTAL_READ)) return 0; if (i->start_offset != 0) /* already past the head */ return 0; assert(i->cooked_offset == 0); r = ca_index_enough_data(i, sizeof(head)); if (r < 0) return r; if (r == 0) return -EAGAIN; n = loop_read(i->fd, &head, sizeof(head)); if (n < 0) return (int) n; if (n != sizeof(head)) return -EPIPE; if (le64toh(head.index.header.size) != sizeof(CaFormatIndex) || le64toh(head.index.header.type) != CA_FORMAT_INDEX) return -EBADMSG; r = ca_feature_flags_are_normalized(le64toh(head.index.feature_flags)); if (r < 0) return r; if (r == 0) return -EINVAL; if (le64toh(head.index.chunk_size_min) < CA_CHUNK_SIZE_LIMIT_MIN || le64toh(head.index.chunk_size_min) > CA_CHUNK_SIZE_LIMIT_MAX) return -EBADMSG; if (le64toh(head.index.chunk_size_avg) < CA_CHUNK_SIZE_LIMIT_MIN || le64toh(head.index.chunk_size_avg) > CA_CHUNK_SIZE_LIMIT_MAX) return -EBADMSG; if (le64toh(head.index.chunk_size_max) < CA_CHUNK_SIZE_LIMIT_MIN || le64toh(head.index.chunk_size_max) > CA_CHUNK_SIZE_LIMIT_MAX) return -EBADMSG; if (!(le64toh(head.index.chunk_size_min) <= le64toh(head.index.chunk_size_avg) && le64toh(head.index.chunk_size_avg) <= le64toh(head.index.chunk_size_max))) return -EBADMSG; if (le64toh(head.table.size) != UINT64_MAX || le64toh(head.table.type) != CA_FORMAT_TABLE) return -EBADMSG; i->start_offset = i->cooked_offset = sizeof(head); i->feature_flags = le64toh(head.index.feature_flags); i->chunk_size_min = le64toh(head.index.chunk_size_min); i->chunk_size_avg = le64toh(head.index.chunk_size_avg); i->chunk_size_max = le64toh(head.index.chunk_size_max); return 0; } int ca_index_open(CaIndex *i) { int r; if (!i) return -EINVAL; r = ca_index_open_fd(i); if (r < 0) return r; r = ca_index_read_head(i); if (r < 0 && r != -EAGAIN) return r; r = ca_index_write_head(i); if (r < 0) return r; return 0; } int ca_index_install(CaIndex *i) { assert(i); if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE, CA_INDEX_INCREMENTAL_READ)) return -ENOTTY; if (!i->wrote_eof) return -EBUSY; if (!i->temporary_path) return 0; if (!i->path) return 0; if (rename(i->temporary_path, i->path) < 0) return -errno; i->temporary_path = mfree(i->temporary_path); return 1; } int ca_index_write_chunk(CaIndex *i, const CaChunkID *id, uint64_t size) { CaFormatTableItem item = {}; uint64_t end; int r; if (!i) return -EINVAL; if (!id) return -EINVAL; if (size == 0) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -ENOTTY; if (i->wrote_eof) return -EBUSY; r = ca_index_open(i); if (r < 0) return r; if (size > i->chunk_size_max) return -EINVAL; end = i->previous_chunk_offset + size; if (end < i->previous_chunk_offset) return -E2BIG; /* { */ /* char ids[CA_CHUNK_ID_FORMAT_MAX]; */ /* fprintf(stderr, "WRITING INDEX CHUNK: %s %zu\n", ca_chunk_id_format(id, ids), size); */ /* } */ item.offset = htole64(end); memcpy(&item.chunk, id, sizeof(CaChunkID)); r = loop_write(i->fd, &item, sizeof(item)); if (r < 0) return r; i->previous_chunk_offset = end; i->cooked_offset += sizeof(item); i->item_position++; return 0; } int ca_index_write_eof(CaIndex *i) { CaFormatTableTail tail = {}; int r; assert(sizeof(CaFormatTableTail) == sizeof(CaFormatTableItem)); if (!i) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -ENOTTY; if (i->wrote_eof) return -EBUSY; r = ca_index_open(i); if (r < 0) return r; tail.index_offset = htole64(sizeof(CaFormatIndex)); tail.size = htole64(offsetof(CaFormatTable, items) + (i->item_position * sizeof(CaFormatTableItem)) + sizeof(tail)); tail.marker = htole64(CA_FORMAT_TABLE_TAIL_MARKER); r = loop_write(i->fd, &tail, sizeof(tail)); if (r < 0) return r; i->cooked_offset += sizeof(tail); i->wrote_eof = true; return 0; } int ca_index_read_chunk(CaIndex *i, CaChunkID *ret_id, uint64_t *ret_offset_end, uint64_t *ret_size) { union { CaFormatTableItem item; CaFormatTableTail tail; } buffer; ssize_t n; int r; assert(sizeof(CaFormatTableTail) == sizeof(CaFormatTableItem)); if (!i) return -EINVAL; r = ca_index_open(i); if (r < 0) return r; if (!IN_SET(i->mode, CA_INDEX_READ, CA_INDEX_INCREMENTAL_READ)) return -ENOTTY; r = ca_index_enough_data(i, sizeof(buffer)+1); if (r < 0) return r; if (r == 0) return -EAGAIN; n = loop_read(i->fd, &buffer, sizeof(buffer)); if (n < 0) return (int) n; if (n != sizeof(buffer)) return -EPIPE; /* { */ /* char ids[CA_CHUNK_ID_FORMAT_MAX]; */ /* fprintf(stderr, "READING INDEX CHUNK: %s\n", ca_chunk_id_format((const CaChunkID*) item.chunk, ids)); */ /* } */ /* Check if this is the end? */ if (buffer.tail.marker == htole64(CA_FORMAT_TABLE_TAIL_MARKER) && buffer.tail._zero_fill1 == 0 && buffer.tail._zero_fill2 == 0 && buffer.tail.index_offset == htole64(sizeof(CaFormatIndex)) && le64toh(buffer.tail.size) == (i->cooked_offset - i->start_offset + offsetof(CaFormatTable, items) + sizeof(CaFormatTableTail))) { uint8_t final_byte; /* We try to read one more byte than we expect. if we can read it there's trailing garbage. */ n = read(i->fd, &final_byte, sizeof(final_byte)); if (n < 0) return -errno; if (n != 0) return -EBADMSG; if (ret_id) memset(ret_id, 0, sizeof(CaChunkID)); if (ret_offset_end) *ret_offset_end = UINT64_MAX; if (ret_size) *ret_size = 0; return 0; /* EOF */ } if (i->previous_chunk_offset != UINT64_MAX && i->previous_chunk_offset >= le64toh(buffer.item.offset)) return -EBADMSG; if (i->previous_chunk_offset != UINT64_MAX && (le64toh(buffer.item.offset) - i->previous_chunk_offset) > i->chunk_size_max) return -EBADMSG; if (ret_id) memcpy(ret_id, buffer.item.chunk, sizeof(CaChunkID)); if (ret_offset_end) *ret_offset_end = le64toh(buffer.item.offset); if (ret_size) *ret_size = i->previous_chunk_offset == UINT64_MAX ? UINT64_MAX : (le64toh(buffer.item.offset) - i->previous_chunk_offset); i->previous_chunk_offset = le64toh(buffer.item.offset); i->item_position++; i->cooked_offset += sizeof(buffer); return 1; } int ca_index_set_position(CaIndex *i, uint64_t position) { uint64_t p, q; if (!i) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_READ, CA_INDEX_INCREMENTAL_READ)) return -ENOTTY; if (i->start_offset == 0) return -ENODATA; p = position * sizeof(CaFormatTableItem); if (p < position) /* Overflow? */ return -EINVAL; q = i->start_offset + p; if (q < p) return -EINVAL; if (lseek(i->fd, q, SEEK_SET) == (off_t) -1) return -errno; i->cooked_offset = q; i->item_position = position; i->previous_chunk_offset = position == 0 ? 0 : UINT64_MAX; return 0; } int ca_index_get_position(CaIndex *i, uint64_t *ret) { if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->start_offset == 0) return -ENODATA; *ret = i->item_position; return 0; } static int read_file_size(CaIndex *i) { struct stat st; assert(i); if (i->file_size != UINT64_MAX) return 0; if (fstat(i->fd, &st) < 0) return -errno; if (!S_ISREG(st.st_mode)) return -EBADFD; i->file_size = st.st_size; return 1; } int ca_index_get_available_chunks(CaIndex *i, uint64_t *ret) { uint64_t available, metadata_size, n; int r; if (!i) return -EINVAL; if (!ret) return -EINVAL; r = ca_index_read_head(i); if (r < 0) return r; if (i->start_offset == 0) return -ENODATA; if (i->mode == CA_INDEX_READ) { r = read_file_size(i); if (r < 0) return r; available = i->file_size; } else if (i->mode == CA_INDEX_INCREMENTAL_READ) available = i->raw_offset; else return -ENOTTY; metadata_size = CA_INDEX_METADATA_SIZE(i);; if (available < metadata_size) { if (i->mode == CA_INDEX_READ || i->wrote_eof) return -EBADMSG; *ret = 0; return 0; } n = available - metadata_size; if ((i->mode == CA_INDEX_READ || i->wrote_eof) && (n % sizeof(CaFormatTableItem) != 0)) return -EBADMSG; *ret = n / sizeof(CaFormatTableItem); return 0; } int ca_index_incremental_write(CaIndex *i, const void *data, size_t size) { uint64_t new_offset; ssize_t n; int r; if (!i) return -EINVAL; if (!data) return -EINVAL; if (size == 0) return -EINVAL; if (i->mode != CA_INDEX_INCREMENTAL_READ) return -ENOTTY; if (i->wrote_eof) return -EBUSY; r = ca_index_open(i); if (r < 0) return r; new_offset = i->raw_offset + size; if (new_offset < i->raw_offset) /* overflow? */ return -EFBIG; n = pwrite(i->fd, data, size, i->raw_offset); if (n < 0) return -errno; if ((size_t) n != size) return -EIO; i->raw_offset = new_offset; return 0; } int ca_index_incremental_eof(CaIndex *i) { int r; if (!i) return -EINVAL; if (i->mode != CA_INDEX_INCREMENTAL_READ) return -ENOTTY; if (i->wrote_eof) return -EBUSY; r = ca_index_open(i); if (r < 0) return r; i->wrote_eof = true; return 0; } int ca_index_incremental_read(CaIndex *i, ReallocBuffer *buffer) { size_t m; ssize_t n; char *p; int r; if (!i) return -EINVAL; if (!buffer) return -EINVAL; if (i->mode != CA_INDEX_INCREMENTAL_WRITE) return -ENOTTY; r = ca_index_open(i); if (r < 0) return r; if (i->raw_offset >= i->cooked_offset) return i->wrote_eof ? 0 : -EAGAIN; m = MIN(BUFFER_SIZE, i->cooked_offset - i->raw_offset); p = realloc_buffer_acquire(buffer, m); if (!p) return -ENOMEM; n = pread(i->fd, p, m, i->raw_offset); if (n < 0) { realloc_buffer_empty(buffer); return -errno; } r = realloc_buffer_shorten(buffer, m - n); if (r < 0) return r; i->raw_offset += n; return 1; } int ca_index_set_chunk_size_min(CaIndex *i, size_t cmin) { if (!i) return -EINVAL; if (cmin < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (cmin > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -EROFS; i->chunk_size_min = cmin; return 0; } int ca_index_set_chunk_size_avg(CaIndex *i, size_t cavg) { if (!i) return -EINVAL; if (cavg < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (cavg > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -EROFS; i->chunk_size_avg = cavg; return 0; } int ca_index_set_chunk_size_max(CaIndex *i, size_t cmax) { if (!i) return -EINVAL; if (cmax < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (cmax > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -EROFS; i->chunk_size_max = cmax; return 0; } int ca_index_get_chunk_size_min(CaIndex *i, size_t *ret) { if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->chunk_size_min == 0) return -ENODATA; *ret = i->chunk_size_min; return 0; } int ca_index_get_chunk_size_avg(CaIndex *i, size_t *ret) { if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->chunk_size_avg == 0) return -ENODATA; *ret = i->chunk_size_avg; return 0; } int ca_index_get_chunk_size_max(CaIndex *i, size_t *ret) { if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->chunk_size_max == 0) return -ENODATA; *ret = i->chunk_size_max; return 0; } int ca_index_get_index_size(CaIndex *i, uint64_t *ret) { uint64_t size, metadata_size; int r; if (!i) return -EINVAL; if (!ret) return -EINVAL; r = ca_index_open(i); if (r < 0) return r; r = ca_index_read_head(i); if (r < 0) return r; switch (i->mode) { case CA_INDEX_READ: r = read_file_size(i); if (r < 0) return r; size = i->file_size; break; case CA_INDEX_INCREMENTAL_READ: if (!i->wrote_eof) return -EAGAIN; size = i->raw_offset; break; default: return -ENOTTY; } /* Some size validation checks */ metadata_size = CA_INDEX_METADATA_SIZE(i); if (size < metadata_size) return -EBADMSG; if ((size - metadata_size) % sizeof(CaFormatTableItem) != 0) return -EBADMSG; *ret = size; return 0; } int ca_index_get_total_chunks(CaIndex *i, uint64_t *ret) { uint64_t size; int r; if (!i) return -EINVAL; if (!ret) return -EINVAL; r = ca_index_get_index_size(i, &size); if (r < 0) return r; *ret = (size - CA_INDEX_METADATA_SIZE(i)) / sizeof(CaFormatTableItem); return 0; } static int ca_index_read_tail(CaIndex *i) { struct { CaFormatTableItem last_item; CaFormatTableTail tail; } buffer = {}; uint64_t size; ssize_t l; int r; if (!i) return -EINVAL; r = ca_index_get_index_size(i, &size); if (r < 0) return r; if (size == CA_INDEX_METADATA_SIZE(i)) { /* If there's not a single chunk, then the blob has size zero, in this case only read the tail */ l = pread(i->fd, &buffer.tail, sizeof(buffer.tail), size - sizeof(buffer.tail)); if (l < 0) return -errno; if (l != sizeof(buffer.tail)) return -EBADMSG; } else { /* If there's at least one chunk, then read the last chunk's data, too */ l = pread(i->fd, &buffer, sizeof(buffer), size - sizeof(buffer)); if (l < 0) return -errno; if (l != sizeof(buffer)) return -EBADMSG; } if (le64toh(buffer.tail.marker) != CA_FORMAT_TABLE_TAIL_MARKER) return -EBADMSG; if (le64toh(buffer.tail.index_offset) != sizeof(CaFormatIndex)) return -EBADMSG; if (le64toh(buffer.tail.size) + sizeof(CaFormatIndex) != size) return -EBADMSG; i->blob_size = le64toh(buffer.last_item.offset); return 0; } int ca_index_get_blob_size(CaIndex *i, uint64_t *ret) { int r; if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->blob_size == UINT64_MAX) { r = ca_index_read_tail(i); if (r < 0) return r; } *ret = i->blob_size; return 0; } int ca_index_seek(CaIndex *i, uint64_t offset, uint64_t *ret_skip) { uint64_t size, n_chunks, left, right; int r; if (!i) return -EINVAL; r = ca_index_get_blob_size(i, &size); if (r < 0) return r; if (offset >= size) return -ENXIO; r = ca_index_get_total_chunks(i, &n_chunks); if (r < 0) return r; if (n_chunks == 0) return -ENXIO; /* Small opimization for seeking within the first chunk */ if (n_chunks == 1 || offset < i->chunk_size_min) { r = ca_index_set_position(i, 0); if (r < 0) return r; if (ret_skip) *ret_skip = offset; return 0; } /* Implement bisection to find the right chunk */ left = 0; right = n_chunks - 2; for (;;) { uint64_t first_chunk_end, second_chunk_end, p; p = left + (right - left) / 2; r = ca_index_set_position(i, p); if (r < 0) return r; r = ca_index_read_chunk(i, NULL, &first_chunk_end, NULL); if (r < 0) return r; if (offset < first_chunk_end) { if (p == 0) { /* This is left of the first chunk boundary? Then it's definitely in the first chunk */ r = ca_index_set_position(i, 0); if (r < 0) return r; if (ret_skip) *ret_skip = offset; return 0; } if (p == right) return -EBADMSG; right = p; continue; } r = ca_index_read_chunk(i, NULL, &second_chunk_end, NULL); if (r < 0) return r; if (offset >= second_chunk_end) { left = p+1; continue; } /* We found it, now let's position the read ptr on the second chunk again */ r = ca_index_set_position(i, p + 1); if (r < 0) return r; if (ret_skip) *ret_skip = offset - first_chunk_end; return 0; } } int ca_index_set_feature_flags(CaIndex *i, uint64_t flags) { if (!i) return -EINVAL; if (!IN_SET(i->mode, CA_INDEX_WRITE, CA_INDEX_INCREMENTAL_WRITE)) return -ENOTTY; if (i->start_offset > 0) return -EBUSY; return ca_feature_flags_normalize(flags, &i->feature_flags); } int ca_index_get_feature_flags(CaIndex *i, uint64_t *ret) { if (!i) return -EINVAL; if (!ret) return -EINVAL; if (i->feature_flags == UINT64_MAX) return -ENODATA; *ret = i->feature_flags; return 0; } src/caindex.h000066400000000000000000000041511322621430500134300ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaindexhfoo #define foocaindexhfoo #include "cachunkid.h" #include "realloc-buffer.h" typedef struct CaIndex CaIndex; CaIndex *ca_index_new_write(void); /* only (cooked) writing */ CaIndex *ca_index_new_read(void); /* only (cooked) reading */ CaIndex *ca_index_new_incremental_write(void); /* incremental cooked writing + raw/byte-wise reading (for uploads) */ CaIndex *ca_index_new_incremental_read(void); /* incremental raw/byte-wise writing + cooked reading (for downloads) */ CaIndex *ca_index_unref(CaIndex *i); static inline void ca_index_unrefp(CaIndex **i) { ca_index_unref(*i); } int ca_index_set_fd(CaIndex *i, int fd); int ca_index_set_path(CaIndex *i, const char *path); int ca_index_set_make_mode(CaIndex *i, mode_t m); int ca_index_open(CaIndex *i); int ca_index_install(CaIndex *i); int ca_index_write_chunk(CaIndex *i, const CaChunkID *id, uint64_t size); int ca_index_write_eof(CaIndex *i); int ca_index_read_chunk(CaIndex *i, CaChunkID *id, uint64_t *ret_offset_end, uint64_t *ret_size); int ca_index_set_position(CaIndex *i, uint64_t position); int ca_index_get_position(CaIndex *i, uint64_t *ret); int ca_index_get_available_chunks(CaIndex *i, uint64_t *ret); int ca_index_incremental_write(CaIndex *i, const void *data, size_t size); int ca_index_incremental_eof(CaIndex *i); int ca_index_incremental_read(CaIndex *i, ReallocBuffer *buffer); int ca_index_set_feature_flags(CaIndex *i, uint64_t flags); int ca_index_get_feature_flags(CaIndex *i, uint64_t *ret); int ca_index_set_chunk_size_min(CaIndex *i, size_t cmin); int ca_index_set_chunk_size_avg(CaIndex *i, size_t cavg); int ca_index_set_chunk_size_max(CaIndex *i, size_t cmax); int ca_index_get_chunk_size_min(CaIndex *i, size_t *ret); int ca_index_get_chunk_size_avg(CaIndex *i, size_t *ret); int ca_index_get_chunk_size_max(CaIndex *i, size_t *ret); int ca_index_get_blob_size(CaIndex *i, uint64_t *ret); int ca_index_get_index_size(CaIndex *i, uint64_t *ret); int ca_index_get_total_chunks(CaIndex *i, uint64_t *ret); int ca_index_seek(CaIndex *i, uint64_t offset, uint64_t *ret_skip); #endif src/calocation.c000066400000000000000000000211761322621430500141320ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "calocation.h" #include "util.h" int ca_location_new( const char *path, CaLocationDesignator designator, uint64_t offset, uint64_t size, CaLocation **ret) { CaLocation *l; if (!CA_LOCATION_DESIGNATOR_VALID(designator)) return -EINVAL; if (size == 0) return -EINVAL; if (designator == CA_LOCATION_VOID && path) return -EINVAL; if (size != UINT64_MAX && offset + size < offset) return -EINVAL; l = new0(CaLocation, 1); if (!l) return -ENOMEM; if (path) { l->path = strdup(path); if (!l->path) { free(l); return -ENOMEM; } } l->designator = designator; l->offset = designator == CA_LOCATION_VOID ? 0 : offset; l->size = size; l->n_ref = 1; *ret = l; return 0; } CaLocation* ca_location_unref(CaLocation *l) { if (!l) return NULL; assert(l->n_ref > 0); l->n_ref--; if (l->n_ref > 0) return NULL; free(l->path); free(l->formatted); ca_file_root_unref(l->root); return mfree(l); } CaLocation* ca_location_ref(CaLocation *l) { if (!l) return NULL; assert(l->n_ref > 0); l->n_ref++; return l; } const char* ca_location_format(CaLocation *l) { if (!l) return NULL; if (!l->formatted) { if (l->size == UINT64_MAX) { if (asprintf(&l->formatted, "%s+%c%" PRIu64, strempty(l->path), (char) l->designator, l->offset) < 0) return NULL; } else { if (asprintf(&l->formatted, "%s+%c%" PRIu64 ":%" PRIu64, strempty(l->path), (char) l->designator, l->offset, l->size) < 0) return NULL; } } return l->formatted; } int ca_location_parse(const char *text, CaLocation **ret) { uint64_t offset, size; const char *e, *c; CaLocation *l; char *n; int r; if (!text) return -EINVAL; if (!ret) return -EINVAL; e = strrchr(text, '+'); if (!e) return -EINVAL; if (e != text && text[0] == '/') return -EINVAL; if (!CA_LOCATION_DESIGNATOR_VALID(e[1])) return -EINVAL; c = strchr(e+2, ':'); if (c) { const char *a; a = strndupa(e+2, c-e-2); r = safe_atou64(a, &offset); if (r < 0) return r; r = safe_atou64(c+1, &size); } else { size = UINT64_MAX; r = safe_atou64(e+2, &offset); } if (r < 0) return r; if (size == 0) return -EINVAL; if (size != UINT64_MAX && offset + size < offset) return -EINVAL; if (e == text) n = NULL; else { n = strndup(text, e - text); if (!n) return -ENOMEM; } if (e[1] == CA_LOCATION_VOID && !isempty(n)) { free(n); return -EINVAL; } l = new0(CaLocation, 1); if (!l) { free(n); return -ENOMEM; } l->path = n; l->offset = e[1] == CA_LOCATION_VOID ? 0 : offset; l->size = size; l->designator = e[1]; l->n_ref = 1; *ret = l; return 0; } int ca_location_patch_size(CaLocation **l, uint64_t size) { CaLocation *n; int r; /* Since we consider CaLocation objects immutable, let's duplicate the object, unless we are the only owner of it */ if (!l) return -EINVAL; if (!*l) return -EINVAL; if ((*l)->size == size) return 0; if ((*l)->n_ref == 1) { (*l)->size = size; (*l)->formatted = mfree((*l)->formatted); return 1; } r = ca_location_new((*l)->path, (*l)->designator, (*l)->offset, size, &n); if (r < 0) return r; n->root = ca_file_root_ref((*l)->root); ca_location_unref(*l); *l = n; return 1; } int ca_location_patch_root(CaLocation **l, CaFileRoot *root) { CaLocation *copy; int r; if (!l) return -EINVAL; if (!*l) return -EINVAL; if ((*l)->root == root) return 0; if ((*l)->n_ref == 1) { ca_file_root_unref((*l)->root); (*l)->root = ca_file_root_ref(root); return 0; } r = ca_location_new((*l)->path, (*l)->designator, (*l)->offset, (*l)->size, ©); if (r < 0) return r; copy->root = ca_file_root_ref(root); ca_location_unref(*l); *l = copy; return 0; } int ca_location_advance(CaLocation **l, uint64_t n_bytes) { CaLocation *n; int r; if (!l) return -EINVAL; if (!*l) return -EINVAL; if (n_bytes == 0) return 0; if ((*l)->size == UINT64_MAX) return -ESPIPE; if (n_bytes > (*l)->size) return -ESPIPE; if ((*l)->n_ref == 1) { if ((*l)->designator != CA_LOCATION_VOID) (*l)->offset += n_bytes; (*l)->size -= n_bytes; (*l)->formatted = mfree((*l)->formatted); return 1; } r = ca_location_new((*l)->path, (*l)->designator, (*l)->offset + n_bytes, (*l)->size - n_bytes, &n); if (r < 0) return r; n->root = ca_file_root_ref((*l)->root); ca_location_unref(*l); *l = n; return 1; } int ca_location_merge(CaLocation **a, CaLocation *b) { CaLocation *copy; int r; if (!a) return -EINVAL; if (!*a) return -EINVAL; if (!b) return -EINVAL; if ((*a)->size == UINT64_MAX) return -EINVAL; if (b->size == UINT64_MAX) return -EINVAL; if ((*a)->root != b->root) return 0; if (!streq_ptr((*a)->path, b->path)) return 0; if ((*a)->designator != b->designator) return 0; if ((*a)->designator != CA_LOCATION_VOID && (*a)->offset + (*a)->size != b->offset) return 0; if ((*a)->n_ref == 1) { (*a)->size += b->size; (*a)->formatted = mfree((*a)->formatted); return 1; } r = ca_location_new((*a)->path, (*a)->designator, (*a)->offset, (*a)->size + b->size, ©); if (r < 0) return r; copy->root = ca_file_root_ref((*a)->root); ca_location_unref(*a); *a = copy; return 1; } int ca_location_open(CaLocation *l) { int r; if (!l) return -EINVAL; if (l->designator == CA_LOCATION_VOID) return -ENOTTY; if (!l->root) return -EUNATCH; if (l->root->invalidated) return -EUNATCH; if (l->root->fd >= 0) { if (isempty(l->path)) r = fcntl(l->root->fd, F_DUPFD_CLOEXEC, 3); else r = openat(l->root->fd, l->path, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW); } else { const char *p; if (isempty(l->path) && isempty(l->root->path)) p = "/"; else if (isempty(l->path)) p = l->root->path; else if (isempty(l->root->path)) p = l->path; else p = strjoina(l->root->path, "/", l->path); r = open(p, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW); } if (r < 0) return -errno; return r; } src/calocation.h000066400000000000000000000042701322621430500141330ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocalocationhfoo #define foocalocationhfoo #include #include #include "cafileroot.h" #include "util.h" /* Describes the location of some data in a directory hierarchy. This is useful for cache management (i.e. remember * where to seek to in the directory hierarchy to generate a serialization matching a specific hash), but also for * tracking data origin for creating file system reflinks. */ typedef enum CaLocationDesignator { CA_LOCATION_ENTRY = 'e', CA_LOCATION_PAYLOAD = 'p', CA_LOCATION_FILENAME = 'n', CA_LOCATION_GOODBYE = 'g', CA_LOCATION_VOID = 'v', /* Used as placeholder if we have to describe a blob of data from no known location */ } CaLocationDesignator; static inline bool CA_LOCATION_DESIGNATOR_VALID(CaLocationDesignator d) { return IN_SET(d, CA_LOCATION_ENTRY, CA_LOCATION_PAYLOAD, CA_LOCATION_FILENAME, CA_LOCATION_GOODBYE, CA_LOCATION_VOID); } /* A location in the serialization of a directory tree. This is considered immutable as soon as it was created * once. When we change it we make copies. */ typedef struct CaLocation { unsigned n_ref; CaLocationDesignator designator; char *path; uint64_t offset; uint64_t size; /* if unspecified, may be UINT64_MAX */ char *formatted; CaFileRoot *root; } CaLocation; int ca_location_new(const char *path, CaLocationDesignator designator, uint64_t offset, uint64_t size, CaLocation **ret); #define ca_location_new_void(size, ret) ca_location_new(NULL, CA_LOCATION_VOID, 0, size, ret) CaLocation* ca_location_unref(CaLocation *l); CaLocation* ca_location_ref(CaLocation *l); const char* ca_location_format(CaLocation *l); int ca_location_parse(const char *text, CaLocation **ret); int ca_location_patch_size(CaLocation **l, uint64_t size); int ca_location_patch_root(CaLocation **l, CaFileRoot *root); int ca_location_advance(CaLocation **l, uint64_t n_bytes); int ca_location_merge(CaLocation **a, CaLocation *b); int ca_location_open(CaLocation *l); #endif src/camakebst.c000066400000000000000000000052031322621430500137410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "camakebst.h" #include "util.h" /* Permutation function originally by L. Bressel, 2017 */ static inline size_t pow_of_2(size_t e) { return (size_t) 1U << e; } static inline size_t log_of_2(size_t k) { assert(sizeof(size_t) == sizeof(unsigned long)); return sizeof(unsigned long)*8 - __builtin_clzl(k) - 1; } static void make_bst_inner( const void *input, size_t n, size_t size, size_t e, void *output, size_t i) { size_t k, p, q; if (n == 0) return; assert(input); assert(size > 0); assert(e > 0); assert(output); p = pow_of_2(e-1); q = pow_of_2(e); if (n >= p - 1 + p / 2) k = (q - 2) / 2; else { size_t v; v = p - 1 + p / 2 - n; k = (q - 2) / 2 - v; } memcpy((uint8_t*) output + i * size, (const uint8_t*) input + k * size, size); /* Prepare left-side subtree */ make_bst_inner(input, k, size, e - 1, output, i*2+1); /* Prepare right-side subtree */ make_bst_inner((const uint8_t*) input + (k + 1) * size, n - k - 1, size, e - 1, output, i*2+2); } void ca_make_bst(const void *input, size_t n, size_t size, void *output) { assert(size > 0); /* Generate a binary search tree stored in an array from a sorted array. Specifically, for any given sorted * array 'input' of 'n' elements of size 'size' permute the array so that the following rule holds: * * For each array item with index i, the item at 2*i+1 is smaller and the item 2*i+2 is larger. * * This structure permits efficient (meaning: O(log(n)) binary searches: start with item i=0 (i.e. the root of * the BST), compare the value with the searched item, if smaller proceed at item i*2+1, if larger proceed at * item i*2+2, and repeat, until either the item is found, or the indexes grow beyond the array size, which * means the entry does not exist. Effectively this implements bisection, but instead of jumping around wildly * in the array during a single search we only search with strictly monotonically increasing indexes. */ make_bst_inner(input, n, size, log_of_2(n) + 1, output, 0); } src/camakebst.h000066400000000000000000000003071322621430500137460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocamakebsthfoo #define foocamakebsthfoo #include void ca_make_bst(const void *input, size_t n, size_t size, void *output); #endif src/canbd.c000066400000000000000000000413131322621430500130600ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "canbd.h" #include "util.h" #define NBD_MAX 1024 struct CaBlockDevice { int device_fd; int socket_fd[2]; char *device_path; pid_t ioctl_process; struct nbd_request last_request; uint64_t size; int friendly_name_fd; char *friendly_name; char *friendly_name_path; dev_t devnum; }; CaBlockDevice *ca_block_device_new(void) { CaBlockDevice *d; d = new0(CaBlockDevice, 1); if (!d) return NULL; d->device_fd = d->socket_fd[0] = d->socket_fd[1] = d->friendly_name_fd = -1; return d; } CaBlockDevice *ca_block_device_unref(CaBlockDevice *d) { if (!d) return NULL; safe_close_pair(d->socket_fd); if (d->device_fd >= 0) { (void) ioctl(d->device_fd, NBD_DISCONNECT); (void) ioctl(d->device_fd, NBD_CLEAR_SOCK); safe_close(d->device_fd); } if (d->ioctl_process != 0) { siginfo_t si = {}; (void) kill(d->ioctl_process, SIGKILL); (void) waitid(P_PID, d->ioctl_process, &si, WEXITED); } free(d->device_path); safe_close(d->friendly_name_fd); free(d->friendly_name); if (d->friendly_name_path) { (void) unlink(d->friendly_name_path); free(d->friendly_name_path); (void) rmdir("/run/casync"); } return mfree(d); } int ca_block_device_set_size(CaBlockDevice *d, uint64_t size) { if (!d) return -EINVAL; if (size <= 0) return -EINVAL; if ((size & 511) != 0) return -EINVAL; if (d->device_fd >= 0) return -EBUSY; d->size = size; return 0; } int ca_block_device_set_friendly_name(CaBlockDevice *d, const char *name) { char *m; if (!d) return -EINVAL; if (isempty(name)) return -EINVAL; if (!filename_is_valid(name)) return -EINVAL; if (strlen(name) > sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) return -EINVAL; if (d->friendly_name_fd >= 0) return -EBUSY; m = strdup(name); if (!m) return -ENOMEM; free(d->friendly_name); d->friendly_name = m; return 0; } static int ca_block_device_establish_friendly_name(CaBlockDevice *d) { int existing_fd = -1, r; char *t = NULL, nl = '\n'; const char *e; unsigned i; ssize_t n; size_t l; if (!d) return -EINVAL; if (d->friendly_name_fd >= 0) return -EBUSY; if (!d->device_path) return -EUNATCH; if (!d->friendly_name) return 0; if (mkdir("/run/casync", 0755) < 0 && errno != EEXIST) return -errno; e = path_startswith(d->device_path, "/dev"); if (!e) { r = -EINVAL; goto fail; } if (!filename_is_valid(e)) { r = -EINVAL; goto fail; } free(d->friendly_name_path); d->friendly_name_path = strjoin("/run/casync/", e); if (asprintf(&t, "/run/casync/.#%s.%" PRIx64 ".tmp", e, random_u64()) < 0) { r = -ENOMEM; goto fail; } d->friendly_name_fd = open(t, O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC, 0644); if (d->friendly_name_fd < 0) { r = -errno; goto fail; } /* We use a BSD file lock here in a slightly creative way: the owning casync instance always owns a LOCK_EX * (exclusive) lock on it. When it dies this lock is released. Other instances or the udev rule tool may * attempt to take a LOCK_SH (shared) lock on it. If that succeeds, then the owning instance must be dead, and * the file invalid. If it fails with EWOULDBLOCK however, then the exclusive lock is still in place, and thus * the owning casync instance still running. */ if (flock(d->friendly_name_fd, LOCK_EX) < 0) { r = -errno; goto fail; } l = strlen(d->friendly_name); n = writev(d->friendly_name_fd, (struct iovec[]) { { .iov_base = d->friendly_name, .iov_len = l }, { .iov_base = &nl, .iov_len = 1 }}, 2); if (n < 0) { r = -errno; goto fail; } if ((size_t) n != l + 1U) { r = -EIO; goto fail; } for (i = 0; i < 10; i++) { struct stat st; r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, d->friendly_name_path); if (r >= 0) { t = mfree(t); break; } if (r != -EEXIST) goto fail; existing_fd = open(d->friendly_name_path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (existing_fd < 0) { if (errno == ENOENT) continue; r = -errno; goto fail; } if (flock(existing_fd, LOCK_SH|LOCK_NB) < 0) { if (errno == EWOULDBLOCK) { /* The file is locked exclusively? If so, strange, some other casync instance still owns this device... */ r = -EBUSY; goto fail; } r = -errno; goto fail; } /* We got the lock? This means the file isn't used anymore */ if (fstat(existing_fd, &st) < 0) { r = -errno; goto fail; } if (!S_ISREG(st.st_mode)) { r = -EINVAL; goto fail; } if (st.st_nlink > 0) { /* we own it, and it's not deleted already? then remove it, it's out of date */ if (unlink(d->friendly_name_path) < 0) { r = -errno; goto fail; } } existing_fd = safe_close(existing_fd); } return 0; fail: if (t) { if (d->friendly_name_fd >= 0) (void) unlink(t); free(t); } if (existing_fd >= 0) safe_close(existing_fd); d->friendly_name_path = mfree(d->friendly_name_path); (void) rmdir("/run/casync"); return r; } int ca_block_device_open(CaBlockDevice *d) { static const int one = 1; bool free_device_path = false; struct stat st; int r; if (!d) return -EINVAL; if (d->device_fd >= 0) return -EBUSY; if (d->ioctl_process) return -EBUSY; if (d->size == 0) return -EUNATCH; assert((d->socket_fd[0] < 0) == (d->socket_fd[1] < 0)); if (d->socket_fd[0] < 0) { if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, d->socket_fd) < 0) return -errno; } if (d->device_path) { d->device_fd = open(d->device_path, O_CLOEXEC|O_RDWR|O_NONBLOCK|O_NOCTTY); if (d->device_fd < 0) return -errno; if (fstat(d->device_fd, &st) < 0) { r = -errno; goto fail; } if (!S_ISBLK(st.st_mode)) { r = -ENOTBLK; goto fail; } if (ioctl(d->device_fd, NBD_SET_SOCK, d->socket_fd[1]) < 0) { r = -errno; goto fail; } } else { unsigned i = 0; r = -EBUSY; for (;;) { char *path; if (i >= NBD_MAX) return r; if (asprintf(&path, "/dev/nbd%u", i) < 0) return -ENOMEM; d->device_fd = open(path, O_CLOEXEC|O_RDWR|O_NONBLOCK|O_NOCTTY); if (d->device_fd < 0) { free(path); r = -errno; goto fail; } if (fstat(d->device_fd, &st) < 0) { r = -errno; goto fail; } if (!S_ISBLK(st.st_mode)) { r = -ENOTBLK; goto fail; } if (ioctl(d->device_fd, NBD_SET_SOCK, d->socket_fd[1]) >= 0) { d->device_path = path; free_device_path = true; break; } r = -errno; free(path); /* If the ioctl() error is EBUSY or EINVAL somebody is using the device, in that case, go for the next */ if (!IN_SET(r, -EBUSY, -EINVAL)) goto fail; d->device_fd = safe_close(d->device_fd); i++; } } d->devnum = st.st_rdev; r = ca_block_device_establish_friendly_name(d); if (r < 0) goto fail; if (ioctl(d->device_fd, NBD_SET_BLKSIZE, (unsigned long) 512) < 0) { r = -errno; goto fail; } if (ioctl(d->device_fd, NBD_SET_SIZE_BLOCKS, (unsigned long) d->size / 512) < 0) { r = -errno; goto fail; } if (ioctl(d->device_fd, NBD_SET_FLAGS, (unsigned long) NBD_FLAG_READ_ONLY) < 0) { r = -errno; goto fail; } if (ioctl(d->device_fd, BLKROSET, (unsigned long) &one) < 0) { r = -errno; goto fail; } d->ioctl_process = fork(); if (d->ioctl_process < 0) { r = -errno; goto fail; } if (d->ioctl_process == 0) { (void) prctl(PR_SET_PDEATHSIG, SIGKILL); (void) prctl(PR_SET_NAME, "nbd-ioctl"); if (ioctl(d->device_fd, NBD_DO_IT) < 0) _exit(EXIT_FAILURE); _exit(EXIT_SUCCESS); } return 0; fail: d->device_fd = safe_close(d->device_fd); safe_close_pair(d->socket_fd); if (free_device_path) d->device_path = mfree(d->device_path); return r; } int ca_block_device_step(CaBlockDevice *d) { ssize_t l; if (!d) return -EINVAL; if (d->last_request.magic != 0) return CA_BLOCK_DEVICE_REQUEST; l = read(d->socket_fd[0], &d->last_request, sizeof(d->last_request)); if (l < 0) { if (errno == EAGAIN) return CA_BLOCK_DEVICE_POLL; return -errno; } if (l != sizeof(d->last_request)) return -EBADMSG; if (be32toh(d->last_request.magic) != NBD_REQUEST_MAGIC) return -EBADMSG; if (be32toh(d->last_request.type) != NBD_CMD_READ) return -EBADMSG; if (be32toh(d->last_request.len) == 0) return -EBADMSG; /* fprintf(stderr, "Got request for +%" PRIu64 " (%" PRIu32 ") fsize=%" PRIu64 "\n", */ /* be64toh(d->last_request.from), */ /* be32toh(d->last_request.len), */ /* d->size); */ return CA_BLOCK_DEVICE_REQUEST; } int ca_block_device_get_request_offset(CaBlockDevice *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (d->last_request.magic == 0) return -ENODATA; *ret = be64toh(d->last_request.from); return 0; } int ca_block_device_get_request_size(CaBlockDevice *d, uint64_t *ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (d->last_request.magic == 0) return -ENODATA; *ret = be32toh(d->last_request.len); return 0; } int ca_block_device_put_data(CaBlockDevice *d, uint64_t offset, const void *data, size_t size) { struct nbd_reply reply = { .magic = htobe32(NBD_REPLY_MAGIC), .error = 0 }; int r; if (!d) return -EINVAL; if (size == 0) return -EINVAL; if (!data) return -EINVAL; if (d->last_request.magic == 0) return -EBADR; if (offset != be64toh(d->last_request.from)) return -EBADR; if (size != be32toh(d->last_request.len)) return -EBADR; memcpy(reply.handle, d->last_request.handle, sizeof(reply.handle)); r = loop_write_block(d->socket_fd[0], &reply, sizeof(reply)); if (r < 0) return r; r = loop_write_block(d->socket_fd[0], data, size); if (r < 0) return r; memset(&d->last_request, 0, sizeof(d->last_request)); return 0; } int ca_block_device_get_poll_fd(CaBlockDevice *d) { if (!d) return -EINVAL; if (d->device_fd < 0) return -EUNATCH; if (d->socket_fd[0] < 0) return -EUNATCH; return d->socket_fd[0]; } int ca_block_device_poll(CaBlockDevice *d, uint64_t timeout_nsec, const sigset_t *ss) { struct pollfd pollfd; int r; if (!d) return -EINVAL; if (d->device_fd < 0) return -EUNATCH; if (d->socket_fd[0] < 0) return -EUNATCH; pollfd = (struct pollfd) { .fd = d->socket_fd[0], .events = POLLIN, }; if (timeout_nsec != UINT64_MAX) { struct timespec ts; ts = nsec_to_timespec(timeout_nsec); r = ppoll(&pollfd, 1, &ts, ss); } else r = ppoll(&pollfd, 1, NULL, ss); if (r < 0) return -errno; return 1; } int ca_block_device_get_path(CaBlockDevice *d, const char **ret) { if (!d) return -EINVAL; if (!ret) return -EINVAL; if (!d->device_path) return -EUNATCH; *ret = d->device_path; return 0; } int ca_block_device_set_path(CaBlockDevice *d, const char *node) { char *c; if (!d) return -EINVAL; if (d->device_fd >= 0) return -EBUSY; if (streq_ptr(node, d->device_path)) return 0; if (node) { c = strdup(node); if (!c) return -ENOMEM; } else c = NULL; free(d->device_path); d->device_path = c; return 1; } int ca_block_device_get_devnum(CaBlockDevice *d, dev_t *ret) { if (!d) return -EINVAL; if (d->device_fd < 0) return -EUNATCH; if (d->devnum == 0) return -EUNATCH; *ret = d->devnum; return 0; } int ca_block_device_test_nbd(const char *name) { unsigned u; size_t n; int r; if (!name) return -EINVAL; n = strspn(name, "/"); if (n < 1) return 0; name += n; if (!startswith(name, "dev")) return 0; name += 3; n = strspn(name, "/"); if (n < 1) return 0; name += n; if (!startswith(name, "nbd")) return 0; name += 3; r = safe_atou(name, &u); if (r < 0) return 0; return 1; } src/canbd.h000066400000000000000000000025111322621430500130620ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocanbdfoo #define foocanbdfoo #include #include #include typedef struct CaBlockDevice CaBlockDevice; enum { CA_BLOCK_DEVICE_CLOSED, CA_BLOCK_DEVICE_REQUEST, CA_BLOCK_DEVICE_POLL, }; CaBlockDevice *ca_block_device_new(void); CaBlockDevice *ca_block_device_unref(CaBlockDevice* d); static inline void ca_block_device_unrefp(CaBlockDevice **d) { ca_block_device_unref(*d); } int ca_block_device_set_size(CaBlockDevice *d, uint64_t size); int ca_block_device_open(CaBlockDevice *d); int ca_block_device_step(CaBlockDevice *d); int ca_block_device_get_request_offset(CaBlockDevice *d, uint64_t *ret); int ca_block_device_get_request_size(CaBlockDevice *d, uint64_t *ret); int ca_block_device_put_data(CaBlockDevice *d, uint64_t offset, const void *data, size_t size); int ca_block_device_poll(CaBlockDevice *d, uint64_t nsec, const sigset_t *ss); int ca_block_device_set_path(CaBlockDevice *d, const char *node); int ca_block_device_get_path(CaBlockDevice *d, const char **ret); int ca_block_device_set_friendly_name(CaBlockDevice *d, const char *name); int ca_block_device_get_devnum(CaBlockDevice *d, dev_t *ret); int ca_block_device_get_poll_fd(CaBlockDevice *d); int ca_block_device_test_nbd(const char *name); #endif src/caorigin.c000066400000000000000000000203211322621430500136000ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "caorigin.h" /* #undef EUNATCH */ /* #define EUNATCH __LINE__ */ /* #undef EINVAL */ /* #define EINVAL __LINE__ */ int ca_origin_new(CaOrigin **ret) { CaOrigin *origin; if (!ret) return -EINVAL; origin = new0(CaOrigin, 1); if (!origin) return -ENOMEM; *ret = origin; return 0; } void ca_origin_flush(CaOrigin *origin) { size_t i; if (!origin) return; for (i = 0; i < origin->n_items; i++) ca_location_unref(ca_origin_get(origin, i)); origin->n_items = 0; origin->n_bytes = 0; } CaOrigin* ca_origin_unref(CaOrigin *origin) { if (!origin) return NULL; ca_origin_flush(origin); free(origin->others); return mfree(origin); } int ca_origin_put(CaOrigin *origin, CaLocation *location) { int r; if (!origin) return -EINVAL; if (!location) return -EINVAL; if (location->size == UINT64_MAX) return -EINVAL; if (origin->n_items == 0) { origin->first = ca_location_ref(location); origin->n_items = 1; origin->n_bytes = location->size; return 0; } if (origin->n_items == 1) r = ca_location_merge(&origin->first, location); else r = ca_location_merge(origin->others + origin->n_items - 2, location); if (r < 0) return r; if (r > 0) { /* merged successfully */ origin->n_bytes += location->size; return 0; } /* Couldn't merge, add one more item */ if (!GREEDY_REALLOC(origin->others, origin->n_allocated, origin->n_items)) return -ENOMEM; origin->others[origin->n_items-1] = ca_location_ref(location); origin->n_items++; origin->n_bytes += location->size; return 0; } CaLocation* ca_origin_get(CaOrigin *origin, size_t i) { if (i >= ca_origin_items(origin)) return NULL; if (i == 0) return origin->first; return origin->others[i-1]; } int ca_origin_concat(CaOrigin *origin, CaOrigin *other, uint64_t n_bytes) { CaLocation **copy; size_t n, i; int r; if (!origin) return -EINVAL; if (n_bytes == 0) return 0; if (n_bytes != UINT64_MAX && ca_origin_bytes(other) < n_bytes) return -ESPIPE; if (n_bytes == UINT64_MAX && ca_origin_items(other) == 0) return 0; n = other->n_items; if (other == origin) { /* If origin and other are identical, make a copy of the location array first, so that we don't run * into our own modifications */ copy = newa(CaLocation*, n); for (i = 0; i < n; i++) copy[i] = ca_location_ref(ca_origin_get(other, i)); } else copy = NULL; for (i = 0; i < n; i++) { CaLocation *l; if (copy) l = copy[i]; else l = ca_origin_get(other, i); assert(l); assert(l->size != UINT64_MAX); if (n_bytes == UINT64_MAX) { r = ca_origin_put(origin, l); if (r < 0) goto finish; } else { ca_location_ref(l); if (l->size > n_bytes) { r = ca_location_patch_size(&l, n_bytes); if (r < 0) { ca_location_unref(l); goto finish; } } n_bytes -= l->size; r = ca_origin_put(origin, l); ca_location_unref(l); if (r < 0) goto finish; if (n_bytes == 0) break; } } r = n > 0; finish: if (copy) { for (i = 0; i < n; i++) ca_location_unref(copy[i]); } return r; } int ca_origin_advance_items(CaOrigin *origin, size_t n_drop) { uint64_t drop_bytes; size_t i; if (n_drop == 0) return 0; if (n_drop > ca_origin_items(origin)) return -ESPIPE; if (n_drop == ca_origin_items(origin)) { ca_origin_flush(origin); return 0; } drop_bytes = origin->first->size; ca_location_unref(origin->first); for (i = 1; i < n_drop; i++) { assert(origin->others[i-1]->size != UINT64_MAX); drop_bytes += origin->others[i-1]->size; ca_location_unref(origin->others[i-1]); } origin->first = origin->others[n_drop-1]; memmove(origin->others, origin->others + n_drop, (origin->n_items - n_drop - 1) * sizeof(CaLocation*)); assert(origin->n_bytes > drop_bytes); origin->n_items -= n_drop; origin->n_bytes -= drop_bytes; return 0; } int ca_origin_advance_bytes(CaOrigin *origin, uint64_t n_bytes) { size_t i; int r; if (n_bytes == 0) return 0; if (n_bytes > ca_origin_bytes(origin)) return -ESPIPE; if (n_bytes == ca_origin_bytes(origin)) { ca_origin_flush(origin); return 0; } for (i = 0; i < origin->n_items; i++) { CaLocation *l; l = ca_origin_get(origin, i); assert(l); assert(l->size != UINT64_MAX); if (l->size > n_bytes) break; n_bytes -= l->size; l = NULL; } r = ca_origin_advance_items(origin, i); if (r < 0) return r; assert(origin->n_bytes > n_bytes); assert(origin->first); assert(origin->first->size > n_bytes); r = ca_location_advance(&origin->first, n_bytes); if (r < 0) return r; origin->n_bytes -= n_bytes; return 0; } int ca_origin_dump(FILE *f, CaOrigin *origin) { size_t i; if (!f) f = stderr; for (i = 0; i < origin->n_items; i++) { const char *c; c = ca_location_format(ca_origin_get(origin, i)); if (!c) return -ENOMEM; if (i > 0) fputs(" → ", f); fputs(c, f); } fputc('\n', f); return 0; } int ca_origin_put_void(CaOrigin *origin, uint64_t n_bytes) { int r; if (!origin) return -EINVAL; if (n_bytes <= 0) return 0; if (origin->n_items == 1 && origin->first->designator == CA_LOCATION_VOID) { /* If we are only void, simply extend it */ r = ca_location_patch_size(&origin->first, origin->first->size + n_bytes); } else if (origin->n_items > 1 && origin->others[origin->n_items - 2]->designator == CA_LOCATION_VOID) { /* If we end with a void, extend it */ r = ca_location_patch_size(origin->others + origin->n_items - 2, origin->others[origin->n_items - 2]->size + n_bytes); } else { CaLocation *v; /* Otherwise event a new void location object */ r = ca_location_new_void(n_bytes, &v); if (r < 0) return r; r = ca_origin_put(origin, v); ca_location_unref(v); return r; } if (r < 0) return r; origin->n_bytes += n_bytes; return 0; } src/caorigin.h000066400000000000000000000023111322621430500136040ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaoriginhfoo #define foocaoriginhfoo #include "calocation.h" /* Describes the origin of a data stream, as a series of location objects. This is primarily useful for tracking data * origins for creating file system reflinks. */ typedef struct CaOrigin { CaLocation *first; CaLocation **others; size_t n_items; size_t n_allocated; uint64_t n_bytes; } CaOrigin; int ca_origin_new(CaOrigin **ret); CaOrigin* ca_origin_unref(CaOrigin *origin); void ca_origin_flush(CaOrigin *origin); int ca_origin_put(CaOrigin *origin, CaLocation *location); CaLocation* ca_origin_get(CaOrigin *origin, size_t i); int ca_origin_concat(CaOrigin *origin, CaOrigin *other, uint64_t n_bytes); int ca_origin_put_void(CaOrigin *origin, uint64_t n_bytes); int ca_origin_advance_items(CaOrigin *origin, size_t n_drop); int ca_origin_advance_bytes(CaOrigin *origin, uint64_t n_bytes); int ca_origin_dump(FILE *f, CaOrigin *origin); static inline size_t ca_origin_items(CaOrigin *origin) { return origin ? origin->n_items : 0; } static inline uint64_t ca_origin_bytes(CaOrigin *origin) { return origin ? origin->n_bytes : 0; } #endif src/caprotocol-util.c000066400000000000000000000015651322621430500151360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "caprotocol-util.h" #include "caprotocol.h" const char *ca_protocol_type_name(uint64_t u) { switch (u) { case CA_PROTOCOL_HELLO: return "hello"; case CA_PROTOCOL_INDEX: return "index"; case CA_PROTOCOL_INDEX_EOF: return "index-eof"; case CA_PROTOCOL_ARCHIVE: return "archive"; case CA_PROTOCOL_ARCHIVE_EOF: return "archive-eof"; case CA_PROTOCOL_REQUEST: return "request"; case CA_PROTOCOL_CHUNK: return "chunk"; case CA_PROTOCOL_MISSING: return "missing"; case CA_PROTOCOL_GOODBYE: return "goodbye"; case CA_PROTOCOL_ABORT: return "abort"; } return NULL; } src/caprotocol-util.h000066400000000000000000000002111322621430500151260ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "caprotocol.h" const char *ca_protocol_type_name(uint64_t u); src/caprotocol.h000066400000000000000000000111431322621430500141610ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaprotocolhfoo #define foocaprotocolhfoo #include #include "util.h" #include "cachunkid.h" enum { CA_PROTOCOL_HELLO = UINT64_C(0x3c71d0948ca5fbee), CA_PROTOCOL_INDEX = UINT64_C(0xb32a91dd2b3e27f8), CA_PROTOCOL_INDEX_EOF = UINT64_C(0x4f0932f1043718f5), CA_PROTOCOL_ARCHIVE = UINT64_C(0x95d6428a69eddcc5), CA_PROTOCOL_ARCHIVE_EOF = UINT64_C(0x450bef663f24cbad), CA_PROTOCOL_REQUEST = UINT64_C(0x8ab427e0f89d9210), CA_PROTOCOL_CHUNK = UINT64_C(0x5213dd180a84bc8c), CA_PROTOCOL_MISSING = UINT64_C(0xd010f9fac82b7b6c), CA_PROTOCOL_GOODBYE = UINT64_C(0xad205dbf1a3686c3), CA_PROTOCOL_ABORT = UINT64_C(0xe7d9136b7efea352), }; /* Protocol description: * * Client C connects to server: * * Both C and S immediately send CA_PROTOCOL_HELLO: * C → S: CA_PROTOCOL_HELLO * S → C: CA_PROTOCOL_HELLO * * On pull: * S → C: CA_PROTOCOL_INDEX * S → C: CA_PROTOCOL_INDEX * … * S → C: CA_PROTOCOL_INDEX * S → C: CA_PROTOCOL_INDEX_EOF * * Followed by multiple: * C → S: CA_PROTOCOL_REQUEST * S → C: CA_PROTOCOL_CHUNK * * Finshed by: * C → S: CA_PROTOCOL_GOODBYE (optional) * * On push: * C → S: CA_PROTOCOL_INDEX * C → S: CA_PROTOCOL_INDEX * … * C → S: CA_PROTOCOL_INDEX * C → S: CA_PROTOCOL_INDEX_EOF * * Followed by multiple: * S → C: CA_PROTOCOL_REQUEST * C → S: CA_PROTOCOL_CHUNK (or CA_PROTOCOL_MISSING) * * Finished by: * S → C: CA_PROTOCOL_GOODBYE * * When a non-recoverable error occurs, either side can send CA_PROTOCOL_ABORTED with an explanation, and terminate the * connection. * * */ typedef struct CaProtocolHeader { le64_t size; le64_t type; } CaProtocolHeader; #define CA_PROTOCOL_SIZE_MIN (sizeof(CaProtocolHeader)) #define CA_PROTOCOL_SIZE_MAX (16*1024*1024) typedef struct CaProtocolHello { CaProtocolHeader header; le64_t feature_flags; } CaProtocolHello; enum { /* Services I provide */ CA_PROTOCOL_READABLE_STORE = 0x1, /* I provide chunks on request to you */ CA_PROTOCOL_WRITABLE_STORE = 0x2, /* I can store chunks for you */ CA_PROTOCOL_READABLE_INDEX = 0x4, /* I provide an index on request to you */ CA_PROTOCOL_WRITABLE_INDEX = 0x8, /* I can store an index for you */ CA_PROTOCOL_READABLE_ARCHIVE = 0x10, /* I provide an archive blob to you */ CA_PROTOCOL_WRITABLE_ARCHIVE = 0x20, /* I can store an archive blob for you */ /* Operations I'd like to execute */ CA_PROTOCOL_PULL_CHUNKS = 0x40, /* I'd like to pull chunks from you */ CA_PROTOCOL_PULL_INDEX = 0x80, /* I'd like to pull an index from you */ CA_PROTOCOL_PULL_ARCHIVE = 0x100, /* I'd like to pull an archive from you */ CA_PROTOCOL_PUSH_CHUNKS = 0x200, /* I'd like to push chunks to you */ CA_PROTOCOL_PUSH_INDEX = 0x400, /* I'd like to push an index to you */ CA_PROTOCOL_PUSH_INDEX_CHUNKS = 0x800, /* I'd like you to pull chunks from me, that are declared in the index I just pulled */ CA_PROTOCOL_PUSH_ARCHIVE = 0x1000, /* I'd like to push an archive to you */ CA_PROTOCOL_FEATURE_FLAGS_MAX = 0x1fff, }; typedef struct CaProtocolFile { /* Used for index as well as archive */ CaProtocolHeader header; uint8_t data[]; } CaProtocolFile; typedef struct CaProtocolFileEOF { /* Used for index as well as archive */ CaProtocolHeader header; } CaProtocolFileEOF; typedef struct CaProtocolRequest { CaProtocolHeader header; le64_t flags; uint8_t chunks[]; /* multiple of CA_CHUNK_ID_SIZE */ } CaProtocolRequest; enum { CA_PROTOCOL_REQUEST_HIGH_PRIORITY = 1, CA_PROTOCOL_REQUEST_FLAG_MAX = 1, }; typedef struct CaProtocolChunk { CaProtocolHeader header; le64_t flags; uint8_t chunk[CA_CHUNK_ID_SIZE]; uint8_t data[]; } CaProtocolChunk; enum { CA_PROTOCOL_CHUNK_COMPRESSED = 1, CA_PROTOCOL_CHUNK_FLAG_MAX = 1, }; typedef struct CaProtocolMissing { CaProtocolHeader header; uint8_t chunk[CA_CHUNK_ID_SIZE]; } CaProtocolMissing; typedef struct CaProtocolGoodbye { CaProtocolHeader header; } CaProtocolGoodbye; typedef struct CaProtocolAbort { CaProtocolHeader header; le64_t error; /* closest errno-style error, or 0 */ char reason[]; } CaProtocolAbort; #endif src/caremote.c000066400000000000000000002411411322621430500136110ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include "caformat-util.h" #include "caformat.h" #include "caprotocol-util.h" #include "caprotocol.h" #include "caremote.h" #include "def.h" #include "realloc-buffer.h" #include "rm-rf.h" #include "util.h" /* #undef EBADMSG */ /* #define EBADMSG __LINE__ */ #define REMOTE_BUFFER_SIZE (1024U*1024U) #define REMOTE_BUFFER_LOW (1024U*4U) typedef enum CaRemoteState { CA_REMOTE_HELLO, CA_REMOTE_RUNNING, CA_REMOTE_EOF, } CaRemoteState; typedef struct CaRemoteFile { char *path, *temporary_path; int fd; ReallocBuffer buffer; bool complete; } CaRemoteFile; struct CaRemote { unsigned n_ref; CaRemoteState state; char *url_prefix; char *callout; /* char *base_url; */ char *index_url; char *archive_url; char *wstore_url; /* The "primary" store, where we write to */ char **rstore_urls; /* Additional, "secondary" stores we check */ char *cache_path; int cache_fd; bool remove_cache; int input_fd; int output_fd; uint64_t rate_limit_bps; ReallocBuffer input_buffer; ReallocBuffer output_buffer; ReallocBuffer chunk_buffer; ReallocBuffer validate_buffer; uint64_t queue_start_high, queue_start_low; uint64_t queue_end_high, queue_end_low; uint64_t local_feature_flags; uint64_t remote_feature_flags; CaRemoteFile index_file; CaRemoteFile archive_file; CaChunkID last_chunk; bool last_chunk_valid; pid_t pid; bool sent_hello; bool sent_goodbye; size_t frame_size; CaDigestType digest_type; CaDigest* validate_digest; uint64_t n_requests; uint64_t n_request_bytes; CaCompressionType compression_type; }; CaRemote* ca_remote_new(void) { CaRemote *rr; rr = new0(CaRemote, 1); if (!rr) return NULL; rr->n_ref = 1; rr->cache_fd = -1; rr->input_fd = -1; rr->output_fd = -1; rr->index_file.fd = -1; rr->archive_file.fd = -1; rr->local_feature_flags = UINT64_MAX; rr->remote_feature_flags = UINT64_MAX; rr->rate_limit_bps = UINT64_MAX; rr->digest_type = _CA_DIGEST_TYPE_INVALID; rr->compression_type = CA_COMPRESSION_DEFAULT; return rr; } CaRemote* ca_remote_ref(CaRemote *rr) { if (!rr) return NULL; assert(rr->n_ref > 0); rr->n_ref++; return rr; } static void ca_remote_remove_cache(CaRemote *rr) { const char *c; assert(rr); if (rr->remove_cache) { /* If we shall remove the cache in its entirety, then do so, rby removing its root */ if (rr->cache_path) { (void) rm_rf(rr->cache_path, REMOVE_ROOT|REMOVE_PHYSICAL); rr->cache_path = mfree(rr->cache_path); rr->cache_fd = safe_close(rr->cache_fd); } else if (rr->cache_fd >= 0) { (void) rm_rf_children(rr->cache_fd, REMOVE_PHYSICAL, NULL); rr->cache_fd = -1; } return; } if (rr->cache_fd < 0) return; /* If we shall not remove the cache, at least remove the queueing symlinks */ FOREACH_STRING(c, "low-priority/", "high-priority/", "chunks/") { int fd; fd = openat(rr->cache_fd, c, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); if (fd < 0) continue; (void) rm_rf_children(fd, REMOVE_PHYSICAL, NULL); (void) unlinkat(rr->cache_fd, c, AT_REMOVEDIR); } } static void ca_remote_file_free(CaRemoteFile *f) { assert(f); f->fd = safe_close(f->fd); if (f->temporary_path) { (void) unlink(f->temporary_path); f->temporary_path = mfree(f->temporary_path); } f->path = mfree(f->path); realloc_buffer_free(&f->buffer); } CaRemote* ca_remote_unref(CaRemote *rr) { if (!rr) return NULL; assert(rr->n_ref > 0); rr->n_ref--; if (rr->n_ref > 0) return NULL; ca_remote_remove_cache(rr); free(rr->url_prefix); free(rr->callout); /* free(rr->base_url); */ free(rr->index_url); free(rr->archive_url); free(rr->wstore_url); strv_free(rr->rstore_urls); free(rr->cache_path); rr->cache_fd = safe_close(rr->cache_fd); if (rr->input_fd > 2) safe_close(rr->input_fd); if (rr->output_fd > 2) safe_close(rr->output_fd); realloc_buffer_free(&rr->input_buffer); realloc_buffer_free(&rr->output_buffer); realloc_buffer_free(&rr->chunk_buffer); realloc_buffer_free(&rr->validate_buffer); ca_remote_file_free(&rr->index_file); ca_remote_file_free(&rr->archive_file); if (rr->pid > 1) { /* (void) kill(rr->pid, SIGTERM); */ (void) wait_for_terminate(rr->pid, NULL); } ca_digest_free(rr->validate_digest); return mfree(rr); } int ca_remote_set_rate_limit_bps(CaRemote *rr, uint64_t rate_limit_bps) { if (!rr) return -EINVAL; rr->rate_limit_bps = rate_limit_bps; return 0; } int ca_remote_set_local_feature_flags(CaRemote *rr, uint64_t flags) { if (!rr) return -EINVAL; if (flags == UINT64_MAX) return -EINVAL; if (flags & ~CA_PROTOCOL_FEATURE_FLAGS_MAX) return -EOPNOTSUPP; /* If I don't provide any services and don't want to do anything, then there's no point in all of this */ if (flags == 0) return -EINVAL; if (rr->local_feature_flags != UINT64_MAX) return -EBUSY; if (rr->sent_hello) return -EBUSY; rr->local_feature_flags = flags; return 0; } int ca_remote_add_local_feature_flags(CaRemote *rr, uint64_t flags) { if (!rr) return -EINVAL; if (flags == UINT64_MAX) return -EINVAL; if (flags & ~CA_PROTOCOL_FEATURE_FLAGS_MAX) return -EOPNOTSUPP; if (flags == 0) return 0; if (rr->sent_hello) return -EBUSY; rr->local_feature_flags |= flags; return 0; } int ca_remote_get_local_feature_flags(CaRemote *rr, uint64_t* flags) { if (!rr) return -EINVAL; if (!flags) return -EINVAL; if (rr->local_feature_flags == UINT64_MAX) return -ENODATA; *flags = rr->local_feature_flags; return 0; } int ca_remote_get_remote_feature_flags(CaRemote *rr, uint64_t* flags) { if (!rr) return -EINVAL; if (!flags) return -EINVAL; if (rr->remote_feature_flags == UINT64_MAX) return -ENODATA; *flags = rr->remote_feature_flags; return 0; } int ca_remote_set_io_fds(CaRemote *rr, int input_fd, int output_fd) { if (!rr) return -EINVAL; if (input_fd < 0) return -EINVAL; if (output_fd < 0) return -EINVAL; if (rr->input_fd >= 0) return -EBUSY; if (rr->output_fd >= 0) return -EBUSY; rr->input_fd = input_fd; rr->output_fd = output_fd; return 0; } int ca_remote_get_io_fds(CaRemote *rr, int *ret_input_fd, int *ret_output_fd) { if (!rr) return -EINVAL; if (!ret_input_fd) return -EINVAL; if (!ret_output_fd) return -EINVAL; if (rr->input_fd < 0 || rr->output_fd < 0) return -EUNATCH; *ret_input_fd = rr->input_fd; *ret_output_fd = rr->output_fd; return 0; } static size_t ca_remote_get_read_size(CaRemote *rr) { assert(rr); /* Return how many bytes we need in the input buffer, so that we can proceed processing frames. We always try * to keep a minimum number of bytes in the buffer, and if the current frame wants to be larger we are happy * with that too. */ return MAX(rr->frame_size, REMOTE_BUFFER_SIZE); } int ca_remote_get_io_events(CaRemote *rr, short *ret_input_events, short *ret_output_events) { if (!rr) return -EINVAL; if (!ret_input_events) return -EINVAL; if (!ret_output_events) return -EINVAL; if (realloc_buffer_size(&rr->input_buffer) < ca_remote_get_read_size(rr)) *ret_input_events = POLLIN; else *ret_input_events = 0; if (realloc_buffer_size(&rr->output_buffer) > 0) *ret_output_events = POLLOUT; else *ret_output_events = 0; return 0; } static int ca_remote_url_prefix_install(CaRemote *rr, const char *url) { const char *e; char *prefix; size_t n, k; assert(rr); assert(url); /* Explicitly mask out / and ./ as indicators for local directories */ if (url[0] == '/') return -EINVAL; if (url[0] == '.' && url[1] == '/') return -EINVAL; if (!strchr(URL_PROTOCOL_FIRST, url[0])) return -EINVAL; n = 1 + strspn(url + 1, URL_PROTOCOL_CHARSET); e = startswith(url + n, "://"); if (!e) return -EINVAL; k = strspn(e, HOSTNAME_CHARSET "@:[]"); if (k <= 0) return -EINVAL; if (e[k] != '/' && e[k] != 0) return -EINVAL; prefix = strndup(url, n + 3 + k); if (!prefix) return -ENOMEM; if (rr->url_prefix) { if (!streq(rr->url_prefix, prefix)) { free(prefix); return -EBUSY; } free(prefix); return 0; } assert(!rr->callout); rr->callout = strndup(url, n); if (!rr->callout) { free(prefix); return -ENOMEM; } rr->url_prefix = prefix; return 1; } static int ca_remote_ssh_prefix_install(CaRemote *rr, const char *url) { char *prefix; size_t n; assert(rr); assert(url); /* Explicitly mask out / and ./ as indicators for local directories */ if (url[0] == '/') return -EINVAL; if (url[0] == '.' && url[1] == '/') return -EINVAL; n = strspn(url, HOSTNAME_CHARSET); if (n <= 0) return -EINVAL; if (url[n] == '@') { size_t k; k = strspn(url + n + 1, HOSTNAME_CHARSET); if (k <= 0) return -EINVAL; if (url[n+1+k] != ':') return -EINVAL; n += 1 + k; } else if (url[n] != ':') return -EINVAL; prefix = strndup(url, n + 1); if (!prefix) return -ENOMEM; if (rr->url_prefix) { if (!streq(rr->url_prefix, prefix)) { free(prefix); return -EBUSY; } free(prefix); return 0; } assert(!rr->callout); rr->url_prefix = prefix; return 0; } static int ca_remote_any_prefix_install(CaRemote *rr, const char *url) { int r; /* First, try to parse as proper URL */ r = ca_remote_url_prefix_install(rr, url); if (r != -EINVAL) return r; /* If that didn't work, parse as ssh-style pseudo-URL */ return ca_remote_ssh_prefix_install(rr, url); } int ca_remote_set_index_url(CaRemote *rr, const char *url) { int r; if (!rr) return -EINVAL; if (!url) return -EINVAL; if (rr->index_url) return -EBUSY; r = ca_remote_any_prefix_install(rr, url); if (r < 0) return r; rr->index_url = strdup(url); if (!rr->index_url) return -ENOMEM; return 0; } int ca_remote_set_archive_url(CaRemote *rr, const char *url) { int r; if (!rr) return -EINVAL; if (!url) return -EINVAL; if (rr->archive_url) return -EBUSY; r = ca_remote_any_prefix_install(rr, url); if (r < 0) return r; rr->archive_url = strdup(url); if (!rr->archive_url) return -ENOMEM; return 0; } int ca_remote_set_store_url(CaRemote *rr, const char *url) { int r; if (!rr) return -EINVAL; if (!url) return -EINVAL; if (rr->wstore_url) return -EBUSY; r = ca_remote_any_prefix_install(rr, url); if (r < 0) return r; rr->wstore_url = strdup(url); if (!rr->wstore_url) return -ENOMEM; return 0; } int ca_remote_add_store_url(CaRemote *rr, const char *url) { int r; if (!rr) return -EINVAL; if (!url) return -EINVAL; r = ca_remote_any_prefix_install(rr, url); if (r < 0) return r; return strv_extend(&rr->rstore_urls, url); } int ca_remote_set_cache_path(CaRemote *rr, const char *path) { if (!rr) return -EINVAL; if (!path) return -EINVAL; if (rr->cache_path) return -EBUSY; if (rr->cache_fd >= 0) return -EBUSY; rr->cache_path = strdup(path); if (!rr->cache_path) return -ENOMEM; return 0; } int ca_remote_set_cache_fd(CaRemote *rr, int fd) { if (!rr) return -EINVAL; if (fd < 0) return -EINVAL; if (rr->cache_path) return -EBUSY; if (rr->cache_fd >= 0) return -EBUSY; rr->cache_fd = fd; return 0; } static int ca_remote_file_set_path(CaRemoteFile *f, const char *path) { assert(f); assert(path); if (f->path) return -EBUSY; if (f->fd >= 0) return -EBUSY; f->path = strdup(path); if (!f->path) return -ENOMEM; return 0; } int ca_remote_set_index_path(CaRemote *rr, const char *path) { if (!rr) return -EINVAL; if (!path) return -EINVAL; return ca_remote_file_set_path(&rr->index_file, path); } static int ca_remote_file_set_fd(CaRemoteFile *f, int fd) { assert(f); assert(fd >= 0); if (f->path) return -EBUSY; if (f->fd >= 0) return -EBUSY; f->fd = fd; return 0; } int ca_remote_set_index_fd(CaRemote *rr, int fd) { if (!rr) return -EINVAL; if (fd < 0) return -EINVAL; return ca_remote_file_set_fd(&rr->index_file, fd); } int ca_remote_set_archive_path(CaRemote *rr, const char *path) { if (!rr) return -EINVAL; if (!path) return -EINVAL; return ca_remote_file_set_path(&rr->archive_file, path); } int ca_remote_set_archive_fd(CaRemote *rr, int fd) { if (!rr) return -EINVAL; if (fd < 0) return -EINVAL; return ca_remote_file_set_fd(&rr->index_file, fd); } static int ca_remote_init_cache(CaRemote *rr) { int r; assert(rr); if (rr->cache_fd >= 0) return 0; if (!rr->cache_path) { const char *d; r = var_tmp_dir(&d); if (r < 0) return r; if (asprintf(&rr->cache_path, "%s/%" PRIx64 ".carem", d, random_u64()) < 0) return -ENOMEM; rr->remove_cache = true; } (void) mkdir(rr->cache_path, 0777); rr->cache_fd = open(rr->cache_path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (rr->cache_fd < 0) return -errno; return 1; } static int ca_remote_enqueue_request(CaRemote *rr, const CaChunkID *id, bool high_priority, bool please_requeue) { char ids[CA_CHUNK_ID_FORMAT_MAX]; uint64_t position; const char *f, *queue_name; char *qpos; int r; assert(rr); assert(id); r = ca_remote_init_cache(rr); if (r < 0) return r; /* Enqueues a GET request. We maintain a 2-level priority queue on disk for this, in two directories * "low-priority" and "high-priority". This could be much easier if we could maintain this entirely in memory, * but given that the list might be quite large we use a symlink farm instead. For each queued GET request we * create two symlinks: one pointing from the queue position to the chunk hash, and one the other way. That * way we can easily enqueue, dequeue and check whether a specific chunk is already queued. */ if (!ca_chunk_id_format(id, ids)) return -EINVAL; if (high_priority) { queue_name = "high-priority/"; position = rr->queue_end_high; } else { queue_name = "low-priority/"; position = rr->queue_end_low; } /* Check whether the chunk is already enqueued */ f = strjoina("chunks/", ids); r = readlinkat_malloc(rr->cache_fd, f, &qpos); if (r < 0 && r != -ENOENT) return r; if (r >= 0) { uint64_t old_position; const char *p; /* Already queued on the same priority? Then there's nothing to do. */ if (startswith(qpos, queue_name)) { r = 0; goto finish; } /* Not matching, but the new priority is low? Then there's nothing to do.*/ if (!high_priority) { r = 0; goto finish; } p = startswith(qpos, "low-priority/"); if (!p) { r = -EBADMSG; goto finish; } r = safe_atou64(p, &old_position); if (r < 0) goto finish; /* Was the old low-priority item already dispatched? Don't requeue the item then, except this is explicitly requested. */ if (old_position < rr->queue_start_low && !please_requeue) { r = 0; goto finish; } if (unlinkat(rr->cache_fd, f, 0) < 0) { r = -errno; goto finish; } if (unlinkat(rr->cache_fd, qpos, 0) < 0) { r = -errno; goto finish; } qpos = mfree(qpos); } if (asprintf(&qpos, "%s%" PRIu64, queue_name, position) < 0) { r = -ENOMEM; goto finish; } if (position == 0) { (void) mkdirat(rr->cache_fd, "chunks", 0777); (void) mkdirat(rr->cache_fd, queue_name, 0777); } if (symlinkat(qpos, rr->cache_fd, f) < 0) { r = -errno; goto finish; } if (symlinkat(ids, rr->cache_fd, qpos) < 0) { r = -errno; goto finish; } if (high_priority) rr->queue_end_high++; else rr->queue_end_low++; /* fprintf(stderr, PID_FMT ": Enqueued request for %s (%s)\n", getpid(), ids, qpos); */ r = 1; finish: free(qpos); return r; } static int ca_remote_dequeue_request(CaRemote *rr, int only_high_priority, CaChunkID *ret, bool *ret_high_priority) { const char *queue_name; uint64_t position; char *ids; CaChunkID id; bool hp; int r; assert(rr); assert(ret); if (rr->cache_fd < 0) return -ENODATA; for (;;) { char *qpos; if (rr->queue_start_high < rr->queue_end_high && only_high_priority != 0) { hp = true; position = rr->queue_start_high; queue_name = "high-priority/"; } else if (rr->queue_start_low < rr->queue_end_low && only_high_priority <= 0) { hp = false; position = rr->queue_start_low; queue_name = "low-priority/"; } else return -ENODATA; if (asprintf(&qpos, "%s%" PRIu64, queue_name, position) < 0) return -ENOMEM; r = readlinkat_malloc(rr->cache_fd, qpos, &ids); free(qpos); if (r >= 0) break; if (r != -ENOENT) return r; /* Hmm, this symlink is missing? I figure it was already processed otherwise */ if (hp) rr->queue_start_high++; else rr->queue_start_low++; } if (!ca_chunk_id_parse(ids, &id)) { free(ids); return -EBADMSG; } /* fprintf(stderr, PID_FMT ": Dequeued request for %s\n", getpid(), ids); */ free(ids); if (hp) rr->queue_start_high++; else rr->queue_start_low++; *ret = id; if (ret_high_priority) *ret_high_priority = hp; return 0; } static int ca_remote_file_open(CaRemote *rr, CaRemoteFile *f, int flags) { int r; assert(f); if (rr->state != CA_REMOTE_RUNNING) return 0; /* Don't open files before we haven't settled on whether we shall do so for read or write */ if (f->fd >= 0) return 0; if (!f->path) return 0; flags |= O_CLOEXEC|O_NOCTTY; if ((flags & O_ACCMODE) == O_RDONLY) { f->fd = open(f->path, flags); if (f->fd < 0) return -errno; return 1; } if (!f->temporary_path) { r = tempfn_random(f->path, &f->temporary_path); if (r < 0) return r; } f->fd = open(f->temporary_path, flags|O_CREAT|O_EXCL|O_NOFOLLOW, 0666); if (f->fd < 0) return -errno; return 1; } static int ca_remote_start(CaRemote *rr) { int r; assert(rr); if (rr->local_feature_flags == UINT64_MAX) return -EUNATCH; /* We support either a readable or a writable index, not both. */ if ((rr->local_feature_flags & CA_PROTOCOL_READABLE_INDEX) && (rr->local_feature_flags & CA_PROTOCOL_WRITABLE_INDEX)) return -EINVAL; /* We support either a readable or a writable archive, not both. */ if ((rr->local_feature_flags & CA_PROTOCOL_READABLE_ARCHIVE) && (rr->local_feature_flags & CA_PROTOCOL_WRITABLE_ARCHIVE)) return -EINVAL; if (rr->input_fd < 0 || rr->output_fd < 0) { int pair1[2], pair2[2]; if (/* isempty(rr->base_url) && */ isempty(rr->archive_url) && isempty(rr->index_url) && isempty(rr->wstore_url) && strv_isempty(rr->rstore_urls)) return -EUNATCH; assert(rr->input_fd < 0); assert(rr->output_fd < 0); assert(rr->pid <= 1); if (pipe2(pair1, O_CLOEXEC|O_NONBLOCK) < 0) return -errno; if (pipe2(pair2, O_CLOEXEC|O_NONBLOCK) < 0) { safe_close_pair(pair1); return -errno; } rr->pid = fork(); if (rr->pid < 0) { safe_close_pair(pair1); safe_close_pair(pair2); return -errno; } if (rr->pid == 0) { char **args, **u; size_t i = 0, skip; int argc; /* Child */ safe_close(pair1[0]); safe_close(pair2[1]); if (dup3(pair2[0], STDIN_FILENO, 0) < 0) { log_error("Failed to duplicate to STDIN: %m"); goto child_fail; } safe_close(pair2[0]); if (dup3(pair1[1], STDOUT_FILENO, 0) < 0) { log_error("Failed to duplicate to STDOUT: %m"); goto child_fail; } safe_close(pair1[1]); (void) prctl(PR_SET_PDEATHSIG, SIGTERM); argc = (rr->callout ? 1 : 3) + 5 + strv_length(rr->rstore_urls); if (rr->rate_limit_bps != UINT64_MAX) argc++; args = newa(char*, argc + 1); if (rr->callout) { const char *e; e = getenv("CASYNC_PROTOCOL_PATH"); if (!e) e = CASYNC_PROTOCOL_PATH; args[i++] = strjoina(e, "/casync-", rr->callout); skip = 0; } else { const char *ssh, *remote_casync; skip = strlen(rr->url_prefix); ssh = getenv("CASYNC_SSH_PATH"); if (!ssh) ssh = "ssh"; remote_casync = getenv("CASYNC_REMOTE_PATH"); if (!remote_casync) remote_casync = "casync"; args[i++] = (char*) ssh; args[i++] = strndupa(rr->url_prefix, skip - 1); args[i++] = (char*) remote_casync; } if (rr->rate_limit_bps != UINT64_MAX) { r = asprintf(args + i, "--rate-limit-bps=%" PRIu64, rr->rate_limit_bps); if (r < 0) return log_oom(); i++; } args[i + CA_REMOTE_ARG_OPERATION] = (char*) ((rr->local_feature_flags & (CA_PROTOCOL_PUSH_CHUNKS|CA_PROTOCOL_PUSH_INDEX|CA_PROTOCOL_PUSH_ARCHIVE)) ? "push" : "pull"); args[i + CA_REMOTE_ARG_BASE_URL] = /* rr->base_url ? rr->base_url + skip :*/ (char*) "-"; args[i + CA_REMOTE_ARG_ARCHIVE_URL] = rr->archive_url ? rr->archive_url + skip : (char*) "-"; args[i + CA_REMOTE_ARG_INDEX_URL] = rr->index_url ? rr->index_url + skip : (char*) "-"; args[i + CA_REMOTE_ARG_WSTORE_URL] = rr->wstore_url ? rr->wstore_url + skip: (char*) "-"; i += _CA_REMOTE_ARG_MAX; STRV_FOREACH(u, rr->rstore_urls) args[i++] = *u + skip; args[i] = NULL; if (rr->callout) execv(args[0], args); else execvp(args[0], args); log_error("Failed to execute %s: %m", args[0]); child_fail: _exit(EXIT_FAILURE); } rr->input_fd = pair1[0]; rr->output_fd = pair2[1]; safe_close(pair1[1]); safe_close(pair2[0]); } if ((rr->remote_feature_flags & CA_PROTOCOL_PULL_INDEX) || (rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX)) { r = ca_remote_file_open(rr, &rr->index_file, O_RDONLY); if (r < 0) return r; } else if ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX) || (rr->local_feature_flags & CA_PROTOCOL_PULL_INDEX)) { r = ca_remote_file_open(rr, &rr->index_file, O_WRONLY); if (r < 0) return r; } if ((rr->remote_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) || (rr->local_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE)) { r = ca_remote_file_open(rr, &rr->archive_file, O_RDONLY); if (r < 0) return r; } else if ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE) || (rr->local_feature_flags & CA_PROTOCOL_PULL_ARCHIVE)) { r = ca_remote_file_open(rr, &rr->archive_file, O_WRONLY); if (r < 0) return r; } return CA_REMOTE_POLL; } static int ca_remote_read(CaRemote *rr) { size_t left, rsize; ssize_t n; void *p; assert(rr); rsize = ca_remote_get_read_size(rr); if (realloc_buffer_size(&rr->input_buffer) >= rsize) return CA_REMOTE_POLL; left = rsize - realloc_buffer_size(&rr->input_buffer); p = realloc_buffer_extend(&rr->input_buffer, left); if (!p) return -ENOMEM; n = read(rr->input_fd, p, left); realloc_buffer_shorten(&rr->input_buffer, n < 0 ? left : left - n); if (n < 0) return errno == EAGAIN ? CA_REMOTE_POLL : -errno; if (n == 0) /* EOF */ return -EPIPE; return CA_REMOTE_STEP; } static int ca_remote_write(CaRemote *rr) { ssize_t n; assert(rr); if (realloc_buffer_size(&rr->output_buffer) == 0) return CA_REMOTE_POLL; n = write(rr->output_fd, realloc_buffer_data(&rr->output_buffer), realloc_buffer_size(&rr->output_buffer)); if (n < 0) return errno == EAGAIN ? CA_REMOTE_POLL : -errno; realloc_buffer_advance(&rr->output_buffer, n); if (rr->sent_goodbye && realloc_buffer_size(&rr->output_buffer) == 0) return CA_REMOTE_FINISHED; return CA_REMOTE_STEP; } static bool operations_and_services_compatible(uint64_t operations, uint64_t services) { if ((operations & CA_PROTOCOL_PULL_CHUNKS) && !(services & CA_PROTOCOL_READABLE_STORE)) return false; if ((operations & CA_PROTOCOL_PULL_INDEX) && !(services & CA_PROTOCOL_READABLE_INDEX)) return false; if ((operations & CA_PROTOCOL_PULL_ARCHIVE) && !(services & CA_PROTOCOL_READABLE_ARCHIVE)) return false; if ((operations & CA_PROTOCOL_PUSH_CHUNKS) && !(services & CA_PROTOCOL_WRITABLE_STORE)) return false; if ((operations & CA_PROTOCOL_PUSH_INDEX) && !(services & CA_PROTOCOL_WRITABLE_INDEX)) return false; if ((operations & CA_PROTOCOL_PUSH_ARCHIVE) && !(services & CA_PROTOCOL_WRITABLE_ARCHIVE)) return false; return true; } static int ca_remote_process_hello(CaRemote *rr, const CaProtocolHello *hello) { uint64_t remote_flags; assert(rr); assert(hello); remote_flags = read_le64(&hello->feature_flags); if ((remote_flags & ~CA_PROTOCOL_FEATURE_FLAGS_MAX) != 0) return -EPROTONOSUPPORT; if ((remote_flags & CA_PROTOCOL_PUSH_INDEX_CHUNKS) && ((remote_flags & (CA_PROTOCOL_PUSH_INDEX|CA_PROTOCOL_PUSH_CHUNKS)) != (CA_PROTOCOL_PUSH_INDEX|CA_PROTOCOL_PUSH_CHUNKS))) return -EBADE; /* Check if what one side needs is provided by the other */ if (!operations_and_services_compatible(remote_flags, rr->local_feature_flags)) return -EBADE; if (!operations_and_services_compatible(rr->local_feature_flags, remote_flags)) return -EBADE; /* Check that between both sides there are are any operations requested */ if (((remote_flags | rr->local_feature_flags) & (CA_PROTOCOL_PULL_INDEX|CA_PROTOCOL_PULL_CHUNKS|CA_PROTOCOL_PULL_ARCHIVE|CA_PROTOCOL_PUSH_CHUNKS|CA_PROTOCOL_PUSH_INDEX|CA_PROTOCOL_PUSH_ARCHIVE)) == 0) return -EBADE; rr->remote_feature_flags = remote_flags; rr->state = CA_REMOTE_RUNNING; return CA_REMOTE_STEP; } static int ca_remote_file_process(CaRemoteFile *f, const CaProtocolFile *p) { size_t sz; int r; assert(f); assert(p); if (f->complete) return -EBADMSG; sz = read_le64(&p->header.size) - offsetof(CaProtocolFile, data); if (f->fd >=0) { r = loop_write(f->fd, p->data, sz); if (r < 0) return r; } else { if (!realloc_buffer_append(&f->buffer, p->data, sz)) return -ENOMEM; } return 0; } static int ca_remote_file_process_eof(CaRemoteFile *f, const CaProtocolFileEOF *eof) { assert(f); assert(eof); if (f->complete) return -EBADMSG; f->complete = true; return 0; } static int ca_remote_process_index(CaRemote *rr, const CaProtocolFile *p) { int r; assert(rr); assert(p); r = ca_remote_file_process(&rr->index_file, p); if (r < 0) return r; return CA_REMOTE_READ_INDEX; } static int ca_remote_process_index_eof(CaRemote *rr, const CaProtocolFileEOF *eof) { int r; assert(rr); assert(eof); r = ca_remote_file_process_eof(&rr->index_file, eof); if (r < 0) return r; return CA_REMOTE_READ_INDEX_EOF; } static int ca_remote_process_archive(CaRemote *rr, const CaProtocolFile *p) { int r; assert(rr); assert(p); r = ca_remote_file_process(&rr->archive_file, p); if (r < 0) return r; return CA_REMOTE_READ_ARCHIVE; } static int ca_remote_process_archive_eof(CaRemote *rr, const CaProtocolFileEOF *eof) { int r; assert(rr); assert(eof); r = ca_remote_file_process_eof(&rr->archive_file, eof); if (r < 0) return r; return CA_REMOTE_READ_ARCHIVE_EOF; } static int ca_remote_process_request(CaRemote *rr, const CaProtocolRequest *req) { const uint8_t *p; size_t ms; int r; assert(rr); assert(req); ms = read_le64(&req->header.size) - offsetof(CaProtocolRequest, chunks); for (p = req->chunks; p < req->chunks + ms; p += CA_CHUNK_ID_SIZE) { r = ca_remote_enqueue_request(rr, (const CaChunkID*) p, read_le64(&req->flags) & CA_PROTOCOL_REQUEST_HIGH_PRIORITY, false); if (r < 0) return r; } return CA_REMOTE_REQUEST; } static int ca_remote_process_chunk(CaRemote *rr, const CaProtocolChunk *chunk) { size_t ms; int r; assert(rr); assert(chunk); if (rr->cache_fd < 0) return -ENOTTY; memcpy(&rr->last_chunk, chunk->chunk, CA_CHUNK_ID_SIZE); rr->last_chunk_valid = true; ms = read_le64(&chunk->header.size) - offsetof(CaProtocolChunk, data); r = ca_chunk_file_save(rr->cache_fd, NULL, &rr->last_chunk, (read_le64(&chunk->flags) & CA_PROTOCOL_CHUNK_COMPRESSED) ? CA_CHUNK_COMPRESSED : CA_CHUNK_UNCOMPRESSED, CA_CHUNK_AS_IS, CA_COMPRESSION_DEFAULT, chunk->data, ms); if (r == -EEXIST) return CA_REMOTE_STEP; if (r < 0) return r; return CA_REMOTE_CHUNK; } static int ca_remote_process_missing(CaRemote *rr, const CaProtocolMissing *missing) { int r; assert(rr); assert(missing); if (rr->cache_fd < 0) return -ENOTTY; r = ca_chunk_file_mark_missing(rr->cache_fd, NULL, (const CaChunkID*) missing->chunk); if (r == -EEXIST) return CA_REMOTE_STEP; if (r < 0) return r; return CA_REMOTE_CHUNK; } static int ca_remote_file_install(CaRemoteFile *f) { assert(f); if (!f->complete) return 0; if (!f->temporary_path) return 0; if (!f->path) return 0; if (rename(f->temporary_path, f->path) < 0) return -errno; f->temporary_path = mfree(f->temporary_path); return 1; } static int ca_remote_process_goodbye(CaRemote *rr, const CaProtocolGoodbye *goodbye) { int r; assert(rr); assert(goodbye); r = ca_remote_file_install(&rr->index_file); if (r < 0) return r; r = ca_remote_file_install(&rr->archive_file); if (r < 0) return r; rr->state = CA_REMOTE_EOF; return CA_REMOTE_FINISHED; } static int ca_remote_process_abort(CaRemote *rr, const CaProtocolAbort *a) { assert(rr); assert(a); if (a->error != 0) return - (int) read_le64(&a->error); return -ECONNABORTED; } static const CaProtocolHello* validate_hello(CaRemote *rr, const CaProtocolHeader *h) { const CaProtocolHello *hello; assert(rr); assert(h); if (read_le64(&h->size) != sizeof(CaProtocolHello)) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_HELLO) return NULL; hello = (const CaProtocolHello*) h; if (read_le64(&hello->feature_flags) == UINT64_MAX) return NULL; if (read_le64(&hello->feature_flags) == 0) return NULL; /* Other side doesn't provide anything, and doesn't want anything? */ return (const CaProtocolHello*) h; } static const CaProtocolFile* validate_file(CaRemote *rr, uint64_t type, const CaProtocolHeader *h) { assert(rr); assert(h); if (read_le64(&h->size) < offsetof(CaProtocolFile, data) + 1) return NULL; if (read_le64(&h->type) != type) return NULL; return (const CaProtocolFile*) h; } static const CaProtocolFileEOF* validate_file_eof(CaRemote *rr, uint64_t type, const CaProtocolHeader *h) { assert(rr); assert(h); if (read_le64(&h->size) != sizeof(CaProtocolFileEOF)) return NULL; if (read_le64(&h->type) != type) return NULL; return (const CaProtocolFileEOF*) h; } static const CaProtocolRequest* validate_request(CaRemote *rr, const CaProtocolHeader *h) { const CaProtocolRequest *req; assert(rr); assert(h); if (read_le64(&h->size) < offsetof(CaProtocolRequest, chunks) + CA_CHUNK_ID_SIZE) return NULL; if ((read_le64(&h->size) - offsetof(CaProtocolRequest, chunks)) % CA_CHUNK_ID_SIZE != 0) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_REQUEST) return NULL; req = (const CaProtocolRequest*) h; if ((read_le64(&req->flags) & ~CA_PROTOCOL_REQUEST_FLAG_MAX) != 0) return NULL; return req; } static const CaProtocolChunk* validate_chunk(CaRemote *rr, const CaProtocolHeader *h) { const CaProtocolChunk *c; uint64_t flags; assert(rr); assert(h); if (read_le64(&h->size) < offsetof(CaProtocolChunk, data) + 1) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_CHUNK) return NULL; c = (const CaProtocolChunk*) h; flags = read_le64(&c->flags); if (flags & ~CA_PROTOCOL_CHUNK_FLAG_MAX) return NULL; return c; } static const CaProtocolMissing* validate_missing(CaRemote *rr, const CaProtocolHeader *h) { assert(rr); assert(h); if (read_le64(&h->size) != sizeof(CaProtocolMissing)) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_MISSING) return NULL; return (const CaProtocolMissing*) h; } static const CaProtocolGoodbye* validate_goodbye(CaRemote *rr, const CaProtocolHeader *h) { assert(rr); assert(h); if (read_le64(&h->size) != sizeof(CaProtocolGoodbye)) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_GOODBYE) return NULL; return (const CaProtocolGoodbye*) h; } static const CaProtocolAbort* validate_abort(CaRemote *rr, const CaProtocolHeader *h) { const CaProtocolAbort *a; const char *p; size_t n; assert(rr); if (read_le64(&h->size) < offsetof(CaProtocolAbort, reason) + 1) return NULL; if (read_le64(&h->type) != CA_PROTOCOL_ABORT) return NULL; a = (const CaProtocolAbort*) h; if (read_le64(&a->error) >= INT32_MAX) return NULL; n = read_le64(&h->size) - offsetof(CaProtocolAbort, reason); for (p = a->reason; p < a->reason + n - 1; p++) { if ((uint8_t) *p < (uint8_t) ' ') return NULL; } if (a->reason[n-1] != 0) return NULL; return a; } static int ca_remote_process_message(CaRemote *rr) { const CaProtocolHeader *h; uint64_t size; int r, step; assert(rr); if (!IN_SET(rr->state, CA_REMOTE_HELLO, CA_REMOTE_RUNNING)) return CA_REMOTE_POLL; if (realloc_buffer_size(&rr->input_buffer) < sizeof(CaProtocolHeader)) return CA_REMOTE_POLL; h = realloc_buffer_data(&rr->input_buffer); size = read_le64(&h->size); if (size < CA_PROTOCOL_SIZE_MIN) return -EBADMSG; if (size > CA_PROTOCOL_SIZE_MAX) return -EBADMSG; if (realloc_buffer_size(&rr->input_buffer) < size) { rr->frame_size = (size_t) size; /* Tell the read logic, that we can't proceed without this much in the buffer */ return CA_REMOTE_POLL; } /* fprintf(stderr, */ /* PID_FMT " Got frame %s of size %" PRIu64 "\n", */ /* getpid(), */ /* strna(ca_protocol_type_name(read_le64(&h->type))), */ /* size); */ switch (read_le64(&h->type)) { case CA_PROTOCOL_HELLO: { const CaProtocolHello *hello; if (rr->state != CA_REMOTE_HELLO) return -EBADMSG; hello = validate_hello(rr, h); if (!hello) return -EBADMSG; step = ca_remote_process_hello(rr, hello); break; } case CA_PROTOCOL_INDEX: { const CaProtocolFile *f; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_INDEX) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX) == 0)) return -EBADMSG; f = validate_file(rr, CA_PROTOCOL_INDEX, h); if (!f) return -EBADMSG; step = ca_remote_process_index(rr, f); break; } case CA_PROTOCOL_INDEX_EOF: { const CaProtocolFileEOF *eof; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_INDEX) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX) == 0)) return -EBADMSG; eof = validate_file_eof(rr, CA_PROTOCOL_INDEX_EOF, h); if (!eof) return -EBADMSG; step = ca_remote_process_index_eof(rr, eof); break; } case CA_PROTOCOL_ARCHIVE: { const CaProtocolFile *f; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE) == 0)) return -EBADMSG; f = validate_file(rr, CA_PROTOCOL_ARCHIVE, h); if (!f) return -EBADMSG; step = ca_remote_process_archive(rr, f); break; } case CA_PROTOCOL_ARCHIVE_EOF: { const CaProtocolFileEOF *eof; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE) == 0)) return -EBADMSG; eof = validate_file_eof(rr, CA_PROTOCOL_ARCHIVE_EOF, h); if (!eof) return -EBADMSG; step = ca_remote_process_archive_eof(rr, eof); break; } case CA_PROTOCOL_REQUEST: { const CaProtocolRequest *req; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PUSH_CHUNKS) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PULL_CHUNKS) == 0)) return -EBADMSG; req = validate_request(rr, h); if (!req) return -EBADMSG; step = ca_remote_process_request(rr, req); break; } case CA_PROTOCOL_CHUNK: { const CaProtocolChunk *chunk; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_CHUNKS) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_CHUNKS) == 0)) return -EBADMSG; chunk = validate_chunk(rr, h); if (!chunk) return -EBADMSG; step = ca_remote_process_chunk(rr, chunk); break; } case CA_PROTOCOL_MISSING: { const CaProtocolMissing *missing; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; if (((rr->local_feature_flags & CA_PROTOCOL_PULL_CHUNKS) == 0) && ((rr->remote_feature_flags & CA_PROTOCOL_PUSH_CHUNKS) == 0)) return -EBADMSG; missing = validate_missing(rr, h); if (!missing) return -EBADMSG; step = ca_remote_process_missing(rr, missing); break; } case CA_PROTOCOL_GOODBYE: { const CaProtocolGoodbye *goodbye; if (rr->state != CA_REMOTE_RUNNING) return -EBADMSG; goodbye = validate_goodbye(rr, h); if (!goodbye) return -EBADMSG; step = ca_remote_process_goodbye(rr, goodbye); break; } case CA_PROTOCOL_ABORT: { const CaProtocolAbort *abort; abort = validate_abort(rr, h); if (!abort) return -EBADMSG; step = ca_remote_process_abort(rr, abort); break; } default: return -EBADMSG; } if (step < 0) return step; r = realloc_buffer_advance(&rr->input_buffer, size); if (r < 0) return r; rr->frame_size = 0; return step; } static int ca_remote_send_hello(CaRemote *rr) { CaProtocolHello *hello; assert(rr); if (rr->sent_hello) return CA_REMOTE_POLL; hello = realloc_buffer_extend0(&rr->output_buffer, sizeof(CaProtocolHello)); if (!hello) return -ENOMEM; write_le64(&hello->header.size, sizeof(CaProtocolHello)); write_le64(&hello->header.type, CA_PROTOCOL_HELLO); write_le64(&hello->feature_flags, rr->local_feature_flags); rr->sent_hello = true; return CA_REMOTE_STEP; } static int ca_remote_send_file( CaRemote *rr, CaRemoteFile *f, uint64_t file_type, uint64_t eof_type, int request_step) { ssize_t n; void *p; int r; assert(rr); assert(f); if (rr->state != CA_REMOTE_RUNNING) return CA_REMOTE_POLL; if (f->complete) return CA_REMOTE_POLL; if (realloc_buffer_size(&rr->output_buffer) > REMOTE_BUFFER_LOW) return CA_REMOTE_POLL; if (f->fd < 0) return request_step; /* request more data from caller */ p = realloc_buffer_extend(&rr->output_buffer, offsetof(CaProtocolFile, data) + BUFFER_SIZE); if (!p) return -ENOMEM; n = read(f->fd, (uint8_t*) p + offsetof(CaProtocolFile, data), BUFFER_SIZE); if (n <= 0) { CaProtocolFileEOF *eof; (void) realloc_buffer_shorten(&rr->output_buffer, offsetof(CaProtocolFile, data) + BUFFER_SIZE); if (n < 0) return -errno; p = realloc_buffer_extend(&rr->output_buffer, sizeof(CaProtocolFileEOF)); if (!p) return -ENOMEM; eof = (CaProtocolFileEOF*) p; write_le64(&eof->header.size, sizeof(CaProtocolFileEOF)); write_le64(&eof->header.type, eof_type); f->complete = true; } else { CaProtocolFile *idx; r = realloc_buffer_shorten(&rr->output_buffer, BUFFER_SIZE - n); if (r < 0) return r; idx = (CaProtocolFile*) p; write_le64(&idx->header.size, offsetof(CaProtocolFile, data) + n); write_le64(&idx->header.type, file_type); } return CA_REMOTE_STEP; } static int ca_remote_send_index(CaRemote *rr) { assert(rr); if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_INDEX) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX)) return CA_REMOTE_POLL; return ca_remote_send_file( rr, &rr->index_file, CA_PROTOCOL_INDEX, CA_PROTOCOL_INDEX_EOF, CA_REMOTE_WRITE_INDEX); } static int ca_remote_send_archive(CaRemote *rr) { assert(rr); if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE)) return CA_REMOTE_POLL; return ca_remote_send_file( rr, &rr->archive_file, CA_PROTOCOL_ARCHIVE, CA_PROTOCOL_ARCHIVE_EOF, CA_REMOTE_WRITE_ARCHIVE); } static int ca_remote_send_request(CaRemote *rr) { size_t header_offset = (size_t) -1; int only_high_priority = -1, r; assert(rr); if (!(rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX_CHUNKS) && !(rr->local_feature_flags & CA_PROTOCOL_PULL_CHUNKS)) return CA_REMOTE_POLL; if (rr->state != CA_REMOTE_RUNNING) return CA_REMOTE_POLL; /* Only write out queue when the send queue is short */ if (realloc_buffer_size(&rr->output_buffer) > REMOTE_BUFFER_LOW) return CA_REMOTE_POLL; for (;;) { CaProtocolRequest *req; bool high_priority; CaChunkID id; void *p; r = ca_remote_dequeue_request(rr, only_high_priority, &id, &high_priority); if (r == -ENODATA) break; if (r < 0) return r; if (header_offset != (size_t) -1) { /* If we already have a request, append one item */ p = realloc_buffer_extend(&rr->output_buffer, CA_CHUNK_ID_SIZE); if (!p) return -ENOMEM; req = realloc_buffer_data_offset(&rr->output_buffer, header_offset); assert(req); write_le64(&req->header.size, read_le64(&req->header.size) + CA_CHUNK_ID_SIZE); } else { header_offset = realloc_buffer_size(&rr->output_buffer); /* If we don't have a request frame yet, allocate one with one item. */ req = realloc_buffer_extend0(&rr->output_buffer, offsetof(CaProtocolRequest, chunks) + CA_CHUNK_ID_SIZE); if (!req) return -ENOMEM; write_le64(&req->header.type, CA_PROTOCOL_REQUEST); write_le64(&req->header.size, offsetof(CaProtocolRequest, chunks) + CA_CHUNK_ID_SIZE); write_le64(&req->flags, high_priority ? CA_PROTOCOL_REQUEST_HIGH_PRIORITY : 0); p = req->chunks; } memcpy(p, &id, CA_CHUNK_ID_SIZE); only_high_priority = high_priority; /* Is the frame already large enough? If so, let's stop it for now */ if (read_le64(&req->header.size) >= BUFFER_SIZE) break; } return header_offset != (size_t) -1 ? CA_REMOTE_STEP : CA_REMOTE_POLL; } int ca_remote_step(CaRemote *rr) { int r; if (!rr) return -EINVAL; if (rr->state == CA_REMOTE_EOF) return -EPIPE; realloc_buffer_empty(&rr->index_file.buffer); realloc_buffer_empty(&rr->archive_file.buffer); r = ca_remote_start(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_write(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_send_hello(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_process_message(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_send_request(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_send_index(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_send_archive(rr); if (r != CA_REMOTE_POLL) return r; r = ca_remote_read(rr); if (r != CA_REMOTE_POLL) return r; return CA_REMOTE_POLL; } int ca_remote_poll(CaRemote *rr, uint64_t timeout_nsec, const sigset_t *ss) { struct pollfd pollfd[2]; size_t n = 0; int r; if (!rr) return -EINVAL; if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (rr->input_fd < 0 || rr->output_fd < 0) return -EUNATCH; if (realloc_buffer_size(&rr->input_buffer) < ca_remote_get_read_size(rr)) { pollfd[n].fd = rr->input_fd; pollfd[n].events = POLLIN; n++; } if (realloc_buffer_size(&rr->output_buffer) > 0) { pollfd[n].fd = rr->output_fd; pollfd[n].events = POLLOUT; n++; } if (n == 0) return 0; if (timeout_nsec != UINT64_MAX) { struct timespec ts; ts = nsec_to_timespec(timeout_nsec); r = ppoll(pollfd, n, &ts, ss); } else r = ppoll(pollfd, n, NULL, ss); if (r < 0) return -errno; return 1; } static int ca_remote_validate_chunk( CaRemote *rr, const CaChunkID *id, CaChunkCompression compression, const void *p, size_t l) { CaChunkID actual; int r; if (!rr) return -EINVAL; if (!id) return -EINVAL; if (!IN_SET(compression, CA_CHUNK_COMPRESSED, CA_CHUNK_UNCOMPRESSED)) return -EINVAL; if (!p) return -EINVAL; if (l < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (l > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (compression == CA_CHUNK_COMPRESSED) { realloc_buffer_empty(&rr->validate_buffer); r = ca_decompress(p, l, &rr->validate_buffer); if (r < 0) return r; p = realloc_buffer_data(&rr->validate_buffer); l = realloc_buffer_size(&rr->validate_buffer); } /* We validate the digests of all incoming chunks. We support multiple digest algorithms in parallel. If the * caller has set a specific algorithm explicitly, we will only validate by it. However, if none such algorithm * was supplied, we'll use the one that worked on the last chunk first, and will then try all others we know * before considering the digest to not match the contents */ if (!rr->validate_digest) { /* Allocate the digest object, and start with sha512-256 if we don't know which algorithm to use. */ r = ca_digest_new(rr->digest_type >= 0 ? rr->digest_type : CA_DIGEST_DEFAULT, &rr->validate_digest); if (r < 0) return r; } r = ca_chunk_id_make(rr->validate_digest, p, l, &actual); if (r < 0) return r; if (!ca_chunk_id_equal(id, &actual)) { CaDigestType old_type, i; /* If an explicit digest algorithm was set, then a mismatch is fatal */ if (rr->digest_type >= 0) return -EBADMSG; old_type = ca_digest_get_type(rr->validate_digest); if (old_type < 0) return -EINVAL; /* Otherwise iterate through all algorithms we know, and see if it works for them */ for (i = 0; i < _CA_DIGEST_TYPE_MAX; i++) { if (i == old_type) continue; r = ca_digest_set_type(rr->validate_digest, i); if (r < 0) return r; r = ca_chunk_id_make(rr->validate_digest, p, l, &actual); if (r < 0) return r; if (ca_chunk_id_equal(id, &actual)) return 0; } return -EBADMSG; } return 0; } int ca_remote_request( CaRemote *rr, const CaChunkID *chunk_id, bool high_priority, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression) { CaChunkCompression compression; int r; if (!rr) return -EINVAL; if (!chunk_id) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (!(rr->local_feature_flags & CA_PROTOCOL_PULL_CHUNKS)) return -ENOTTY; if (rr->state == CA_REMOTE_EOF) return -EPIPE; r = ca_remote_init_cache(rr); if (r < 0) return r; realloc_buffer_empty(&rr->chunk_buffer); r = ca_chunk_file_load(rr->cache_fd, NULL, chunk_id, desired_compression, rr->compression_type, &rr->chunk_buffer, &compression); if (r == -ENOENT) { /* We don't have it right now. Enqueue it */ r = ca_remote_enqueue_request(rr, chunk_id, high_priority, true); if (r < 0) return r; if (r > 0) return -EAGAIN; /* Not a failure, but we don't have it right now, but have enqueued it. */ return -EALREADY; /* Not a failure, but we don't have it right now, but it was already enqueued it. */ } if (r == -EADDRNOTAVAIL) /* We really don't have this */ return -ENOENT; if (r < 0) return r; /* We already have the chunk. Now, validate it before returning it. */ r = ca_remote_validate_chunk(rr, chunk_id, compression, realloc_buffer_data(&rr->chunk_buffer), realloc_buffer_size(&rr->chunk_buffer)); if (r < 0) return r; *ret = realloc_buffer_data(&rr->chunk_buffer); *ret_size = realloc_buffer_size(&rr->chunk_buffer); if (ret_effective_compression) *ret_effective_compression = compression; rr->n_requests++; rr->n_request_bytes += realloc_buffer_size(&rr->chunk_buffer); return 1; } int ca_remote_request_async(CaRemote *rr, const CaChunkID *chunk_id, bool high_priority) { if (!rr) return -EINVAL; if (!chunk_id) return -EINVAL; if (!(rr->local_feature_flags & CA_PROTOCOL_PULL_CHUNKS) && !(rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX_CHUNKS)) return -ENOTTY; if (rr->state == CA_REMOTE_EOF) return -EPIPE; return ca_remote_enqueue_request(rr, chunk_id, high_priority, true); } int ca_remote_next_request(CaRemote *rr, CaChunkID *ret) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_CHUNKS) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX_CHUNKS)) return -ENOTTY; if (rr->state == CA_REMOTE_EOF) return -EPIPE; return ca_remote_dequeue_request(rr, -1, ret, NULL); } int ca_remote_can_put_chunk(CaRemote *rr) { if (!rr) return -EINVAL; /* Returns > 0 if there's buffer space to enqueue more chunks */ if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_CHUNKS) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_CHUNKS)) return 0; if (rr->state != CA_REMOTE_RUNNING) return 0; /* can't take your data right now. */ if (realloc_buffer_size(&rr->output_buffer) > REMOTE_BUFFER_LOW) return 0; /* won't take your data right now, already got enough in my queue */ return 1; } int ca_remote_put_chunk( CaRemote *rr, const CaChunkID *chunk_id, CaChunkCompression compression, const void *data, uint64_t size) { CaProtocolChunk *chunk; uint64_t msz; int r; if (!rr) return -EINVAL; if (!chunk_id) return -EINVAL; if (!data) return -EINVAL; if (size < CA_CHUNK_SIZE_LIMIT_MIN) return -EINVAL; if (size > CA_CHUNK_SIZE_LIMIT_MAX) return -EINVAL; if (!IN_SET(compression, CA_CHUNK_COMPRESSED, CA_CHUNK_UNCOMPRESSED)) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_CHUNKS) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_CHUNKS)) return -ENOTTY; r = ca_remote_can_put_chunk(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; msz = offsetof(CaProtocolChunk, data) + size; if (msz < size) /* overflow? */ return -EFBIG; if (msz > CA_PROTOCOL_SIZE_MAX) return -EFBIG; chunk = realloc_buffer_extend(&rr->output_buffer, msz); if (!chunk) return -ENOMEM; write_le64(&chunk->header.type, CA_PROTOCOL_CHUNK); write_le64(&chunk->header.size, msz); write_le64(&chunk->flags, compression == CA_CHUNK_COMPRESSED ? CA_PROTOCOL_CHUNK_COMPRESSED : 0); memcpy(chunk->chunk, chunk_id, CA_CHUNK_ID_SIZE); memcpy(chunk->data, data, size); return 0; } int ca_remote_put_missing(CaRemote *rr, const CaChunkID *chunk_id) { CaProtocolMissing *missing; int r; if (!rr) return -EINVAL; if (!chunk_id) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_CHUNKS) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_CHUNKS)) return -ENOTTY; r = ca_remote_can_put_chunk(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; missing = realloc_buffer_extend0(&rr->output_buffer, sizeof(CaProtocolMissing)); if (!missing) return -ENOMEM; write_le64(&missing->header.type, CA_PROTOCOL_MISSING); write_le64(&missing->header.size, sizeof(CaProtocolMissing)); memcpy(missing->chunk, chunk_id, CA_CHUNK_ID_SIZE); return 0; } static int ca_remote_file_can_put(CaRemote *rr, CaRemoteFile *f) { assert(rr); assert(f); if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (f->complete) return -EBUSY; if (rr->state != CA_REMOTE_RUNNING) return 0; if (realloc_buffer_size(&rr->output_buffer) > REMOTE_BUFFER_LOW) return 0; return 1; } int ca_remote_can_put_index(CaRemote *rr) { if (!rr) return -EINVAL; /* Returns > 0 if there's buffer space to enqueue more index data */ if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_INDEX) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX)) return 0; return ca_remote_file_can_put(rr, &rr->index_file); } static int ca_remote_file_put(CaRemote *rr, CaRemoteFile *f, uint64_t type, const void *data, size_t size) { CaProtocolFile *p; size_t msz; assert(rr); assert(f); msz = offsetof(CaProtocolFile, data) + size; if (msz < size) /* overflow? */ return -EFBIG; if (msz > CA_PROTOCOL_SIZE_MAX) return -EFBIG; p = realloc_buffer_extend(&rr->output_buffer, msz); if (!p) return -ENOMEM; write_le64(&p->header.type, type); write_le64(&p->header.size, msz); memcpy(p->data, data, size); return 0; } int ca_remote_put_index(CaRemote *rr, const void *data, size_t size) { int r; if (!rr) return -EINVAL; if (!data) return -EINVAL; if (size == 0) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_INDEX) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX)) return -ENOTTY; r = ca_remote_can_put_index(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; return ca_remote_file_put(rr, &rr->index_file, CA_PROTOCOL_INDEX, data, size); } static int ca_remote_file_put_eof(CaRemote *rr, CaRemoteFile *f, uint64_t type) { CaProtocolFileEOF *eof; assert(rr); assert(f); eof = realloc_buffer_extend(&rr->output_buffer, sizeof(CaProtocolFileEOF)); if (!eof) return -ENOMEM; write_le64(&eof->header.type, type); write_le64(&eof->header.size, sizeof(CaProtocolFileEOF)); f->complete = true; return 0; } int ca_remote_put_index_eof(CaRemote *rr) { int r; if (!rr) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_INDEX) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_INDEX)) return -ENOTTY; r = ca_remote_can_put_index(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; return ca_remote_file_put_eof(rr, &rr->index_file, CA_PROTOCOL_INDEX_EOF); } static int ca_remote_file_read(CaRemote *rr, CaRemoteFile *f, const void **ret, size_t *ret_size) { assert(rr); assert(f); assert(ret); assert(ret_size); if (f->fd >= 0) /* either the caller can use this function, or we write the data directly to a file, not both */ return -ENOTTY; if (realloc_buffer_size(&f->buffer) == 0) { if (f->complete) { *ret = NULL; *ret_size = 0; return 0; /* eof */ } return -EAGAIN; } *ret = realloc_buffer_data(&f->buffer); *ret_size = realloc_buffer_size(&f->buffer); return 1; } int ca_remote_read_index(CaRemote *rr, const void **ret, size_t *ret_size) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PUSH_INDEX) && !(rr->local_feature_flags & CA_PROTOCOL_PULL_INDEX)) return -ENOTTY; return ca_remote_file_read(rr, &rr->index_file, ret, ret_size); } int ca_remote_can_put_archive(CaRemote *rr) { if (!rr) return -EINVAL; /* Returns > 0 if there's buffer space to enqueue more archive data */ if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE)) return 0; return ca_remote_file_can_put(rr, &rr->archive_file); } int ca_remote_put_archive(CaRemote *rr, const void *data, size_t size) { int r; if (!rr) return -EINVAL; if (!data) return -EINVAL; if (size == 0) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE)) return -ENOTTY; r = ca_remote_can_put_archive(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; return ca_remote_file_put(rr, &rr->archive_file, CA_PROTOCOL_ARCHIVE, data, size); } int ca_remote_put_archive_eof(CaRemote *rr) { int r; if (!rr) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PULL_ARCHIVE) && !(rr->local_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE)) return -ENOTTY; r = ca_remote_can_put_archive(rr); if (r < 0) return r; if (r == 0) return -EAGAIN; return ca_remote_file_put_eof(rr, &rr->archive_file, CA_PROTOCOL_ARCHIVE_EOF); } int ca_remote_read_archive(CaRemote *rr, const void **ret, size_t *ret_size) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (!(rr->remote_feature_flags & CA_PROTOCOL_PUSH_ARCHIVE) && !(rr->local_feature_flags & CA_PROTOCOL_PULL_ARCHIVE)) return -ENOTTY; return ca_remote_file_read(rr, &rr->archive_file, ret, ret_size); } int ca_remote_goodbye(CaRemote *rr) { CaProtocolGoodbye *goodbye; int r; if (!rr) return -EINVAL; if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (rr->sent_goodbye) return -EALREADY; r = ca_remote_file_install(&rr->index_file); if (r < 0) return r; r = ca_remote_file_install(&rr->archive_file); if (r < 0) return r; goodbye = realloc_buffer_extend(&rr->output_buffer, sizeof(CaProtocolGoodbye)); if (!goodbye) return -ENOMEM; write_le64(&goodbye->header.type, CA_PROTOCOL_GOODBYE); write_le64(&goodbye->header.size, sizeof(CaProtocolGoodbye)); rr->sent_goodbye = true; return 0; } int ca_remote_abort(CaRemote *rr, int error, const char *message) { CaProtocolAbort *a; size_t l; if (!rr) return -EINVAL; if (error < 0) return -EINVAL; if (error >= INT32_MAX) return -EINVAL; if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (rr->sent_goodbye) return -EALREADY; l = strlen(message); a = realloc_buffer_extend(&rr->output_buffer, offsetof(CaProtocolAbort, reason) + l + 1); if (!a) return -ENOMEM; write_le64(&a->header.type, CA_PROTOCOL_ABORT); write_le64(&a->header.size, offsetof(CaProtocolAbort, reason) + l + 1); write_le64(&a->error, error); strcpy(a->reason, message); rr->sent_goodbye = true; return 0; } int ca_remote_has_pending_requests(CaRemote *rr) { if (!rr) return -EINVAL; /* If there's no cache, then we can't have anything queued */ if (rr->cache_fd < 0) return 0; /* Does this have locally queued requests? */ if ((rr->queue_start_high < rr->queue_end_high) || (rr->queue_start_low < rr->queue_end_low)) return 1; return 0; } int ca_remote_next_chunk( CaRemote *rr, CaChunkCompression desired_compression, CaChunkID *ret_id, const void **ret_data, size_t *ret_size, CaChunkCompression *ret_effective_compression) { int r; if (!rr) return -EINVAL; if (!ret_id) return -EINVAL; if (!ret_data != !ret_size) return -EINVAL; if (!rr->last_chunk_valid) return -ENODATA; if (rr->state == CA_REMOTE_EOF) return -EPIPE; if (rr->cache_fd < 0) return -ENODATA; if (ret_data) { CaChunkCompression compression; realloc_buffer_empty(&rr->chunk_buffer); r = ca_chunk_file_load(rr->cache_fd, NULL, &rr->last_chunk, desired_compression, rr->compression_type, &rr->chunk_buffer, &compression); if (r < 0) return r; r = ca_remote_validate_chunk(rr, &rr->last_chunk, compression, realloc_buffer_data(&rr->chunk_buffer), realloc_buffer_size(&rr->chunk_buffer)); if (r < 0) return r; *ret_data = realloc_buffer_data(&rr->chunk_buffer); *ret_size = realloc_buffer_size(&rr->chunk_buffer); if (ret_effective_compression) *ret_effective_compression = compression; } else { if (ret_effective_compression) *ret_effective_compression = desired_compression; r = 0; } *ret_id = rr->last_chunk; return r; } int ca_remote_has_unwritten(CaRemote *rr) { if (!rr) return -EINVAL; if (rr->state == CA_REMOTE_EOF) return -EPIPE; return realloc_buffer_size(&rr->output_buffer) > 0; } int ca_remote_has_chunks(CaRemote *rr) { DIR *d; int r; if (!rr) return -EINVAL; /* Returns true, when this remote has any chunks queued now, or before. */ r = ca_remote_has_pending_requests(rr); if (r != 0) return r; if (rr->cache_fd < 0) return 0; r = xopendirat(rr->cache_fd, "chunks", 0, &d); if (r == -ENOENT) return 0; if (r < 0) return r; for (;;) { struct dirent *de; errno = 0; de = readdir(d); if (!de) { r = -errno; closedir(d); if (r == 0) break; return r; } if (!dot_or_dot_dot(de->d_name)) { closedir(d); return 1; } } return 0; } int ca_remote_forget_chunk(CaRemote *rr, const CaChunkID *id) { char ids[CA_CHUNK_ID_FORMAT_MAX], *qpos; const char *f; int r; /* Forget everything we know about the specified chunk, and the chunk itself. Specifically: * * - Remove the chunks/ symlink * - Remove the low-priority/ or high-priority symlink * - Remove the cached chunk */ if (!rr) return -EINVAL; if (!id) return -EINVAL; if (rr->cache_fd < 0) return 0; if (!ca_chunk_id_format(id, ids)) return -EINVAL; f = strjoina("chunks/", ids); r = readlinkat_malloc(rr->cache_fd, f, &qpos); if (r < 0 && r != -ENOENT) return r; if (r >= 0) { const char *p; p = startswith(qpos, "low-priority/"); if (!p) { p = startswith(qpos, "high-priority/"); if (!p) { r = -EBADMSG; goto finish; } } r = safe_atou64(p, NULL); if (r < 0) goto finish; if (unlinkat(rr->cache_fd, f, 0) < 0) { r = -errno; goto finish; } if (unlinkat(rr->cache_fd, qpos, 0) < 0) { r = -errno; goto finish; } } r = ca_chunk_file_remove(rr->cache_fd, NULL, id); if (r < 0 && r != -ENOENT) goto finish; r = 0; finish: free(qpos); return r; } int ca_remote_set_digest_type(CaRemote *rr, CaDigestType type) { int r; if (!rr) return -EINVAL; if (type >= _CA_DIGEST_TYPE_MAX) return -EOPNOTSUPP; if (type < 0) rr->digest_type = _CA_DIGEST_TYPE_INVALID; else { if (rr->validate_digest) { r = ca_digest_set_type(rr->validate_digest, type); if (r < 0) return r; } rr->digest_type = type; } return 0; } int ca_remote_get_digest_type(CaRemote *rr, CaDigestType *ret) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; *ret = rr->digest_type; return 0; } int ca_remote_get_requests(CaRemote *rr, uint64_t *ret) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; *ret = rr->n_requests; return 0; } int ca_remote_get_request_bytes(CaRemote *rr, uint64_t *ret) { if (!rr) return -EINVAL; if (!ret) return -EINVAL; *ret = rr->n_request_bytes; return 0; } int ca_remote_set_compression_type(CaRemote *rr, CaCompressionType ct) { if (!rr) return -EINVAL; if (ct < 0) return -EINVAL; if (ct >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; rr->compression_type = ct; return 0; } src/caremote.h000066400000000000000000000123111322621430500136110ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaremotehfoo #define foocaremotehfoo #include #include #include #include "cachunk.h" #include "cachunkid.h" typedef struct CaRemote CaRemote; enum { CA_REMOTE_POLL, /* Nothing to do, sleep with ca_remote_poll() please! */ CA_REMOTE_FINISHED, /* Done! */ CA_REMOTE_STEP, /* Did something, call me again */ CA_REMOTE_REQUEST, /* push: Got a REQUEST message, please call ca_remote_next_request() to find out more */ CA_REMOTE_WRITE_INDEX, /* push: Please provide index data now, via ca_remote_write_index() */ CA_REMOTE_WRITE_ARCHIVE, /* push: Please provide archive data now, via ca_remote_write_archive() */ CA_REMOTE_CHUNK, /* pull: Got a CHUNK message, ask me if yours is now with ca_remote_request() */ CA_REMOTE_READ_INDEX, /* pull: Got more INDEX data, retrieve it via ca_remote_read_index() */ CA_REMOTE_READ_INDEX_EOF, /* pull: INDEX data is now complete */ CA_REMOTE_READ_ARCHIVE, /* pull: Got more ARCHIVE data, retrieve it via ca_remote_read_archive() */ CA_REMOTE_READ_ARCHIVE_EOF, /* pull: ARCHIVE data is now complete */ }; enum { CA_REMOTE_ARG_OPERATION = 0, CA_REMOTE_ARG_BASE_URL, CA_REMOTE_ARG_ARCHIVE_URL, CA_REMOTE_ARG_INDEX_URL, CA_REMOTE_ARG_WSTORE_URL, /* This should be last except MAX */ _CA_REMOTE_ARG_MAX, }; CaRemote* ca_remote_new(void); CaRemote* ca_remote_ref(CaRemote *rr); CaRemote* ca_remote_unref(CaRemote *rr); static inline void ca_remote_unrefp(CaRemote **rr) { ca_remote_unref(*rr); } int ca_remote_set_local_feature_flags(CaRemote *rr, uint64_t flags); int ca_remote_add_local_feature_flags(CaRemote *rr, uint64_t flags); int ca_remote_get_local_feature_flags(CaRemote *rr, uint64_t* flags); int ca_remote_get_remote_feature_flags(CaRemote *rr, uint64_t* flags); int ca_remote_set_digest_type(CaRemote *rr, CaDigestType type); int ca_remote_get_digest_type(CaRemote *rr, CaDigestType *ret); int ca_remote_set_rate_limit_bps(CaRemote *rr, uint64_t rate_limit_bps); int ca_remote_set_io_fds(CaRemote *rr, int input_fd, int output_fd); int ca_remote_get_io_fds(CaRemote *rr, int *ret_input_fd, int *ret_output_fd); int ca_remote_get_io_events(CaRemote *rr, short *ret_input_events, short *ret_output_events); /* int ca_remote_set_base_url(CaRemote *rr, const char *url); */ int ca_remote_set_archive_url(CaRemote *rr, const char *url); int ca_remote_set_index_url(CaRemote *rr, const char *url); int ca_remote_set_store_url(CaRemote *rr, const char *url); int ca_remote_add_store_url(CaRemote *rr, const char *url); int ca_remote_set_cache_path(CaRemote *rr, const char *path); int ca_remote_set_cache_fd(CaRemote *rr, int fd); int ca_remote_set_index_path(CaRemote *rr, const char *path); int ca_remote_set_index_fd(CaRemote *rr, int fd); int ca_remote_set_archive_path(CaRemote *rr, const char *path); int ca_remote_set_archive_fd(CaRemote *rr, int fd); int ca_remote_step(CaRemote *rr); int ca_remote_poll(CaRemote *rr, uint64_t timeout_nsec, const sigset_t *ss); /* When we are in "pull" mode, interfaces for retrieving chunks, or enqueing requests for them */ int ca_remote_request(CaRemote *rr, const CaChunkID *chunk_id, bool priority, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression); int ca_remote_request_async(CaRemote *rr, const CaChunkID *chunk_id, bool priority); int ca_remote_next_chunk(CaRemote *rr, CaChunkCompression desired_compression, CaChunkID *ret_id, const void **ret_data, size_t *ret_size, CaChunkCompression *ret_compression); /* When we are in "push" mode, interfaces for processing requests and pushing chunks */ int ca_remote_next_request(CaRemote *rr, CaChunkID *ret); int ca_remote_can_put_chunk(CaRemote *rr); int ca_remote_put_chunk(CaRemote *rr, const CaChunkID *chunk_id, CaChunkCompression compression, const void *data, uint64_t size); int ca_remote_put_missing(CaRemote *rr, const CaChunkID *chunk_id); /* pull mode: Read index data */ int ca_remote_read_index(CaRemote *rr, const void **ret, size_t *ret_size); /* pull mode: Read archive data */ int ca_remote_read_archive(CaRemote *rr, const void **ret, size_t *ret_size); /* push mode: Write index data */ int ca_remote_can_put_index(CaRemote *rr); int ca_remote_put_index(CaRemote *rr, const void *p, size_t size); int ca_remote_put_index_eof(CaRemote *rr); /* push mode: Write archive data */ int ca_remote_can_put_archive(CaRemote *rr); int ca_remote_put_archive(CaRemote *rr, const void *p, size_t size); int ca_remote_put_archive_eof(CaRemote *rr); /* Enqueue a goodbye frame */ int ca_remote_goodbye(CaRemote *rr); int ca_remote_abort(CaRemote *rr, int error, const char *message); int ca_remote_has_pending_requests(CaRemote *rr); int ca_remote_has_unwritten(CaRemote *rr); int ca_remote_has_chunks(CaRemote *rr); int ca_remote_forget_chunk(CaRemote *rr, const CaChunkID *id); int ca_remote_get_requests(CaRemote *rr, uint64_t *ret); int ca_remote_get_request_bytes(CaRemote *rr, uint64_t *ret); int ca_remote_set_compression_type(CaRemote *rr, CaCompressionType ct); #endif src/caseed.c000066400000000000000000000540751322621430500132460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "cachunk.h" #include "cachunker.h" #include "caencoder.h" #include "cafileroot.h" #include "caformat-util.h" #include "caformat.h" #include "calocation.h" #include "caseed.h" #include "realloc-buffer.h" #include "rm-rf.h" #include "util.h" /* #undef EINVAL */ /* #define EINVAL __LINE__ */ /* #undef ENXIO */ /* #define ENXIO __LINE__ */ /* #undef EUNATCH */ /* #define EUNATCH __LINE__ */ struct CaSeed { CaEncoder *encoder; int base_fd; int cache_fd; char *cache_path; CaChunker chunker; CaDigest *chunk_digest; bool ready:1; bool remove_cache:1; bool cache_hardlink:1; bool cache_chunks:1; ReallocBuffer buffer; CaLocation *buffer_location; CaFileRoot *root; uint64_t feature_flags; uint64_t n_requests; uint64_t n_request_bytes; }; CaSeed *ca_seed_new(void) { CaSeed *s; s = new0(CaSeed, 1); if (!s) return NULL; s->cache_fd = -1; s->base_fd = -1; s->cache_chunks = true; s->chunker = (CaChunker) CA_CHUNKER_INIT; assert_se(ca_feature_flags_normalize(CA_FORMAT_DEFAULT, &s->feature_flags) >= 0); return s; } static void ca_seed_remove_and_close_cache(CaSeed *s) { assert(s); if (!s->remove_cache) return; if (s->cache_path) { (void) rm_rf(s->cache_path, REMOVE_ROOT|REMOVE_PHYSICAL); s->cache_path = mfree(s->cache_path); s->cache_fd = safe_close(s->cache_fd); } else if (s->cache_fd >= 0) { (void) rm_rf_children(s->cache_fd, REMOVE_PHYSICAL, NULL); s->cache_fd = -1; } } CaSeed *ca_seed_unref(CaSeed *s) { if (!s) return NULL; ca_file_root_invalidate(s->root); ca_seed_remove_and_close_cache(s); ca_encoder_unref(s->encoder); safe_close(s->base_fd); safe_close(s->cache_fd); free(s->cache_path); ca_digest_free(s->chunk_digest); realloc_buffer_free(&s->buffer); ca_location_unref(s->buffer_location); ca_file_root_unref(s->root); return mfree(s); } int ca_seed_set_base_fd(CaSeed *s, int fd) { if (!s) return -EINVAL; if (fd < 0) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; s->base_fd = fd; return 0; } int ca_seed_set_base_path(CaSeed *s, const char *path) { if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; s->base_fd = open(path, O_CLOEXEC|O_NOCTTY|O_RDONLY); if (s->base_fd < 0) return -errno; return 0; } int ca_seed_set_cache_fd(CaSeed *s, int fd) { if (!s) return -EINVAL; if (fd < 0) return -EINVAL; if (s->cache_fd >= 0) return -EBUSY; if (s->cache_path) return -EBUSY; s->cache_fd = fd; return 0; } int ca_seed_set_cache_path(CaSeed *s, const char *path) { if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->cache_fd >= 0) return -EBUSY; if (s->cache_path) return -EBUSY; s->cache_path = strdup(path); if (!s->cache_path) return -ENOMEM; return 0; } static int ca_seed_open(CaSeed *s) { int r; if (!s) return -EINVAL; if (!s->encoder) { if (s->base_fd < 0) return -EUNATCH; s->encoder = ca_encoder_new(); if (!s->encoder) return -ENOMEM; r = ca_encoder_set_feature_flags(s->encoder, s->feature_flags); if (r < 0) return r; r = ca_encoder_set_base_fd(s->encoder, s->base_fd); if (r < 0) return r; r = ca_encoder_enable_hardlink_digest(s->encoder, s->cache_hardlink); if (r < 0) return r; s->base_fd = -1; } if (s->cache_fd < 0) { if (!s->cache_path) { const char *d; r = var_tmp_dir(&d); if (r < 0) return r; if (asprintf(&s->cache_path, "%s/%" PRIx64 ".cased", d, random_u64()) < 0) return -ENOMEM; s->remove_cache = true; } (void) mkdir(s->cache_path, 0777); s->cache_fd = open(s->cache_path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); if (s->cache_fd < 0) return -errno; } return 0; } static int ca_seed_make_chunk_id(CaSeed *s, const void *p, size_t l, CaChunkID *ret) { int r; if (!s) return -EINVAL; if (!p && l > 0) return -EINVAL; if (!ret) return -EINVAL; if (!s->chunk_digest) { r = ca_digest_new(ca_feature_flags_to_digest_type(s->feature_flags), &s->chunk_digest); if (r < 0) return r; } return ca_chunk_id_make(s->chunk_digest, p, l, ret); } static int ca_seed_write_cache_entry(CaSeed *s, CaLocation *location, const void *data, size_t l) { char ids[CA_CHUNK_ID_FORMAT_MAX]; const char *t, *four, *combined; CaChunkID id; int r; assert(s); assert(location); assert(data); assert(l > 0); r = ca_location_patch_size(&location, l); if (r < 0) return r; t = ca_location_format(location); if (!t) return -ENOMEM; r = ca_seed_make_chunk_id(s, data, l, &id); if (r < 0) return r; if (!ca_chunk_id_format(&id, ids)) return -EINVAL; four = strndupa(ids, 4); combined = strjoina(four, "/", ids); (void) mkdirat(s->cache_fd, four, 0777); if (symlinkat(t, s->cache_fd, combined) < 0) { if (errno != EEXIST) return -errno; } return 0; } static int ca_seed_cache_chunks(CaSeed *s) { uint64_t offset = 0; const void *p; size_t l; int r; assert(s); r = ca_encoder_get_data(s->encoder, &p, &l); if (r == -ENODATA) return 0; if (r < 0) return r; if (!s->cache_chunks) return 0; while (l > 0) { const void *chunk; size_t chunk_size, k; if (!s->buffer_location) { r = ca_encoder_current_location(s->encoder, offset, &s->buffer_location); if (r < 0) return r; } k = ca_chunker_scan(&s->chunker, p, l); if (k == (size_t) -1) { if (!realloc_buffer_append(&s->buffer, p, l)) return -ENOMEM; return 0; } if (realloc_buffer_size(&s->buffer) == 0) { chunk = p; chunk_size = k; } else { if (!realloc_buffer_append(&s->buffer, p, k)) return -ENOMEM; chunk = realloc_buffer_data(&s->buffer); chunk_size = realloc_buffer_size(&s->buffer); } r = ca_seed_write_cache_entry(s, s->buffer_location, chunk, chunk_size); if (r < 0) return r; realloc_buffer_empty(&s->buffer); s->buffer_location = ca_location_unref(s->buffer_location); p = (const uint8_t*) p + k; l -= k; offset += k; } return 0; } static int ca_seed_cache_final_chunk(CaSeed *s) { int r; assert(s); if (!s->cache_chunks) return 0; if (realloc_buffer_size(&s->buffer) == 0) return 0; if (!s->buffer_location) return 0; r = ca_seed_write_cache_entry(s, s->buffer_location, realloc_buffer_data(&s->buffer), realloc_buffer_size(&s->buffer)); if (r < 0) return 0; realloc_buffer_empty(&s->buffer); s->buffer_location = ca_location_unref(s->buffer_location); return 0; } static int ca_seed_cache_hardlink(CaSeed *s) { const char *t, *four, *combined; char v[CA_CHUNK_ID_FORMAT_MAX]; CaLocation *location = NULL; char *path = NULL; CaChunkID digest; mode_t mode; int r; assert(s); if (!s->cache_hardlink) return 0; if (!s->encoder) return -EUNATCH; r = ca_encoder_current_mode(s->encoder, &mode); if (r < 0) return r; if (!S_ISREG(mode)) return 0; r = ca_encoder_get_hardlink_digest(s->encoder, &digest); if (r < 0) return r; r = ca_encoder_current_path(s->encoder, &path); if (r < 0) return r; r = ca_location_new(path, CA_LOCATION_ENTRY, 0, UINT64_MAX, &location); free(path); if (r < 0) return r; t = ca_location_format(location); if (!t) { r = -ENOMEM; goto finish; } if (!ca_chunk_id_format(&digest, v)) { r = -EINVAL; goto finish; } four = strndupa(v, 4); combined = strjoina(four, "/", v); (void) mkdirat(s->cache_fd, four, 0777); if (symlinkat(t, s->cache_fd, combined) < 0 && errno != EEXIST) { r = -errno; goto finish; } r = 0; finish: ca_location_unref(location); return r; } int ca_seed_step(CaSeed *s) { int r; if (!s) return -EINVAL; if (s->ready) return -EALREADY; if (!s->cache_chunks && !s->cache_hardlink) { s->ready = true; return CA_SEED_READY; } r = ca_seed_open(s); if (r < 0) return r; for (;;) { int step; step = ca_encoder_step(s->encoder); if (step < 0) return step; switch (step) { case CA_ENCODER_FINISHED: r = ca_seed_cache_final_chunk(s); if (r < 0) return r; s->ready = true; return CA_SEED_READY; case CA_ENCODER_DATA: case CA_ENCODER_NEXT_FILE: case CA_ENCODER_DONE_FILE: case CA_ENCODER_PAYLOAD: r = ca_seed_cache_chunks(s); if (r < 0) return r; if (step == CA_ENCODER_DONE_FILE) { r = ca_seed_cache_hardlink(s); if (r < 0) return r; } return step == CA_ENCODER_NEXT_FILE ? CA_SEED_NEXT_FILE : step == CA_ENCODER_DONE_FILE ? CA_SEED_DONE_FILE : CA_SEED_STEP; default: assert(false); } } } int ca_seed_get(CaSeed *s, const CaChunkID *chunk_id, const void **ret, size_t *ret_size, CaOrigin **ret_origin) { char id[CA_CHUNK_ID_FORMAT_MAX], *target = NULL; const char *four, *combined; CaFileRoot *root = NULL; CaOrigin *origin = NULL; CaLocation *l = NULL; uint64_t size, n = 0; void *p = NULL; int r, step; if (!s) return -EINVAL; if (!chunk_id) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (s->cache_fd < 0) return -EUNATCH; if (!s->cache_chunks) return -ENOMEDIUM; if (!ca_chunk_id_format(chunk_id, id)) return -EINVAL; four = strndupa(id, 4); combined = strjoina(four, "/", id); r = readlinkat_malloc(s->cache_fd, combined, &target); if (r < 0) return r; /* fprintf(stderr, "GOT %s → %s\n", combined, target); */ r = ca_location_parse(target, &l); free(target); if (r < 0) return r; if (l->size == UINT64_MAX) /* If the size is not specified, then this is a hardlink entry */ return -ENOENT; if (l->size > s->chunker.chunk_size_max) { ca_location_unref(l); return -EINVAL; } size = l->size; step = ca_encoder_seek_location(s->encoder, l); l = ca_location_unref(l); if (step == -ENXIO) /* location doesn't exist anymore? Then the seed has been modified */ return -ESTALE; if (step < 0) return step; p = realloc_buffer_acquire(&s->buffer, size); if (!p) return -ENOMEM; if (ret_origin) { r = ca_seed_get_file_root(s, &root); if (r < 0) return r; r = ca_origin_new(&origin); if (r < 0) return r; } for (;;) { switch (step) { case CA_ENCODER_FINISHED: /* Premature end? Then the seed has been modified */ return -ESTALE; case CA_ENCODER_DATA: case CA_ENCODER_NEXT_FILE: case CA_ENCODER_DONE_FILE: case CA_ENCODER_PAYLOAD: { const void *q; size_t w; uint64_t m; r = ca_encoder_get_data(s->encoder, &q, &w); if (r == -ENODATA) break; if (r < 0) goto finish; m = MIN(w, size - n); memcpy((uint8_t*) p + n, q, m); n += m; if (origin) { r = ca_encoder_current_location(s->encoder, 0, &l); if (r < 0) goto finish; r = ca_location_patch_size(&l, m); if (r < 0) { l = ca_location_unref(l); goto finish; } r = ca_location_patch_root(&l, root); if (r < 0) { l = ca_location_unref(l); goto finish; } r = ca_origin_put(origin, l); l = ca_location_unref(l); if (r < 0) goto finish; } if (n >= size) { CaChunkID test_id; r = ca_seed_make_chunk_id(s, p, size, &test_id); if (r < 0) goto finish; if (!ca_chunk_id_equal(chunk_id, &test_id)) { /* fprintf(stderr, "SEED CHUNK CORRUPTED (%" PRIu64 "):\n", size); */ /* hexdump(stderr, p, MIN(1024U, size)); */ r = -ESTALE; goto finish; } /* fprintf(stderr, "SEED CHUNK GOOD\n"); */ *ret = p; *ret_size = size; if (ret_origin) *ret_origin = origin; s->n_requests++; s->n_request_bytes += size; return 0; } break; } default: assert(false); } step = ca_encoder_step(s->encoder); if (step < 0) { r = step; goto finish; } } finish: ca_origin_unref(origin); return r; } int ca_seed_has(CaSeed *s, const CaChunkID *chunk_id) { char id[CA_CHUNK_ID_FORMAT_MAX]; const char *four, *combined; if (!s) return -EINVAL; if (!chunk_id) return -EINVAL; if (s->cache_fd < 0) return -EUNATCH; if (!s->cache_chunks) return -ENOMEDIUM; if (!ca_chunk_id_format(chunk_id, id)) return -EINVAL; four = strndupa(id, 4); combined = strjoina(four, "/", id); if (faccessat(s->cache_fd, combined, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { if (errno == ENOENT) return 0; return -errno; } return 1; } int ca_seed_get_hardlink_target( CaSeed *s, const CaChunkID *id, char **ret) { char v[CA_CHUNK_ID_FORMAT_MAX]; const char *four, *combined; CaLocation *l = NULL; char *target; int r; if (!s) return -EINVAL; if (!id) return -EINVAL; if (!ret) return -EINVAL; if (s->cache_fd < 0) return -EUNATCH; if (!s->cache_hardlink) return -ENOMEDIUM; if (!ca_chunk_id_format(id, v)) return -EINVAL; four = strndupa(v, 4); combined = strjoina(four, "/", v); r = readlinkat_malloc(s->cache_fd, combined, &target); if (r < 0) return r; r = ca_location_parse(target, &l); free(target); if (r < 0) return r; if (l->designator != CA_LOCATION_ENTRY || l->offset != 0 || l->size != UINT64_MAX) { /* If the size is specified, this this a normal chunk reference */ r = -ENOENT; goto finish; } *ret = l->path; l->path = NULL; r = 0; finish: ca_location_unref(l); return r; } int ca_seed_current_path(CaSeed *seed, char **ret) { if (!seed) return -EINVAL; if (!ret) return -EINVAL; if (seed->ready) return -EALREADY; return ca_encoder_current_path(seed->encoder, ret); } int ca_seed_current_mode(CaSeed *seed, mode_t *ret) { if (!seed) return -EINVAL; if (!ret) return -EINVAL; if (seed->ready) return -EALREADY; return ca_encoder_current_mode(seed->encoder, ret); } int ca_seed_set_feature_flags(CaSeed *s, uint64_t flags) { if (!s) return -EINVAL; return ca_feature_flags_normalize(flags, &s->feature_flags); } int ca_seed_set_chunk_size(CaSeed *s, size_t cmin, size_t cavg, size_t cmax) { if (!s) return -EINVAL; ca_chunker_set_size(&s->chunker, cmin, cavg, cmax); return 0; } int ca_seed_get_file_root(CaSeed *s, CaFileRoot **ret) { int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->root) { int base_fd; if (s->base_fd >= 0) base_fd = s->base_fd; else if (s->encoder) { base_fd = ca_encoder_get_base_fd(s->encoder); if (base_fd < 0) return base_fd; } else return -EUNATCH; r = ca_file_root_new(NULL, base_fd, &s->root); if (r < 0) return r; } *ret = s->root; return 0; } int ca_seed_set_hardlink(CaSeed *s, bool b) { int r; if (!s) return -EINVAL; if (s->cache_hardlink == b) return 0; if (s->encoder) { r = ca_encoder_enable_hardlink_digest(s->encoder, b); if (r < 0) return r; } s->cache_hardlink = b; return 1; } int ca_seed_set_chunks(CaSeed *s, bool b) { if (!s) return -EINVAL; if (s->cache_chunks == b) return 0; s->cache_chunks = b; return 1; } int ca_seed_get_requests(CaSeed *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->cache_chunks) return -ENOTTY; *ret = s->n_requests; return 0; } int ca_seed_get_request_bytes(CaSeed *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->cache_chunks) return -ENOTTY; *ret = s->n_request_bytes; return 0; } src/caseed.h000066400000000000000000000025361322621430500132460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocaseedhfoo #define foocaseedhfoo #include #include "cachunkid.h" #include "caorigin.h" typedef struct CaSeed CaSeed; enum { CA_SEED_READY = 0, CA_SEED_STEP = 1, CA_SEED_NEXT_FILE = 2, CA_SEED_DONE_FILE = 3, }; CaSeed *ca_seed_new(void); CaSeed *ca_seed_unref(CaSeed *s); int ca_seed_set_base_fd(CaSeed *s, int fd); int ca_seed_set_base_path(CaSeed *s, const char *path); int ca_seed_set_cache_fd(CaSeed *s, int fd); int ca_seed_set_cache_path(CaSeed *s, const char *path); int ca_seed_step(CaSeed *s); int ca_seed_get(CaSeed *s, const CaChunkID *chunk_id, const void **ret, size_t *ret_size, CaOrigin **ret_origin); int ca_seed_has(CaSeed *s, const CaChunkID *chunk_id); int ca_seed_get_hardlink_target(CaSeed *s, const CaChunkID *id, char **ret); int ca_seed_current_path(CaSeed *seed, char **ret); int ca_seed_current_mode(CaSeed *seed, mode_t *ret); int ca_seed_set_feature_flags(CaSeed *s, uint64_t flags); int ca_seed_set_chunk_size(CaSeed *s, size_t cmin, size_t cavg, size_t cmax); int ca_seed_set_hardlink(CaSeed *s, bool b); int ca_seed_set_chunks(CaSeed *s, bool b); int ca_seed_get_file_root(CaSeed *s, CaFileRoot **ret); int ca_seed_get_requests(CaSeed *s, uint64_t *ret); int ca_seed_get_request_bytes(CaSeed *s, uint64_t *ret); #endif src/castore.c000066400000000000000000000261661322621430500134620ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include "castore.h" #include "def.h" #include "dirent-util.h" #include "realloc-buffer.h" #include "rm-rf.h" #include "util.h" /* #undef EINVAL */ /* #define EINVAL __LINE__ */ /* #undef EBADMSG */ /* #define EBADMSG __LINE__ */ struct CaStore { char *root; bool is_cache:1; bool mkdir_done:1; ReallocBuffer buffer; CaDigestType digest_type; ReallocBuffer validate_buffer; CaDigest *validate_digest; CaChunkCompression compression; CaCompressionType compression_type; uint64_t n_requests; uint64_t n_request_bytes; }; struct CaStoreIterator { CaStore *store; DIR *rootdir; struct dirent *subdir_de; DIR *subdir; }; CaStore* ca_store_new(void) { CaStore *store; store = new0(CaStore, 1); if (!store) return NULL; store->digest_type = _CA_DIGEST_TYPE_INVALID; store->compression = CA_CHUNK_COMPRESSED; store->compression_type = CA_COMPRESSION_DEFAULT; return store; } CaStore *ca_store_new_cache(void) { CaStore *s; s = new0(CaStore, 1); if (!s) return NULL; s->is_cache = true; s->compression = CA_CHUNK_AS_IS; return s; } CaStore* ca_store_unref(CaStore *store) { if (!store) return NULL; if (store->is_cache && store->root) (void) rm_rf(store->root, REMOVE_ROOT|REMOVE_PHYSICAL); free(store->root); realloc_buffer_free(&store->buffer); ca_digest_free(store->validate_digest); realloc_buffer_free(&store->validate_buffer); return mfree(store); } int ca_store_set_path(CaStore *store, const char *path) { if (!store) return -EINVAL; if (!path) return -EINVAL; if (store->root) return -EBUSY; if (endswith(path, "/")) store->root = strdup(path); else store->root = strjoin(path, "/", NULL); if (!store->root) return -ENOMEM; return 0; } int ca_store_set_compression(CaStore *store, CaChunkCompression c) { if (!store) return -EINVAL; if (c < 0) return -EINVAL; if (c >= _CA_CHUNK_COMPRESSION_MAX) return -EINVAL; store->compression = c; return 0; } int ca_store_set_compression_type(CaStore *store, CaCompressionType compression_type) { if (!store) return -EINVAL; if (compression_type < 0) return -EINVAL; if (compression_type >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; store->compression_type = compression_type; return 0; } int ca_store_get( CaStore *store, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression) { CaChunkCompression effective; ReallocBuffer *v; CaChunkID actual; int r; if (!store) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (!store->root) return store->is_cache ? -ENOENT : -EUNATCH; realloc_buffer_empty(&store->buffer); r = ca_chunk_file_load(AT_FDCWD, store->root, chunk_id, desired_compression, store->compression_type, &store->buffer, &effective); if (r < 0) return r; if (effective == CA_CHUNK_COMPRESSED) { realloc_buffer_empty(&store->validate_buffer); r = ca_decompress(realloc_buffer_data(&store->buffer), realloc_buffer_size(&store->buffer), &store->validate_buffer); if (r < 0) return r; v = &store->validate_buffer; } else v = &store->buffer; if (!store->validate_digest) { r = ca_digest_new(store->digest_type >= 0 ? store->digest_type : CA_DIGEST_DEFAULT, &store->validate_digest); if (r < 0) return r; } r = ca_chunk_id_make(store->validate_digest, realloc_buffer_data(v), realloc_buffer_size(v), &actual); if (r < 0) return r; if (!ca_chunk_id_equal(chunk_id, &actual)) { CaDigestType old_type, i; bool good = false; /* If a digest is explicitly configured, only accept this digest */ if (store->digest_type >= 0) return -EBADMSG; old_type = ca_digest_get_type(store->validate_digest); if (old_type < 0) return -EINVAL; for (i = 0; i < _CA_DIGEST_TYPE_MAX; i++) { if (i == old_type) continue; r = ca_digest_set_type(store->validate_digest, i); if (r < 0) return r; r = ca_chunk_id_make(store->validate_digest, realloc_buffer_data(v), realloc_buffer_size(v), &actual); if (r < 0) return r; if (ca_chunk_id_equal(chunk_id, &actual)) { good = true; break; } } if (!good) return -EBADMSG; } *ret = realloc_buffer_data(&store->buffer); *ret_size = realloc_buffer_size(&store->buffer); if (ret_effective_compression) *ret_effective_compression = effective; store->n_requests++; store->n_request_bytes += realloc_buffer_size(&store->buffer); return r; } int ca_store_has(CaStore *store, const CaChunkID *chunk_id) { if (!store) return -EINVAL; if (!store->root) return store->is_cache ? -ENOENT : -EUNATCH; return ca_chunk_file_test(AT_FDCWD, store->root, chunk_id); } int ca_store_put( CaStore *store, const CaChunkID *chunk_id, CaChunkCompression effective_compression, const void *data, uint64_t size) { int r; if (!store) return -EINVAL; if (!store->root) { const char *d; if (!store->is_cache) return -EUNATCH; r = var_tmp_dir(&d); if (r < 0) return r; if (asprintf(&store->root, "%s/%" PRIx64 ".castr/", d, random_u64()) < 0) return -ENOMEM; } if (!store->mkdir_done) { if (mkdir(store->root, 0777) < 0 && errno != EEXIST) return -errno; store->mkdir_done = true; } return ca_chunk_file_save( AT_FDCWD, store->root, chunk_id, effective_compression, store->compression, store->compression_type, data, size); } int ca_store_get_requests(CaStore *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; *ret = s->n_requests; return 0; } int ca_store_get_request_bytes(CaStore *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; *ret = s->n_request_bytes; return 0; } int ca_store_set_digest_type(CaStore *s, CaDigestType type) { int r; if (!s) return -EINVAL; if (type >= _CA_DIGEST_TYPE_MAX) return -EOPNOTSUPP; if (type < 0) s->digest_type = _CA_DIGEST_TYPE_INVALID; else { if (s->validate_digest) { r = ca_digest_set_type(s->validate_digest, type); if (r < 0) return r; } s->digest_type = type; } return 0; } CaStoreIterator* ca_store_iterator_new(CaStore *store) { CaStoreIterator *it; it = new0(CaStoreIterator, 1); if (!it) return NULL; it->store = store; return it; } CaStoreIterator* ca_store_iterator_unref(CaStoreIterator *iter) { if (!iter) return NULL; if (iter->rootdir) closedir(iter->rootdir); if (iter->subdir) closedir(iter->subdir); return mfree(iter); } int ca_store_iterator_next( CaStoreIterator *iter, int *rootdir_fd, const char **subdir, int *subdir_fd, const char **chunk) { struct dirent *de; if (!iter->rootdir) { iter->rootdir = opendir(iter->store->root); if (!iter->rootdir) return -errno; } for (;;) { if (!iter->subdir) { int fd; errno = 0; iter->subdir_de = readdir(iter->rootdir); if (!iter->subdir_de) { if (errno > 0) return -errno; return 0; /* done */ } fd = openat(dirfd(iter->rootdir), iter->subdir_de->d_name, O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (fd < 0) { if (errno == EISDIR) continue; return -errno; } iter->subdir = fdopendir(fd); if (!iter->subdir) { safe_close(fd); return -errno; } } FOREACH_DIRENT_ALL(de, iter->subdir, return -errno) { if (!dirent_is_file_with_suffix(de, ".cacnk")) continue; if (rootdir_fd) *rootdir_fd = dirfd(iter->rootdir); if (subdir) *subdir = iter->subdir_de->d_name; if (subdir_fd) *subdir_fd = dirfd(iter->subdir); if (chunk) *chunk = de->d_name; return 1; /* success */ } assert_se(closedir(iter->subdir) == 0); iter->subdir = NULL; } } src/castore.h000066400000000000000000000031541322621430500134570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocastorehfoo #define foocastorehfoo #include "cachunk.h" #include "cachunkid.h" #include "cautil.h" typedef struct CaStore CaStore; typedef struct CaStoreIterator CaStoreIterator; CaStore* ca_store_new(void); CaStore *ca_store_new_cache(void); CaStore* ca_store_unref(CaStore *store); static inline void ca_store_unrefp(CaStore **store) { ca_store_unref(*store); } int ca_store_set_path(CaStore *store, const char *path); int ca_store_set_compression(CaStore *store, CaChunkCompression c); int ca_store_set_compression_type(CaStore *store, CaCompressionType compression); int ca_store_get(CaStore *store, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression); int ca_store_has(CaStore *store, const CaChunkID *chunk_id); int ca_store_put(CaStore *store, const CaChunkID *chunk_id, CaChunkCompression effective_compression, const void *data, uint64_t size); int ca_store_get_requests(CaStore *s, uint64_t *ret); int ca_store_get_request_bytes(CaStore *s, uint64_t *ret); int ca_store_set_digest_type(CaStore *s, CaDigestType type); CaStoreIterator* ca_store_iterator_new(CaStore *store); CaStoreIterator* ca_store_iterator_unref(CaStoreIterator *iter); static inline void ca_store_iterator_unrefp(CaStoreIterator **iter) { ca_store_iterator_unref(*iter); } int ca_store_iterator_next( CaStoreIterator *iter, int *rootdir_fd, const char **subdir, int *subdir_fd, const char **chunk); #endif src/casync-http.c000066400000000000000000000536471322621430500142630ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "caprotocol.h" #include "caremote.h" #include "cautil.h" #include "realloc-buffer.h" #include "util.h" static bool arg_verbose = false; static curl_off_t arg_rate_limit_bps = 0; static enum { ARG_PROTOCOL_HTTP, ARG_PROTOCOL_FTP, ARG_PROTOCOL_HTTPS, ARG_PROTOCOL_SFTP, _ARG_PROTOCOL_INVALID = -1, } arg_protocol = _ARG_PROTOCOL_INVALID; typedef enum ProcessUntil { PROCESS_UNTIL_WRITTEN, PROCESS_UNTIL_CAN_PUT_CHUNK, PROCESS_UNTIL_CAN_PUT_INDEX, PROCESS_UNTIL_CAN_PUT_ARCHIVE, PROCESS_UNTIL_HAVE_REQUEST, PROCESS_UNTIL_FINISHED, } ProcessUntil; static int process_remote(CaRemote *rr, ProcessUntil until) { int r; assert(rr); for (;;) { switch (until) { case PROCESS_UNTIL_CAN_PUT_CHUNK: r = ca_remote_can_put_chunk(rr); if (r == -EPIPE) return r; if (r < 0) return log_error_errno(r, "Failed to determine whether we can add a chunk to the buffer: %m"); if (r > 0) return 0; break; case PROCESS_UNTIL_CAN_PUT_INDEX: r = ca_remote_can_put_index(rr); if (r == -EPIPE) return r; if (r < 0) return log_error_errno(r, "Failed to determine whether we can add an index fragment to the buffer: %m"); if (r > 0) return 0; break; case PROCESS_UNTIL_CAN_PUT_ARCHIVE: r = ca_remote_can_put_archive(rr); if (r == -EPIPE) return r; if (r < 0) return log_error_errno(r, "Failed to determine whether we can add an archive fragment to the buffer: %m"); if (r > 0) return 0; break; case PROCESS_UNTIL_HAVE_REQUEST: r = ca_remote_has_pending_requests(rr); if (r == -EPIPE) return r; if (r < 0) return log_error_errno(r, "Failed to determine whether there are pending requests."); if (r > 0) return 0; break; case PROCESS_UNTIL_WRITTEN: r = ca_remote_has_unwritten(rr); if (r == -EPIPE) return r; if (r < 0) return log_error_errno(r, "Failed to determine whether there's more data to write."); if (r > 0) return 0; break; case PROCESS_UNTIL_FINISHED: break; default: assert(false); } r = ca_remote_step(rr); if (r == -EPIPE || r == CA_REMOTE_FINISHED) { if (until == PROCESS_UNTIL_FINISHED) return 0; return -EPIPE; } if (r < 0) return log_error_errno(r, "Failed to process remoting engine: %m"); if (r != CA_REMOTE_POLL) continue; r = ca_remote_poll(rr, UINT64_MAX, NULL); if (r < 0) return log_error_errno(r, "Failed to poll remoting engine: %m"); } } static size_t write_index(const void *buffer, size_t size, size_t nmemb, void *userdata) { CaRemote *rr = userdata; size_t product; int r; product = size * nmemb; r = process_remote(rr, PROCESS_UNTIL_CAN_PUT_INDEX); if (r < 0) return 0; r = ca_remote_put_index(rr, buffer, product); if (r < 0) { log_error("Failed to put index: %m"); return 0; } return product; } static int write_index_eof(CaRemote *rr) { int r; assert(rr); r = process_remote(rr, PROCESS_UNTIL_CAN_PUT_INDEX); if (r < 0) return r; r = ca_remote_put_index_eof(rr); if (r < 0) return log_error_errno(r, "Failed to put index EOF: %m"); return 0; } static size_t write_archive(const void *buffer, size_t size, size_t nmemb, void *userdata) { CaRemote *rr = userdata; size_t product; int r; product = size * nmemb; r = process_remote(rr, PROCESS_UNTIL_CAN_PUT_ARCHIVE); if (r < 0) return 0; r = ca_remote_put_archive(rr, buffer, product); if (r < 0) { log_error("Failed to put archive: %m"); return 0; } return product; } static int write_archive_eof(CaRemote *rr) { int r; assert(rr); r = process_remote(rr, PROCESS_UNTIL_CAN_PUT_ARCHIVE); if (r < 0) return r; r = ca_remote_put_archive_eof(rr); if (r < 0) return log_error_errno(r, "Failed to put archive EOF: %m"); return 0; } static size_t write_chunk(const void *buffer, size_t size, size_t nmemb, void *userdata) { ReallocBuffer *chunk_buffer = userdata; size_t product, z; product = size * nmemb; z = realloc_buffer_size(chunk_buffer) + product; if (z < realloc_buffer_size(chunk_buffer)) { log_error("Overflow"); return 0; } if (z > (CA_PROTOCOL_SIZE_MAX - offsetof(CaProtocolChunk, data))) { log_error("Chunk too large"); return 0; } if (!realloc_buffer_append(chunk_buffer, buffer, product)) { log_oom(); return 0; } return product; } static char *chunk_url(const char *store_url, const CaChunkID *id) { char ids[CA_CHUNK_ID_FORMAT_MAX], *buffer; const char *suffix; size_t n; /* Chop off URL arguments and multiple trailing dashes, then append the chunk ID and ".cacnk" */ suffix = ca_compressed_chunk_suffix(); n = strcspn(store_url, "?;"); while (n > 0 && store_url[n-1] == '/') n--; buffer = new(char, n + 1 + 4 + 1 + CA_CHUNK_ID_FORMAT_MAX-1 + strlen(suffix) + 1); if (!buffer) return NULL; ca_chunk_id_format(id, ids); strcpy(mempcpy(mempcpy(mempcpy(mempcpy(mempcpy(buffer, store_url, n), "/", 1), ids, 4), "/", 1), ids, CA_CHUNK_ID_FORMAT_MAX-1), suffix); return buffer; } static int acquire_file(CaRemote *rr, CURL *curl, const char *url, size_t (*callback)(const void *p, size_t size, size_t nmemb, void *userdata)) { long protocol_status; assert(curl); assert(url); assert(callback); if (curl_easy_setopt(curl, CURLOPT_URL, url) != CURLE_OK) { log_error("Failed to set CURL URL to: %s", url); return -EIO; } if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback) != CURLE_OK) { log_error("Failed to set CURL callback function."); return -EIO; } if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, rr) != CURLE_OK) { log_error("Failed to set CURL private data."); return -EIO; } if (arg_verbose) log_error("Acquiring %s...", url); if (curl_easy_perform(curl) != CURLE_OK) { log_error("Failed to acquire %s", url); return -EIO; } if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &protocol_status) != CURLE_OK) { log_error("Failed to query response code"); return -EIO; } if (IN_SET(arg_protocol, ARG_PROTOCOL_HTTP, ARG_PROTOCOL_HTTPS) && protocol_status != 200) { char *m; if (arg_verbose) log_error("HTTP server failure %li while requesting %s.", protocol_status, url); if (asprintf(&m, "HTTP request on %s failed with status %li", url, protocol_status) < 0) return log_oom(); (void) ca_remote_abort(rr, protocol_status == 404 ? ENOMEDIUM : EBADR, m); free(m); return 0; } else if (arg_protocol == ARG_PROTOCOL_FTP && (protocol_status < 200 || protocol_status > 299)) { char *m; if (arg_verbose) log_error("FTP server failure %li while requesting %s.", protocol_status, url); if (asprintf(&m, "FTP request on %s failed with status %li", url, protocol_status) < 0) return log_oom(); (void) ca_remote_abort(rr, EBADR, m); free(m); return 0; } else if (arg_protocol == ARG_PROTOCOL_SFTP && (protocol_status != 0)) { char *m; if (arg_verbose) log_error("SFTP server failure %li while requesting %s.", protocol_status, url); if (asprintf(&m, "SFTP request on %s failed with status %li", url, protocol_status) < 0) return log_oom(); (void) ca_remote_abort(rr, EBADR, m); free(m); return 0; } return 1; } static int run(int argc, char *argv[]) { const char *base_url, *archive_url, *index_url, *wstore_url; size_t n_stores = 0, current_store = 0; CURL *curl = NULL; _cleanup_(ca_remote_unrefp) CaRemote *rr = NULL; _cleanup_(realloc_buffer_free) ReallocBuffer chunk_buffer = {}; _cleanup_free_ char *url_buffer = NULL; long protocol_status; int r; if (argc < _CA_REMOTE_ARG_MAX) { log_error("Expected at least %d arguments.", _CA_REMOTE_ARG_MAX); return -EINVAL; } /* fprintf(stderr, "base=%s archive=%s index=%s wstore=%s\n", argv[1], argv[2], argv[3], argv[4]); */ base_url = empty_or_dash_to_null(argv[CA_REMOTE_ARG_BASE_URL]); archive_url = empty_or_dash_to_null(argv[CA_REMOTE_ARG_ARCHIVE_URL]); index_url = empty_or_dash_to_null(argv[CA_REMOTE_ARG_INDEX_URL]); wstore_url = empty_or_dash_to_null(argv[CA_REMOTE_ARG_WSTORE_URL]); n_stores = !!wstore_url + (argc - _CA_REMOTE_ARG_MAX); if (base_url) { log_error("Pushing/pulling to base via HTTP not yet supported."); return -EOPNOTSUPP; } if (!archive_url && !index_url && n_stores == 0) { log_error("Nothing to do."); return -EINVAL; } rr = ca_remote_new(); if (!rr) { r = log_oom(); goto finish; } r = ca_remote_set_local_feature_flags(rr, (n_stores > 0 ? CA_PROTOCOL_READABLE_STORE : 0) | (index_url ? CA_PROTOCOL_READABLE_INDEX : 0) | (archive_url ? CA_PROTOCOL_READABLE_ARCHIVE : 0)); if (r < 0) { log_error("Failed to set feature flags: %m"); goto finish; } r = ca_remote_set_io_fds(rr, STDIN_FILENO, STDOUT_FILENO); if (r < 0) { log_error("Failed to set I/O file descriptors: %m"); goto finish; } curl = curl_easy_init(); if (!curl) { r = log_oom(); goto finish; } if (curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) { log_error("Failed to turn on location following."); r = -EIO; goto finish; } if (curl_easy_setopt(curl, CURLOPT_PROTOCOLS, arg_protocol == ARG_PROTOCOL_FTP ? CURLPROTO_FTP : arg_protocol == ARG_PROTOCOL_SFTP? CURLPROTO_SFTP: CURLPROTO_HTTP|CURLPROTO_HTTPS) != CURLE_OK) { log_error("Failed to limit protocols to HTTP/HTTPS/FTP/SFTP."); r = -EIO; goto finish; } if (arg_protocol == ARG_PROTOCOL_SFTP) { /* activate the ssh agent. For this to work you need to have ssh-agent running (type set | grep SSH_AGENT to check) */ if (curl_easy_setopt(curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_AGENT) != CURLE_OK) log_error("Failed to turn on ssh agent support, ignoring."); } if (arg_rate_limit_bps > 0) { if (curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, arg_rate_limit_bps) != CURLE_OK) { log_error("Failed to set CURL send speed limit."); r = -EIO; goto finish; } if (curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, arg_rate_limit_bps) != CURLE_OK) { log_error("Failed to set CURL receive speed limit."); r = -EIO; goto finish; } } /* (void) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */ if (archive_url) { r = acquire_file(rr, curl, archive_url, write_archive); if (r < 0) goto finish; if (r == 0) goto flush; r = write_archive_eof(rr); if (r < 0) goto finish; } if (index_url) { r = acquire_file(rr, curl, index_url, write_index); if (r < 0) goto finish; if (r == 0) goto flush; r = write_index_eof(rr); if (r < 0) goto finish; } for (;;) { const char *store_url; CaChunkID id; if (n_stores == 0) /* No stores? Then we did all we could do */ break; r = process_remote(rr, PROCESS_UNTIL_HAVE_REQUEST); if (r == -EPIPE) { r = 0; goto finish; } if (r < 0) goto finish; r = ca_remote_next_request(rr, &id); if (r == -ENODATA) continue; if (r < 0) { log_error_errno(r, "Failed to determine next chunk to get: %m"); goto finish; } current_store = current_store % n_stores; if (wstore_url) store_url = current_store == 0 ? wstore_url : argv[current_store + _CA_REMOTE_ARG_MAX - 1]; else store_url = argv[current_store + _CA_REMOTE_ARG_MAX]; /* current_store++; */ free(url_buffer); url_buffer = chunk_url(store_url, &id); if (!url_buffer) { r = log_oom(); goto finish; } if (curl_easy_setopt(curl, CURLOPT_URL, url_buffer) != CURLE_OK) { log_error("Failed to set CURL URL to: %s", index_url); r = -EIO; goto finish; } if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk) != CURLE_OK) { log_error("Failed to set CURL callback function."); r = -EIO; goto finish; } if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk_buffer) != CURLE_OK) { log_error("Failed to set CURL private data."); r = -EIO; goto finish; } if (arg_rate_limit_bps > 0) { if (curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, arg_rate_limit_bps) != CURLE_OK) { log_error("Failed to set CURL send speed limit."); r = -EIO; goto finish; } if (curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, arg_rate_limit_bps) != CURLE_OK) { log_error("Failed to set CURL receive speed limit."); r = -EIO; goto finish; } } if (arg_verbose) log_info("Acquiring %s...", url_buffer); if (curl_easy_perform(curl) != CURLE_OK) { log_error("Failed to acquire %s", url_buffer); r = -EIO; goto finish; } if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &protocol_status) != CURLE_OK) { log_error("Failed to query response code"); r = -EIO; goto finish; } r = process_remote(rr, PROCESS_UNTIL_CAN_PUT_CHUNK); if (r == -EPIPE) { r = 0; goto finish; } if (r < 0) goto finish; if ((IN_SET(arg_protocol, ARG_PROTOCOL_HTTP, ARG_PROTOCOL_HTTPS) && protocol_status == 200) || (arg_protocol == ARG_PROTOCOL_FTP && (protocol_status >= 200 && protocol_status <= 299))|| (arg_protocol == ARG_PROTOCOL_SFTP && (protocol_status == 0))) { r = ca_remote_put_chunk(rr, &id, CA_CHUNK_COMPRESSED, realloc_buffer_data(&chunk_buffer), realloc_buffer_size(&chunk_buffer)); if (r < 0) { log_error_errno(r, "Failed to write chunk: %m"); goto finish; } } else { if (arg_verbose) log_error("HTTP/FTP/SFTP server failure %li while requesting %s.", protocol_status, url_buffer); r = ca_remote_put_missing(rr, &id); if (r < 0) { log_error_errno(r, "Failed to write missing message: %m"); goto finish; } } realloc_buffer_empty(&chunk_buffer); r = process_remote(rr, PROCESS_UNTIL_WRITTEN); if (r == -EPIPE) { r = 0; goto finish; } if (r < 0) goto finish; } flush: r = process_remote(rr, PROCESS_UNTIL_FINISHED); finish: if (curl) curl_easy_cleanup(curl); return r; } static void help(void) { printf("%s -- casync HTTP helper. Do not execute manually.\n", program_invocation_short_name); } static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "rate-limit-bps", required_argument, NULL, 'l' }, {} }; int c; assert(argc >= 0); assert(argv); if (strstr(argv[0], "https")) arg_protocol = ARG_PROTOCOL_HTTPS; else if (strstr(argv[0], "http")) arg_protocol = ARG_PROTOCOL_HTTP; else if (strstr(argv[0], "sftp")) arg_protocol = ARG_PROTOCOL_SFTP; else if (strstr(argv[0], "ftp")) arg_protocol = ARG_PROTOCOL_FTP; else { log_error("Failed to determine set of protocols to use, refusing."); return -EINVAL; } if (getenv_bool("CASYNC_VERBOSE") > 0) arg_verbose = true; while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) { switch (c) { case 'h': help(); return 0; case 'v': arg_verbose = true; break; case 'l': arg_rate_limit_bps = strtoll(optarg, NULL, 10); break; case '?': return -EINVAL; default: assert(false); } } return 1; } int main(int argc, char* argv[]) { static const struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = SA_RESTART, }; int r; assert_se(sigaction(SIGPIPE, &sa, NULL) >= 0); r = parse_argv(argc, argv); if (r <= 0) goto finish; if (optind >= argc) { log_error("Verb expected."); r = -EINVAL; goto finish; } if (streq(argv[optind], "pull")) r = run(argc - optind, argv + optind); else { log_error("Unknown verb: %s", argv[optind]); r = -EINVAL; } finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } src/casync-tool.c000066400000000000000000004222501322621430500142470ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #include #include #include #include "caformat-util.h" #include "caformat.h" #include "cafuse.h" #include "caindex.h" #include "canbd.h" #include "caprotocol.h" #include "caremote.h" #include "castore.h" #include "casync.h" #include "gc.h" #include "notify.h" #include "parse-util.h" #include "signal-handler.h" #include "util.h" #ifdef HAVE_UDEV #include #include "udev-util.h" #endif static enum arg_what { WHAT_ARCHIVE, WHAT_ARCHIVE_INDEX, WHAT_BLOB, WHAT_BLOB_INDEX, WHAT_DIRECTORY, _WHAT_INVALID = -1, } arg_what = _WHAT_INVALID; static bool arg_verbose = false; static bool arg_dry_run = false; static bool arg_exclude_nodump = true; static bool arg_exclude_submounts = false; static bool arg_reflink = true; static bool arg_hardlink = false; static bool arg_punch_holes = true; static bool arg_delete = true; static bool arg_undo_immutable = false; static bool arg_recursive = true; static bool arg_seed_output = true; static char *arg_store = NULL; static char **arg_extra_stores = NULL; static char **arg_seeds = NULL; static size_t arg_chunk_size_min = 0; static size_t arg_chunk_size_avg = 0; static size_t arg_chunk_size_max = 0; static uint64_t arg_rate_limit_bps = UINT64_MAX; static uint64_t arg_with = 0; static uint64_t arg_without = 0; static uid_t arg_uid_shift = 0, arg_uid_range = 0x10000U; static bool arg_uid_shift_apply = false; static bool arg_mkdir = true; static CaDigestType arg_digest = CA_DIGEST_DEFAULT; static CaCompressionType arg_compression = CA_COMPRESSION_DEFAULT; static void help(void) { printf("%1$s [OPTIONS...] make [ARCHIVE|ARCHIVE_INDEX|BLOB_INDEX] [PATH]\n" "%1$s [OPTIONS...] extract [ARCHIVE|ARCHIVE_INDEX|BLOB_INDEX] [PATH]\n" "%1$s [OPTIONS...] list [ARCHIVE|ARCHIVE_INDEX|DIRECTORY]\n" "%1$s [OPTIONS...] mtree [ARCHIVE|ARCHIVE_INDEX|DIRECTORY]\n" "%1$s [OPTIONS...] stat [ARCHIVE|ARCHIVE_INDEX|DIRECTORY] [PATH]\n" "%1$s [OPTIONS...] digest [ARCHIVE|BLOB|ARCHIVE_INDEX|BLOB_INDEX|DIRECTORY]\n" #if HAVE_FUSE "%1$s [OPTIONS...] mount [ARCHIVE|ARCHIVE_INDEX] PATH\n" #endif "%1$s [OPTIONS...] mkdev [BLOB|BLOB_INDEX] [NODE]\n\n" "Content-Addressable Data Synchronization Tool\n\n" " -h --help Show this help\n" " --version Show brief version information\n" " -v --verbose Show terse status information during runtime\n" " -n --dry-run When garbage collecting, only print what would\n" " be done\n" " --store=PATH The primary chunk store to use\n" " --extra-store=PATH Additional chunk store to look for chunks in\n" " --chunk-size=[MIN:]AVG[:MAX]\n" " The minimal/average/maximum number of bytes in a\n" " chunk\n" " --digest=DIGEST Pick digest algorithm (sha512-256 or sha256)\n" " --compression=COMPRESSION\n" " Pick compression algorithm (zstd, xz or gzip)\n" " --seed=PATH Additional file or directory to use as seed\n" " --rate-limit-bps=LIMIT Maximum bandwidth in bytes/s for remote\n" " communication\n" " --exclude-nodump=no Don't exclude files with chattr(1)'s +d 'nodump'\n" " flag when creating archive\n" " --exclude-submounts=yes Exclude submounts when creating archive\n" " --reflink=no Don't create reflinks from seeds when extracting\n" " --hardlink=yes Create hardlinks from seeds when extracting\n" " --punch-holes=no Don't create sparse files when extracting\n" " --delete=no Don't delete existing files not listed in archive\n" " after extraction\n" " --undo-immutable=yes When removing existing files, undo chattr(1)'s +i\n" " 'immutable' flag when extracting\n" " --seed-output=no Don't implicitly add pre-existing output as seed\n" " when extracting\n" " --recursive=no List non-recursively\n" #if HAVE_FUSE " --mkdir=no Don't automatically create mount directory if it\n" " is missing\n" #endif " --uid-shift=yes|SHIFT Shift UIDs/GIDs\n" " --uid-range=RANGE Restrict UIDs/GIDs to range\n\n" "Input/output selector:\n" " --what=archive Operate on archive file\n" " --what=archive-index Operate on archive index file\n" " --what=blob Operate on blob file\n" " --what=blob-index Operate on blob index file\n" " --what=directory Operate on directory\n\n" " --what=help Print allowed values\n\n" "Archive feature sets:\n" " --with=best Store most accurate information\n" " --with=unix Store UNIX baseline information\n" " --with=fat Store FAT information\n" " --with=chattr Store chattr(1) file attributes\n" " --with=fat-attrs Store FAT file attributes\n" " --with=privileged Store file data that requires privileges to\n" " restore\n" " --with=fuse Store file data that can exposed again via\n" " 'casync mount'\n" " (and similar: --without=fat-attrs, --without=privileged, ...)\n" " --without=all Disable all optional attributes\n\n" "Individual archive features:\n" " --with=16bit-uids Store reduced 16bit UID/GID information\n" " --with=32bit-uids Store full 32bit UID/GID information\n" " --with=user-names Store user and group names\n" " --with=sec-time Store timestamps with 1s granularity\n" " --with=usec-time Store timestamps with 1µs granularity\n" " --with=nsec-time Store timestamps with 1ns granularity\n" " --with=2sec-time Store timestamps with 2s granularity\n" " --with=read-only Store per-file read only flag\n" " --with=permissions Store full per-file UNIX permissions\n" " --with=symlinks Store symbolic links\n" " --with=device-nodes Store block and character device nodes\n" " --with=fifos Store named pipe nodes\n" " --with=sockets Store AF_UNIX file system socket nodes\n" " --with=flag-hidden Store FAT \"hidden\" file flag\n" " --with=flag-system Store FAT \"system\" file flag\n" " --with=flag-archive Store FAT \"archive\" file flag\n" " --with=flag-append Store \"append-only\" file flag\n" " --with=flag-noatime Store \"disable access time\" file flag\n" " --with=flag-compr Store \"enable compression\" file flag\n" " --with=flag-nocow Store \"disable copy-on-write\" file flag\n" " --with=flag-nodump Store \"disable dumping\" file flag\n" " --with=flag-dirsync Store \"synchronous\" directory flag\n" " --with=flag-immutable Store \"immutable\" file flag\n" " --with=flag-sync Store \"synchronous\" file flag\n" " --with=flag-nocomp Store \"disable compression\" file flag\n" " --with=flag-projinherit Store \"project quota inheritance\" flag\n" " --with=subvolume Store btrfs subvolume information\n" " --with=subvolume-ro Store btrfs subvolume read-only property\n" " --with=xattrs Store extended file attributes\n" " --with=acl Store file access control lists\n" " --with=selinux Store SELinux file labels\n" " --with=fcaps Store file capabilities\n" " (and similar: --without=16bit-uids, --without=32bit-uids, ...)\n", program_invocation_short_name); } static void version(void) { printf("%s " PACKAGE_VERSION "\n", program_invocation_short_name); } static int parse_chunk_sizes(const char *v, size_t *ret_min, size_t *ret_avg, size_t *ret_max) { uint64_t a, b, c; char *k; int r; assert(v); assert(ret_min); assert(ret_max); if (streq(v, "auto")) { *ret_min = 0; *ret_avg = 0; *ret_max = 0; return 0; } k = strchr(v, ':'); if (k) { char *j, *p; j = strchr(k+1, ':'); if (!j) { log_error("--chunk-size= requires either a single average chunk size or a triplet of minimum, average and maximum chunk size."); return -EINVAL; } p = strndupa(v, k - v); r = parse_size(p, &a); if (r < 0) return log_error_errno(r, "Can't parse minimum chunk size: %s", v); if (a < CA_CHUNK_SIZE_LIMIT_MIN) { log_error("Minimum chunk size must be >= %zu.", CA_CHUNK_SIZE_LIMIT_MIN); return -ERANGE; } p = strndupa(k + 1, j - k - 1); r = parse_size(p, &b); if (r < 0) return log_error_errno(r, "Can't parse average chunk size: %s", v); if (b < a) { log_error("Average chunk size must be larger than minimum chunk size."); return -EINVAL; } r = parse_size(j + 1, &c); if (r < 0) return log_error_errno(r, "Can't parse maximum chunk size: %s", v); if (c < b) { log_error("Average chunk size must be smaller than maximum chunk size."); return -EINVAL; } if (c > CA_CHUNK_SIZE_LIMIT_MAX) { log_error("Maximum chunk size must be <= %zu.", CA_CHUNK_SIZE_LIMIT_MAX); return -ERANGE; } } else { r = parse_size(v, &b); if (r < 0) return log_error_errno(r, "Can't parse average chunk size: %s", v); if (b < CA_CHUNK_SIZE_LIMIT_MIN) { log_error("Average chunk size must be >= %zu.", CA_CHUNK_SIZE_LIMIT_MIN); return -ERANGE; } if (b > CA_CHUNK_SIZE_LIMIT_MAX) { log_error("Average chunk size must be <= %zu.", CA_CHUNK_SIZE_LIMIT_MAX); return -ERANGE; } a = 0; c = 0; } *ret_min = a; *ret_avg = b; *ret_max = c; return 0; } static int parse_what_selector(const char *arg, enum arg_what *what) { if (streq(arg, "archive")) *what = WHAT_ARCHIVE; else if (streq(arg, "archive-index")) *what = WHAT_ARCHIVE_INDEX; else if (streq(arg, "blob")) *what = WHAT_BLOB; else if (streq(arg, "blob-index")) *what = WHAT_BLOB_INDEX; else if (streq(arg, "directory")) *what = WHAT_DIRECTORY; else if (streq(arg, "help")) { printf("Allowed --what= selectors:\n" "archive-index\n" "blob\n" "blob-index\n" "directory\n"); return 0; } else { log_error("Failed to parse --what= selector: %s", arg); return -EINVAL; } return 1; } static int parse_argv(int argc, char *argv[]) { enum { ARG_STORE = 0x100, ARG_EXTRA_STORE, ARG_CHUNK_SIZE, ARG_SEED, ARG_RATE_LIMIT_BPS, ARG_WITH, ARG_WITHOUT, ARG_WHAT, ARG_EXCLUDE_NODUMP, ARG_EXCLUDE_SUBMOUNTS, ARG_UNDO_IMMUTABLE, ARG_PUNCH_HOLES, ARG_REFLINK, ARG_HARDLINK, ARG_SEED_OUTPUT, ARG_DELETE, ARG_UID_SHIFT, ARG_UID_RANGE, ARG_RECURSIVE, ARG_MKDIR, ARG_DIGEST, ARG_COMPRESSION, ARG_VERSION, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "verbose", no_argument, NULL, 'v' }, { "dry-run", no_argument, NULL, 'n' }, { "store", required_argument, NULL, ARG_STORE }, { "extra-store", required_argument, NULL, ARG_EXTRA_STORE }, { "chunk-size", required_argument, NULL, ARG_CHUNK_SIZE }, { "seed", required_argument, NULL, ARG_SEED }, { "rate-limit-bps", required_argument, NULL, ARG_RATE_LIMIT_BPS }, { "with", required_argument, NULL, ARG_WITH }, { "without", required_argument, NULL, ARG_WITHOUT }, { "what", required_argument, NULL, ARG_WHAT }, { "exclude-nodump", required_argument, NULL, ARG_EXCLUDE_NODUMP }, { "exclude-submounts", required_argument, NULL, ARG_EXCLUDE_SUBMOUNTS }, { "undo-immutable", required_argument, NULL, ARG_UNDO_IMMUTABLE }, { "delete", required_argument, NULL, ARG_DELETE }, { "punch-holes", required_argument, NULL, ARG_PUNCH_HOLES }, { "reflink", required_argument, NULL, ARG_REFLINK }, { "hardlink", required_argument, NULL, ARG_HARDLINK }, { "seed-output", required_argument, NULL, ARG_SEED_OUTPUT }, { "uid-shift", required_argument, NULL, ARG_UID_SHIFT }, { "uid-range", required_argument, NULL, ARG_UID_RANGE }, { "recursive", required_argument, NULL, ARG_RECURSIVE }, { "mkdir", required_argument, NULL, ARG_MKDIR }, { "digest", required_argument, NULL, ARG_DIGEST }, { "compression", required_argument, NULL, ARG_COMPRESSION }, {} }; int c, r; assert(argc >= 0); assert(argv); if (getenv_bool("CASYNC_VERBOSE") > 0) arg_verbose = true; while ((c = getopt_long(argc, argv, "hvn", options, NULL)) >= 0) { switch (c) { case 'h': help(); return 0; case ARG_VERSION: version(); return 0; case 'v': arg_verbose = true; break; case 'n': arg_dry_run = true; break; case ARG_STORE: { char *p; p = strdup(optarg); if (!p) return log_oom(); free(arg_store); arg_store = p; break; } case ARG_EXTRA_STORE: r = strv_extend(&arg_extra_stores, optarg); if (r < 0) return log_oom(); break; case ARG_CHUNK_SIZE: r = parse_chunk_sizes(optarg, &arg_chunk_size_min, &arg_chunk_size_avg, &arg_chunk_size_max); if (r < 0) return r; break; case ARG_SEED: r = strv_extend(&arg_seeds, optarg); if (r < 0) return log_oom(); break; case ARG_RATE_LIMIT_BPS: r = parse_size(optarg, &arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Unable to parse rate limit %s: %m", optarg); if (arg_rate_limit_bps == 0) { log_error("Rate limit size cannot be zero."); return -EINVAL; } break; case ARG_WITH: { uint64_t u; r = ca_with_feature_flags_parse_one(optarg, &u); if (r < 0) { log_error("Failed to parse --with= feature flag: %s", optarg); return -EINVAL; } arg_with |= u; break; } case ARG_WITHOUT: { uint64_t u; r = ca_with_feature_flags_parse_one(optarg, &u); if (r < 0) { log_error("Failed to parse --without= feature flag: %s", optarg); return -EINVAL; } arg_without |= u; break; } case ARG_WHAT: r = parse_what_selector(optarg, &arg_what); if (r <= 0) return r; break; case ARG_EXCLUDE_NODUMP: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --exclude-nodump= parameter: %s", optarg); return r; } arg_exclude_nodump = r; break; case ARG_EXCLUDE_SUBMOUNTS: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --exclude-submounts= parameter: %s", optarg); return r; } arg_exclude_submounts = r; break; case ARG_UNDO_IMMUTABLE: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --undo-immutable= parameter: %s", optarg); return r; } arg_undo_immutable = r; break; case ARG_PUNCH_HOLES: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --punch-holes= parameter: %s", optarg); return r; } arg_punch_holes = r; break; case ARG_REFLINK: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --reflink= parameter: %s", optarg); return r; } arg_reflink = r; break; case ARG_HARDLINK: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --hardlink= parameter: %s", optarg); return r; } arg_hardlink = r; break; case ARG_DELETE: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --delete= parameter: %s", optarg); return r; } arg_delete = r; break; case ARG_SEED_OUTPUT: r = parse_boolean(optarg); if (r < 0) { log_error("Failed to parse --seed-output= parameter: %s", optarg); return r; } arg_seed_output = r; break; case ARG_MKDIR: r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --mkdir= parameter: %s", optarg); arg_mkdir = r; break; case ARG_UID_SHIFT: { uid_t uid; r = parse_uid(optarg, &uid); if (r < 0) { r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --uid-shift= parameter: %s", optarg); arg_uid_shift_apply = r; } else { arg_uid_shift = uid; arg_uid_shift_apply = true; } break; } case ARG_UID_RANGE: { uint64_t u; /* The valid values for the range are 1..0x100000000. However, we store this in a 32bit uid_t, * which requires us to map 0x100000000 to 0. */ r = safe_atou64(optarg, &u); if (r < 0 || u == 0 || u > UINT64_C(0x100000000)) { r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --uid-range= parameter: %s", optarg); arg_uid_shift_apply = r; } else { if (u == UINT64_C(0x100000000)) arg_uid_range = 0; else arg_uid_range = (uid_t) u; arg_uid_shift_apply = true; } break; } case ARG_RECURSIVE: r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --recursive= parameter: %s", optarg); arg_recursive = r; break; case ARG_DIGEST: { CaDigestType t; t = ca_digest_type_from_string(optarg); if (t < 0) return log_error_errno(t, "Failed to parse --digest= parameter: %s", optarg); arg_digest = t; break; } case ARG_COMPRESSION: { CaCompressionType cc; cc = ca_compression_type_from_string(optarg); if (cc < 0) return log_error_errno(cc, "Failed to parse --compression= parameter: %s", optarg); arg_compression = cc; break; } case '?': return -EINVAL; default: assert(false); } } /* Propagate our verbose setting to helpers we fork off */ if (arg_verbose) (void) putenv((char*) "CASYNC_VERBOSE=1"); else unsetenv("CASYNC_VERBOSE"); return 1; } static int set_default_store(const char *index_path) { const char *e; int r; if (arg_store) return 0; e = getenv("CASYNC_STORE"); if (e) /* If the default store is set via an environment variable, use that */ arg_store = strdup(e); else if (index_path) { /* Otherwise, derive it from the index file path */ r = ca_locator_patch_last_component(index_path, "default.castr", &arg_store); if (r < 0) return log_error_errno(r, "Failed to automatically derive store location from index: %m"); } else /* And if we don't know any, then place it in the current directory */ arg_store = strdup("default.castr"); if (!arg_store) return log_oom(); return 1; } static int load_seeds_and_extra_stores(CaSync *s) { char **i; int r; assert(s); STRV_FOREACH(i, arg_extra_stores) { r = ca_sync_add_store_auto(s, *i); if (r < 0) log_error("Failed to add extra store %s, ignoring: %m", *i); } STRV_FOREACH(i, arg_seeds) { r = ca_sync_add_seed_path(s, *i); if (r < 0) log_error("Failed to add seed %s, ignoring: %m", *i); } return 0; } static uint64_t combined_with_flags(uint64_t default_with_flags) { return (arg_with == 0 ? default_with_flags : arg_with) & ~arg_without; } static int load_feature_flags(CaSync *s, uint64_t default_with_flags) { uint64_t flags; int r; assert(s); flags = combined_with_flags(default_with_flags); if (arg_exclude_nodump) flags |= CA_FORMAT_EXCLUDE_NODUMP; if (arg_exclude_submounts) flags |= CA_FORMAT_EXCLUDE_SUBMOUNTS; flags |= ca_feature_flags_from_digest_type(arg_digest); r = ca_sync_set_feature_flags(s, flags); if (r < 0 && r != -ENOTTY) /* only encoder syncs have a feature flags field */ return log_error_errno(r, "Failed to set feature flags: %m"); r = ca_sync_set_feature_flags_mask(s, flags); if (r < 0 && r != -ENOTTY) /* only decoder syncs have a feature flags mask field */ return log_error_errno(r, "Failed to set feature flags mask: %m"); if (arg_uid_shift_apply) { r = ca_sync_set_uid_shift(s, arg_uid_shift); if (r < 0) return log_error_errno(r, "Failed to set UID shift: %m"); r = ca_sync_set_uid_range(s, arg_uid_range); if (r < 0) return log_error_errno(r, "Failed to set UID range: %m"); } r = ca_sync_set_undo_immutable(s, arg_undo_immutable); if (r < 0 && r != -ENOTTY) return log_error_errno(r, "Failed to set undo immutable flag: %m"); r = ca_sync_set_compression_type(s, arg_compression); if (r < 0 && r != -ENOTTY) return log_error_errno(r, "Failed to set compression: %m"); r = ca_sync_set_delete(s, arg_delete); if (r < 0 && r != -ENOTTY) { fprintf(stderr, "Failed to set deletion flag: %s\n", strerror(-r)); return r; } return 0; } static int load_chunk_size(CaSync *s) { uint64_t cavg, cmin, cmax; int r; if (arg_chunk_size_avg != 0) { r = ca_sync_set_chunk_size_avg(s, arg_chunk_size_avg); if (r < 0) return log_error_errno(r, "Failed to set average chunk size to %zu: %m", arg_chunk_size_avg); } if (arg_chunk_size_min != 0) { r = ca_sync_set_chunk_size_min(s, arg_chunk_size_min); if (r < 0) return log_error_errno(r, "Failed to set minimum chunk size to %zu: %m", arg_chunk_size_min); } if (arg_chunk_size_max != 0) { r = ca_sync_set_chunk_size_max(s, arg_chunk_size_max); if (r < 0) return log_error_errno(r, "Failed to set minimum chunk size to %zu: %m", arg_chunk_size_min); } if (!arg_verbose) return 1; r = ca_sync_get_chunk_size_avg(s, &cavg); if (r < 0) return log_error_errno(r, "Failed to read average chunk size: %m"); r = ca_sync_get_chunk_size_min(s, &cmin); if (r < 0) return log_error_errno(r, "Failed to read minimum chunk size: %m"); r = ca_sync_get_chunk_size_max(s, &cmax); if (r < 0) return log_error_errno(r, "Failed to read maximum chunk size: %m"); log_error("Selected chunk sizes: min=%" PRIu64 "..avg=%" PRIu64 "..max=%" PRIu64, cmin, cavg, cmax); return 1; } static int verbose_print_feature_flags(CaSync *s) { static bool printed = false; uint64_t flags; _cleanup_free_ char *t = NULL; int r; assert(s); if (!arg_verbose) return 0; if (printed) return 0; r = ca_sync_get_feature_flags(s, &flags); if (r == -ENODATA) /* we don't know them yet? */ return 0; if (r < 0) return log_error_errno(r, "Failed to query feature flags: %m"); r = ca_with_feature_flags_format(flags, &t); if (r < 0) return log_error_errno(r, "Failed to format feature flags: %m"); log_info("Using feature flags: %s", strnone(t)); log_info("Excluding files with chattr(1) -d flag: %s", yes_no(flags & CA_FORMAT_EXCLUDE_NODUMP)); log_info("Excluding submounts: %s", yes_no(flags & CA_FORMAT_EXCLUDE_SUBMOUNTS)); log_info("Digest algorithm: %s", ca_digest_type_to_string(ca_feature_flags_to_digest_type(flags))); printed = true; return 0; } static int verbose_print_path(CaSync *s, const char *verb) { _cleanup_free_ char *path = NULL; int r; if (!arg_verbose) return 0; r = ca_sync_current_path(s, &path); if (r == -ENOTDIR) /* Root isn't a directory */ return 0; if (r < 0) return log_error_errno(r, "Failed to query current path: %m"); log_info("%s%s%s", verb ?: "", verb ? " " : "", isempty(path) ? "./" : path); return 1; } static int verbose_print_done_make(CaSync *s) { uint64_t n_chunks = UINT64_MAX, size = UINT64_MAX, n_reused = UINT64_MAX, covering; char buffer[FORMAT_BYTES_MAX]; int r; assert(s); if (!arg_verbose) return 0; r = ca_sync_get_covering_feature_flags(s, &covering); if (r != -ENODATA) { uint64_t selected, too_much; if (r < 0) return log_error_errno(r, "Failed to determine covering flags: %m"); r = ca_sync_get_feature_flags(s, &selected); if (r < 0) return log_error_errno(r, "Failed to determine used flags: %m"); too_much = selected & ~covering; if (too_much != 0) { _cleanup_free_ char *t = NULL; r = ca_with_feature_flags_format(too_much, &t); if (r < 0) return log_error_errno(r, "Failed to format feature flags: %m"); log_error("Selected feature flags not actually applicable to backing file systems: %s", strnone(t)); } } r = ca_sync_current_archive_chunks(s, &n_chunks); if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to determine number of chunks: %m"); r = ca_sync_current_archive_reused_chunks(s, &n_reused); if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to determine number of reused chunks: %m"); r = ca_sync_current_archive_offset(s, &size); if (r < 0 && r != -ENODATA) return log_error_errno(r, "Failed to determine archive size: %m"); if (size != UINT64_MAX) log_info("Archive size: %s", format_bytes(buffer, sizeof(buffer), size)); if (n_chunks != UINT64_MAX) log_info("Number of chunks: %" PRIu64, n_chunks); if (n_reused != UINT64_MAX) { if (n_chunks != UINT64_MAX && n_chunks > 0) log_info("Reused chunks: %"PRIu64 " (%"PRIu64 "%%)", n_reused, n_reused * 100U / n_chunks); else log_info("Reused chunks: %" PRIu64, n_reused); } if (size != UINT64_MAX && n_chunks != UINT64_MAX) log_error("Effective average chunk size: %s", format_bytes(buffer, sizeof(buffer), size / n_chunks)); return 1; } static int verbose_print_done_extract(CaSync *s) { char buffer[FORMAT_BYTES_MAX]; uint64_t n_bytes, n_requests; int r; if (!arg_verbose) return 0; r = ca_sync_get_punch_holes_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of punch holes bytes: %m"); log_error("Zero bytes written as sparse files: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } r = ca_sync_get_reflink_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of reflink bytes: %m"); log_error("Bytes cloned through reflinks: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } r = ca_sync_get_hardlink_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of hardlink bytes: %m"); log_error("Bytes cloned through hardlinks: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } r = ca_sync_get_local_requests(s, &n_requests); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of successful local store requests: %m"); log_error("Chunk requests fulfilled from local store: %" PRIu64, n_requests); } r = ca_sync_get_local_request_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine size of successful local store requests: %m"); log_error("Bytes used from local store: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } r = ca_sync_get_seed_requests(s, &n_requests); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of successful local seed requests: %m"); log_error("Chunk requests fulfilled from local seed: %" PRIu64, n_requests); } r = ca_sync_get_seed_request_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine size of successful local seed requests: %m"); log_error("Bytes used from local seed: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } r = ca_sync_get_remote_requests(s, &n_requests); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine number of successful remote store requests: %m"); log_error("Chunk requests fulfilled from remote store: %" PRIu64, n_requests); } r = ca_sync_get_remote_request_bytes(s, &n_bytes); if (!IN_SET(r, -ENODATA, -ENOTTY)) { if (r < 0) return log_error_errno(r, "Failed to determine size of successful remote store requests: %m"); log_error("Bytes used from remote store: %s", format_bytes(buffer, sizeof(buffer), n_bytes)); } return 1; } static int process_step_generic(CaSync *s, int step, bool quit_ok) { int r; assert(s); switch (step) { case CA_SYNC_FINISHED: case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_FOUND: return 0; case CA_SYNC_NEXT_FILE: return verbose_print_path(s, "Processing"); case CA_SYNC_DONE_FILE: return verbose_print_path(s, "Processed"); case CA_SYNC_SEED_NEXT_FILE: return verbose_print_path(s, "Seeding"); case CA_SYNC_SEED_DONE_FILE: return verbose_print_path(s, "Seeded"); case CA_SYNC_POLL: r = sync_poll_sigset(s); if (r == -ESHUTDOWN) { if (!quit_ok) log_error("Got exit signal, quitting."); } else if (r < 0) log_error_errno(r, "Failed to poll synchronizer: %m"); return r; case CA_SYNC_NOT_FOUND: log_error("Seek path not available in archive."); return -ENOENT; } assert(false); } static int verb_make(int argc, char *argv[]) { typedef enum MakeOperation { MAKE_ARCHIVE, MAKE_ARCHIVE_INDEX, MAKE_BLOB_INDEX, _MAKE_OPERATION_INVALID = -1, } MakeOperation; MakeOperation operation = _MAKE_OPERATION_INVALID; _cleanup_free_ char *input = NULL, *output = NULL; _cleanup_(safe_close_nonstdp) int input_fd = -1; int r; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; struct stat st; if (argc > 3) { log_error("A pair of output and input path/URL expected."); return -EINVAL; } if (argc > 1) { output = ca_strip_file_url(argv[1]); if (!output) return log_oom(); } if (argc > 2) { input = ca_strip_file_url(argv[2]); if (!input) return log_oom(); } if (arg_what == WHAT_ARCHIVE) operation = MAKE_ARCHIVE; else if (arg_what == WHAT_ARCHIVE_INDEX) operation = MAKE_ARCHIVE_INDEX; else if (arg_what == WHAT_BLOB_INDEX) operation = MAKE_BLOB_INDEX; else if (arg_what != _WHAT_INVALID) { log_error("\"make\" operation may only be combined with --what=archive, --what=archive-index or --what=blob-index."); return -EINVAL; } if (operation == _MAKE_OPERATION_INVALID && output && !streq(output, "-")) { if (ca_locator_has_suffix(output, ".catar")) operation = MAKE_ARCHIVE; else if (ca_locator_has_suffix(output, ".caidx")) operation = MAKE_ARCHIVE_INDEX; else if (ca_locator_has_suffix(output, ".caibx")) operation = MAKE_BLOB_INDEX; else { log_error("File to create does not have valid suffix, refusing. (May be one of: .catar, .caidx, .caibx)"); return -EINVAL; } } if (!input && IN_SET(operation, MAKE_ARCHIVE, MAKE_ARCHIVE_INDEX)) { input = strdup("."); if (!input) return log_oom(); } if (!input || streq(input, "-")) input_fd = STDIN_FILENO; else { CaLocatorClass input_class; input_class = ca_classify_locator(input); if (input_class < 0) { log_error("Failed to determine class of locator: %s", input); return -EINVAL; } if (input_class != CA_LOCATOR_PATH) { log_error("Input must be local path: %s", input); return -EINVAL; } input_fd = open(input, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (input_fd < 0) return log_error_errno(errno, "Failed to open %s: %m", input); } if (fstat(input_fd, &st) < 0) return log_error_errno(errno, "Failed to stat input: %m"); if (S_ISDIR(st.st_mode)) { if (operation == _MAKE_OPERATION_INVALID) operation = MAKE_ARCHIVE; else if (!IN_SET(operation, MAKE_ARCHIVE, MAKE_ARCHIVE_INDEX)) { log_error("Input is a directory, but attempted to make blob index. Refusing."); return -EINVAL; } } else if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) { if (operation == _MAKE_OPERATION_INVALID) operation = MAKE_BLOB_INDEX; else if (operation != MAKE_BLOB_INDEX) { log_error("Input is a regular file or block device, but attempted to make a directory archive. Refusing."); return -EINVAL; } } else { log_error("Input is a neither a directory, a regular file, nor a block device. Refusing."); return -EINVAL; } if (streq_ptr(output, "-")) output = mfree(output); if (operation == _MAKE_OPERATION_INVALID) { log_error("Failed to determine what to make. Use --what=archive, --what=archive-index or --what=blob-index."); return -EINVAL; } if (IN_SET(operation, MAKE_ARCHIVE_INDEX, MAKE_BLOB_INDEX)) { r = set_default_store(output); if (r < 0) return r; } s = ca_sync_new_encode(); if (!s) return log_oom(); r = load_chunk_size(s); if (r < 0) return r; if (arg_rate_limit_bps != UINT64_MAX) { r = ca_sync_set_rate_limit_bps(s, arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Failed to set rate limit: %m"); } r = ca_sync_set_base_fd(s, input_fd); if (r < 0) return log_error_errno(r, "Failed to set sync base: %m"); input_fd = -1; if (output) { r = ca_sync_set_make_mode(s, st.st_mode & 0666); if (r < 0) return log_error_errno(r, "Failed to set make permission mode: %m"); } if (operation == MAKE_ARCHIVE) { if (output) r = ca_sync_set_archive_auto(s, output); else r = ca_sync_set_archive_fd(s, STDOUT_FILENO); if (r < 0) return log_error_errno(r, "Failed to set sync archive: %m"); } else { if (output) r = ca_sync_set_index_auto(s, output); else r = ca_sync_set_index_fd(s, STDOUT_FILENO); if (r < 0) return log_error_errno(r, "Failed to set sync index: %m"); } if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) return log_error_errno(r, "Failed to set store: %m"); } r = load_feature_flags(s, operation == MAKE_BLOB_INDEX ? 0 : CA_FORMAT_WITH_MASK); if (r < 0) return r; r = ca_sync_enable_archive_digest(s, true); if (r < 0) return log_error_errno(r, "Failed to enable archive digest: %m"); (void) send_notify("READY=1"); for (;;) { if (quit) { log_info("Got exit signal, quitting."); return -ESHUTDOWN; } r = ca_sync_step(s); if (r < 0) return log_error_errno(r, "Failed to run synchronizer: %m"); switch (r) { case CA_SYNC_FINISHED: { CaChunkID digest; char t[CA_CHUNK_ID_FORMAT_MAX]; verbose_print_done_make(s); assert_se(ca_sync_get_archive_digest(s, &digest) >= 0); printf("%s\n", ca_chunk_id_format(&digest, t)); return 0; } case CA_SYNC_NEXT_FILE: r = verbose_print_path(s, "Packing"); if (r < 0) return r; break; case CA_SYNC_DONE_FILE: r = verbose_print_path(s, "Packed"); if (r < 0) return r; break; case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_POLL: r = process_step_generic(s, r, false); if (r < 0) return r; break; case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: default: assert(false); } verbose_print_feature_flags(s); if (arg_verbose) progress(); } return r; } static const char *normalize_seek_path(const char *p) { /* Normalizes the seek path. Specifically, if the seek path is specified as root directory or empty, we'll * simply suppress it entirely. */ if (!p) return p; p += strspn(p, "/"); if (isempty(p)) return NULL; return p; } static int verb_extract(int argc, char *argv[]) { typedef enum ExtractOperation { EXTRACT_ARCHIVE, EXTRACT_ARCHIVE_INDEX, EXTRACT_BLOB_INDEX, _EXTRACT_OPERATION_INVALID = -1, } ExtractOperation; ExtractOperation operation = _EXTRACT_OPERATION_INVALID; int r; _cleanup_(safe_close_nonstdp) int output_fd = -1, input_fd = -1; _cleanup_free_ char *input = NULL, *output = NULL; const char *seek_path = NULL; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; if (argc > 4) { log_error("Input path/URL, output path, and subtree path expected."); return -EINVAL; } if (argc > 1) { input = ca_strip_file_url(argv[1]); if (!input) return log_oom(); } if (argc > 2) { output = ca_strip_file_url(argv[2]); if (!output) return log_oom(); } if (argc > 3) seek_path = argv[3]; if (arg_what == WHAT_ARCHIVE) operation = EXTRACT_ARCHIVE; else if (arg_what == WHAT_ARCHIVE_INDEX) operation = EXTRACT_ARCHIVE_INDEX; else if (arg_what == WHAT_BLOB_INDEX) operation = EXTRACT_BLOB_INDEX; else if (arg_what != _WHAT_INVALID) { log_error("\"extract\" operation may only be combined with --what=archive, --what=archive-index, --what=blob-index."); return -EINVAL; } if (operation == _EXTRACT_OPERATION_INVALID && input && !streq(input, "-")) { if (ca_locator_has_suffix(input, ".catar")) operation = EXTRACT_ARCHIVE; else if (ca_locator_has_suffix(input, ".caidx")) operation = EXTRACT_ARCHIVE_INDEX; else if (ca_locator_has_suffix(input, ".caibx")) operation = EXTRACT_BLOB_INDEX; else { log_error("File to read from does not have valid suffix, refusing. (May be one of: .catar, .caidx, .caibx)"); return -EINVAL; } } if (!input || streq(input, "-")) input_fd = STDIN_FILENO; if (!output && IN_SET(operation, EXTRACT_ARCHIVE, EXTRACT_ARCHIVE_INDEX)) { output = strdup("."); if (!output) return log_oom(); } if (!output || streq(output, "-")) { output_fd = STDOUT_FILENO; output = NULL; } else { CaLocatorClass output_class; output_class = ca_classify_locator(output); if (output_class < 0) { log_error("Failed to determine locator class: %s", output); return -EINVAL; } if (output_class != CA_LOCATOR_PATH) { log_error("Output must be local path: %s", output); return -EINVAL; } output_fd = open(output, O_CLOEXEC|O_WRONLY|O_NOCTTY); if (output_fd < 0 && errno == EISDIR) output_fd = open(output, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); if (output_fd < 0 && errno != ENOENT) return log_error_errno(errno, "Failed to open %s: %m", output); } if (output_fd >= 0) { struct stat st; if (fstat(output_fd, &st) < 0) return log_error_errno(errno, "Failed to stat output: %m"); if (S_ISDIR(st.st_mode)) { if (operation == _EXTRACT_OPERATION_INVALID) operation = EXTRACT_ARCHIVE_INDEX; else if (!IN_SET(operation, EXTRACT_ARCHIVE, EXTRACT_ARCHIVE_INDEX)) { log_error("Output is a directory, but attempted to extract blob index. Refusing."); return -EINVAL; } } else if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) { if (operation == _EXTRACT_OPERATION_INVALID) operation = EXTRACT_BLOB_INDEX; else if (operation != EXTRACT_BLOB_INDEX) { log_error("Output is a regular file or block device, but attempted to extract an archive."); return -EINVAL; } } else { log_error("Output is neither a directory, a regular file, nor a block device. Refusing."); return -EINVAL; } } if (operation == _EXTRACT_OPERATION_INVALID) { log_error("Couldn't figure out what to extract. Refusing. Use --what=archive, --what=archive-index or --what=blob-index."); return -EINVAL; } if (!IN_SET(operation, EXTRACT_ARCHIVE, EXTRACT_ARCHIVE_INDEX) && seek_path) { log_error("Subtree path only supported when extracting archive or archive index."); return -EINVAL; } seek_path = normalize_seek_path(seek_path); s = ca_sync_new_decode(); if (!s) return log_oom(); if (IN_SET(operation, EXTRACT_ARCHIVE_INDEX, EXTRACT_BLOB_INDEX)) { r = set_default_store(input); if (r < 0) return r; if (arg_seed_output) { r = ca_sync_add_seed_path(s, output); if (r < 0 && r != -ENOENT) log_error_errno(r, "Failed to add existing file as seed %s, ignoring: %m", output); } } if (arg_rate_limit_bps != UINT64_MAX) { r = ca_sync_set_rate_limit_bps(s, arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Failed to set rate limit: %m"); } if (seek_path) { if (output_fd >= 0) r = ca_sync_set_boundary_fd(s, output_fd); else r = ca_sync_set_boundary_path(s, output); } else { if (output_fd >= 0) r = ca_sync_set_base_fd(s, output_fd); else { r = ca_sync_set_base_mode(s, IN_SET(operation, EXTRACT_ARCHIVE, EXTRACT_ARCHIVE_INDEX) ? S_IFDIR : S_IFREG); if (r < 0) return log_error_errno(r, "Failed to set base mode to directory: %m"); r = ca_sync_set_base_path(s, output); } } if (r < 0) return log_error_errno(r, "Failed to set sync base: %m"); output_fd = -1; if (operation == EXTRACT_ARCHIVE) { if (input_fd >= 0) r = ca_sync_set_archive_fd(s, input_fd); else r = ca_sync_set_archive_auto(s, input); if (r < 0) return log_error_errno(r, "Failed to set sync archive: %m"); } else { if (input_fd >= 0) r = ca_sync_set_index_fd(s, input_fd); else r = ca_sync_set_index_auto(s, input); if (r < 0) return log_error_errno(r, "Failed to set sync index: %m"); } input_fd = -1; if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) return log_error_errno(r, "Failed to set store: %m"); } r = load_seeds_and_extra_stores(s); if (r < 0) return r; r = load_feature_flags(s, CA_FORMAT_WITH_MASK); if (r < 0) return r; r = ca_sync_set_punch_holes(s, arg_punch_holes); if (r < 0) return log_error_errno(r, "Failed to configure hole punching: %m"); r = ca_sync_set_reflink(s, arg_reflink); if (r < 0) return log_error_errno(r, "Failed to configure reflinking: %m"); r = ca_sync_set_hardlink(s, arg_hardlink); if (r < 0) return log_error_errno(r, "Failed to configure hardlinking: %m"); if (seek_path) { r = ca_sync_seek_path(s, seek_path); if (r < 0) return log_error_errno(r, "Failed to seek to %s: %m", seek_path); } (void) send_notify("READY=1"); for (;;) { if (quit) { log_error("Got exit signal, quitting."); return -ESHUTDOWN; } r = ca_sync_step(s); if (r == -ENOMEDIUM) return log_error_errno(r, "File, URL or resource not found."); if (r < 0) return log_error_errno(r, "Failed to run synchronizer: %m"); switch (r) { case CA_SYNC_FINISHED: verbose_print_done_extract(s); return 0; case CA_SYNC_NEXT_FILE: r = verbose_print_path(s, "Extracting"); if (r < 0) return r; break; case CA_SYNC_DONE_FILE: r = verbose_print_path(s, "Extracted"); if (r < 0) return r; break; case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_POLL: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: r = process_step_generic(s, r, false); if (r < 0) return r; break; default: assert(false); } verbose_print_feature_flags(s); if (arg_verbose) progress(); } return r; } static int mtree_escape_full(const char *p, size_t l, char **ret) { const char *a; char *n, *b; assert(p); assert(ret); if (l == (size_t) -1) l = strlen(p); n = new(char, l*4+1); if (!n) return -ENOMEM; for (a = p, b = n; a < p + l; a++) { if ((uint8_t) *a <= (uint8_t) ' ' || (uint8_t) *a >= 127U || IN_SET(*a, '\\', '#')) { *(b++) = '\\'; *(b++) = octchar((uint8_t) *a / 64U); *(b++) = octchar(((uint8_t) *a / 8U) % 8U); *(b++) = octchar((uint8_t) *a % 8U); } else *(b++) = *a; } *b = 0; *ret = n; return 0; } static int mtree_escape(const char *p, char **ret) { return mtree_escape_full(p, (size_t) -1, ret); } static int list_one_file(const char *arg0, CaSync *s, bool *toplevel_shown) { _cleanup_free_ char *path = NULL; mode_t mode; int r; r = ca_sync_current_mode(s, &mode); if (r < 0) return log_error_errno(r, "Failed to query current mode: %m"); r = ca_sync_current_path(s, &path); if (r < 0) return log_error_errno(r, "Failed to query current path: %m"); if (streq(arg0, "list")) { char ls_mode[LS_FORMAT_MODE_MAX]; printf("%s %s\n", ls_format_mode(mode, ls_mode), path); if (!arg_recursive && *toplevel_shown) { r = ca_sync_seek_next_sibling(s); if (r < 0) return log_error_errno(r, "Failed to seek to next sibling: %m"); } *toplevel_shown = true; } else if (streq(arg0, "mtree")) { const char *target = NULL, *user = NULL, *group = NULL; uint64_t mtime = UINT64_MAX, size = UINT64_MAX; uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; dev_t rdev = (dev_t) -1; char *escaped; (void) ca_sync_current_target(s, &target); (void) ca_sync_current_mtime(s, &mtime); (void) ca_sync_current_size(s, &size); (void) ca_sync_current_uid(s, &uid); (void) ca_sync_current_gid(s, &gid); (void) ca_sync_current_user(s, &user); (void) ca_sync_current_group(s, &group); (void) ca_sync_current_rdev(s, &rdev); r = mtree_escape(path, &escaped); if (r < 0) return log_oom(); fputs(isempty(escaped) ? "." : escaped, stdout); free(escaped); if (S_ISLNK(mode)) fputs(" type=link", stdout); else if (S_ISDIR(mode)) fputs(" type=dir", stdout); else if (S_ISREG(mode)) fputs(" type=file", stdout); else if (S_ISSOCK(mode)) fputs(" type=socket", stdout); else if (S_ISCHR(mode)) fputs(" type=char", stdout); else if (S_ISBLK(mode)) fputs(" type=block", stdout); else if (S_ISFIFO(mode)) fputs(" type=fifo", stdout); printf(" mode=%04o", mode & 07777); if (size != UINT64_MAX) printf(" size=%" PRIu64, size); if (target) { if (mtree_escape(target, &escaped) < 0) return log_oom(); printf(" link=%s", escaped); free(escaped); } if (rdev != (dev_t) -1) printf(" device=linux,%" PRIu32 ",%" PRIu32, (uint32_t) major(rdev), (uint32_t) minor(rdev)); if (uid_is_valid(uid)) printf(" uid=" UID_FMT, uid); if (uid_is_valid(gid)) printf(" gid=" GID_FMT, gid); if (user) { if (mtree_escape(user, &escaped) < 0) return log_oom(); printf(" uname=%s", escaped); free(escaped); } if (group) { if (mtree_escape(group, &escaped) < 0) return log_oom(); printf(" gname=%s", escaped); free(escaped); } if (mtime != UINT64_MAX) printf(" time=%" PRIu64 ".%09" PRIu64, mtime / UINT64_C(1000000000), mtime % UINT64_C(1000000000)); if (!S_ISREG(mode)) /* End this in a newline — unless this is a regular file, * in which case we'll print the payload checksum shortly */ putchar('\n'); } else { const char *target = NULL, *user = NULL, *group = NULL; uint64_t mtime = UINT64_MAX, size = UINT64_MAX, offset = UINT64_MAX; char ls_mode[LS_FORMAT_MODE_MAX], ls_flags[LS_FORMAT_CHATTR_MAX], ls_fat_attrs[LS_FORMAT_FAT_ATTRS_MAX]; uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; dev_t rdev = (dev_t) -1; unsigned flags = (unsigned) -1; uint32_t fat_attrs = (uint32_t) -1; char *escaped; const char *xname; const void *xvalue; size_t xsize; assert(streq(arg0, "stat")); (void) ca_sync_current_target(s, &target); (void) ca_sync_current_mtime(s, &mtime); (void) ca_sync_current_size(s, &size); (void) ca_sync_current_uid(s, &uid); (void) ca_sync_current_gid(s, &gid); (void) ca_sync_current_user(s, &user); (void) ca_sync_current_group(s, &group); (void) ca_sync_current_rdev(s, &rdev); (void) ca_sync_current_chattr(s, &flags); (void) ca_sync_current_fat_attrs(s, &fat_attrs); (void) ca_sync_current_archive_offset(s, &offset); if (mtree_escape(path, &escaped) < 0) return log_oom(); printf(" File: %s\n" " Mode: %s\n", isempty(escaped) ? "." : escaped, strna(ls_format_mode(mode, ls_mode))); escaped = mfree(escaped); if (flags != (unsigned) -1) printf("FileAttr: %s\n", strna(ls_format_chattr(flags, ls_flags))); if (fat_attrs != (uint32_t) -1) printf(" FATAttr: %s\n", strna(ls_format_fat_attrs(fat_attrs, ls_fat_attrs))); if (offset != UINT64_MAX) printf(" Offset: %" PRIu64 "\n", offset); if (mtime != UINT64_MAX) { char d[128]; time_t t; struct tm tm; t = (time_t) (mtime / UINT64_C(1000000000)); if (localtime_r(&t, &tm) && strftime(d, sizeof(d), "%Y-%m-%d %H:%M:%S", &tm) > 0) printf(" Time: %s.%09" PRIu64"\n", d, mtime % UINT64_C(1000000000)); } if (size != UINT64_MAX) printf(" Size: %" PRIu64 "\n", size); if (uid_is_valid(uid) || user) { printf(" User: "); if (uid == 0) user = "root"; if (user) if (mtree_escape(user, &escaped) < 0) return log_oom(); if (uid_is_valid(uid) && user) printf("%s (" UID_FMT ")\n", escaped, uid); else if (uid_is_valid(uid)) printf(UID_FMT "\n", uid); else printf("%s\n", escaped); escaped = mfree(escaped); } if (gid_is_valid(gid) || group) { printf(" Group: "); if (gid == 0) group = "root"; if (group) if (mtree_escape(group, &escaped) < 0) return log_oom(); if (gid_is_valid(gid) && group) printf("%s (" GID_FMT ")\n", escaped, gid); else if (gid_is_valid(gid)) printf(GID_FMT "\n", gid); else printf("%s\n", escaped); escaped = mfree(escaped); } if (target) { if (mtree_escape(target, &escaped) < 0) return log_oom(); printf(" Target: %s\n", escaped); escaped = mfree(escaped); } if (rdev != (dev_t) -1) printf(" Device: %lu:%lu\n", (unsigned long) major(rdev), (unsigned long) minor(rdev)); r = ca_sync_current_xattr(s, CA_ITERATE_FIRST, &xname, &xvalue, &xsize); for (;;) { _cleanup_free_ char *n = NULL, *v = NULL; if (r < 0) return log_error_errno(r, "Failed to enumerate extended attributes: %m"); if (r == 0) break; if (mtree_escape(xname, &n) < 0) return log_oom(); if (mtree_escape_full(xvalue, xsize, &v) < 0) return log_oom(); printf(" XAttr: %s → %s\n", n, v); r = ca_sync_current_xattr(s, CA_ITERATE_NEXT, &xname, &xvalue, &xsize); } if (S_ISDIR(mode)) /* If this is a directory, we are done now. Otherwise, continue * so that we can show the payload and hardlink digests */ return 0; } return 0; } static int verb_list(int argc, char *argv[]) { typedef enum ListOperation { LIST_ARCHIVE, LIST_ARCHIVE_INDEX, LIST_DIRECTORY, _LIST_OPERATION_INVALID = -1 } ListOperation; ListOperation operation = _LIST_OPERATION_INVALID; const char *seek_path = NULL; int r; _cleanup_(safe_close_nonstdp) int input_fd = -1; _cleanup_free_ char *input = NULL; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; bool toplevel_shown = false; if (argc > 3) { log_error("Input path/URL and subtree path expected."); return -EINVAL; } if (argc > 1) { input = ca_strip_file_url(argv[1]); if (!input) return log_oom(); } if (argc > 2) seek_path = argv[2]; if (arg_what == WHAT_ARCHIVE) operation = LIST_ARCHIVE; else if (arg_what == WHAT_ARCHIVE_INDEX) operation = LIST_ARCHIVE_INDEX; else if (arg_what == WHAT_DIRECTORY) operation = LIST_DIRECTORY; else if (arg_what != _WHAT_INVALID) { log_error("\"%s\" operation may only be combined with --what=archive, archive-index, or directory.", argv[0]); return -EINVAL; } if (operation == _LIST_OPERATION_INVALID && input && !streq(input, "-")) { if (ca_locator_has_suffix(input, ".catar")) operation = LIST_ARCHIVE; else if (ca_locator_has_suffix(input, ".caidx")) operation = LIST_ARCHIVE_INDEX; } if (!input && IN_SET(operation, LIST_DIRECTORY, _LIST_OPERATION_INVALID)) { input = strdup("."); if (!input) return log_oom(); } if (!input || streq(input, "-")) input_fd = STDIN_FILENO; else { CaLocatorClass input_class = _CA_LOCATOR_CLASS_INVALID; input_class = ca_classify_locator(input); if (input_class < 0) { log_error("Failed to determine type of locator: %s", input); return -EINVAL; } if (operation == LIST_DIRECTORY && input_class != CA_LOCATOR_PATH) { log_error("Input must be local path: %s", input); return -EINVAL; } if (input_class == CA_LOCATOR_PATH) { input_fd = open(input, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (input_fd < 0) return log_error_errno(errno, "Failed to open \"%s\": %m", input); } } if (input_fd >= 0) { struct stat st; if (fstat(input_fd, &st) < 0) return log_error_errno(errno, "Failed to stat input: %m"); if (S_ISDIR(st.st_mode)) { if (operation == _LIST_OPERATION_INVALID) operation = LIST_DIRECTORY; else if (operation != LIST_DIRECTORY) { log_error("Input is a directory, but attempted to list archive or index."); return -EINVAL; } } else if (S_ISREG(st.st_mode)) { if (operation == _LIST_OPERATION_INVALID) operation = LIST_ARCHIVE; else if (!IN_SET(operation, LIST_ARCHIVE, LIST_ARCHIVE_INDEX)) { log_error("Input is a regular file, but attempted to list it as directory."); return -EINVAL; } } else { log_error("Input is neither a file or directory. Refusing."); return -EINVAL; } } if (streq_ptr(input, "-")) input = mfree(input); if (operation == _LIST_OPERATION_INVALID) { log_error("Failed to determine what to list. Use --what=archive, archive-index, or directory."); return -EINVAL; } if (!IN_SET(operation, LIST_ARCHIVE, LIST_ARCHIVE_INDEX) && seek_path) { log_error("Subtree path only supported when listing archive or archive index."); return -EINVAL; } seek_path = normalize_seek_path(seek_path); if (operation == LIST_ARCHIVE_INDEX) { r = set_default_store(input); if (r < 0) return r; } if (operation == LIST_DIRECTORY) s = ca_sync_new_encode(); else s = ca_sync_new_decode(); if (!s) return log_oom(); r = load_chunk_size(s); if (r < 0) return r; if (operation == LIST_ARCHIVE) { if (input_fd >= 0) r = ca_sync_set_archive_fd(s, input_fd); else r = ca_sync_set_archive_auto(s, input); } else if (operation == LIST_ARCHIVE_INDEX) { if (input_fd >= 0) r = ca_sync_set_index_fd(s, input_fd); else r = ca_sync_set_index_auto(s, input); } else if (operation == LIST_DIRECTORY) r = ca_sync_set_base_fd(s, input_fd); else assert(false); if (r < 0) return log_error_errno(r, "Failed to set sync input: %m"); input_fd = -1; if (operation != LIST_DIRECTORY) { r = ca_sync_set_base_mode(s, S_IFDIR); if (r < 0) return log_error_errno(r, "Failed to set base mode to directory: %m"); } if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) return log_error_errno(r, "Failed to set store: %m"); } r = load_seeds_and_extra_stores(s); if (r < 0) return r; r = load_feature_flags(s, CA_FORMAT_WITH_MASK); if (r < 0) return r; if (operation != LIST_DIRECTORY) { r = ca_sync_set_payload(s, false); if (r < 0) return log_error_errno(r, "Failed to enable skipping over payload: %m"); } if (seek_path) { r = ca_sync_seek_path(s, seek_path); if (r < 0) return log_error_errno(r, "Failed to seek to %s: %m", seek_path); } if (STR_IN_SET(argv[0], "mtree", "stat")) { r = ca_sync_enable_payload_digest(s, true); if (r < 0) return log_error_errno(r, "Failed to enable payload digest: %m"); } if (streq(argv[0], "stat")) { r = ca_sync_enable_hardlink_digest(s, true); if (r < 0) return log_error_errno(r, "Failed to enable hardlink digest: %m"); } (void) send_notify("READY=1"); for (;;) { if (quit) { log_info("Got exit signal, quitting."); return -ESHUTDOWN; } r = ca_sync_step(s); if (r == -ENOMEDIUM) return log_error_errno(r, "File, URL or resource not found."); if (r < 0) return log_error_errno(r, "Failed to run synchronizer: %m"); switch (r) { case CA_SYNC_FINISHED: return 0; case CA_SYNC_NEXT_FILE: { r = list_one_file(argv[0], s, &toplevel_shown); if (r < 0) return r; break; } case CA_SYNC_DONE_FILE: { mode_t mode; r = ca_sync_current_mode(s, &mode); if (r < 0) return log_error_errno(r, "Failed to query current mode: %m"); if (streq(argv[0], "mtree") && S_ISREG(mode)) { static const char * const table[_CA_DIGEST_TYPE_MAX] = { [CA_DIGEST_SHA256] = "sha256digest", [CA_DIGEST_SHA512_256] = "sha512256digest", }; CaChunkID digest; char v[CA_CHUNK_ID_FORMAT_MAX]; r = ca_sync_get_payload_digest(s, &digest); if (r < 0) { log_error("Failed to read digest."); return -EINVAL; } printf(" %s=%s\n", table[arg_digest], ca_chunk_id_format(&digest, v)); } else if (streq(argv[0], "stat") && !S_ISDIR(mode)) { CaChunkID payload_digest, hardlink_digest; char v[CA_CHUNK_ID_FORMAT_MAX]; r = ca_sync_get_payload_digest(s, &payload_digest); if (r >= 0) printf(" Digest: %s\n", ca_chunk_id_format(&payload_digest, v)); r = ca_sync_get_hardlink_digest(s, &hardlink_digest); if (r >= 0) printf("HLDigest: %s\n", ca_chunk_id_format(&hardlink_digest, v)); return 0; } break; } case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_POLL: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: r = process_step_generic(s, r, false); if (r < 0) return r; break; default: assert(false); } verbose_print_feature_flags(s); if (arg_verbose) progress(); } return r; } static int verb_digest(int argc, char *argv[]) { typedef enum DigestOperation { DIGEST_ARCHIVE, DIGEST_ARCHIVE_INDEX, DIGEST_BLOB, DIGEST_BLOB_INDEX, DIGEST_DIRECTORY, _DIGEST_OPERATION_INVALID = -1, } DigestOperation; DigestOperation operation = _DIGEST_OPERATION_INVALID; bool set_base_mode = false; const char *seek_path = NULL; int r; _cleanup_(safe_close_nonstdp) int input_fd = -1; _cleanup_free_ char *input = NULL; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; bool show_payload_digest = false; int seeking = false; if (argc > 3) { log_error("Input path/URL and subtree path expected."); return -EINVAL; } if (argc > 1) { input = ca_strip_file_url(argv[1]); if (!input) return log_oom(); } if (argc > 2) seek_path = argv[2]; if (arg_what == WHAT_ARCHIVE) operation = DIGEST_ARCHIVE; else if (arg_what == WHAT_ARCHIVE_INDEX) operation = DIGEST_ARCHIVE_INDEX; else if (arg_what == WHAT_BLOB) operation = DIGEST_BLOB; else if (arg_what == WHAT_BLOB_INDEX) operation = DIGEST_BLOB_INDEX; else if (arg_what == WHAT_DIRECTORY) operation = DIGEST_DIRECTORY; else if (arg_what != _WHAT_INVALID) { log_error("\"make\" operation may only be combined with --what=archive, --what=blob, --what=archive-index, --what=blob-index or --what=directory."); return -EINVAL; } if (operation == _DIGEST_OPERATION_INVALID && input && !streq(input, "-")) { if (ca_locator_has_suffix(input, ".catar")) operation = DIGEST_ARCHIVE; else if (ca_locator_has_suffix(input, ".caidx")) operation = DIGEST_ARCHIVE_INDEX; else if (ca_locator_has_suffix(input, ".caibx")) operation = DIGEST_BLOB_INDEX; } if (!input && IN_SET(operation, DIGEST_DIRECTORY, _DIGEST_OPERATION_INVALID)) { input = strdup("."); if (!input) return log_oom(); } if (!input || streq(input, "-")) input_fd = STDIN_FILENO; else { CaLocatorClass input_class; input_class = ca_classify_locator(input); if (input_class < 0) { log_error("Failed to determine class of locator: %s", input); return -EINVAL; } if (operation == DIGEST_DIRECTORY && input_class != CA_LOCATOR_PATH) { log_error("Input must be local path: %s", input); return -EINVAL; } if (input_class == CA_LOCATOR_PATH) { input_fd = open(input, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (input_fd < 0) return log_error_errno(errno, "Failed to open %s: %m", input); } } if (input_fd >= 0) { struct stat st; if (fstat(input_fd, &st) < 0) return log_error_errno(errno, "Failed to stat input: %m"); if (S_ISDIR(st.st_mode)) { if (operation == _DIGEST_OPERATION_INVALID) operation = DIGEST_DIRECTORY; else if (operation != DIGEST_DIRECTORY) { log_error("Input is a directory, but attempted to list as blob. Refusing."); return -EINVAL; } } else if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) { if (operation == _DIGEST_OPERATION_INVALID) operation = seek_path ? DIGEST_ARCHIVE : DIGEST_BLOB; else if (!IN_SET(operation, DIGEST_ARCHIVE, DIGEST_BLOB, DIGEST_ARCHIVE_INDEX, DIGEST_BLOB_INDEX)) { log_error("Input is not a regular file or block device, but attempted to list as one. Refusing."); return -EINVAL; } } else { log_error("Input is a neither a directory, a regular file, nor a block device. Refusing."); return -EINVAL; } } if (streq_ptr(input, "-")) input = mfree(input); if (operation == _DIGEST_OPERATION_INVALID) { log_error("Failed to determine what to calculate digest of. Use --what=archive, --what=blob, --what=archive-index, --what=blob-index or --what=directory."); return -EINVAL; } if (!IN_SET(operation, DIGEST_ARCHIVE, DIGEST_ARCHIVE_INDEX) && seek_path) { log_error("Subtree path only supported when calculating message digest of archive or archive index."); return -EINVAL; } seek_path = normalize_seek_path(seek_path); if (IN_SET(operation, DIGEST_ARCHIVE_INDEX, DIGEST_BLOB_INDEX)) { r = set_default_store(input); if (r < 0) return r; } if (operation == DIGEST_DIRECTORY || (operation == DIGEST_BLOB && input_fd >= 0)) s = ca_sync_new_encode(); else s = ca_sync_new_decode(); if (!s) return log_oom(); r = load_chunk_size(s); if (r < 0) return r; if (operation == DIGEST_DIRECTORY || (operation == DIGEST_BLOB && input_fd >= 0)) r = ca_sync_set_base_fd(s, input_fd); else if (IN_SET(operation, DIGEST_ARCHIVE_INDEX, DIGEST_BLOB_INDEX)) { if (input_fd >= 0) r = ca_sync_set_index_fd(s, input_fd); else r = ca_sync_set_index_auto(s, input); set_base_mode = true; } else { assert(IN_SET(operation, DIGEST_BLOB, DIGEST_ARCHIVE)); set_base_mode = true; r = ca_sync_set_archive_auto(s, input); } if (r < 0) return log_error_errno(r, "Failed to set sync input: %s", strerror(-r)); input_fd = -1; if (set_base_mode) { r = ca_sync_set_base_mode(s, IN_SET(operation, DIGEST_ARCHIVE, DIGEST_ARCHIVE_INDEX) ? S_IFDIR : S_IFREG); if (r < 0) return log_error_errno(r, "Failed to set base mode to regular file: %m"); } if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) return log_error_errno(r, "Failed to set store: %m"); } r = load_seeds_and_extra_stores(s); if (r < 0) return r; r = load_feature_flags(s, IN_SET(operation, DIGEST_BLOB, DIGEST_BLOB_INDEX) ? 0 : CA_FORMAT_WITH_MASK); if (r < 0) return r; r = ca_sync_enable_archive_digest(s, true); if (r < 0) return log_error_errno(r, "Failed to enable archive digest: %m"); if (seek_path) { r = ca_sync_seek_path(s, seek_path); if (r < 0) return log_error_errno(r, "Failed to seek to %s: %m", seek_path); seeking = true; } (void) send_notify("READY=1"); for (;;) { if (quit) { log_error("Got exit signal, quitting."); return -ESHUTDOWN; } r = ca_sync_step(s); if (r == -ENOMEDIUM) return log_error_errno(r, "File, URL or resource not found."); if (r < 0) return log_error_errno(r, "Failed to run synchronizer: %m"); switch (r) { case CA_SYNC_FINISHED: if (!show_payload_digest) { /* When we calc the digest of a directory tree (or top-level blob), show the archive digest */ CaChunkID digest; char t[CA_CHUNK_ID_FORMAT_MAX]; r = ca_sync_get_archive_digest(s, &digest); if (r < 0) return log_error_errno(r, "Failed to get archive digest: %m"); printf("%s\n", ca_chunk_id_format(&digest, t)); return 0; } break; case CA_SYNC_NEXT_FILE: if (seeking) { mode_t mode; /* If we are seeking to a specific path in our archive, then check here if it is a regular file * (in which case we show the payload checksum) or a directory (in which case we show the * archive checksum from here. If it is neither, we return failure. */ r = ca_sync_current_mode(s, &mode); if (r < 0) return log_error_errno(r, "Failed to get current mode: %m"); if (S_ISREG(mode)) { show_payload_digest = true; r = ca_sync_enable_payload_digest(s, true); if (r < 0) return log_error_errno(r, "Failed to enable payload digest: %m"); } else if (S_ISDIR(mode)) show_payload_digest = false; else { log_error("Path %s does not refer to a file or directory: %m", seek_path); return -ENOTTY; } seeking = false; } r = process_step_generic(s, r, false); if (r < 0) return r; break; case CA_SYNC_DONE_FILE: if (show_payload_digest) { /* When we calc the digest of a file, show the payload digest */ CaChunkID digest; char t[CA_CHUNK_ID_FORMAT_MAX]; r = ca_sync_get_payload_digest(s, &digest); if (r < 0) return log_error_errno(r, "Failed to get payload digest: %m"); printf("%s\n", ca_chunk_id_format(&digest, t)); return 0; } /* fall through */ case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_POLL: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: r = process_step_generic(s, r, false); if (r < 0) return r; break; default: assert(false); } verbose_print_feature_flags(s); if (arg_verbose) progress(); } return r; } static int verb_mount(int argc, char *argv[]) { #if HAVE_FUSE typedef enum MountOperation { MOUNT_ARCHIVE, MOUNT_ARCHIVE_INDEX, _MOUNT_OPERATION_INVALID = -1, } MountOperation; MountOperation operation = _MOUNT_OPERATION_INVALID; const char *mount_path = NULL; int r; _cleanup_(safe_close_nonstdp) int input_fd = -1; _cleanup_free_ char *input = NULL; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; if (argc > 3 || argc < 2) { log_error("An archive path/URL expected, followed by a mount path."); return -EINVAL; } if (argc > 2) { input = ca_strip_file_url(argv[1]); if (!input) return log_oom(); mount_path = argv[2]; } else mount_path = argv[1]; if (arg_what == WHAT_ARCHIVE) operation = MOUNT_ARCHIVE; else if (arg_what == WHAT_ARCHIVE_INDEX) operation = MOUNT_ARCHIVE_INDEX; else if (arg_what != _WHAT_INVALID) { log_error("\"mount\" operation may only be combined with --what=archive and --what=archive-index."); return -EINVAL; } if (operation == _MOUNT_OPERATION_INVALID && input && !streq(input, "-")) { if (ca_locator_has_suffix(input, ".caidx")) operation = MOUNT_ARCHIVE_INDEX; } if (operation == _MOUNT_OPERATION_INVALID) operation = MOUNT_ARCHIVE; s = ca_sync_new_decode(); if (!s) return log_oom(); if (!input || streq(input, "-")) input_fd = STDIN_FILENO; if (operation == MOUNT_ARCHIVE_INDEX) { r = set_default_store(input); if (r < 0) return r; } if (arg_rate_limit_bps != UINT64_MAX) { r = ca_sync_set_rate_limit_bps(s, arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Failed to set rate limit: %m"); } if (operation == MOUNT_ARCHIVE) { if (input_fd >= 0) r = ca_sync_set_archive_fd(s, input_fd); else r = ca_sync_set_archive_auto(s, input); } else if (operation == MOUNT_ARCHIVE_INDEX) { if (input_fd >= 0) r = ca_sync_set_index_fd(s, input_fd); else r = ca_sync_set_index_auto(s, input); } else assert(false); if (r < 0) return log_error_errno(r, "Failed to set sync input: %m"); input_fd = -1; r = ca_sync_set_base_mode(s, S_IFDIR); if (r < 0) return log_error_errno(r, "Failed to set base mode to directory: %m"); if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) return log_error_errno(r, "Failed to set store: %m"); } r = load_seeds_and_extra_stores(s); if (r < 0) return r; return ca_fuse_run(s, input, mount_path, arg_mkdir); #else log_error("Compiled without support for fuse."); return -ENOSYS; #endif } static int verb_mkdev(int argc, char *argv[]) { typedef enum MkDevOperation { MKDEV_BLOB, MKDEV_BLOB_INDEX, _MKDEV_OPERATION_INVALID = -1, } MkDevOperation; MkDevOperation operation = _MKDEV_OPERATION_INVALID; _cleanup_(ca_block_device_unrefp) CaBlockDevice *nbd = NULL; _cleanup_(realloc_buffer_free) ReallocBuffer buffer = {}; _cleanup_(safe_close_nonstdp) int input_fd = -1; bool make_symlink = false, rm_symlink = false; _cleanup_(ca_sync_unrefp) CaSync *s = NULL; const char *path = NULL, *name = NULL; _cleanup_free_ char *input = NULL; bool initialized, sent_ready = false; dev_t devnum; int r; #if HAVE_UDEV _cleanup_(udev_monitor_unrefp) struct udev_monitor *monitor = NULL; _cleanup_(udev_device_unrefp) struct udev_device *d = NULL; _cleanup_(udev_unrefp) struct udev *udev = NULL; #endif if (argc > 3) { log_error("An blob path/URL expected, possibly followed by a device or symlink name."); return -EINVAL; } if (argc > 1) { input = ca_strip_file_url(argv[1]); if (!input) { r = log_oom(); goto finish; } } if (argc > 2) name = argv[2]; if (arg_what == WHAT_BLOB) operation = MKDEV_BLOB; else if (arg_what == WHAT_BLOB_INDEX) operation = MKDEV_BLOB_INDEX; else if (arg_what != _WHAT_INVALID) { log_error("\"mkdev\" operation may only be combined with --what=blob and --what=blob-index."); r = -EINVAL; goto finish; } if (operation == _MKDEV_OPERATION_INVALID && input && !streq(input, "-")) { if (ca_locator_has_suffix(input, ".caibx")) operation = MKDEV_BLOB_INDEX; } if (operation == _MKDEV_OPERATION_INVALID) operation = MKDEV_BLOB; s = ca_sync_new_decode(); if (!s) { r = log_oom(); goto finish; } if (!input || streq(input, "-")) input_fd = STDIN_FILENO; if (operation == MKDEV_BLOB_INDEX) { r = set_default_store(input); if (r < 0) goto finish; } if (arg_rate_limit_bps != UINT64_MAX) { r = ca_sync_set_rate_limit_bps(s, arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Failed to set rate limit: %m"); } if (operation == MKDEV_BLOB) { if (input_fd >= 0) r = ca_sync_set_archive_fd(s, input_fd); else r = ca_sync_set_archive_auto(s, input); } else if (operation == MKDEV_BLOB_INDEX) { if (input_fd >= 0) r = ca_sync_set_index_fd(s, input_fd); else r = ca_sync_set_index_auto(s, input); } else assert(false); if (r < 0) { log_error_errno(r, "Failed to set sync input: %m"); goto finish; } input_fd = -1; r = ca_sync_set_base_mode(s, S_IFREG); if (r < 0) { log_error_errno(r, "Failed to set base mode to regular file: %m"); goto finish; } if (arg_store) { r = ca_sync_set_store_auto(s, arg_store); if (r < 0) { log_error_errno(r, "Failed to set store: %m"); goto finish; } } r = load_seeds_and_extra_stores(s); if (r < 0) goto finish; nbd = ca_block_device_new(); if (!nbd) { r = log_oom(); goto finish; } if (name) { r = ca_block_device_test_nbd(name); if (r < 0) { log_error_errno(r, "Failed to test whether %s is an nbd device: %m", name); goto finish; } else if (r > 0) { r = ca_block_device_set_path(nbd, name); if (r < 0) { log_error_errno(r, "Failed to set device path to %s: %m", name); goto finish; } } else { const char *k; k = path_startswith(name, "/dev"); if (k) { r = ca_block_device_set_friendly_name(nbd, k); if (r < 0) { log_error("Failed to set friendly name to %s: %m", k); goto finish; } } else make_symlink = true; } } /* First loop: process as enough so that we can figure out the size of the blob */ for (;;) { uint64_t size; if (quit) { r = 0; goto finish; } r = ca_sync_get_archive_size(s, &size); if (r >= 0) { r = ca_block_device_set_size(nbd, (size + 511) & ~511); if (r < 0) { log_error_errno(r, "Failed to set NBD size: %m"); goto finish; } break; } if (r == -ESPIPE) { log_error_errno(r, "Seekable archive required."); goto finish; } if (r != -EAGAIN) { log_error_errno(r, "Failed to determine archive size: %m"); goto finish; } r = ca_sync_step(s); if (r == -ENOMEDIUM) { log_error_errno(r, "File, URL or resource not found."); goto finish; } if (r < 0) { log_error_errno(r, "Failed to run synchronizer: %m"); goto finish; } switch (r) { case CA_SYNC_FINISHED: log_error("Premature end of archive."); r = -EBADMSG; goto finish; case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_POLL: r = process_step_generic(s, r, true); if (r < 0) goto finish; break; default: assert(false); } } r = ca_block_device_open(nbd); if (r < 0) { log_error_errno(r, "Failed to open NBD device: %m"); goto finish; } r = ca_block_device_get_path(nbd, &path); if (r < 0) { log_error_errno(r, "Failed to determine NBD device path: %m"); goto finish; } r = ca_block_device_get_devnum(nbd, &devnum); if (r < 0) { log_error_errno(r, "Failed to get device ID: %m"); goto finish; } #if HAVE_UDEV udev = udev_new(); if (!udev) { r = log_error_errno(errno, "Failed to allocate udev context: %m"); goto finish; } monitor = udev_monitor_new_from_netlink(udev, "udev"); if (!monitor) { r = log_error_errno(errno, "Failed to acquire udev monitor: %m"); goto finish; } r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL); if (r < 0) { log_error_errno(r, "Failed to addd udev match: %m"); goto finish; } r = udev_monitor_enable_receiving(monitor); if (r < 0) { log_error_errno(r, "Failed to start udev monitor: %m"); goto finish; } d = udev_device_new_from_devnum(udev, 'b', devnum); if (!d) { r = log_error_errno(errno, "Failed to get NBD udev device: %m"); goto finish; } initialized = udev_device_get_is_initialized(d) != 0; d = udev_device_unref(d); #else initialized = true; #endif if (make_symlink) { if (symlink(path, name) < 0) { r = log_error_errno(errno, "Failed to create symlink %s → %s: %m", name, path); goto finish; } rm_symlink = true; } printf("Attached: %s\n", name ?: path); for (;;) { uint64_t req_offset = 0, req_size = 0; if (quit) { r = 0; /* for the "mkdev" verb quitting does not indicate an incomplete operation, hence return success */ goto finish; } #if HAVE_UDEV if (!initialized) { _cleanup_(udev_device_unrefp) struct udev_device *t = NULL; t = udev_monitor_receive_device(monitor); if (t && udev_device_get_devnum(t) == devnum) initialized = true; } #endif if (initialized && !sent_ready) { _cleanup_free_ char *t = NULL; #if HAVE_UDEV monitor = udev_monitor_unref(monitor); udev = udev_unref(udev); #endif t = strjoin("READY=1\n" "DEVICE=", path, "\n"); if (!t) { r = log_oom(); goto finish; } (void) send_notify(t); sent_ready = true; } r = ca_block_device_step(nbd); if (r < 0) { log_error_errno(r, "Failed to read NBD request: %m"); goto finish; } if (r == CA_BLOCK_DEVICE_CLOSED) break; if (r == CA_BLOCK_DEVICE_POLL) { sigset_t ss; int nbd_poll_fd, udev_fd; nbd_poll_fd = ca_block_device_get_poll_fd(nbd); if (nbd_poll_fd < 0) { r = log_error_errno(nbd_poll_fd, "Failed to acquire NBD poll file descriptor: %m"); goto finish; } #if HAVE_UDEV if (monitor) { udev_fd = udev_monitor_get_fd(monitor); if (udev_fd < 0) { r = log_error_errno(udev_fd, "Failed to acquire udev monitor fd: %m"); goto finish; } } else #endif udev_fd = -1; block_exit_handler(SIG_BLOCK, &ss); if (quit) r = -ESHUTDOWN; else { struct pollfd p[2] = { [0] = { .fd = nbd_poll_fd, .events = POLLIN }, [1] = { .fd = udev_fd, .events = POLLIN }, }; r = ppoll(p, udev_fd < 0 ? 1 : 2, NULL, &ss); if ((r >= 0 || errno == EINTR) && quit) r = -ESHUTDOWN; else if (r < 0) r = -errno; else r = 0; } block_exit_handler(SIG_UNBLOCK, NULL); if (r == -ESHUTDOWN) { r = 0; goto finish; } else if (r < 0) { log_error_errno(r, "Failed to poll for NBD requests: %m"); goto finish; } continue; } assert(r == CA_BLOCK_DEVICE_REQUEST); r = ca_block_device_get_request_offset(nbd, &req_offset); if (r < 0) { log_error_errno(r, "Failed to get NBD request offset: %m"); goto finish; } r = ca_block_device_get_request_size(nbd, &req_size); if (r < 0) { log_error_errno(r, "Failed to get NBD request size: %m"); goto finish; } r = ca_sync_seek_offset(s, req_offset); if (r < 0) { log_error_errno(r, "Failed to seek: %m"); goto finish; } realloc_buffer_empty(&buffer); for (;;) { bool done = false; if (quit) { r = 0; goto finish; } r = ca_sync_step(s); if (r == -ENOMEDIUM) { log_error_errno(r, "File, URL or resource not found."); goto finish; } if (r < 0) { log_error_errno(r, "Failed to run synchronizer: %m"); goto finish; } switch (r) { case CA_SYNC_FINISHED: /* We hit EOF but the reply is not yet completed, in this case, fill up with zeroes */ assert(realloc_buffer_size(&buffer) < req_size); if (!realloc_buffer_extend0(&buffer, req_size - realloc_buffer_size(&buffer))) { r = log_oom(); goto finish; } r = ca_block_device_put_data(nbd, req_offset, realloc_buffer_data(&buffer), req_size); if (r < 0) { log_error_errno(r, "Failed to send reply: %m"); goto finish; } r = realloc_buffer_advance(&buffer, req_size); if (r < 0) { log_error_errno(r, "Failed to advance buffer: %m"); goto finish; } done = true; break; case CA_SYNC_PAYLOAD: { const void *p; size_t sz; r = ca_sync_get_payload(s, &p, &sz); if (r < 0) { log_error_errno(r, "Failed to retrieve synchronizer payload: %m"); goto finish; } if (realloc_buffer_size(&buffer) == 0 && sz >= req_size) { /* If this is a full reply, then propagate this directly */ r = ca_block_device_put_data(nbd, req_offset, p, MIN(sz, req_size)); if (r < 0) { log_error_errno(r, "Failed to send reply: %m"); goto finish; } done = true; } else { if (!realloc_buffer_append(&buffer, p, sz)) { r = log_oom(); goto finish; } if (realloc_buffer_size(&buffer) >= req_size) { r = ca_block_device_put_data(nbd, req_offset, realloc_buffer_data(&buffer), req_size); if (r < 0) { log_error_errno(r, "Failed to send reply: %m"); goto finish; } r = realloc_buffer_advance(&buffer, req_size); if (r < 0) { log_error_errno(r, "Failed to advance buffer: %m"); goto finish; } done = true; } } break; } case CA_SYNC_STEP: case CA_SYNC_SEED_NEXT_FILE: case CA_SYNC_SEED_DONE_FILE: case CA_SYNC_POLL: case CA_SYNC_FOUND: case CA_SYNC_NOT_FOUND: r = process_step_generic(s, r, true); if (r == -ESHUTDOWN) { r = 0; goto finish; } if (r < 0) goto finish; break; default: assert(false); } if (done) break; } } finish: if (rm_symlink) (void) unlink(name); return r; } static void free_stores(CaStore **stores, size_t n_stores) { size_t i; assert(stores || n_stores == 0); for (i = 0; i < n_stores; i++) ca_store_unref(stores[i]); free(stores); } typedef struct { CaStore **stores; size_t n_stores; } CaStoresCleanup; static void free_storesp(CaStoresCleanup *stores_cleanup) { free_stores(stores_cleanup->stores, stores_cleanup->n_stores); } static int allocate_stores( const char *wstore_path, char **rstore_paths, size_t n_rstores, CaStore ***ret, size_t *ret_n) { _cleanup_(free_storesp) CaStoresCleanup stores = {}; size_t n = 0; char **rstore_path; int r; assert(ret); assert(ret_n); stores.n_stores = !!wstore_path + n_rstores; if (stores.n_stores > 0) { stores.stores = new0(CaStore*, stores.n_stores); if (!stores.stores) return log_oom(); } if (wstore_path) { stores.stores[n] = ca_store_new(); if (!stores.stores[n]) return log_oom(); n++; r = ca_store_set_path(stores.stores[n-1], wstore_path); if (r < 0) return log_error_errno(r, "Unable to set store path %s: %m", wstore_path); } STRV_FOREACH(rstore_path, rstore_paths) { stores.stores[n] = ca_store_new(); if (!stores.stores[n]) return log_oom(); n++; r = ca_store_set_path(stores.stores[n-1], *rstore_path); if (r < 0) return log_error_errno(r, "Unable to set store path %s: %m", *rstore_path); } *ret = stores.stores; *ret_n = n; stores = (CaStoresCleanup) {}; /* prevent freeing */ return 0; } static int verb_pull(int argc, char *argv[]) { const char *base_path, *archive_path, *index_path, *wstore_path; size_t i; _cleanup_(free_storesp) CaStoresCleanup stores = {}; _cleanup_(ca_remote_unrefp) CaRemote *rr = NULL; int r; if (argc < 5) { log_error("Expected at least 5 arguments."); return -EINVAL; } base_path = empty_or_dash_to_null(argv[1]); archive_path = empty_or_dash_to_null(argv[2]); index_path = empty_or_dash_to_null(argv[3]); wstore_path = empty_or_dash_to_null(argv[4]); stores.n_stores = !!wstore_path + (argc - 5); if (base_path) { log_error("Pull from base or archive not yet supported."); return -EOPNOTSUPP; } if (!archive_path && !index_path && stores.n_stores == 0) { log_error("Nothing to do."); return -EINVAL; } /* fprintf(stderr, "pull archive: %s index: %s wstore: %s\n", strna(archive_path), strna(index_path), strna(wstore_path)); */ rr = ca_remote_new(); if (!rr) return log_oom(); r = ca_remote_set_local_feature_flags(rr, (stores.n_stores > 0 ? CA_PROTOCOL_READABLE_STORE : 0) | (index_path ? CA_PROTOCOL_READABLE_INDEX : 0) | (archive_path ? CA_PROTOCOL_READABLE_ARCHIVE : 0)); if (r < 0) return log_error_errno(r, "Failed to set feature flags: %m"); if (arg_rate_limit_bps != UINT64_MAX) { r = ca_remote_set_rate_limit_bps(rr, arg_rate_limit_bps); if (r < 0) return log_error_errno(r, "Failed to set rate limit: %m"); } r = ca_remote_set_io_fds(rr, STDIN_FILENO, STDOUT_FILENO); if (r < 0) return log_error_errno(r, "Failed to set I/O file descriptors: %m"); if (index_path) { r = ca_remote_set_index_path(rr, index_path); if (r < 0) return log_error_errno(r, "Unable to set index file %s: %m", index_path); } if (archive_path) { r = ca_remote_set_archive_path(rr, archive_path); if (r < 0) return log_error_errno(r, "Unable to set archive file %s: %m", archive_path); } r = allocate_stores(wstore_path, argv + 5, argc - 5, &stores.stores, &stores.n_stores); if (r < 0) return r; for (;;) { unsigned put_count; sigset_t ss; int step; if (quit) { log_info("Got exit signal, quitting."); return -ESHUTDOWN; } step = ca_remote_step(rr); if (step == -EPIPE || step == CA_REMOTE_FINISHED) /* When somebody pulls from us, he's welcome to terminate any time he likes */ break; if (step < 0) return log_error_errno(step, "Failed to process remote: %m"); put_count = 0; for (;;) { CaChunkCompression compression; bool found = false; const void *p; CaChunkID id; uint64_t l; r = ca_remote_can_put_chunk(rr); if (r < 0) return log_error_errno(r, "Failed to determine whether there's buffer space for sending: %m"); if (r == 0) /* No space to put more */ break; r = ca_remote_next_request(rr, &id); if (r == -ENODATA) /* No data requested */ break; if (r < 0) return log_error_errno(r, "Failed to determine which chunk to send next: %m"); for (i = 0; i < stores.n_stores; i++) { r = ca_store_get(stores.stores[i], &id, CA_CHUNK_COMPRESSED, &p, &l, &compression); if (r >= 0) { found = true; break; } if (r != -ENOENT) return log_error_errno(r, "Failed to query store: %m"); } if (found) r = ca_remote_put_chunk(rr, &id, compression, p, l); else r = ca_remote_put_missing(rr, &id); if (r < 0) return log_error_errno(r, "Failed to enqueue response: %m"); put_count ++; } if (put_count > 0) /* We enqueued more, let's do another step, maybe the remoter wants to write this mow */ continue; if (step != CA_REMOTE_POLL) continue; block_exit_handler(SIG_BLOCK, &ss); if (quit) r = -ESHUTDOWN; else { r = ca_remote_poll(rr, UINT64_MAX, &ss); if ((r == -EINTR || r >= 0) && quit) r = -ESHUTDOWN; } block_exit_handler(SIG_UNBLOCK, NULL); if (r == -ESHUTDOWN) return log_error_errno(r, "Got exit signal, quitting."); if (r < 0) return log_error_errno(r, "Failed to poll remoting engine: %m"); } return 0; } static int verb_push(int argc, char *argv[]) { const char *base_path, *archive_path, *index_path, *wstore_path; bool index_processed = false, index_written = false, archive_written = false; _cleanup_(ca_index_unrefp) CaIndex *index = NULL; _cleanup_(ca_remote_unrefp) CaRemote *rr = NULL; _cleanup_(free_storesp) CaStoresCleanup stores = {}; int r; if (argc < 5) { log_error("Expected at least 5 arguments."); return -EINVAL; } base_path = empty_or_dash_to_null(argv[1]); archive_path = empty_or_dash_to_null(argv[2]); index_path = empty_or_dash_to_null(argv[3]); wstore_path = empty_or_dash_to_null(argv[4]); stores.n_stores = !!wstore_path + (argc - 5); if (base_path) { log_error("Push to base not yet supported."); return -EOPNOTSUPP; } if (!archive_path && !index_path && stores.n_stores == 0) { log_error("Nothing to do."); return -EINVAL; } /* log_error("push archive: %s index: %s wstore: %s", strna(archive_path), strna(index_path), strna(wstore_path)); */ rr = ca_remote_new(); if (!rr) return log_oom(); r = ca_remote_set_local_feature_flags(rr, (wstore_path ? CA_PROTOCOL_WRITABLE_STORE : 0) | (index_path ? CA_PROTOCOL_WRITABLE_INDEX : 0) | (archive_path ? CA_PROTOCOL_WRITABLE_ARCHIVE : 0)); if (r < 0) log_error_errno(r, "Failed to set feature flags: %m"); if (arg_rate_limit_bps != UINT64_MAX) { r = ca_remote_set_rate_limit_bps(rr, arg_rate_limit_bps); if (r < 0) log_error_errno(r, "Failed to set rate limit: %m"); } r = ca_remote_set_io_fds(rr, STDIN_FILENO, STDOUT_FILENO); if (r < 0) log_error_errno(r, "Failed to set I/O file descriptors: %m"); if (index_path) { index = ca_index_new_incremental_read(); if (!index) return log_oom(); r = ca_index_set_path(index, index_path); if (r < 0) return log_error_errno(r, "Unable to set index file %s: %m", index_path); r = ca_index_open(index); if (r < 0) return log_error_errno(r, "Failed to open index file %s: %m", index_path); } if (archive_path) { r = ca_remote_set_archive_path(rr, archive_path); if (r < 0) return log_error_errno(r, "Unable to set archive file %s: %m", archive_path); } r = allocate_stores(wstore_path, argv + 5, argc - 5, &stores.stores, &stores.n_stores); if (r < 0) return r; for (;;) { bool finished; int step; if (quit) { log_error("Got exit signal, quitting."); return -ESHUTDOWN; } step = ca_remote_step(rr); if (step < 0) return log_error_errno(step, "Failed to process remote: %m"); if (step == CA_REMOTE_FINISHED) break; switch (step) { case CA_REMOTE_POLL: { sigset_t ss; block_exit_handler(SIG_BLOCK, &ss); if (quit) r = -ESHUTDOWN; else { r = ca_remote_poll(rr, UINT64_MAX, &ss); if ((r == -EINTR || r >= 0) && quit) r = -ESHUTDOWN; } block_exit_handler(SIG_UNBLOCK, NULL); if (r == -ESHUTDOWN) return log_info_errno(r, "Got exit signal, quitting."); if (r < 0) return log_error_errno(r, "Failed to run remoting engine: %m"); break; } case CA_REMOTE_STEP: case CA_REMOTE_READ_ARCHIVE: break; case CA_REMOTE_READ_ARCHIVE_EOF: archive_written = true; break; case CA_REMOTE_READ_INDEX: { const void *p; size_t n; r = ca_remote_read_index(rr, &p, &n); if (r < 0) return log_error_errno(r, "Failed to read index data: %m"); r = ca_index_incremental_write(index, p, n); if (r < 0) return log_error_errno(r, "Failed to write index data: %m"); break; } case CA_REMOTE_READ_INDEX_EOF: r = ca_index_incremental_eof(index); if (r < 0) return log_error_errno(r, "Failed to write index EOF: %m"); index_written = true; break; case CA_REMOTE_CHUNK: { CaChunkCompression compression; const void *p; CaChunkID id; size_t n; r = ca_remote_next_chunk(rr, CA_CHUNK_AS_IS, &id, &p, &n, &compression); if (r < 0) return log_error_errno(r, "Failed to determine most recent chunk: %m"); r = ca_store_put(stores.stores[0], &id, compression, p, n); /* Write to wstore */ if (r < 0 && r != -EEXIST) return log_error_errno(r, "Failed to write chunk to store: %m"); r = ca_remote_forget_chunk(rr, &id); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to forget chunk: %m"); break; } default: assert(false); } /* Request all chunks from the client that the index it just send us listed but we don't have locally yet. */ for (;;) { /* char ids[CA_CHUNK_ID_FORMAT_MAX]; */ uint64_t remote_flags; CaChunkID id; size_t i; if (!index) break; if (index_processed) break; if (!wstore_path) break; r = ca_remote_get_remote_feature_flags(rr, &remote_flags); if (r == -ENODATA) break; if (r < 0) return log_error_errno(r, "Failed to get remote feature flags: %m"); /* Only request chunks if this is requested by the client side */ if ((remote_flags & CA_PROTOCOL_PUSH_INDEX_CHUNKS) == 0) { index_processed = true; break; } r = ca_index_read_chunk(index, &id, NULL, NULL); if (r == -EAGAIN) /* Not read enough yet */ break; if (r < 0) return log_error_errno(r, "Failed to read index: %m"); if (r == 0) { /* EOF */ index_processed = true; break; } /* fprintf(stderr, "Need %s\n", ca_chunk_id_format(&id, ids)); */ r = 0; for (i = 0; i < stores.n_stores; i++) { r = ca_store_has(stores.stores[i], &id); if (r < 0) log_error_errno(r, "Failed to test whether chunk exists locally already: %m"); if (r > 0) break; } if (r > 0) { /* fprintf(stderr, "Already have %s\n", ca_chunk_id_format(&id, ids)); */ continue; } /* fprintf(stderr, "Requesting %s\n", ca_chunk_id_format(&id, ids)); */ r = ca_remote_request_async(rr, &id, false); if (r < 0 && r != -EALREADY && r != -EAGAIN) return log_error_errno(r, "Failed to request chunk: %m"); /* if (r > 0) */ /* fprintf(stderr, "New request for %s\n", ca_chunk_id_format(&id, ids)); */ } finished = true; /* If the index isn't written yet, don't finish yet */ if (index_path && (!index_written || !index_processed)) finished = false; /* If the archive isn't written yet, don't finish yet */ if (archive_path && !archive_written) finished = false; /* If there are any chunks queued still, don't finish yet */ r = ca_remote_has_chunks(rr); if (r < 0) return log_error_errno(r, "Failed to determine if further requests are pending: %m"); if (r > 0) finished = false; if (finished) { r = ca_remote_goodbye(rr); if (r < 0 && r != -EALREADY) return log_error_errno(r, "Failed to enqueue goodbye: %m"); } } if (index) { r = ca_index_install(index); if (r < 0) return log_error_errno(r, "Failed to install index on location: %m"); } return 0; } static int verb_udev(int argc, char *argv[]) { const char *e; char pretty[FILENAME_MAX+1]; const char *p; _cleanup_(safe_closep) int fd = -1; ssize_t n; if (argc != 2) { log_error("Expected one argument."); return -EINVAL; } e = path_startswith(argv[1], "/dev"); if (!e || !filename_is_valid(e)) { log_error("Argument is not a valid device node path: %s.", argv[2]); return -EINVAL; } p = strjoina("/run/casync/", e); fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) { if (errno == ENOENT) return 0; return log_error_errno(errno, "Failed to open %s: %m", p); } if (flock(fd, LOCK_SH|LOCK_NB) < 0) { if (errno != EWOULDBLOCK) return log_error_errno(errno, "Failed to check if %s is locked: %m", p); /* If we got EWOULDBLOCK, everything is good, there's a casync locking this */ } else { /* Uh? We managed to lock this file? in that case casync behind it died, let's ignore this, and quit immediately. */ fd = safe_close(fd); return 0; } n = read(fd, pretty, sizeof(pretty)); if (n < 0) return log_error_errno(errno, "Failed to read from %s: %m", p); if ((size_t) n >= sizeof(pretty)) { log_error("Stored name read from %s too long.", p); return -EINVAL; } if ((size_t) n <= 0 || pretty[n-1] != '\n') { log_error("Stored name not newline terminated."); return -EINVAL; } pretty[n-1] = 0; if (!filename_is_valid(pretty)) { log_error("Stored name is invalid: %s", pretty); return -EINVAL; } printf("CASYNC_NAME=%s\n", pretty); return 0; } static int verb_gc(int argc, char *argv[]) { int i, r; _cleanup_(ca_chunk_collection_unrefp) CaChunkCollection *coll = NULL; _cleanup_(ca_store_unrefp) CaStore *store = NULL; if (argc < 2) { log_error("Expected at least one argument."); return -EINVAL; } coll = ca_chunk_collection_new(); if (!coll) return log_oom(); /* This sets the same store for all indices, based on the first index. */ r = set_default_store(argv[1]); if (r < 0) return r; if (!arg_store) { log_error("Failed to determine store, use --store= to set store."); return -EINVAL; } store = ca_store_new(); if (!store) return log_oom(); r = ca_store_set_path(store, arg_store); if (r < 0) { fprintf(stderr, "Set to set store to \"%s\": %s", arg_store, strerror(-r)); return r; } for (i = 1; i < argc; i++) { const char *path = argv[i]; r = ca_chunk_collection_add_index(coll, path); if (r < 0) return r; } { size_t usage, size; assert_se(ca_chunk_collection_usage(coll, &usage) == 0); assert_se(ca_chunk_collection_size(coll, &size) == 0); if (arg_verbose) printf("Chunk store usage: %zu references, %zu chunks\n", usage, size); } r = ca_gc_cleanup_unused(store, coll, arg_verbose * CA_GC_VERBOSE | arg_dry_run * CA_GC_DRY_RUN); if (r < 0) log_error_errno(r, "Chunk cleanup failed: %m"); return r; } static int dispatch_verb(int argc, char *argv[]) { int r; if (argc < 1) { log_error("Missing verb. (Invoke '%s --help' for a list of available verbs.)", program_invocation_short_name); return -EINVAL; } if (streq(argv[0], "help")) { help(); r = 0; } else if (streq(argv[0], "make")) r = verb_make(argc, argv); else if (streq(argv[0], "extract")) r = verb_extract(argc, argv); else if (STR_IN_SET(argv[0], "list", "mtree", "stat")) r = verb_list(argc, argv); else if (streq(argv[0], "digest")) r = verb_digest(argc, argv); else if (streq(argv[0], "mkdev")) r = verb_mkdev(argc, argv); else if (streq(argv[0], "mount")) r = verb_mount(argc, argv); else if (streq(argv[0], "pull")) /* "Secret" verb, only to be called by ssh-based remoting. */ r = verb_pull(argc, argv); else if (streq(argv[0], "push")) /* Same here. */ r = verb_push(argc, argv); else if (streq(argv[0], "udev")) /* "Secret" verb, only to be called by the udev nbd rules */ r = verb_udev(argc, argv); else if (streq(argv[0], "gc")) r = verb_gc(argc, argv); else { log_error("Unknown verb '%s'. (Invoke '%s --help' for a list of available verbs.)", argv[0], program_invocation_short_name); r = -EINVAL; } return r; } int main(int argc, char *argv[]) { int r; disable_sigpipe(); r = parse_argv(argc, argv); if (r <= 0) goto finish; install_exit_handler(NULL); block_exit_handler(SIG_UNBLOCK, NULL); r = dispatch_verb(argc - optind, argv + optind); install_exit_handler(SIG_DFL); finish: free(arg_store); strv_free(arg_extra_stores); strv_free(arg_seeds); /* fprintf(stderr, PID_FMT ": exiting with error code: %m", getpid()); */ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } src/casync.c000066400000000000000000003072511322621430500132770ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "cachunk.h" #include "cachunker.h" #include "cadecoder.h" #include "caencoder.h" #include "caformat-util.h" #include "caformat.h" #include "caindex.h" #include "caprotocol.h" #include "caremote.h" #include "caseed.h" #include "castore.h" #include "casync.h" #include "def.h" #include "realloc-buffer.h" #include "util.h" /* #undef EINVAL */ /* #define EINVAL __LINE__ */ /* #undef ENXIO */ /* #define ENXIO __LINE__ */ /* #undef EUNATCH */ /* #define EUNATCH __LINE__ */ typedef enum CaDirection { CA_SYNC_ENCODE, CA_SYNC_DECODE, } CaDirection; typedef struct CaSync { CaDirection direction; bool started; CaEncoder *encoder; CaDecoder *decoder; CaChunker chunker; CaIndex *index; CaRemote *remote_index; CaRemote *remote_archive; CaChunkID next_chunk; uint64_t next_chunk_size; bool next_chunk_valid; CaStore *wstore; CaStore **rstores; size_t n_rstores; CaStore *cache_store; CaRemote *remote_wstore; CaRemote **remote_rstores; size_t n_remote_rstores; size_t current_remote; CaSeed **seeds; size_t n_seeds; size_t current_seed; /* The seed we are currently indexing */ bool index_flags_propagated; int base_fd; int boundary_fd; int archive_fd; char *base_path, *temporary_base_path; char *boundary_path; char *archive_path, *temporary_archive_path; mode_t base_mode; mode_t make_mode; ReallocBuffer buffer; ReallocBuffer index_buffer; ReallocBuffer archive_buffer; ReallocBuffer compress_buffer; CaDigest *chunk_digest; bool archive_eof; bool remote_index_eof; size_t rate_limit_bps; uint64_t feature_flags; uint64_t feature_flags_mask; uint64_t n_written_chunks; uint64_t n_reused_chunks; uint64_t n_prefetched_chunks; uint64_t archive_size; uint64_t chunk_skip; bool punch_holes:1; bool reflink:1; bool hardlink:1; bool delete:1; bool payload:1; bool undo_immutable:1; bool archive_digest:1; bool hardlink_digest:1; bool payload_digest:1; CaFileRoot *archive_root; uid_t uid_shift; uid_t uid_range; /* uid_range == 0 means "full range" */ uint64_t chunk_size_min; uint64_t chunk_size_avg; uint64_t chunk_size_max; CaCompressionType compression_type; } CaSync; static CaSync *ca_sync_new(void) { CaSync *s; s = new0(CaSync, 1); if (!s) return NULL; s->base_fd = s->boundary_fd = s->archive_fd = -1; s->base_mode = (mode_t) -1; s->make_mode = (mode_t) -1; s->chunker = (CaChunker) CA_CHUNKER_INIT; s->archive_size = UINT64_MAX; s->punch_holes = true; s->reflink = true; s->delete = true; s->payload = true; s->feature_flags = s->feature_flags_mask = UINT64_MAX; s->compression_type = CA_COMPRESSION_DEFAULT; return s; } CaSync *ca_sync_new_encode(void) { CaSync *s; s = ca_sync_new(); if (!s) return NULL; s->direction = CA_SYNC_ENCODE; assert_se(ca_feature_flags_normalize(CA_FORMAT_DEFAULT, &s->feature_flags) >= 0); return s; } CaSync *ca_sync_new_decode(void) { CaSync *s; s = ca_sync_new(); if (!s) return NULL; s->direction = CA_SYNC_DECODE; return s; } int ca_sync_set_chunk_size_min(CaSync *s, uint64_t v) { int r; if (!s) return -EINVAL; r = ca_chunker_set_size(&s->chunker, v, s->chunk_size_avg, s->chunk_size_max); if (r < 0) return r; s->chunk_size_min = v; return 0; } int ca_sync_set_chunk_size_avg(CaSync *s, uint64_t v) { int r; if (!s) return -EINVAL; r = ca_chunker_set_size(&s->chunker, s->chunk_size_min, v, s->chunk_size_max); if (r < 0) return r; s->chunk_size_avg = v; return 0; } int ca_sync_set_chunk_size_max(CaSync *s, uint64_t v) { int r; if (!s) return -EINVAL; r = ca_chunker_set_size(&s->chunker, s->chunk_size_min, s->chunk_size_avg, v); if (r < 0) return r; s->chunk_size_max = v; return 0; } int ca_sync_get_chunk_size_avg(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; *ret = s->chunker.chunk_size_avg; return 0; } int ca_sync_get_chunk_size_min(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; *ret = s->chunker.chunk_size_min; return 0; } int ca_sync_get_chunk_size_max(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; *ret = s->chunker.chunk_size_max; return 0; } int ca_sync_set_punch_holes(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_set_punch_holes(s->decoder, enabled); if (r < 0) return r; } s->punch_holes = enabled; return 0; } int ca_sync_set_reflink(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_set_reflink(s->decoder, enabled); if (r < 0) return r; } s->reflink = enabled; return 0; } int ca_sync_set_hardlink(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_enable_hardlink_digest(s->decoder, s->hardlink_digest || enabled); if (r < 0) return r; r = ca_decoder_set_hardlink(s->decoder, enabled); if (r < 0) return r; } s->hardlink = enabled; return 0; } int ca_sync_set_delete(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_set_delete(s->decoder, enabled); if (r < 0) return r; } s->delete = enabled; return 0; } int ca_sync_set_payload(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_set_payload(s->decoder, enabled || s->remote_archive); if (r < 0) return r; } s->payload = enabled; return 0; } int ca_sync_set_undo_immutable(CaSync *s, bool enabled) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) { r = ca_decoder_set_undo_immutable(s->decoder, enabled); if (r < 0) return r; } s->undo_immutable = enabled; return 0; } int ca_sync_set_uid_shift(CaSync *s, uid_t u) { int r; if (!s) return -EINVAL; if (s->decoder) { r = ca_decoder_set_uid_shift(s->decoder, u); if (r < 0) return r; } if (s->encoder) { r = ca_encoder_set_uid_shift(s->encoder, u); if (r < 0) return r; } s->uid_shift = u; return 0; } int ca_sync_set_uid_range(CaSync *s, uid_t u) { int r; if (!s) return -EINVAL; if (s->decoder) { r = ca_decoder_set_uid_range(s->decoder, u); if (r < 0) return r; } if (s->encoder) { r = ca_encoder_set_uid_range(s->encoder, u); if (r < 0) return r; } s->uid_range = u; return 0; } CaSync *ca_sync_unref(CaSync *s) { size_t i; if (!s) return NULL; ca_encoder_unref(s->encoder); ca_decoder_unref(s->decoder); ca_store_unref(s->wstore); for (i = 0; i < s->n_rstores; i++) ca_store_unref(s->rstores[i]); free(s->rstores); ca_store_unref(s->cache_store); ca_remote_unref(s->remote_wstore); for (i = 0; i < s->n_remote_rstores; i++) ca_remote_unref(s->remote_rstores[i]); free(s->remote_rstores); for (i = 0; i < s->n_seeds; i++) ca_seed_unref(s->seeds[i]); free(s->seeds); safe_close(s->base_fd); safe_close(s->boundary_fd); safe_close(s->archive_fd); free(s->base_path); free(s->archive_path); free(s->boundary_path); if (s->temporary_base_path) { (void) unlink(s->temporary_base_path); free(s->temporary_base_path); } if (s->temporary_archive_path) { (void) unlink(s->temporary_archive_path); free(s->temporary_archive_path); } ca_index_unref(s->index); ca_remote_unref(s->remote_index); ca_remote_unref(s->remote_archive); realloc_buffer_free(&s->buffer); realloc_buffer_free(&s->index_buffer); realloc_buffer_free(&s->archive_buffer); realloc_buffer_free(&s->compress_buffer); ca_file_root_unref(s->archive_root); ca_digest_free(s->chunk_digest); return mfree(s); } int ca_sync_set_rate_limit_bps(CaSync *s, uint64_t rate_limit_bps) { if (!s) return -EINVAL; s->rate_limit_bps = rate_limit_bps; return 0; } int ca_sync_set_feature_flags(CaSync *s, uint64_t flags) { if (!s) return -EINVAL; if (s->direction != CA_SYNC_ENCODE) return -ENOTTY; if (s->encoder) return -EBUSY; return ca_feature_flags_normalize(flags, &s->feature_flags); } int ca_sync_get_feature_flags(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (s->direction == CA_SYNC_ENCODE) *ret = s->feature_flags; else { if (!s->decoder) return -ENODATA; return ca_decoder_get_feature_flags(s->decoder, ret); } return 0; } int ca_sync_get_covering_feature_flags(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (s->direction != CA_SYNC_ENCODE) return -ENOTTY; if (!s->encoder) return -ENODATA; return ca_encoder_get_covering_feature_flags(s->encoder, ret); } int ca_sync_set_feature_flags_mask(CaSync *s, uint64_t mask) { if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (s->decoder) return -EBUSY; return ca_feature_flags_normalize_mask(mask, &s->feature_flags_mask); } static int ca_sync_allocate_index(CaSync *s) { assert(s); if (s->index) return -EBUSY; if (s->remote_index) return -EBUSY; if (s->direction == CA_SYNC_ENCODE) s->index = ca_index_new_write(); else if (s->direction == CA_SYNC_DECODE) s->index = ca_index_new_read(); else assert(false); if (!s->index) return -ENOMEM; return 0; } int ca_sync_set_index_fd(CaSync *s, int fd) { int r; if (!s) return -EINVAL; if (fd < 0) return -EINVAL; r = ca_sync_allocate_index(s); if (r < 0) return r; r = ca_index_set_fd(s->index, fd); if (r < 0) { s->index = ca_index_unref(s->index); return r; } return 0; } int ca_sync_set_index_path(CaSync *s, const char *path) { int r; if (!s) return -EINVAL; if (!path) return -EINVAL; r = ca_sync_allocate_index(s); if (r < 0) return r; r = ca_index_set_path(s->index, path); if (r < 0) { s->index = ca_index_unref(s->index); return r; } return 0; } int ca_sync_set_index_remote(CaSync *s, const char *url) { uint64_t flags; int r; if (!s) return -EINVAL; if (!url) return -EINVAL; if (s->index) return -EBUSY; if (s->remote_index) return -EBUSY; flags = s->direction == CA_SYNC_ENCODE ? CA_PROTOCOL_PUSH_INDEX : CA_PROTOCOL_PULL_INDEX; if (s->remote_wstore) { /* Try to reuse the main store remote for the index too, if it matches the same server */ r = ca_remote_set_index_url(s->remote_wstore, url); if (r >= 0) { r = ca_remote_add_local_feature_flags(s->remote_wstore, flags); if (r < 0) return r; s->remote_index = ca_remote_ref(s->remote_wstore); return 0; } if (r != -EBUSY) /* Fail, except when the reason is that it matches the same server. */ return r; } s->remote_index = ca_remote_new(); if (!s->remote_index) return -ENOMEM; if (s->rate_limit_bps > 0) { r = ca_remote_set_rate_limit_bps(s->remote_index, s->rate_limit_bps); if (r < 0) return r; } r = ca_remote_set_index_url(s->remote_index, url); if (r < 0) return r; r = ca_remote_set_local_feature_flags(s->remote_index, flags); if (r < 0) return r; return 0; } int ca_sync_set_index_auto(CaSync *s, const char *locator) { CaLocatorClass c; if (!s) return -EINVAL; if (!locator) return -EINVAL; c = ca_classify_locator(locator); if (c < 0) return -EINVAL; if (c == CA_LOCATOR_PATH) return ca_sync_set_index_path(s, locator); return ca_sync_set_index_remote(s, locator); } int ca_sync_set_base_fd(CaSync *s, int fd) { if (!s) return -EINVAL; if (fd < 0) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; if (s->base_mode != (mode_t) -1) return -EBUSY; if (s->base_path) return -EBUSY; if (s->boundary_fd >= 0) return -EBUSY; if (s->boundary_path) return -EBUSY; s->base_fd = fd; return 0; } int ca_sync_set_base_path(CaSync *s, const char *path) { if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; if (s->base_path) return -EBUSY; if (s->boundary_fd >= 0) return -EBUSY; if (s->boundary_path) return -EBUSY; if (s->base_mode == (mode_t) -1 || S_ISDIR(s->base_mode)) { s->base_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); if (s->base_fd >= 0) /* Base exists already and is a directory */ return 0; if (s->direction == CA_SYNC_ENCODE && errno != ENOTDIR) return -errno; } if (s->direction == CA_SYNC_ENCODE) { s->base_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (s->base_fd < 0) return -errno; return 0; } assert(s->direction == CA_SYNC_DECODE); s->base_path = strdup(path); if (!s->base_path) return -ENOMEM; return 0; } int ca_sync_set_make_mode(CaSync *s, mode_t m) { if (!s) return -EINVAL; if (m & ~0666) return -EINVAL; if (s->direction != CA_SYNC_ENCODE) return -ENOTTY; if (s->make_mode != (mode_t) -1) return -EBUSY; s->make_mode = m; return 0; } int ca_sync_set_base_mode(CaSync *s, mode_t m) { if (!s) return -EINVAL; if (m & ~(07777 | S_IFMT)) return -EINVAL; if (!S_ISREG(m) && !S_ISDIR(m) && !S_ISBLK(m)) return -ENOTTY; if (s->direction == CA_SYNC_ENCODE) return -ENOTTY; if (s->base_fd >= 0) return -EBUSY; if (s->base_mode != (mode_t) -1) return -EBUSY; if (s->boundary_fd >= 0) return -EBUSY; if (s->boundary_path) return -EBUSY; s->base_mode = m; return 0; } int ca_sync_set_boundary_fd(CaSync *s, int fd) { if (!s) return -EINVAL; if (fd < 0) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; if (s->base_mode != (mode_t) -1) return -EBUSY; if (s->base_path) return -EBUSY; if (s->boundary_fd >= 0) return -EBUSY; if (s->boundary_path) return -EBUSY; if (s->direction == CA_SYNC_ENCODE) return -ENOTTY; s->boundary_fd = fd; return 0; } int ca_sync_set_boundary_path(CaSync *s, const char *p) { if (!s) return -EINVAL; if (!p) return -EINVAL; if (s->base_fd >= 0) return -EBUSY; if (s->base_mode != (mode_t) -1) return -EBUSY; if (s->base_path) return -EBUSY; if (s->boundary_fd >= 0) return -EBUSY; if (s->boundary_path) return -EBUSY; if (s->direction == CA_SYNC_ENCODE) return -ENOTTY; s->boundary_fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); if (s->boundary_fd >= 0) /* Base exists already is a directory, good */ return 0; if (errno != ENOENT) return -errno; s->boundary_path = strdup(p); if (!s->boundary_path) return -ENOMEM; return 0; } int ca_sync_set_archive_fd(CaSync *s, int fd) { if (!s) return -EINVAL; if (fd < 0) return -EINVAL; if (s->archive_fd >= 0) return -EBUSY; if (s->archive_path) return -EBUSY; if (s->remote_archive) return -EBUSY; s->archive_fd = fd; return 0; } int ca_sync_set_archive_path(CaSync *s, const char *path) { if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->archive_fd >= 0) return -EBUSY; if (s->archive_path) return -EBUSY; if (s->remote_archive) return -EBUSY; if (s->direction == CA_SYNC_ENCODE) { s->archive_path = strdup(path); if (!s->archive_path) return -ENOMEM; return 0; } assert(s->direction == CA_SYNC_DECODE); s->archive_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (s->archive_fd < 0) return -errno; return 0; } int ca_sync_set_archive_remote(CaSync *s, const char *url) { int r; if (!s) return -EINVAL; if (!url) return -EINVAL; if (s->archive_fd >= 0) return -EBUSY; if (s->archive_path) return -EBUSY; if (s->remote_archive) return -EBUSY; s->remote_archive = ca_remote_new(); if (!s->remote_archive) return -ENOMEM; r = ca_remote_set_archive_url(s->remote_archive, url); if (r < 0) return r; r = ca_remote_set_local_feature_flags(s->remote_archive, s->direction == CA_SYNC_ENCODE ? CA_PROTOCOL_PUSH_ARCHIVE : CA_PROTOCOL_PULL_ARCHIVE); if (r < 0) return r; return 0; } int ca_sync_set_archive_auto(CaSync *s, const char *locator) { CaLocatorClass c; if (!s) return -EINVAL; if (!locator) return -EINVAL; c = ca_classify_locator(locator); if (c < 0) return -EINVAL; if (c == CA_LOCATOR_PATH) return ca_sync_set_archive_path(s, locator); return ca_sync_set_archive_remote(s, locator); } int ca_sync_set_store_path(CaSync *s, const char *path) { int r; if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->wstore) return -EBUSY; if (s->remote_wstore) return -EBUSY; s->wstore = ca_store_new(); if (!s->wstore) return -ENOMEM; r = ca_store_set_path(s->wstore, path); if (r < 0) { s->wstore = ca_store_unref(s->wstore); return r; } return 0; } int ca_sync_set_store_remote(CaSync *s, const char *url) { uint64_t flags; int r; if (!s) return -EINVAL; if (!url) return -EINVAL; if (s->wstore) return -EBUSY; if (s->remote_wstore) return -EBUSY; flags = s->direction == CA_SYNC_ENCODE ? CA_PROTOCOL_PUSH_CHUNKS : CA_PROTOCOL_PULL_CHUNKS; if (s->remote_index) { /* Try to reuse the index remote for the main store too, if it matches the same server */ r = ca_remote_set_store_url(s->remote_index, url); if (r >= 0) { r = ca_remote_add_local_feature_flags(s->remote_index, flags); if (r < 0) return r; s->remote_wstore = ca_remote_ref(s->remote_index); return 0; } if (r != -EBUSY) return r; } s->remote_wstore = ca_remote_new(); if (!s->remote_wstore) return -ENOMEM; if (s->rate_limit_bps > 0) { r = ca_remote_set_rate_limit_bps(s->remote_wstore, s->rate_limit_bps); if (r < 0) return r; } r = ca_remote_set_store_url(s->remote_wstore, url); if (r < 0) return r; r = ca_remote_set_local_feature_flags(s->remote_wstore, flags); if (r < 0) return r; return 0; } int ca_sync_set_store_auto(CaSync *s, const char *locator) { CaLocatorClass c; if (!s) return -EINVAL; if (!locator) return -EINVAL; c = ca_classify_locator(locator); if (c < 0) return -EINVAL; if (c == CA_LOCATOR_PATH) return ca_sync_set_store_path(s, locator); return ca_sync_set_store_remote(s, locator); } int ca_sync_add_store_path(CaSync *s, const char *path) { CaStore **array, *store; int r; if (!s) return -EINVAL; if (!path) return -EINVAL; store = ca_store_new(); if (!store) return -ENOMEM; r = ca_store_set_path(store, path); if (r < 0) { ca_store_unref(store); return r; } array = realloc_multiply(s->rstores, sizeof(CaStore*), s->n_rstores+1); if (!array) { ca_store_unref(store); return -ENOMEM; } s->rstores = array; s->rstores[s->n_rstores++] = store; return 0; } int ca_sync_add_store_remote(CaSync *s, const char *url) { CaRemote **array, *remote; int r; if (!s) return -EINVAL; if (!url) return -EINVAL; remote = ca_remote_new(); if (!remote) return -ENOMEM; r = ca_remote_set_store_url(remote, url); if (r < 0) { ca_remote_unref(remote); return r; } array = realloc_multiply(s->remote_rstores, sizeof(CaRemote*), s->n_remote_rstores+1); if (!array) { ca_remote_unref(remote); return -ENOMEM; } s->remote_rstores = array; s->remote_rstores[s->n_remote_rstores++] = remote; return 0; } int ca_sync_add_store_auto(CaSync *s, const char *locator) { CaLocatorClass c; if (!s) return -EINVAL; if (!locator) return -EINVAL; c = ca_classify_locator(locator); if (c < 0) return -EINVAL; if (c == CA_LOCATOR_PATH) return ca_sync_add_store_path(s, locator); return ca_sync_add_store_remote(s, locator); } static int ca_sync_extend_seeds_array(CaSync *s) { CaSeed **new_seeds; assert(s); new_seeds = realloc_multiply(s->seeds, sizeof(CaSeed*), s->n_seeds+1); if (!new_seeds) return -ENOMEM; s->seeds = new_seeds; return 0; } int ca_sync_add_seed_fd(CaSync *s, int fd) { CaSeed *seed; int r; if (!s) return -EINVAL; if (fd < 0) return -EINVAL; r = ca_sync_extend_seeds_array(s); if (r < 0) return r; seed = ca_seed_new(); if (!seed) return -ENOMEM; r = ca_seed_set_base_fd(seed, fd); if (r < 0) { ca_seed_unref(seed); return r; } s->seeds[s->n_seeds++] = seed; return 0; } int ca_sync_add_seed_path(CaSync *s, const char *path) { CaSeed *seed; int r; if (!s) return -EINVAL; if (!path) return -EINVAL; r = ca_sync_extend_seeds_array(s); if (r < 0) return r; seed = ca_seed_new(); if (!seed) return -ENOMEM; r = ca_seed_set_base_path(seed, path); if (r < 0) { ca_seed_unref(seed); return r; } s->seeds[s->n_seeds++] = seed; return 0; } static int ca_sync_start(CaSync *s) { size_t i; int r; assert(s); if (s->started) return 0; if (s->direction == CA_SYNC_ENCODE && s->archive_path && s->archive_fd < 0) { if (!s->temporary_archive_path) { r = tempfn_random(s->archive_path, &s->temporary_archive_path); if (r < 0) return r; } s->archive_fd = open(s->temporary_archive_path, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT|O_EXCL, s->make_mode & 0666); if (s->archive_fd < 0) { s->temporary_archive_path = mfree(s->temporary_archive_path); return -errno; } } if (s->direction == CA_SYNC_ENCODE && !s->encoder) { if (s->base_fd < 0) return -EUNATCH; s->encoder = ca_encoder_new(); if (!s->encoder) return -ENOMEM; r = ca_encoder_set_feature_flags(s->encoder, s->feature_flags); if (r < 0) { s->encoder = ca_encoder_unref(s->encoder); return r; } r = ca_encoder_set_base_fd(s->encoder, s->base_fd); if (r < 0) { s->encoder = ca_encoder_unref(s->encoder); return r; } s->base_fd = -1; r = ca_encoder_set_uid_shift(s->encoder, s->uid_shift); if (r < 0) return r; r = ca_encoder_set_uid_range(s->encoder, s->uid_range); if (r < 0) return r; } if (s->direction == CA_SYNC_DECODE && !s->decoder) { if (s->boundary_fd < 0 && s->boundary_path) { if (mkdir(s->boundary_path, 0777) < 0 && errno != EEXIST) return -errno; s->boundary_fd = open(s->boundary_path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); if (s->boundary_fd < 0) return -errno; } if (s->base_fd < 0 && s->base_path) { if (s->base_mode == (mode_t) -1) return -EUNATCH; if (S_ISDIR(s->base_mode)) { if (mkdir(s->base_path, 0777) < 0 && errno != EEXIST) return -errno; s->base_fd = open(s->base_path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); if (s->base_fd < 0) return -errno; } else if (S_ISREG(s->base_mode)) { if (!s->temporary_base_path) { r = tempfn_random(s->base_path, &s->temporary_base_path); if (r < 0) return r; } s->base_fd = open(s->temporary_base_path, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT|O_EXCL, 0666); if (s->base_fd < 0) { s->temporary_base_path = mfree(s->temporary_base_path); return -errno; } } else { assert(S_ISBLK(s->base_mode)); s->base_fd = open(s->base_path, O_WRONLY|O_CLOEXEC|O_NOCTTY); if (s->base_fd < 0) return -errno; } } s->decoder = ca_decoder_new(); if (!s->decoder) return -ENOMEM; /* There are three ways we can initialize the decoder: * * 1. We extract the whole archive. In this case we specify the "base" fd for the extraction, which is * going to be the top-level object we'll write to. If the archive is a directory tree, this needs * to be a directory fd, otherwise a regular file or block device. * * 2. We do not extract anything (i.e. only list the contents or such). In this case we don't specify * any fd, but we do specify the file type (directory or regular file/block device) of the top-level * object we'd extract to if we'd extract. This is necessary so that we know whether to simply * decode a raw file contents blob, or a full directory tree. * * 3. We extract a subtree of the whole archive. In this case we specify a "boundary" directory fd, * where to place the objected seeked to. Note that difference from the "base" fd: we'll always * extract the seeked to file as a subfile of the specified fd. */ if (s->boundary_fd >= 0) { r = ca_decoder_set_boundary_fd(s->decoder, s->boundary_fd); if (r < 0) return r; s->boundary_fd = -1; } else if (s->base_fd >= 0) { r = ca_decoder_set_base_fd(s->decoder, s->base_fd); if (r < 0) return r; s->base_fd = -1; } else { if (s->base_mode == (mode_t) -1) return -EUNATCH; r = ca_decoder_set_base_mode(s->decoder, s->base_mode); if (r < 0) return r; } if (s->archive_size != UINT64_MAX) { r = ca_decoder_set_archive_size(s->decoder, s->archive_size); if (r < 0) return r; } r = ca_decoder_set_punch_holes(s->decoder, s->punch_holes); if (r < 0) return r; r = ca_decoder_set_reflink(s->decoder, s->reflink); if (r < 0) return r; r = ca_decoder_set_hardlink(s->decoder, s->hardlink); if (r < 0) return r; r = ca_decoder_set_delete(s->decoder, s->delete); if (r < 0) return r; r = ca_decoder_set_payload(s->decoder, s->payload || s->remote_archive); if (r < 0) return r; r = ca_decoder_set_undo_immutable(s->decoder, s->undo_immutable); if (r < 0) return r; r = ca_decoder_set_uid_shift(s->decoder, s->uid_shift); if (r < 0) return r; r = ca_decoder_set_uid_range(s->decoder, s->uid_range); if (r < 0) return r; r = ca_decoder_set_feature_flags_mask(s->decoder, s->feature_flags_mask); if (r < 0) return r; } if (s->remote_index && !s->index) { if (s->direction == CA_SYNC_DECODE) s->index = ca_index_new_incremental_read(); else { assert(s->direction == CA_SYNC_ENCODE); s->index = ca_index_new_incremental_write(); } if (!s->index) return -ENOMEM; } if (s->remote_index && s->remote_wstore == s->remote_index && s->direction == CA_SYNC_ENCODE && !s->cache_store) { /* If we use the same server for index and storage, then we can optimize things a bit, and make the * server request what it is missing so far. */ s->cache_store = ca_store_new_cache(); if (!s->cache_store) return -ENOMEM; r = ca_store_set_digest_type(s->cache_store, ca_feature_flags_to_digest_type(s->feature_flags)); if (r < 0) return r; r = ca_remote_add_local_feature_flags(s->remote_index, CA_PROTOCOL_PUSH_INDEX_CHUNKS); if (r < 0) return r; } if (s->encoder) { /* If we are writing an index file we need the archive digest unconditionally, as it is included in its end */ r = ca_encoder_enable_archive_digest(s->encoder, s->archive_digest || s->index); if (r < 0) return r; r = ca_encoder_enable_payload_digest(s->encoder, s->payload_digest); if (r < 0) return r; r = ca_encoder_enable_hardlink_digest(s->encoder, s->hardlink_digest); if (r < 0) return r; } if (s->decoder) { r = ca_decoder_enable_archive_digest(s->decoder, s->archive_digest); if (r < 0) return r; r = ca_decoder_enable_payload_digest(s->decoder, s->payload_digest); if (r < 0) return r; r = ca_decoder_enable_hardlink_digest(s->decoder, s->hardlink_digest || s->hardlink); if (r < 0) return r; } if (s->index) { if (s->direction == CA_SYNC_ENCODE) { /* Propagate the chunk size to the index we generate */ r = ca_index_set_feature_flags(s->index, s->feature_flags); if (r < 0) return r; r = ca_index_set_chunk_size_min(s->index, s->chunker.chunk_size_min); if (r < 0) return r; r = ca_index_set_chunk_size_avg(s->index, s->chunker.chunk_size_avg); if (r < 0) return r; r = ca_index_set_chunk_size_max(s->index, s->chunker.chunk_size_max); if (r < 0) return r; if (s->make_mode != (mode_t) -1) { r = ca_index_set_make_mode(s->index, s->make_mode); if (r < 0 && r != -ENOTTY) return r; } } r = ca_index_open(s->index); if (r < 0) return r; } for (i = 0; i < s->n_seeds; i++) { /* Tell seeders whether to calculate hardlink and/or chunk seeds */ r = ca_seed_set_hardlink(s->seeds[i], s->hardlink); if (r < 0) return r; r = ca_seed_set_chunks(s->seeds[i], !!s->index); if (r < 0) return r; } /* Tell the wstore which compression algorithm to use */ if (s->wstore) { r = ca_store_set_compression_type(s->wstore, s->compression_type); if (r < 0) return r; } if (s->remote_wstore) { r = ca_remote_set_compression_type(s->remote_wstore, s->compression_type); if (r < 0) return r; } s->started = true; return 1; } static int ca_sync_write_archive(CaSync *s, const void *p, size_t l) { assert(s); assert(p || l == 0); if (s->archive_fd < 0) return 0; return loop_write(s->archive_fd, p, l); } static int ca_sync_write_remote_archive(CaSync *s, const void *p, size_t l) { assert(s); assert(p || l == 0); if (!s->remote_archive) return 0; return ca_remote_put_archive(s->remote_archive, p, l); } static int ca_sync_write_one_chunk(CaSync *s, const void *p, size_t l) { CaChunkID id; int r; assert(s); assert(p || l == 0); r = ca_sync_make_chunk_id(s, p, l, &id); if (r < 0) return r; s->n_written_chunks++; if (s->wstore) { r = ca_store_put(s->wstore, &id, CA_CHUNK_UNCOMPRESSED, p, l); if (r == -EEXIST) s->n_reused_chunks++; else if (r < 0) return r; } if (s->cache_store) { r = ca_store_put(s->cache_store, &id, CA_CHUNK_UNCOMPRESSED, p, l); if (r < 0 && r != -EEXIST) return r; } if (s->index) { r = ca_index_write_chunk(s->index, &id, l); if (r < 0) return r; } return 0; } static int ca_sync_write_chunks(CaSync *s, const void *p, size_t l) { int r; assert(s); assert(p || l == 0); if (!s->wstore && !s->cache_store && !s->index) return 0; while (l > 0) { const void *chunk; size_t chunk_size, k; k = ca_chunker_scan(&s->chunker, p, l); if (k == (size_t) -1) { if (!realloc_buffer_append(&s->buffer, p, l)) return -ENOMEM; return 0; } if (realloc_buffer_size(&s->buffer) == 0) { chunk = p; chunk_size = k; } else { if (!realloc_buffer_append(&s->buffer, p, k)) return -ENOMEM; chunk = realloc_buffer_data(&s->buffer); chunk_size = realloc_buffer_size(&s->buffer); } r = ca_sync_write_one_chunk(s, chunk, chunk_size); if (r < 0) return r; realloc_buffer_empty(&s->buffer); p = (const uint8_t*) p + k; l -= k; } return 0; } static int ca_sync_write_final_chunk(CaSync *s) { int r; assert(s); if (!s->wstore && !s->cache_store && !s->index) return 0; if (realloc_buffer_size(&s->buffer) == 0) return 0; r = ca_sync_write_one_chunk(s, realloc_buffer_data(&s->buffer), realloc_buffer_size(&s->buffer)); if (r < 0) return r; if (s->index) { r = ca_index_write_eof(s->index); if (r < 0) return r; r = ca_index_install(s->index); if (r < 0) return r; } return 0; } static int ca_sync_install_archive(CaSync *s) { assert(s); if (!s->temporary_archive_path) return 0; if (!s->archive_path) return 0; if (rename(s->temporary_archive_path, s->archive_path) < 0) return -errno; s->temporary_archive_path = mfree(s->temporary_archive_path); return 0; } static int ca_sync_write_remote_archive_eof(CaSync *s) { assert(s); if (!s->remote_archive) return 0; return ca_remote_put_archive_eof(s->remote_archive); } static int ca_sync_step_encode(CaSync *s) { int r, step; assert(s); if (s->archive_eof) return CA_SYNC_POLL; if (!s->encoder) return CA_SYNC_POLL; if (s->remote_archive) { /* If we shall store the result remotely, wait until the remote side accepts more data */ r = ca_remote_can_put_archive(s->remote_archive); if (r < 0) return r; if (r == 0) return CA_SYNC_POLL; } step = ca_encoder_step(s->encoder); if (step < 0) return step; switch (step) { case CA_ENCODER_FINISHED: r = ca_sync_write_final_chunk(s); if (r < 0) return r; r = ca_sync_install_archive(s); if (r < 0) return r; r = ca_sync_write_remote_archive_eof(s); if (r < 0) return r; s->archive_eof = true; /* If we install an index or archive remotely, let's decide the peer when it's done */ if (s->remote_index || s->remote_archive) return CA_SYNC_STEP; return CA_SYNC_FINISHED; case CA_ENCODER_NEXT_FILE: case CA_ENCODER_DONE_FILE: case CA_ENCODER_PAYLOAD: case CA_ENCODER_DATA: { const void *p; size_t l; r = ca_encoder_get_data(s->encoder, &p, &l); if (r >= 0) { r = ca_sync_write_chunks(s, p, l); if (r < 0) return r; r = ca_sync_write_archive(s, p, l); if (r < 0) return r; r = ca_sync_write_remote_archive(s, p, l); if (r < 0) return r; } else if (r != -ENODATA) return r; return step == CA_ENCODER_NEXT_FILE ? CA_SYNC_NEXT_FILE : step == CA_ENCODER_DONE_FILE ? CA_SYNC_DONE_FILE : step == CA_ENCODER_PAYLOAD ? CA_SYNC_PAYLOAD : CA_SYNC_STEP; } default: assert(false); } } static bool ca_sync_shall_seed(CaSync *s) { assert(s); if (s->direction != CA_SYNC_DECODE) return false; /* only run the seeds when decoding */ if (s->n_seeds == 0) /* no point in bothering if there are no seeds */ return false; if (!s->index && !s->hardlink) /* If there's no chunk index and hardlinking is turned off, there's no point of managing seeds. */ return false; return true; } static bool ca_sync_seed_ready(CaSync *s) { assert(s); if (!ca_sync_shall_seed(s)) return true; return s->current_seed >= s->n_seeds; } static int ca_sync_process_decoder_request(CaSync *s) { int r; assert(s); assert(s->decoder); if (s->index) { CaOrigin *origin = NULL; uint64_t chunk_size; const void *p; for (;;) { if (s->next_chunk_valid) { if (s->chunk_skip < s->next_chunk_size) break; s->chunk_skip -= s->next_chunk_size; s->next_chunk_valid = false; } r = ca_index_read_chunk(s->index, &s->next_chunk, NULL, &s->next_chunk_size); if (r == -EAGAIN) /* Not enough data */ return CA_SYNC_POLL; if (r < 0) return r; if (r == 0) { /* EOF */ r = ca_decoder_put_eof(s->decoder); if (r < 0) return r; return CA_SYNC_STEP; } s->next_chunk_valid = true; } /* If we haven't indexed all seeds yet, then let's not start decoding yet. If we came this far, we know * that the index header has been read at least, hence the seeders can be initialized with the index' * chunk size, hence let's wait for them to complete. */ if (!ca_sync_seed_ready(s)) return CA_SYNC_POLL; r = ca_sync_get(s, &s->next_chunk, CA_CHUNK_UNCOMPRESSED, &p, &chunk_size, NULL, &origin); if (r == -EAGAIN) /* Don't have this right now, but requested it now */ return CA_SYNC_STEP; if (r == -EALREADY) /* Don't have this right now, but it was already enqueued. */ return CA_SYNC_POLL; if (r < 0) return r; if (s->next_chunk_size != UINT64_MAX && /* next_chunk_size will be -1 if we just seeked in the index file */ s->next_chunk_size != chunk_size) { ca_origin_unref(origin); return -EBADMSG; } s->next_chunk_valid = false; if (s->chunk_skip > 0) { /* If we just seeked, then we might have seeked to a location inside of a chunk, hence skip as * many bytes as necessary */ if (s->chunk_skip >= chunk_size) { ca_origin_unref(origin); return -EINVAL; } p = (const uint8_t*) p + s->chunk_skip; chunk_size -= s->chunk_skip; s->chunk_skip = 0; } r = ca_decoder_put_data(s->decoder, p, chunk_size, origin); ca_origin_unref(origin); if (r < 0) return r; return CA_SYNC_STEP; } if (s->archive_fd >= 0) { void *p; ssize_t n; p = realloc_buffer_acquire(&s->archive_buffer, BUFFER_SIZE); if (!p) return -ENOMEM; n = read(s->archive_fd, p, BUFFER_SIZE); if (n < 0) return -errno; assert((size_t) n <= BUFFER_SIZE); if (n == 0) { r = ca_decoder_put_eof(s->decoder); if (r < 0) return r; } else { CaOrigin *origin; CaLocation *location; uint64_t offset; if (!s->archive_root) { r = ca_file_root_new(s->archive_path, s->archive_fd, &s->archive_root); if (r < 0) return r; } r = ca_decoder_get_request_offset(s->decoder, &offset); if (r < 0) return r; r = ca_origin_new(&origin); if (r < 0) return r; r = ca_location_new(NULL, CA_LOCATION_PAYLOAD, offset, n, &location); if (r < 0) { ca_origin_unref(origin); return r; } location->root = ca_file_root_ref(s->archive_root); r = ca_origin_put(origin, location); ca_location_unref(location); if (r < 0) { ca_origin_unref(origin); return r; } r = ca_decoder_put_data(s->decoder, p, n, origin); ca_origin_unref(origin); if (r < 0) return r; } realloc_buffer_empty(&s->archive_buffer); return CA_SYNC_STEP; } return CA_SYNC_POLL; } static int ca_sync_install_base(CaSync *s) { assert(s); if (!s->temporary_base_path) return 0; if (!s->base_path) return 0; if (rename(s->temporary_base_path, s->base_path) < 0) return -errno; s->temporary_base_path = mfree(s->temporary_base_path); return 0; } static void ca_sync_reset_seek(CaSync *s) { assert(s); s->archive_eof = false; s->remote_index_eof = false; s->next_chunk_valid = false; s->chunk_skip = 0; } static int ca_sync_process_decoder_seek(CaSync *s) { uint64_t offset; int r; assert(s); assert(s->decoder); ca_sync_reset_seek(s); r = ca_decoder_get_seek_offset(s->decoder, &offset); if (r < 0) return r; if (s->index) { r = ca_index_seek(s->index, offset, &s->chunk_skip); if (r < 0) return r; } else if (s->archive_fd >= 0) { off_t f; f = lseek(s->archive_fd, (off_t) offset, SEEK_SET); if (f == (off_t) -1) return -errno; } else return -EOPNOTSUPP; return CA_SYNC_STEP; } static int ca_sync_process_decoder_skip(CaSync *s) { uint64_t size; int r; assert(s); assert(s->decoder); r = ca_decoder_get_skip_size(s->decoder, &size); if (r < 0) return r; if (s->index) { if (s->chunk_skip + size < s->chunk_skip) return -EOVERFLOW; s->chunk_skip += size; } else if (s->archive_fd >= 0) { r = skip_bytes_fd(s->archive_fd, size); if (r < 0) return r; } else return -EOPNOTSUPP; return CA_SYNC_STEP; } static int ca_sync_try_hardlink(CaSync *s) { CaChunkID digest; mode_t mode; size_t i; int r; assert(s); if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (!s->hardlink) return 0; if (s->n_seeds == 0) return 0; r = ca_decoder_current_mode(s->decoder, &mode); if (r < 0) return r; if (!S_ISREG(mode)) return r; r = ca_decoder_get_hardlink_digest(s->decoder, &digest); if (r < 0) return r; for (i = 0; i < s->n_seeds; i++) { CaFileRoot *root; char *p; r = ca_seed_get_hardlink_target(s->seeds[i], &digest, &p); if (r == -ENOENT) continue; if (r < 0) return r; r = ca_seed_get_file_root(s->seeds[i], &root); if (r < 0) { free(p); return r; } r = ca_decoder_try_hardlink(s->decoder, root, p); free(p); if (r < 0) return r; if (r > 0) return 1; } return 0; } static int ca_sync_step_decode(CaSync *s) { int step, r; assert(s); if (!s->decoder) return CA_SYNC_POLL; if (s->archive_eof) return -EPIPE; step = ca_decoder_step(s->decoder); if (step < 0) return step; switch (step) { case CA_DECODER_FINISHED: r = ca_sync_install_base(s); if (r < 0) return r; s->archive_eof = true; return CA_SYNC_FINISHED; case CA_DECODER_NEXT_FILE: return CA_SYNC_NEXT_FILE; case CA_DECODER_DONE_FILE: r = ca_sync_try_hardlink(s); if (r < 0) return r; return CA_SYNC_DONE_FILE; case CA_DECODER_STEP: return CA_SYNC_STEP; case CA_DECODER_PAYLOAD: return CA_SYNC_PAYLOAD; case CA_DECODER_REQUEST: return ca_sync_process_decoder_request(s); case CA_DECODER_SEEK: return ca_sync_process_decoder_seek(s); case CA_DECODER_SKIP: return ca_sync_process_decoder_skip(s); case CA_DECODER_FOUND: return CA_SYNC_FOUND; case CA_DECODER_NOT_FOUND: return CA_SYNC_NOT_FOUND; default: assert(false); } } static CaSeed *ca_sync_current_seed(CaSync *s) { assert(s); if (!ca_sync_shall_seed(s)) return NULL; if (s->current_seed >= s->n_seeds) return NULL; return s->seeds[s->current_seed]; } static int ca_sync_seed_step(CaSync *s) { int r; assert(s); if (!ca_sync_shall_seed(s)) return CA_SYNC_POLL; if (s->index && !s->index_flags_propagated) /* Index flags/chunk sizes not propagated to the seeds yet. Let's wait until then */ return CA_SYNC_POLL; for (;;) { CaSeed *seed; seed = ca_sync_current_seed(s); if (!seed) break; r = ca_seed_step(seed); if (r < 0) return r; switch (r) { case CA_SEED_READY: break; case CA_SEED_STEP: return CA_SYNC_STEP; case CA_SEED_NEXT_FILE: return CA_SYNC_SEED_NEXT_FILE; case CA_SEED_DONE_FILE: return CA_SYNC_SEED_DONE_FILE; default: assert(false); } s->current_seed++; } return CA_SYNC_POLL; } static size_t ca_sync_n_remotes(CaSync *s) { size_t n; assert(s); n = s->n_remote_rstores; if (s->remote_archive) n++; if (s->remote_index) n++; if (s->remote_wstore && s->remote_wstore != s->remote_index) n++; return n; } static CaRemote *ca_sync_current_remote(CaSync *s) { size_t c; assert(s); s->current_remote %= ca_sync_n_remotes(s); c = s->current_remote; if (s->remote_archive) { if (c == 0) return s->remote_archive; c--; } if (s->remote_index) { if (c == 0) return s->remote_index; c--; } if (s->remote_wstore && s->remote_wstore != s->remote_index) { if (c == 0) return s->remote_wstore; c--; } return s->remote_rstores[c]; } static int ca_sync_remote_prefetch(CaSync *s) { uint64_t available, saved, requested = 0; int r; assert(s); if (!s->index) return CA_SYNC_POLL; if (s->direction != CA_SYNC_DECODE) return CA_SYNC_POLL; if (!s->remote_wstore) return CA_SYNC_POLL; if (!ca_sync_seed_ready(s)) return CA_SYNC_POLL; r = ca_index_get_available_chunks(s->index, &available); if (r == -ENODATA || r == -EAGAIN) return CA_SYNC_POLL; if (r < 0) return r; if (s->n_prefetched_chunks >= available) /* Already prefetched all we have */ return CA_SYNC_POLL; r = ca_index_get_position(s->index, &saved); if (r < 0) return r; r = ca_index_set_position(s->index, s->n_prefetched_chunks); if (r < 0) return r; for (;;) { CaChunkID id; r = ca_index_read_chunk(s->index, &id, NULL, NULL); if (r == 0 || r == -EAGAIN) break; if (r < 0) return r; s->n_prefetched_chunks++; r = ca_sync_has_local(s, &id); if (r < 0) return r; if (r > 0) continue; r = ca_remote_request_async(s->remote_wstore, &id, false); if (r < 0) return r; requested ++; } r = ca_index_set_position(s->index, saved); if (r < 0) return r; return requested > 0 ? CA_SYNC_STEP : CA_SYNC_POLL; } static int ca_sync_remote_push_index(CaSync *s) { int r; assert(s); if (!s->remote_index) return CA_SYNC_POLL; if (!s->index) return CA_SYNC_POLL; if (s->direction != CA_SYNC_ENCODE) return CA_SYNC_POLL; if (s->remote_index_eof) /* Already fully written? */ return CA_SYNC_POLL; r = ca_remote_can_put_index(s->remote_index); if (r < 0) return r; if (r == 0) return CA_SYNC_POLL; r = ca_index_incremental_read(s->index, &s->index_buffer); if (r == -EAGAIN) return CA_SYNC_POLL; if (r < 0) return r; if (r == 0) { r = ca_remote_put_index_eof(s->remote_index); if (r < 0) return r; s->remote_index_eof = true; return CA_SYNC_STEP; } r = ca_remote_put_index(s->remote_index, realloc_buffer_data(&s->index_buffer), realloc_buffer_size(&s->index_buffer)); if (r < 0) return r; return CA_SYNC_STEP; } static int ca_sync_remote_push_chunk(CaSync *s) { const void *p; CaChunkID id; uint64_t l; int r; assert(s); if (!s->remote_index) return CA_SYNC_POLL; if (!s->remote_wstore) return CA_SYNC_POLL; if (s->direction != CA_SYNC_ENCODE) return CA_SYNC_POLL; r = ca_remote_can_put_chunk(s->remote_wstore); if (r < 0) return r; if (r == 0) return CA_SYNC_POLL; r = ca_remote_next_request(s->remote_index, &id); if (r == -ENODATA) return CA_SYNC_POLL; if (r < 0) return r; r = ca_sync_get_local(s, &id, CA_CHUNK_COMPRESSED, &p, &l, NULL, NULL); if (r == -ENOENT) { r = ca_remote_put_missing(s->remote_wstore, &id); if (r < 0) return r; return CA_SYNC_STEP; } if (r < 0) return r; r = ca_remote_put_chunk(s->remote_wstore, &id, CA_CHUNK_COMPRESSED, p, l); if (r < 0) return r; return CA_SYNC_STEP; } static int ca_sync_remote_step_one(CaSync *s, CaRemote *rr) { int r, step; assert(s); assert(rr); step = ca_remote_step(rr); switch (step) { case CA_REMOTE_READ_INDEX: { const void *data; size_t size; r = ca_remote_read_index(rr, &data, &size); if (r < 0) return r; r = ca_index_incremental_write(s->index, data, size); if (r < 0) return r; break; } case CA_REMOTE_READ_INDEX_EOF: r = ca_index_incremental_eof(s->index); if (r < 0) return r; break; case CA_REMOTE_READ_ARCHIVE: { const void *data; size_t size; r = ca_remote_read_archive(rr, &data, &size); if (r < 0) return r; r = ca_decoder_put_data(s->decoder, data, size, NULL); if (r < 0) return r; break; } case CA_REMOTE_READ_ARCHIVE_EOF: r = ca_decoder_put_eof(s->decoder); if (r < 0) return r; break; } return step; } static int ca_sync_remote_step(CaSync *s) { size_t i; int r; assert(s); for (i = 0; i < ca_sync_n_remotes(s); i++) { CaRemote *remote; remote = ca_sync_current_remote(s); if (!remote) continue; s->current_remote++; r = ca_sync_remote_step_one(s, remote); if (r < 0) return r; switch (r) { case CA_REMOTE_POLL: case CA_REMOTE_WRITE_INDEX: case CA_REMOTE_WRITE_ARCHIVE: break; case CA_REMOTE_STEP: case CA_REMOTE_REQUEST: case CA_REMOTE_CHUNK: case CA_REMOTE_READ_INDEX: case CA_REMOTE_READ_INDEX_EOF: case CA_REMOTE_READ_ARCHIVE: case CA_REMOTE_READ_ARCHIVE_EOF: return CA_SYNC_STEP; case CA_REMOTE_FINISHED: return CA_SYNC_FINISHED; default: assert(false); } } return CA_SYNC_POLL; } static int ca_sync_propagate_flags_to_stores(CaSync *s, uint64_t flags) { CaDigestType dtype; size_t i; int r; dtype = ca_feature_flags_to_digest_type(flags); if (dtype < 0) return -EINVAL; if (s->wstore) { r = ca_store_set_digest_type(s->wstore, dtype); if (r < 0) return r; } for (i = 0; i < s->n_rstores; i++) { r = ca_store_set_digest_type(s->rstores[i], dtype); if (r < 0) return r; } if (s->cache_store) { r = ca_store_set_digest_type(s->cache_store, dtype); if (r < 0) return r; } return 0; } static int ca_sync_propagate_flags_to_seeds(CaSync *s, uint64_t flags, size_t cmin, size_t cavg, size_t cmax) { size_t i; int r; assert(s); for (i = 0; i < s->n_seeds; i++) { r = ca_seed_set_feature_flags(s->seeds[i], flags); if (r < 0) return r; r = ca_seed_set_chunk_size(s->seeds[i], cmin, cavg, cmax); if (r < 0) return r; } return 0; } static int ca_sync_propagate_flags_to_remotes(CaSync *s, uint64_t flags) { CaDigestType dtype; size_t i; int r; dtype = ca_feature_flags_to_digest_type(flags); if (dtype < 0) return -EINVAL; if (s->remote_archive) { r = ca_remote_set_digest_type(s->remote_archive, dtype); if (r < 0) return r; } if (s->remote_index) { r = ca_remote_set_digest_type(s->remote_index, dtype); if (r < 0) return r; } if (s->remote_wstore) { r = ca_remote_set_digest_type(s->remote_wstore, dtype); if (r < 0) return r; } for (i = 0; i < s->n_remote_rstores; i++) { r = ca_remote_set_digest_type(s->remote_rstores[i], dtype); if (r < 0) return r; } return 0; } static int ca_sync_propagate_flags_to_decoder(CaSync *s, uint64_t flags) { assert(s); if (!s->decoder) return 0; return ca_decoder_set_expected_feature_flags(s->decoder, flags); } static int ca_sync_propagate_index_flags(CaSync *s) { size_t cmin, cavg, cmax; uint64_t flags; int r; assert(s); /* If we read the header of the index file, make sure to propagate the flags and chunk size stored in it to the * seeds and remotes. */ if (s->direction != CA_SYNC_DECODE) return CA_SYNC_POLL; if (!s->index || s->index_flags_propagated) /* The flags/chunk size is already propagated */ return CA_SYNC_POLL; r = ca_index_get_feature_flags(s->index, &flags); if (r == -ENODATA) /* haven't read enough from the index header yet, let's wait */ return CA_SYNC_POLL; r = ca_index_get_chunk_size_min(s->index, &cmin); if (r < 0) return r; r = ca_index_get_chunk_size_avg(s->index, &cavg); if (r < 0) return r; r = ca_index_get_chunk_size_max(s->index, &cmax); if (r < 0) return r; r = ca_sync_propagate_flags_to_stores(s, flags); if (r < 0) return r; r = ca_sync_propagate_flags_to_seeds(s, flags, cmin, cavg, cmax); if (r < 0) return r; r = ca_sync_propagate_flags_to_remotes(s, flags); if (r < 0) return r; r = ca_sync_propagate_flags_to_decoder(s, flags); if (r < 0) return r; s->index_flags_propagated = true; return CA_SYNC_STEP; } int ca_sync_step(CaSync *s) { int r; if (!s) return -EINVAL; r = ca_sync_start(s); if (r < 0) return r; r = ca_sync_propagate_index_flags(s); if (r != CA_SYNC_POLL) return r; r = ca_sync_seed_step(s); if (r != CA_SYNC_POLL) return r; r = ca_sync_remote_prefetch(s); if (r != CA_SYNC_POLL) return r; r = ca_sync_remote_push_index(s); if (r != CA_SYNC_POLL) return r; r = ca_sync_remote_push_chunk(s); if (r != CA_SYNC_POLL) return r; /* Try to decode as much as we already have received, before accepting new remote data */ r = ca_sync_step_decode(s); if (r != CA_SYNC_POLL) return r; /* Then process any remote traffic and flush our own buffers */ r = ca_sync_remote_step(s); if (r != CA_SYNC_POLL) return r; /* Finally, generate new data */ return ca_sync_step_encode(s); } int ca_sync_get_local( CaSync *s, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression, CaOrigin **ret_origin) { size_t i; int r; if (!s) return -EINVAL; if (!chunk_id) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; for (i = 0; i < s->n_seeds; i++) { CaOrigin *origin = NULL; const void *p; size_t l; r = ca_seed_get(s->seeds[i], chunk_id, &p, &l, ret_origin ? &origin : NULL); if (r == -ESTALE) { log_info("Chunk cache is not up-to-date, ignoring."); continue; } if (r == -ENOLINK) { log_info("Can't reproduce name table for GOODBYE record, ignoring."); continue; } if (r == -ENOENT) continue; if (r < 0) return r; if (desired_compression == CA_CHUNK_COMPRESSED) { realloc_buffer_empty(&s->compress_buffer); r = ca_compress(s->compression_type, p, l, &s->compress_buffer); if (r < 0) { ca_origin_unref(origin); return r; } *ret = realloc_buffer_data(&s->compress_buffer); *ret_size = realloc_buffer_size(&s->compress_buffer); if (ret_effective_compression) *ret_effective_compression = CA_CHUNK_COMPRESSED; } else { *ret = p; *ret_size = l; if (ret_effective_compression) *ret_effective_compression = CA_CHUNK_UNCOMPRESSED; } if (ret_origin) *ret_origin = origin; return r; } if (s->wstore) { r = ca_store_get(s->wstore, chunk_id, desired_compression, ret, ret_size, ret_effective_compression); if (r >= 0) { if (ret_origin) *ret_origin = NULL; return r; } if (r != -ENOENT) return r; } if (s->cache_store) { r = ca_store_get(s->cache_store, chunk_id, desired_compression, ret, ret_size, ret_effective_compression); if (r >= 0) { if (ret_origin) *ret_origin = NULL; return r; } if (r != -ENOENT) return r; } for (i = 0; i < s->n_rstores; i++) { r = ca_store_get(s->rstores[i], chunk_id, desired_compression, ret, ret_size, ret_effective_compression); if (r >= 0) { if (ret_origin) *ret_origin = NULL; return r; } if (r != -ENOENT) return r; } return -ENOENT; } int ca_sync_get(CaSync *s, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression, CaOrigin **ret_origin) { size_t i; int r; if (!s) return -EINVAL; if (!chunk_id) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; r = ca_sync_get_local(s, chunk_id, desired_compression, ret, ret_size, ret_effective_compression, ret_origin); if (r != -ENOENT) return r; if (s->remote_wstore) { r = ca_remote_request(s->remote_wstore, chunk_id, true, desired_compression, ret, ret_size, ret_effective_compression); if (r >= 0) { if (ret_origin) *ret_origin = NULL; return r; } if (r != -ENOENT) return r; } for (i = 0; i < s->n_remote_rstores; i++) { r = ca_remote_request(s->remote_rstores[i], chunk_id, true, desired_compression, ret, ret_size, ret_effective_compression); if (r >= 0) { if (ret_origin) *ret_origin = NULL; return r; } if (r != -ENOENT) return r; } return -ENOENT; } int ca_sync_has_local(CaSync *s, const CaChunkID *chunk_id) { size_t i; int r; if (!s) return -EINVAL; if (!chunk_id) return -EINVAL; for (i = 0; i < s->n_seeds; i++) { r = ca_seed_has(s->seeds[i], chunk_id); if (r != 0) return r; } if (s->wstore) { r = ca_store_has(s->wstore, chunk_id); if (r != 0) return r; } if (s->cache_store) { r = ca_store_has(s->cache_store, chunk_id); if (r != 0) return r; } for (i = 0; i < s->n_rstores; i++) { r = ca_store_has(s->rstores[i], chunk_id); if (r != 0) return r; } return 0; } int ca_sync_make_chunk_id(CaSync *s, const void *p, size_t l, CaChunkID *ret) { int r; if (!s) return -EINVAL; if (!p && l > 0) return -EINVAL; if (!ret) return -EINVAL; if (!s->chunk_digest) { r = ca_digest_new(ca_feature_flags_to_digest_type(s->feature_flags), &s->chunk_digest); if (r < 0) return r; } return ca_chunk_id_make(s->chunk_digest, p, l, ret); } int ca_sync_get_archive_digest(CaSync *s, CaChunkID *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->archive_digest) return -ENOMEDIUM; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_get_archive_digest(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_get_archive_digest(s->decoder, ret); return -ENOTTY; } int ca_sync_get_payload_digest(CaSync *s, CaChunkID *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->payload_digest) return -ENOMEDIUM; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_get_payload_digest(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_get_payload_digest(s->decoder, ret); return -ENOTTY; } int ca_sync_get_hardlink_digest(CaSync *s, CaChunkID *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!s->hardlink_digest) return -ENOMEDIUM; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_get_hardlink_digest(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_get_hardlink_digest(s->decoder, ret); return -ENOTTY; } int ca_sync_current_path(CaSync *s, char **ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return ca_seed_current_path(seed, ret); if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_path(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_path(s->decoder, ret); return -ENOTTY; } int ca_sync_current_mode(CaSync *s, mode_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return ca_seed_current_mode(seed, ret); if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_mode(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_mode(s->decoder, ret); return -ENOTTY; } int ca_sync_current_target(CaSync *s, const char **ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_target(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_target(s->decoder, ret); return -ENOTTY; } int ca_sync_current_mtime(CaSync *s, uint64_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_mtime(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_mtime(s->decoder, ret); return -ENOTTY; } int ca_sync_current_size(CaSync *s, uint64_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_size(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_size(s->decoder, ret); return -ENOTTY; } int ca_sync_current_uid(CaSync *s, uid_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_uid(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_uid(s->decoder, ret); return -ENOTTY; } int ca_sync_current_gid(CaSync *s, gid_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_gid(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_gid(s->decoder, ret); return -ENOTTY; } int ca_sync_current_user(CaSync *s, const char **ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_user(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_user(s->decoder, ret); return -ENOTTY; } int ca_sync_current_group(CaSync *s, const char **ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_group(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_group(s->decoder, ret); return -ENOTTY; } int ca_sync_current_rdev(CaSync *s, dev_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_rdev(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_rdev(s->decoder, ret); return -ENOTTY; } int ca_sync_current_chattr(CaSync *s, unsigned *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_chattr(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_chattr(s->decoder, ret); return -ENOTTY; } int ca_sync_current_fat_attrs(CaSync *s, uint32_t *ret) { CaSeed *seed; if (!s) return -EINVAL; if (!ret) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_fat_attrs(s->encoder, ret); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_fat_attrs(s->decoder, ret); return -ENOTTY; } int ca_sync_current_xattr(CaSync *s, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size) { CaSeed *seed; if (!s) return -EINVAL; if (!ret_name) return -EINVAL; if (where < 0 || where > _CA_ITERATE_MAX) return -EINVAL; seed = ca_sync_current_seed(s); if (seed) return -ENODATA; if (s->direction == CA_SYNC_ENCODE && s->encoder) return ca_encoder_current_xattr(s->encoder, where, ret_name, ret_value, ret_size); if (s->direction == CA_SYNC_DECODE && s->decoder) return ca_decoder_current_xattr(s->decoder, where, ret_name, ret_value, ret_size); return -ENOTTY; } static int ca_sync_add_pollfd(CaRemote *rr, struct pollfd *pollfd) { int r; assert(pollfd); if (!rr) return 0; r = ca_remote_get_io_fds(rr, &pollfd[0].fd, &pollfd[1].fd); if (r < 0) return r; r = ca_remote_get_io_events(rr, &pollfd[0].events, &pollfd[1].events); if (r < 0) return r; return 2; } int ca_sync_poll(CaSync *s, uint64_t timeout_nsec, const sigset_t *ss) { struct pollfd *pollfd; size_t i, n = 0, n_pollfd; int r; if (!s) return -EINVAL; n_pollfd = (!!s->remote_archive + !!s->remote_index + !!s->remote_wstore + s->n_remote_rstores) * 2; if (n_pollfd == 0) return -EUNATCH; pollfd = newa(struct pollfd, n_pollfd); r = ca_sync_add_pollfd(s->remote_archive, pollfd); if (r < 0) return r; n += r; r = ca_sync_add_pollfd(s->remote_index, pollfd + n); if (r < 0) return r; n += r; r = ca_sync_add_pollfd(s->remote_wstore, pollfd + n); if (r < 0) return r; n += r; for (i = 0; i < s->n_remote_rstores; i++) { r = ca_sync_add_pollfd(s->remote_rstores[i], pollfd + n); if (r < 0) return r; n += r; } assert_se(n == n_pollfd); if (timeout_nsec != UINT64_MAX) { struct timespec ts; ts = nsec_to_timespec(timeout_nsec); r = ppoll(pollfd, n, &ts, ss); } else r = ppoll(pollfd, n, NULL, ss); if (r < 0) return -errno; return n; } int ca_sync_current_archive_chunks(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->direction != CA_SYNC_ENCODE) return -ENODATA; if (!s->wstore && !s->cache_store && !s->index) return -ENODATA; *ret = s->n_written_chunks; return 0; } int ca_sync_current_archive_reused_chunks(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->direction != CA_SYNC_ENCODE) return -ENODATA; if (!s->wstore) /* we can count this only on local wstores */ return -ENODATA; *ret = s->n_reused_chunks; return 0; } int ca_sync_current_archive_offset(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->encoder) return ca_encoder_current_archive_offset(s->encoder, ret); if (s->decoder) return ca_decoder_current_archive_offset(s->decoder, ret); return -ENOTTY; } static int ca_sync_acquire_archive_size(CaSync *s) { int r; assert(s); /* Makes sure the decoder knows how large the archive is. This is a requirement for working seeks, as the seek * tables are located at the end of archives */ if (s->archive_size != UINT64_MAX) return 0; if (s->archive_fd >= 0) { struct stat st; if (fstat(s->archive_fd, &st) < 0) return -errno; if (!S_ISREG(st.st_mode)) return -ESPIPE; s->archive_size = st.st_size; } else if (s->index) { r = ca_index_get_blob_size(s->index, &s->archive_size); if (r < 0) return r; } else return -EOPNOTSUPP; r = ca_decoder_set_archive_size(s->decoder, s->archive_size); if (r < 0) return r; return 1; } int ca_sync_seek_offset(CaSync *s, uint64_t offset) { int r; if (!s) return -EINVAL; if (offset == UINT64_MAX) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; r = ca_sync_start(s); if (r < 0) return r; if (!s->decoder) return -ESPIPE; r = ca_sync_acquire_archive_size(s); if (r < 0) return r; r = ca_decoder_seek_offset(s->decoder, offset); if (r < 0) return r; ca_sync_reset_seek(s); return 0; } int ca_sync_seek_path_offset(CaSync *s, const char *path, uint64_t offset) { int r; if (!s) return -EINVAL; if (!path) return -EINVAL; if (offset == UINT64_MAX) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; r = ca_sync_start(s); if (r < 0) return r; if (!s->decoder) return -ESPIPE; r = ca_sync_acquire_archive_size(s); if (r < 0) return r; r = ca_decoder_seek_path_offset(s->decoder, path, offset); if (r < 0) return r; ca_sync_reset_seek(s); return 0; } int ca_sync_seek_path(CaSync *s, const char *path) { int r; if (!s) return -EINVAL; if (!path) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; r = ca_sync_start(s); if (r < 0) return r; if (!s->decoder) return -ESPIPE; r = ca_sync_acquire_archive_size(s); if (r < 0) return r; r = ca_decoder_seek_path(s->decoder, path); if(r < 0) return r; ca_sync_reset_seek(s); return 0; } int ca_sync_seek_next_sibling(CaSync *s) { int r; if (!s) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; r = ca_sync_start(s); if (r < 0) return r; if (!s->decoder) return -ESPIPE; r = ca_sync_acquire_archive_size(s); if (r < 0) return r; r = ca_decoder_seek_next_sibling(s->decoder); if(r < 0) return r; ca_sync_reset_seek(s); return 0; } int ca_sync_get_payload(CaSync *s, const void **ret, size_t *ret_size) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (!ret_size) return -EINVAL; if (s->decoder) return ca_decoder_get_payload(s->decoder, ret, ret_size); else if (s->encoder) return ca_encoder_get_data(s->encoder, ret, ret_size); return -ENOTTY; } int ca_sync_get_archive_size(CaSync *s, uint64_t *ret_size) { int r; if (!s) return -EINVAL; if (!ret_size) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; r = ca_sync_start(s); if (r < 0) return r; if (!s->decoder) return -ESPIPE; r = ca_sync_acquire_archive_size(s); if (r < 0) return r; *ret_size = s->archive_size; return 0; } int ca_sync_get_punch_holes_bytes(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (!s->punch_holes) return -ENODATA; if (!s->decoder) { *ret = 0; return 0; } return ca_decoder_get_punch_holes_bytes(s->decoder, ret); } int ca_sync_get_reflink_bytes(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (!s->reflink) return -ENODATA; if (!s->decoder) { *ret = 0; return 0; } return ca_decoder_get_reflink_bytes(s->decoder, ret); } int ca_sync_get_hardlink_bytes(CaSync *s, uint64_t *ret) { if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->direction != CA_SYNC_DECODE) return -ENOTTY; if (!s->hardlink) return -ENODATA; if (!s->decoder) { *ret = 0; return 0; } return ca_decoder_get_hardlink_bytes(s->decoder, ret); } int ca_sync_enable_archive_digest(CaSync *s, bool b) { int r; if (!s) return -EINVAL; if (s->archive_digest == b) return 0; if (s->encoder) { /* If we are writing an index file we need the archive digest unconditionally, as it is included in its end */ r = ca_encoder_enable_archive_digest(s->encoder, b || s->index); if (r < 0) return r; } if (s->decoder) { r = ca_decoder_enable_archive_digest(s->decoder, b); if (r < 0) return r; } s->archive_digest = b; return 1; } int ca_sync_enable_payload_digest(CaSync *s, bool b) { int r; if (!s) return -EINVAL; if (s->payload_digest == b) return 0; if (s->encoder) { r = ca_encoder_enable_payload_digest(s->encoder, b); if (r < 0) return r; } if (s->decoder) { r = ca_decoder_enable_payload_digest(s->decoder, b); if (r < 0) return r; } s->payload_digest = b; return 1; } int ca_sync_enable_hardlink_digest(CaSync *s, bool b) { int r; if (!s) return -EINVAL; if (s->hardlink_digest == b) return 0; if (s->encoder) { r = ca_encoder_enable_hardlink_digest(s->encoder, b); if (r < 0) return r; } if (s->decoder) { r = ca_decoder_enable_hardlink_digest(s->decoder, b || s->hardlink); if (r < 0) return r; } s->hardlink_digest = b; return 1; } int ca_sync_get_seed_requests(CaSync *s, uint64_t *ret) { uint64_t sum = 0; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; for (i = 0; i < s->n_seeds; i++) { uint64_t x; r = ca_seed_get_requests(s->seeds[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_get_seed_request_bytes(CaSync *s, uint64_t *ret) { uint64_t sum = 0; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; for (i = 0; i < s->n_seeds; i++) { uint64_t x; r = ca_seed_get_request_bytes(s->seeds[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_get_local_requests(CaSync *s, uint64_t *ret) { uint64_t sum; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->wstore) { r = ca_store_get_requests(s->wstore, &sum); if (r < 0) return r; } else sum = 0; for (i = 0; i < s->n_rstores; i++) { uint64_t x; r = ca_store_get_requests(s->rstores[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_get_local_request_bytes(CaSync *s, uint64_t *ret) { uint64_t sum; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->wstore) { r = ca_store_get_request_bytes(s->wstore, &sum); if (r < 0) return r; } else sum = 0; for (i = 0; i < s->n_rstores; i++) { uint64_t x; r = ca_store_get_request_bytes(s->rstores[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_get_remote_requests(CaSync *s, uint64_t *ret) { uint64_t sum; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->remote_wstore) { r = ca_remote_get_requests(s->remote_wstore, &sum); if (r < 0) return r; } else sum = 0; for (i = 0; i < s->n_remote_rstores; i++) { uint64_t x; r = ca_remote_get_requests(s->remote_rstores[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_get_remote_request_bytes(CaSync *s, uint64_t *ret) { uint64_t sum; size_t i; int r; if (!s) return -EINVAL; if (!ret) return -EINVAL; if (s->remote_wstore) { r = ca_remote_get_request_bytes(s->remote_wstore, &sum); if (r < 0) return r; } else sum = 0; for (i = 0; i < s->n_remote_rstores; i++) { uint64_t x; r = ca_remote_get_request_bytes(s->remote_rstores[i], &x); if (r < 0) return r; sum += x; } *ret = sum; return 0; } int ca_sync_set_compression_type(CaSync *s, CaCompressionType compression) { if (!s) return -EINVAL; if (compression < 0) return -EINVAL; if (compression >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; if (s->started) return -EBUSY; s->compression_type = compression; return 0; } src/casync.h000066400000000000000000000157451322621430500133100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocasynchfoo #define foocasynchfoo #include #include #include "cachunk.h" #include "cachunkid.h" #include "cacommon.h" #include "caorigin.h" typedef struct CaSync CaSync; enum { CA_SYNC_FINISHED, /* Synchronization is complete */ CA_SYNC_STEP, /* Did something, call me again soon! */ CA_SYNC_PAYLOAD, /* Did something, and there's payload you might want to read */ CA_SYNC_NEXT_FILE, /* Started synchronizing a new file, find out which one with ca_sync_current_path() */ CA_SYNC_DONE_FILE, /* Done synchronizing a file, find out which one with ca_sync_current_path() */ CA_SYNC_SEED_NEXT_FILE, /* Started indexing a new file as seed, find out which one with ca_sync_current_path() */ CA_SYNC_SEED_DONE_FILE, /* Finished indexing a file as seed, find out which one with ca_sync_current_path() */ CA_SYNC_POLL, /* Can't proceed with remote feedback, please use ca_sync_poll() to wait for it */ CA_SYNC_FOUND, /* Entry looked for was found and is read next */ CA_SYNC_NOT_FOUND, /* Entry you were looking for couldn't be found */ }; CaSync *ca_sync_new_encode(void); CaSync *ca_sync_new_decode(void); CaSync *ca_sync_unref(CaSync *sync); DEFINE_TRIVIAL_CLEANUP_FUNC(CaSync *, ca_sync_unref); int ca_sync_set_rate_limit_bps(CaSync *s, uint64_t rate_limit_bps); int ca_sync_set_feature_flags(CaSync *s, uint64_t flags); int ca_sync_get_feature_flags(CaSync *s, uint64_t *ret); int ca_sync_set_feature_flags_mask(CaSync *s, uint64_t mask); int ca_sync_get_covering_feature_flags(CaSync *s, uint64_t *ret); int ca_sync_set_punch_holes(CaSync *s, bool enabled); int ca_sync_set_reflink(CaSync *s, bool enabled); int ca_sync_set_hardlink(CaSync *s, bool enabled); int ca_sync_set_delete(CaSync *s, bool enabled); int ca_sync_set_payload(CaSync *s, bool enabled); int ca_sync_set_undo_immutable(CaSync *s, bool enabled); int ca_sync_set_compression_type(CaSync *s, CaCompressionType compression); int ca_sync_set_uid_shift(CaSync *s, uid_t uid); int ca_sync_set_uid_range(CaSync *s, uid_t uid); /* Mode mask to use for created archive or index files */ int ca_sync_set_make_mode(CaSync *sync, mode_t mode); /* The index file, that contains the hashes + offsets */ int ca_sync_set_index_fd(CaSync *sync, int fd); int ca_sync_set_index_path(CaSync *sync, const char *path); int ca_sync_set_index_remote(CaSync *sync, const char *url); int ca_sync_set_index_auto(CaSync *s, const char *locator); /* The raw, unarchived ("user") tree */ int ca_sync_set_base_fd(CaSync *sync, int fd); int ca_sync_set_base_path(CaSync *sync, const char *path); int ca_sync_set_base_mode(CaSync *sync, mode_t mode); /* The raw, unarchived ("user") "boundary" tree, in case seeking is used */ int ca_sync_set_boundary_fd(CaSync *sync, int fd); int ca_sync_set_boundary_path(CaSync *sync, const char *path); /* The serialization of the user tree */ int ca_sync_set_archive_fd(CaSync *sync, int fd); int ca_sync_set_archive_path(CaSync *sync, const char *path); int ca_sync_set_archive_remote(CaSync *sync, const char *url); int ca_sync_set_archive_auto(CaSync *sync, const char *url); /* The store to place data in (i.e. the "primary" store) */ int ca_sync_set_store_path(CaSync *sync, const char *path); int ca_sync_set_store_remote(CaSync *sync, const char *url); int ca_sync_set_store_auto(CaSync *s, const char *locator); /* Additional stores to use */ int ca_sync_add_store_path(CaSync *sync, const char *path); int ca_sync_add_store_remote(CaSync *sync, const char *url); int ca_sync_add_store_auto(CaSync *sync, const char *locator); /* Additional seeds to use */ int ca_sync_add_seed_fd(CaSync *sync, int fd); int ca_sync_add_seed_path(CaSync *sync, const char *path); int ca_sync_step(CaSync *sync); int ca_sync_poll(CaSync *s, uint64_t timeout_nsec, const sigset_t *ss); int ca_sync_current_path(CaSync *sync, char **ret); int ca_sync_current_mode(CaSync *sync, mode_t *ret); int ca_sync_current_target(CaSync *sync, const char **ret); int ca_sync_current_uid(CaSync *sync, uid_t *ret); int ca_sync_current_gid(CaSync *sync, gid_t *ret); int ca_sync_current_user(CaSync *sync, const char **ret); int ca_sync_current_group(CaSync *sync, const char **ret); int ca_sync_current_mtime(CaSync *sync, uint64_t *nsec); int ca_sync_current_size(CaSync *sync, uint64_t *ret); int ca_sync_current_rdev(CaSync *sync, dev_t *ret); int ca_sync_current_chattr(CaSync *sync, unsigned *ret); int ca_sync_current_fat_attrs(CaSync *sync, uint32_t *ret); int ca_sync_current_xattr(CaSync *sync, CaIterate where, const char **ret_name, const void **ret_value, size_t *ret_size); int ca_sync_get_archive_size(CaSync *s, uint64_t *ret); /* Low level chunk access */ int ca_sync_get_local(CaSync *s, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression, CaOrigin **ret_origin); int ca_sync_get(CaSync *s, const CaChunkID *chunk_id, CaChunkCompression desired_compression, const void **ret, uint64_t *ret_size, CaChunkCompression *ret_effective_compression, CaOrigin **ret_origin); int ca_sync_has_local(CaSync *s, const CaChunkID *chunk_id); int ca_sync_make_chunk_id(CaSync *s, const void *p, size_t l, CaChunkID *ret); int ca_sync_set_chunk_size_min(CaSync *s, uint64_t v); int ca_sync_set_chunk_size_avg(CaSync *s, uint64_t v); int ca_sync_set_chunk_size_max(CaSync *s, uint64_t v); int ca_sync_get_chunk_size_avg(CaSync *s, uint64_t *ret); int ca_sync_get_chunk_size_min(CaSync *s, uint64_t *ret); int ca_sync_get_chunk_size_max(CaSync *s, uint64_t *ret); int ca_sync_current_archive_chunks(CaSync *s, uint64_t *ret); int ca_sync_current_archive_reused_chunks(CaSync *s, uint64_t *ret); int ca_sync_current_archive_offset(CaSync *s, uint64_t *ret); int ca_sync_seek_offset(CaSync *s, uint64_t offset); int ca_sync_seek_path(CaSync *s, const char *path); int ca_sync_seek_path_offset(CaSync *s, const char *path, uint64_t offset); int ca_sync_seek_next_sibling(CaSync *s); int ca_sync_get_payload(CaSync *s, const void **ret, size_t *ret_size); int ca_sync_get_punch_holes_bytes(CaSync *s, uint64_t *ret); int ca_sync_get_reflink_bytes(CaSync *s, uint64_t *ret); int ca_sync_get_hardlink_bytes(CaSync *s, uint64_t *ret); int ca_sync_enable_hardlink_digest(CaSync *s, bool b); int ca_sync_enable_payload_digest(CaSync *s, bool b); int ca_sync_enable_archive_digest(CaSync *s, bool b); int ca_sync_get_archive_digest(CaSync *s, CaChunkID *ret); int ca_sync_get_payload_digest(CaSync *s, CaChunkID *ret); int ca_sync_get_hardlink_digest(CaSync *s, CaChunkID *ret); int ca_sync_get_seed_requests(CaSync *s, uint64_t *ret); int ca_sync_get_seed_request_bytes(CaSync *s, uint64_t *ret); int ca_sync_get_local_requests(CaSync *s, uint64_t *ret); int ca_sync_get_local_request_bytes(CaSync *s, uint64_t *ret); int ca_sync_get_remote_requests(CaSync *s, uint64_t *ret); int ca_sync_get_remote_request_bytes(CaSync *s, uint64_t *ret); #endif src/cautil.c000066400000000000000000000221771322621430500133010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "cautil.h" #include "util.h" static bool ca_is_definitely_path(const char *s) { assert(s); /* We consider ".", ".." and everything starting with either "/" or "./" a file system path. */ if (s[0] == '/') return true; if (s[0] == '.') { if (s[1] == 0) return true; if (s[1] == '/') return true; if (s[1] == '.' && s[2] == 0) return true; } return false; } bool ca_is_url(const char *s) { const char *e; size_t n, k; assert(s); /* Checks whether something appears to be a URL. This is inspired by RFC3986, but a bit more restricted, so * that we can clearly distuingish URLs from file system paths, and ssh specifications. For example, the kind * of URLs we are interested in must contain '://' as host/path separator. * * We explicit exclude all strings starting with either "/" or "./" as URL from being detected as URLs, so that * this can always be used for explicitly referencing local directories. */ if (ca_is_definitely_path(s)) return false; if (!strchr(URL_PROTOCOL_FIRST, s[0])) return false; n = 1 + strspn(s + 1, URL_PROTOCOL_CHARSET); e = startswith(s + n, "://"); if (!e) return false; k = strspn(e, HOSTNAME_CHARSET "@:[]"); if (k <= 0) return false; if (!IN_SET(e[k], '/', ';', '?', 0)) return false; return true; } bool ca_is_ssh_path(const char *s) { size_t n; assert(s); if (ca_is_definitely_path(s)) return false; n = strspn(s, HOSTNAME_CHARSET); if (n <= 0) return false; if (s[n] == '@') { size_t k; k = strspn(s + n + 1, HOSTNAME_CHARSET); if (k <= 0) return false; if (s[n + 1 + k] != ':') return false; if (isempty(s + n + 1 + k + 1)) return false; } else if (s[n] == ':') { if (isempty(s + n + 1)) return false; return true; } else return false; return true; } CaLocatorClass ca_classify_locator(const char *s) { if (isempty(s)) return _CA_LOCATOR_CLASS_INVALID; if (ca_is_url(s)) return CA_LOCATOR_URL; if (ca_is_ssh_path(s)) return CA_LOCATOR_SSH; return CA_LOCATOR_PATH; } char *ca_strip_file_url(const char *p) { const char *e, *f; char *t, *result; assert(p); /* If the input is a file:// URL, turn it into a normal path, in a very defensive way. */ e = startswith(p, "file://"); if (!e) return strdup(p); if (*e == '/') goto unescape; e = startswith(e, "localhost/"); if (e) { e --; goto unescape; } return strdup(p); unescape: result = new(char, strlen(e) + 1); if (!result) return NULL; for (f = e, t = result; *f; f++) { int a, b; if (f[0] == '%' && (a = unhexchar(f[1])) >= 0 && (b = unhexchar(f[2])) >= 0) { *(t++) = (char) (((uint8_t) a << 4) | (uint8_t) b); f += 2; continue; } *(t++) = *f; } *t = 0; return result; } bool ca_locator_has_suffix(const char *p, const char *suffix) { const char *e, *q; if (isempty(suffix)) return true; if (isempty(p)) return false; if (ca_is_url(p)) { size_t n; n = strlen(suffix); e = strrchr(p, '?'); if (!e) e = strrchr(p, ';'); if (!e) e = strchr(p, 0); if ((size_t) (e - p) < n) return false; return memcmp(e - n, suffix, n) == 0; } e = strrchr(p, '/'); if (e) e++; else e = p; q = endswith(e, suffix); return q && q != e; } bool ca_xattr_name_is_valid(const char *s) { const char *dot; /* Can't be empty */ if (isempty(s)) return false; /* Must contain dot */ dot = strchr(s, '.'); if (!dot) return false; /* Dot may not be at beginning or end */ if (dot == s) return false; if (dot[1] == 0) return false; /* Overall lengths must be <= 255, according to xattr(7) */ if (strlen(s) > 255) return false; return true; } bool ca_xattr_name_store(const char *name) { /* We only store xattrs from the "user." and "trusted." namespaces. The other namespaces have special * semantics, and we'll support them with explicit records instead. That's at least "security.capability" and * "security.selinux". */ if (!ca_xattr_name_is_valid(name)) return false; /* silently ignore xattrs with invalid names */ return startswith(name, "user.") || startswith(name, "trusted."); } const char *ca_compressed_chunk_suffix(void) { static const char *cached = NULL; const char *e; /* Old casync versions used the ".xz" suffix for storing compressed chunks, instead of ".cacnk" as today. To * maintain minimal compatibility, support overiding the suffix with an environment variable. */ if (cached) return cached; e = getenv("CASYNC_COMPRESSED_CHUNK_SUFFIX"); if (!e) e = ".cacnk"; cached = e; return cached; } int ca_locator_patch_last_component(const char *locator, const char *last_component, char **ret) { CaLocatorClass class; char *result; if (!locator) return -EINVAL; if (!last_component) return -EINVAL; if (!ret) return -EINVAL; class = ca_classify_locator(locator); if (class < 0) return -EINVAL; switch (class) { case CA_LOCATOR_URL: { const char *p, *q; char *d; /* First, skip protocol and hostname part */ p = strstr(locator, "://"); assert(p); p += 3; p += strcspn(p, "/;?"); /* Chop off any arguments */ q = p + strcspn(p, ";?"); /* Find the last "/" */ for (;;) { if (q <= p) break; if (q[-1] == '/') break; q--; } d = strndupa(locator, q - locator); if (endswith(d, "/")) result = strjoin(d, last_component); else result = strjoin(d, "/", last_component); break; } case CA_LOCATOR_SSH: { const char *prefix; const char *p; p = strchr(locator, ':'); assert(p); p++; prefix = strndupa(locator, p - locator); if (strchr(p, '/')) { char *d; d = dirname_malloc(p); if (!d) return -ENOMEM; if (endswith(d, "/")) result = strjoin(prefix, d, last_component); else result = strjoin(prefix, d, "/", last_component); free(d); } else result = strjoin(prefix, last_component); break; } case CA_LOCATOR_PATH: if (strchr(locator, '/')) { char *d; d = dirname_malloc(locator); if (!d) return -ENOMEM; if (endswith(d, "/")) result = strjoin(d, last_component); else result = strjoin(d, "/", last_component); free(d); } else result = strdup(last_component); break; default: assert_not_reached("Unknown locator type"); } if (!result) return -ENOMEM; *ret = result; return 0; } src/cautil.h000066400000000000000000000013361322621430500133000ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocautilhfoo #define foocautilhfoo #include bool ca_is_url(const char *s); bool ca_is_ssh_path(const char *s); typedef enum CaLocatorClass { CA_LOCATOR_PATH, CA_LOCATOR_SSH, CA_LOCATOR_URL, _CA_LOCATOR_CLASS_INVALID = -1, } CaLocatorClass; CaLocatorClass ca_classify_locator(const char *s); char *ca_strip_file_url(const char *p); bool ca_locator_has_suffix(const char *p, const char *suffix); bool ca_xattr_name_is_valid(const char *s); bool ca_xattr_name_store(const char *p); const char *ca_compressed_chunk_suffix(void); int ca_locator_patch_last_component(const char *locator, const char *last_component, char **ret); #endif src/chattr.c000066400000000000000000000051611322621430500132770ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "util.h" #include "chattr.h" int read_attr_fd(int fd, unsigned *ret) { assert(fd >= 0); assert(ret); if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) { if (!IN_SET(errno, ENOTTY, ENOSYS, EBADF, EOPNOTSUPP, EINVAL)) return -errno; /* If a file system or node type doesn't support chattr flags, then all flags should be considered 0 */ *ret = 0; return 0; } return 1; } int write_attr_fd(int fd, unsigned attr) { assert(fd >= 0); if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) { /* If we shall write the attributes as 0, and we can't write them because the file system or node type * doesn't support them, that's fine */ if (attr == 0 && IN_SET(errno, ENOTTY, ENOSYS, EBADF, EOPNOTSUPP, EINVAL)) return 0; return -errno; } return 1; } int mask_attr_fd(int fd, unsigned value, unsigned mask) { unsigned old_attr, new_attr; int r; assert(fd >= 0); if (mask == 0) return 0; r = read_attr_fd(fd, &old_attr); if (r < 0) return r; new_attr = (old_attr & ~mask) | (value & mask); if (new_attr == old_attr) return 0; return write_attr_fd(fd, new_attr); } int read_fat_attr_fd(int fd, uint32_t *ret) { assert(fd >= 0); assert(ret); if (ioctl(fd, FAT_IOCTL_GET_ATTRIBUTES, ret) < 0) { if (!IN_SET(errno, ENOTTY, ENOSYS, EBADF, EOPNOTSUPP, EINVAL)) return -errno; *ret = 0; return 0; } return 1; } int write_fat_attr_fd(int fd, uint32_t attr) { assert(fd >= 0); if (ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr) < 0) { if (attr == 0 && IN_SET(errno, ENOTTY, ENOSYS, EBADF, EOPNOTSUPP, EINVAL)) return 0; return -errno; } return 1; } int mask_fat_attr_fd(int fd, uint32_t value, uint32_t mask) { uint32_t old_attr, new_attr; int r; assert(fd >= 0); if (mask == 0) return 0; r = read_fat_attr_fd(fd, &old_attr); if (r < 0) return r; new_attr = (old_attr & ~mask) | (value & mask); if (new_attr == old_attr) return 0; return write_fat_attr_fd(fd, new_attr); } src/chattr.h000066400000000000000000000006331322621430500133030ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foochattrhfoo #define foochattrhfoo #include int read_attr_fd(int fd, unsigned *ret); int write_attr_fd(int fd, unsigned attr); int mask_attr_fd(int fd, unsigned value, unsigned mask); int read_fat_attr_fd(int fd, uint32_t *ret); int write_fat_attr_fd(int fd, uint32_t attr); int mask_fat_attr_fd(int fd, uint32_t value, uint32_t mask); #endif src/compressor.c000066400000000000000000000261551322621430500142140ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "compressor.h" #include "util.h" /* #undef EIO */ /* #define EIO __LINE__ */ /* #undef EBADMSG */ /* #define EBADMSG __LINE__ */ int detect_compression(const void *buffer, size_t size) { static const uint8_t xz_signature[] = { 0xfd, '7', 'z', 'X', 'Z', 0x00 }; static const uint8_t gzip_signature[] = { 0x1f, 0x8b }; static const uint8_t zstd_signature[] = { 0x28, 0xb5, 0x2f, 0xfd }; if (size >= sizeof(xz_signature) && memcmp(buffer, xz_signature, sizeof(xz_signature)) == 0) return CA_COMPRESSION_XZ; if (size >= sizeof(gzip_signature) && memcmp(buffer, gzip_signature, sizeof(gzip_signature)) == 0) return CA_COMPRESSION_GZIP; if (size >= sizeof(zstd_signature) && memcmp(buffer, zstd_signature, sizeof(zstd_signature)) == 0) return CA_COMPRESSION_ZSTD; if (size < MAX3(sizeof(xz_signature), sizeof(gzip_signature), sizeof(zstd_signature))) return -EAGAIN; /* Not ready to decide yet */ return -EBADMSG; } int compressor_start_decode(CompressorContext *c, CaCompressionType compressor) { int r; if (!c) return -EINVAL; if (compressor < 0) return -EINVAL; if (compressor >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; switch (compressor) { case CA_COMPRESSION_XZ: { lzma_ret xzr; xzr = lzma_stream_decoder(&c->xz, UINT64_MAX, LZMA_TELL_UNSUPPORTED_CHECK); if (xzr != LZMA_OK) return -EIO; break; } case CA_COMPRESSION_GZIP: r = inflateInit2(&c->gzip, 15 | 16); if (r != Z_OK) return -EIO; break; case CA_COMPRESSION_ZSTD: c->zstd.dstream = ZSTD_createDStream(); if (!c->zstd.dstream) return -ENOMEM; ZSTD_initDStream(c->zstd.dstream); break; default: assert_not_reached("Unknown decompressor."); } c->operation = COMPRESSOR_DECODE; c->compressor = compressor; return 0; } int compressor_start_encode(CompressorContext *c, CaCompressionType compressor) { int r; if (!c) return -EINVAL; if (compressor < 0) return -EINVAL; if (compressor >= _CA_COMPRESSION_TYPE_MAX) return -EOPNOTSUPP; switch (compressor) { case CA_COMPRESSION_XZ: { lzma_ret xzr; xzr = lzma_easy_encoder(&c->xz, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); if (xzr != LZMA_OK) return -EIO; break; } case CA_COMPRESSION_GZIP: r = deflateInit2(&c->gzip, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY); if (r != Z_OK) return -EIO; break; case CA_COMPRESSION_ZSTD: c->zstd.cstream = ZSTD_createCStream(); if (!c->zstd.cstream) return -ENOMEM; ZSTD_initCStream(c->zstd.cstream, 3); break; default: assert_not_reached("Unknown compressor."); } c->operation = COMPRESSOR_ENCODE; c->compressor = compressor; return 0; } void compressor_finish(CompressorContext *c) { if (!c) return; if (c->operation == COMPRESSOR_UNINITIALIZED) return; switch (c->compressor) { case CA_COMPRESSION_XZ: lzma_end(&c->xz); break; case CA_COMPRESSION_GZIP: if (c->operation == COMPRESSOR_DECODE) inflateEnd(&c->gzip); else if (c->operation == COMPRESSOR_ENCODE) deflateEnd(&c->gzip); else assert_not_reached("Unknown operation."); break; case CA_COMPRESSION_ZSTD: if (c->operation == COMPRESSOR_ENCODE) ZSTD_freeCStream(c->zstd.cstream); else if (c->operation == COMPRESSOR_DECODE) ZSTD_freeDStream(c->zstd.dstream); else assert_not_reached("Unknown operation."); break; default: assert_not_reached("Unknown compressor."); } } int compressor_input(CompressorContext *c, const void *p, size_t sz) { if (!c) return -EINVAL; if (sz > 0 && !p) return -EINVAL; switch (c->compressor) { case CA_COMPRESSION_XZ: c->xz.next_in = p; c->xz.avail_in = sz; break; case CA_COMPRESSION_GZIP: c->gzip.next_in = (void*) p; c->gzip.avail_in = sz; break; case CA_COMPRESSION_ZSTD: c->zstd.input = (ZSTD_inBuffer) { .src = p, .size = sz, }; break; default: assert_not_reached("Unknown compressor."); } return 0; } int compressor_decode(CompressorContext *c, void *p, size_t sz, size_t *ret_done) { int r; if (!c) return -EINVAL; if (sz > 0 && !p) return -EINVAL; if (!ret_done) return -EINVAL; if (c->operation != COMPRESSOR_DECODE) return -ENOTTY; switch (c->compressor) { case CA_COMPRESSION_XZ: { lzma_ret xzr; c->xz.next_out = p; c->xz.avail_out = sz; assert(c->xz.avail_out > 0); assert(c->xz.avail_in > 0); xzr = lzma_code(&c->xz, LZMA_RUN); if (xzr == LZMA_STREAM_END) { if (c->xz.avail_in > 0) return -EBADMSG; *ret_done = sz - c->xz.avail_out; return COMPRESSOR_EOF; } else if (xzr != LZMA_OK) return -EIO; *ret_done = sz - c->xz.avail_out; if (c->xz.avail_in > 0) return COMPRESSOR_MORE; return COMPRESSOR_GOOD; } case CA_COMPRESSION_GZIP: c->gzip.next_out = p; c->gzip.avail_out = sz; assert(c->gzip.avail_out > 0); assert(c->gzip.avail_in > 0); r = inflate(&c->gzip, Z_NO_FLUSH); if (r == Z_STREAM_END) { if (c->gzip.avail_in > 0) return -EBADMSG; *ret_done = sz - c->gzip.avail_out; return COMPRESSOR_EOF; } else if (r != Z_OK) return -EIO; *ret_done = sz - c->gzip.avail_out; if (c->gzip.avail_in > 0) return COMPRESSOR_MORE; return COMPRESSOR_GOOD; case CA_COMPRESSION_ZSTD: { int ret; size_t k; c->zstd.output = (ZSTD_outBuffer) { .dst = p, .size = sz, }; assert(c->zstd.output.size > c->zstd.output.pos); assert(c->zstd.input.size > c->zstd.input.pos); k = ZSTD_decompressStream(c->zstd.dstream, &c->zstd.output, &c->zstd.input); if (ZSTD_isError(k)) return -EIO; if (k == 0) { if (c->zstd.input.size > c->zstd.input.pos) return -EBADMSG; ret = COMPRESSOR_EOF; } else if (c->zstd.input.pos < c->zstd.input.size) ret = COMPRESSOR_MORE; else ret = COMPRESSOR_GOOD; *ret_done = c->zstd.output.pos; return ret; } default: assert_not_reached("Unknown compressor."); } } int compressor_encode(CompressorContext *c, bool finalize, void *p, size_t sz, size_t *ret_done) { int r; if (!c) return -EINVAL; if (sz > 0 && !p) return -EINVAL; if (!ret_done) return -EINVAL; if (c->operation != COMPRESSOR_ENCODE) return -ENOTTY; switch (c->compressor) { case CA_COMPRESSION_XZ: { lzma_ret xzr; c->xz.next_out = p; c->xz.avail_out = sz; xzr = lzma_code(&c->xz, finalize ? LZMA_FINISH : LZMA_RUN); if (xzr == LZMA_STREAM_END) { assert(c->xz.avail_in == 0); *ret_done = sz - c->xz.avail_out; return COMPRESSOR_EOF; } else if (xzr != LZMA_OK) return -EIO; *ret_done = sz - c->xz.avail_out; if (c->xz.avail_in > 0) return COMPRESSOR_MORE; return COMPRESSOR_GOOD; } case CA_COMPRESSION_GZIP: c->gzip.next_out = p; c->gzip.avail_out = sz; r = deflate(&c->gzip, finalize ? Z_FINISH : Z_NO_FLUSH); if (r == Z_STREAM_END) { assert(c->gzip.avail_in == 0); *ret_done = sz - c->gzip.avail_out; return COMPRESSOR_EOF; } else if (r != Z_OK) return -EIO; *ret_done = sz - c->gzip.avail_out; if (c->gzip.avail_in > 0) return COMPRESSOR_MORE; return COMPRESSOR_GOOD; case CA_COMPRESSION_ZSTD: { size_t k; c->zstd.output = (ZSTD_outBuffer) { .dst = p, .size = sz, }; assert(c->zstd.output.size > c->zstd.output.pos); if (c->zstd.input.size > c->zstd.input.pos) k = ZSTD_compressStream(c->zstd.cstream, &c->zstd.output, &c->zstd.input); else { assert(finalize); k = ZSTD_endStream(c->zstd.cstream, &c->zstd.output); } if (ZSTD_isError(k)) return -EIO; *ret_done = c->zstd.output.pos; if (c->zstd.input.pos < c->zstd.input.size) return COMPRESSOR_MORE; if (k == 0) return COMPRESSOR_EOF; return COMPRESSOR_GOOD; } default: assert_not_reached("Unknown compressor."); } } src/compressor.h000066400000000000000000000032341322621430500142120ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foocompresshorhfoo #define foocompresshorhfoo #include #include #include #include #include "cacompression.h" typedef enum CompressorOperation { COMPRESSOR_UNINITIALIZED, COMPRESSOR_ENCODE, COMPRESSOR_DECODE, } CompressorOperation; typedef struct CompressorContext { CompressorOperation operation; CaCompressionType compressor; union { lzma_stream xz; struct z_stream_s gzip; struct { ZSTD_CStream *cstream; ZSTD_DStream *dstream; ZSTD_inBuffer input; ZSTD_outBuffer output; } zstd; }; } CompressorContext; #define COMPRESSOR_CONTEXT_INIT \ { \ .operation = COMPRESSOR_UNINITIALIZED, \ .compressor = _CA_COMPRESSION_TYPE_INVALID, \ } int compressor_start_decode(CompressorContext *c, CaCompressionType compressor); int compressor_start_encode(CompressorContext *c, CaCompressionType compressor); void compressor_finish(CompressorContext *c); int compressor_input(CompressorContext *c, const void *p, size_t sz); enum { COMPRESSOR_EOF, COMPRESSOR_MORE, COMPRESSOR_GOOD, }; int compressor_decode(CompressorContext *c, void *p, size_t size, size_t *ret_done); int compressor_encode(CompressorContext *c, bool finalize, void *p, size_t size, size_t *ret_done); int detect_compression(const void *buffer, size_t size); #endif src/copy.c000066400000000000000000000123051322621430500127620ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "copy.h" #if !HAVE_COPY_FILE_RANGE # ifndef __NR_copy_file_range # if defined(__x86_64__) # define __NR_copy_file_range 326 # elif defined(__i386__) # define __NR_copy_file_range 377 # elif defined __s390__ # define __NR_copy_file_range 375 # elif defined __arm__ # define __NR_copy_file_range 391 # elif defined __aarch64__ # define __NR_copy_file_range 285 # elif defined __powerpc__ # define __NR_copy_file_range 379 # else # warning "__NR_copy_file_range not defined for your architecture" # endif # endif static inline ssize_t copy_file_range( int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned flags) { # ifdef __NR_copy_file_range return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); # else errno = ENOSYS; return -1; # endif } #endif static ssize_t try_copy_file_range( int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned flags) { static int have = -1; ssize_t r; if (have == false) return -ENOSYS; r = copy_file_range(fd_in, off_in, fd_out, off_out, len, flags); if (have < 0) have = r >= 0 || errno != ENOSYS; if (r >= 0) return r; else return -errno; } int copy_bytes(int fdf, int fdt, uint64_t max_bytes) { bool try_cfr = true, try_sendfile = true, try_splice = true; size_t m = SSIZE_MAX; /* that is the maximum that sendfile and c_f_r accept */ int r; assert(fdf >= 0); assert(fdt >= 0); for (;;) { ssize_t n; if (max_bytes != (uint64_t) -1) { if (max_bytes <= 0) return 1; /* return > 0 if we hit the max_bytes limit */ if (m > max_bytes) m = max_bytes; } /* First try copy_file_range(), unless we already tried */ if (try_cfr) { n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); if (n < 0) { if (!IN_SET(n, -EINVAL, -ENOSYS, -EXDEV, -EBADF)) return n; try_cfr = false; /* use fallback below */ } else if (n == 0) /* EOF */ break; else /* Success! */ goto next; } /* First try sendfile(), unless we already tried */ if (try_sendfile) { n = sendfile(fdt, fdf, NULL, m); if (n < 0) { if (!IN_SET(errno, EINVAL, ENOSYS)) return -errno; try_sendfile = false; /* use fallback below */ } else if (n == 0) /* EOF */ break; else /* Success! */ goto next; } /* Then try splice, unless we already tried */ if (try_splice) { n = splice(fdf, NULL, fdt, NULL, m, 0); if (n < 0) { if (!IN_SET(errno, EINVAL, ENOSYS)) return -errno; try_splice = false; /* use fallback below */ } else if (n == 0) /* EOF */ break; else /* Success! */ goto next; } /* As a fallback just copy bits by hand */ { uint8_t buf[MIN(m, BUFFER_SIZE)]; n = read(fdf, buf, sizeof(buf)); if (n < 0) return -errno; if (n == 0) /* EOF */ break; r = loop_write(fdt, buf, (size_t) n); if (r < 0) return r; } next: if (max_bytes != (uint64_t) -1) { assert(max_bytes >= (uint64_t) n); max_bytes -= n; } /* sendfile accepts at most SSIZE_MAX-offset bytes to copy, * so reduce our maximum by the amount we already copied, * but don't go below our copy buffer size, unless we are * close the limit of bytes we are allowed to copy. */ m = MAX(MIN(BUFFER_SIZE, max_bytes), m - n); } return 0; /* return 0 if we hit EOF earlier than the size limit */ } src/def.h000066400000000000000000000002171322621430500125520ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foodefhfoo #define foodefhfoo #define NODES_MAX 64 #define BUFFER_SIZE (64U*1024U) #endif src/dirent-util.c000066400000000000000000000026321322621430500142520ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010-2012 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include "dirent-util.h" bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { assert(de); if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) return false; if (de->d_name[0] == '.') return false; if (!suffix) return true; return endswith(de->d_name, suffix); } struct dirent* readdir_no_dot(DIR *dirp) { struct dirent* d; for (;;) { d = readdir(dirp); if (d && dot_or_dot_dot(d->d_name)) continue; return d; } } src/dirent-util.h000066400000000000000000000027611322621430500142620ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #pragma once #include #include #include #include "util.h" bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; struct dirent* readdir_no_dot(DIR *dirp); #define FOREACH_DIRENT_ALL(de, d, on_error) \ for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ if (!de) { \ if (errno > 0) { \ on_error; \ } \ break; \ } else src/fssize.c000066400000000000000000000135551322621430500133230ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "fssize.h" #include "util.h" #define FAT_SIGNATURE UINT16_C(0xaa55) #define _ANDROID_BOOTIMG_MAGIC_1 UINT32_C(0x52444e41) #define _ANDROID_BOOTIMG_MAGIC_2 UINT32_C(0x2144494f) int read_file_system_size(int fd, uint64_t *ret) { /* The squashfs and Android bootimg super block starts at offset 1024 */ union { struct { uint8_t ignored[3]; uint8_t system_id[8]; le16_t sector_size; uint8_t sec_per_cluster; le16_t reserved; uint8_t fats; le16_t dir_entries; le16_t sectors; uint8_t media; le16_t fat_length; le16_t secs_track; le16_t heads; le32_t hidden; le32_t total_sect; /* skip the boot code in the middle */ uint8_t _skip[474]; uint16_t signature; /* ignore the rest */ } _packed_ fat; struct { le32_t s_magic; le32_t inodes; le32_t mkfs_time; le32_t block_size; le32_t fragments; le16_t compression; le16_t block_log; le16_t flags; le16_t no_ids; le16_t s_major; le16_t s_minor; le64_t root_inode; le64_t bytes_used; /* ignore the rest */ } _packed_ squashfs; struct { le32_t magic; le32_t magic2; le32_t kernel_size; le32_t kernel_addr; le32_t initrd_size; le32_t initrd_addr; le32_t second_size; le32_t second_addr; le32_t tags_addr; le32_t page_size; le32_t dtb_size; /* ignore the rest */ } _packed_ android_bootimg; struct { /* The ext2, ext3, ext4 superblock starts at offset 1024 */ uint8_t _skip[1024]; le32_t s_inodes_count; le32_t s_blocks_count; le32_t s_r_blocks_count; le32_t s_free_blocks_count; le32_t s_free_inodes_count; le32_t s_first_data_block; le32_t s_log_block_size; le32_t s_log_frag_size; le32_t s_blocks_per_group; le32_t s_frags_per_group; le32_t s_inodes_per_group; le32_t s_mtime; le32_t s_wtime; le16_t s_mnt_count; le16_t s_max_mnt_count; le16_t s_magic; /* ignore the rest */ } _packed_ ext234; } superblock; ssize_t n; assert(fd >= 0); assert(ret); n = pread(fd, &superblock, sizeof(superblock), 0); if (n < 0) return -errno; if (n != sizeof(superblock)) return 0; /* don't know such short file systems */ if (le32toh(superblock.squashfs.s_magic == SQUASHFS_MAGIC)) { *ret = ALIGN_TO(le64toh(superblock.squashfs.bytes_used), UINT64_C(4096)); return 1; } if (le32toh(superblock.android_bootimg.magic) == _ANDROID_BOOTIMG_MAGIC_1 && le32toh(superblock.android_bootimg.magic2) == _ANDROID_BOOTIMG_MAGIC_2) { uint32_t pagesize; pagesize = le32toh(superblock.android_bootimg.page_size); if (IS_POWER_OF_TWO(pagesize)) { *ret = (uint64_t) ALIGN_TO(608, pagesize) /* header size */ + (uint64_t) ALIGN_TO(le32toh(superblock.android_bootimg.kernel_size), pagesize) + (uint64_t) ALIGN_TO(le32toh(superblock.android_bootimg.initrd_size), pagesize) + (uint64_t) ALIGN_TO(le32toh(superblock.android_bootimg.second_size), pagesize) + (uint64_t) ALIGN_TO(le32toh(superblock.android_bootimg.dtb_size), pagesize); return 1; } } if (le16toh(superblock.fat.signature) == FAT_SIGNATURE) { uint16_t sector_size; sector_size = le16toh(superblock.fat.sector_size); if (IS_POWER_OF_TWO(sector_size)) { uint64_t l; l = (uint64_t) le16toh(superblock.fat.sectors) * le16toh(superblock.fat.sector_size); if (l == 0) l = (uint64_t) le32toh(superblock.fat.total_sect) * le16toh(superblock.fat.sector_size); if (l > 0) { *ret = l; return 1; } } } if (le16toh(superblock.ext234.s_magic) == EXT2_SUPER_MAGIC) { uint64_t shift; shift = 10 + le32toh(superblock.ext234.s_log_block_size); if (shift < 64) { *ret = (uint64_t) le32toh(superblock.ext234.s_blocks_count) * (UINT64_C(1) << shift); return 1; } } return 0; } src/fssize.h000066400000000000000000000002501322621430500133140ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foofssizehfoo #define foofssizehfoo #include int read_file_system_size(int fd, uint64_t *ret); #endif src/gc.c000066400000000000000000000131661322621430500124070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "caindex.h" #include "gc.h" #include "set.h" typedef struct CaChunkCollection { size_t n_used; Set *used_chunks; } CaChunkCollection; static void chunk_hash_func(const void *p, struct siphash *state) { const CaChunkID *id = p; siphash24_compress(id, sizeof(CaChunkID), state); } static int chunk_compare_func(const void *a, const void *b) { return memcmp(a, b, sizeof(CaChunkID)); } const struct hash_ops chunk_hash_ops = { .hash = chunk_hash_func, .compare = chunk_compare_func, }; CaChunkCollection* ca_chunk_collection_new(void) { CaChunkCollection *c; c = new0(CaChunkCollection, 1); if (!c) return NULL; c->used_chunks = set_new(&chunk_hash_ops); return c; } CaChunkCollection* ca_chunk_collection_unref(CaChunkCollection *c) { if (!c) return NULL; set_free_free(c->used_chunks); return mfree(c); } int ca_chunk_collection_usage(CaChunkCollection *c, size_t *ret) { if (!c) return -EINVAL; if (!ret) return -EINVAL; *ret = c->n_used; return 0; } int ca_chunk_collection_size(CaChunkCollection *c, size_t *ret) { if (!c) return -EINVAL; if (!ret) return -EINVAL; *ret = set_size(c->used_chunks); return 0; } static int gc_add_chunk_id(CaChunkCollection *coll, CaChunkID *id) { CaChunkID *copy; int r; coll->n_used++; copy = memdup(id, sizeof(CaChunkID)); if (!copy) return log_oom(); r = set_consume(coll->used_chunks, copy); if (r == -EEXIST) return 0; return r; } int ca_chunk_collection_add_index(CaChunkCollection *coll, const char *path) { _cleanup_(ca_index_unrefp) CaIndex* index = NULL; int r; index = ca_index_new_read(); if (index == 0) return log_oom(); r = ca_index_set_path(index, path); if (r < 0) return log_error_errno(r, "Failed to set index path to \"%s\": %m", path); r = ca_index_open(index); if (r < 0) return log_error_errno(r, "Failed to open index \"%s\": %m", path); for (;;) { CaChunkID id; char ids[CA_CHUNK_ID_FORMAT_MAX]; r = ca_index_read_chunk(index, &id, NULL, NULL); if (r < 0) assert_se(r >= 0); if (r < 0) return log_error_errno(r, "Failed to open index \"%s\": %m", path); if (r == 0) break; r = gc_add_chunk_id(coll, &id); if (r < 0) return log_error_errno(r, "Failed to add chunk ID %s: %m", ca_chunk_id_format(&id, ids)); } return 0; } int ca_gc_cleanup_unused(CaStore *store, CaChunkCollection *coll, unsigned flags) { _cleanup_(ca_store_iterator_unrefp) CaStoreIterator* iter; int r; _cleanup_free_ char *ids = NULL; size_t ids_size = 0; size_t removed_chunks = 0, all_chunks = 0, removed_dirs = 0; if (!store || !coll) return -EINVAL; iter = ca_store_iterator_new(store); if (!iter) return log_oom(); while (true) { int rootdir_fd, subdir_fd; const char *subdir, *chunk, *dot; CaChunkID id; r = ca_store_iterator_next(iter, &rootdir_fd, &subdir, &subdir_fd, &chunk); if (r < 0) return log_error_errno(r, "Failed to iterate over store: %m"); if (r == 0) break; all_chunks++; assert_se(dot = strchr(chunk, '.')); /* we requested .chunk extension before */ if (!GREEDY_REALLOC(ids, ids_size, dot - chunk + 1)) return log_oom(); strncpy(ids, chunk, dot - chunk); ids[dot - chunk] = '\0'; if (!ca_chunk_id_parse(ids, &id)) { log_error("Failed to parse chunk ID \"%s\", ignoring.", ids); continue; } if (set_contains(coll->used_chunks, &id)) continue; if (flags & CA_GC_VERBOSE) printf("%s chunk %s.\n", flags & CA_GC_DRY_RUN ? "Would remove" : "Removing", chunk); if (!(flags & CA_GC_DRY_RUN)) { if (unlinkat(subdir_fd, chunk, 0) < 0) { log_error_errno(errno, "Failed to unlink chunk file \"%s\", ignoring: %m", chunk); continue; } r = unlinkat(rootdir_fd, subdir, AT_REMOVEDIR); if (r >= 0) removed_dirs++; } removed_chunks++; } if (flags & CA_GC_DRY_RUN) printf("Would remove %zu chunks, %zu chunks remaining.\n", removed_chunks, all_chunks - removed_chunks); else if (flags & CA_GC_VERBOSE) printf("Removed %zu chunks, %zu directories, %zu chunks remaining.\n", removed_chunks, removed_dirs, all_chunks - removed_chunks); return 0; } src/gc.h000066400000000000000000000013471322621430500124120ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "cachunk.h" #include "castore.h" typedef struct CaChunkCollection CaChunkCollection; CaChunkCollection* ca_chunk_collection_new(void); CaChunkCollection* ca_chunk_collection_unref(CaChunkCollection *c); static inline void ca_chunk_collection_unrefp(CaChunkCollection **c) { ca_chunk_collection_unref(*c); } int ca_chunk_collection_add_index(CaChunkCollection *coll, const char *path); int ca_chunk_collection_usage(CaChunkCollection *c, size_t *ret); int ca_chunk_collection_size(CaChunkCollection *c, size_t *ret); enum { CA_GC_VERBOSE = 1U, CA_GC_DRY_RUN = 2U, }; int ca_gc_cleanup_unused(CaStore *store, CaChunkCollection *coll, unsigned flags); src/hash-funcs.c000066400000000000000000000035321322621430500140510ustar00rootroot00000000000000/*** This file is part of systemd. Copyright 2010 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include "hash-funcs.h" void string_hash_func(const void *p, struct siphash *state) { siphash24_compress(p, strlen(p) + 1, state); } int string_compare_func(const void *a, const void *b) { return strcmp(a, b); } const struct hash_ops string_hash_ops = { .hash = string_hash_func, .compare = string_compare_func }; void trivial_hash_func(const void *p, struct siphash *state) { siphash24_compress(&p, sizeof(p), state); } int trivial_compare_func(const void *a, const void *b) { return a < b ? -1 : (a > b ? 1 : 0); } const struct hash_ops trivial_hash_ops = { .hash = trivial_hash_func, .compare = trivial_compare_func }; void uint64_hash_func(const void *p, struct siphash *state) { siphash24_compress(p, sizeof(uint64_t), state); } int uint64_compare_func(const void *_a, const void *_b) { uint64_t a, b; a = *(const uint64_t*) _a; b = *(const uint64_t*) _b; return a < b ? -1 : (a > b ? 1 : 0); } const struct hash_ops uint64_hash_ops = { .hash = uint64_hash_func, .compare = uint64_compare_func }; src/hash-funcs.h000066400000000000000000000035431322621430500140600ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #pragma once #include "util.h" #include "siphash24.h" typedef void (*hash_func_t)(const void *p, struct siphash *state); typedef int (*compare_func_t)(const void *a, const void *b); struct hash_ops { hash_func_t hash; compare_func_t compare; }; void string_hash_func(const void *p, struct siphash *state); int string_compare_func(const void *a, const void *b) _pure_; extern const struct hash_ops string_hash_ops; /* This will compare the passed pointers directly, and will not * dereference them. This is hence not useful for strings or * suchlike. */ void trivial_hash_func(const void *p, struct siphash *state); int trivial_compare_func(const void *a, const void *b) _const_; extern const struct hash_ops trivial_hash_ops; /* 32bit values we can always just embed in the pointer itself, but * in order to support 32bit archs we need store 64bit values * indirectly, since they don't fit in a pointer. */ void uint64_hash_func(const void *p, struct siphash *state); int uint64_compare_func(const void *a, const void *b) _pure_; extern const struct hash_ops uint64_hash_ops; src/hashmap.c000066400000000000000000001606071322621430500134420ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include #include #include "hashmap.h" #include "mempool.h" #include "set.h" #include "util.h" #ifdef ENABLE_DEBUG_HASHMAP #include #include "list.h" #endif /* Disable the mempool for now */ #define is_main_thread() false /* * Implementation of hashmaps. * Addressing: open * - uses less RAM compared to closed addressing (chaining), because * our entries are small (especially in Sets, which tend to contain * the majority of entries in systemd). * Collision resolution: Robin Hood * - tends to equalize displacement of entries from their optimal buckets. * Probe sequence: linear * - though theoretically worse than random probing/uniform hashing/double * hashing, it is good for cache locality. * * References: * Celis, P. 1986. Robin Hood Hashing. * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf * - The results are derived for random probing. Suggests deletion with * tombstones and two mean-centered search methods. None of that works * well for linear probing. * * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 * http://www.math.uu.se/~svante/papers/sj157.pdf * - Applies to Robin Hood with linear probing. Contains remarks on * the unsuitability of mean-centered search with linear probing. * * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes * in a successful search), and Janson writes about displacement. C = d + 1. * * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ * - Explanation of backward shift deletion with pictures. * * Khuong, P. 2013. The Other Robin Hood Hashing. * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ * - Short summary of random vs. linear probing, and tombstones vs. backward shift. */ /* * XXX Ideas for improvement: * For unordered hashmaps, randomize iteration order, similarly to Perl: * http://blog.booking.com/hardening-perls-hash-function.html */ /* INV_KEEP_FREE = 1 / (1 - max_load_factor) * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ #define INV_KEEP_FREE 5U /* Fields common to entries of all hashmap/set types */ struct hashmap_base_entry { const void *key; }; /* Entry types for specific hashmap/set types * hashmap_base_entry must be at the beginning of each entry struct. */ struct plain_hashmap_entry { struct hashmap_base_entry b; void *value; }; struct ordered_hashmap_entry { struct plain_hashmap_entry p; unsigned iterate_next, iterate_previous; }; struct set_entry { struct hashmap_base_entry b; }; /* In several functions it is advantageous to have the hash table extended * virtually by a couple of additional buckets. We reserve special index values * for these "swap" buckets. */ #define _IDX_SWAP_BEGIN (UINT_MAX - 3) #define IDX_PUT (_IDX_SWAP_BEGIN + 0) #define IDX_TMP (_IDX_SWAP_BEGIN + 1) #define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) #define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ #define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ assert_cc(IDX_FIRST == _IDX_SWAP_END); assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); /* Storage space for the "swap" buckets. * All entry types can fit into a ordered_hashmap_entry. */ struct swap_entries { struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; }; /* Distance from Initial Bucket */ typedef uint8_t dib_raw_t; #define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ #define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ #define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ #define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ #define DIB_FREE UINT_MAX #ifdef ENABLE_DEBUG_HASHMAP struct hashmap_debug_info { LIST_FIELDS(struct hashmap_debug_info, debug_list); unsigned max_entries; /* high watermark of n_entries */ /* who allocated this hashmap */ int line; const char *file; const char *func; /* fields to detect modification while iterating */ unsigned put_count; /* counts puts into the hashmap */ unsigned rem_count; /* counts removals from hashmap */ unsigned last_rem_idx; /* remembers last removal index */ }; /* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER; #define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; #else /* !ENABLE_DEBUG_HASHMAP */ #define HASHMAP_DEBUG_FIELDS #endif /* ENABLE_DEBUG_HASHMAP */ enum HashmapType { HASHMAP_TYPE_PLAIN, HASHMAP_TYPE_ORDERED, HASHMAP_TYPE_SET, _HASHMAP_TYPE_MAX }; struct _packed_ indirect_storage { void *storage; /* where buckets and DIBs are stored */ uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ unsigned n_entries; /* number of stored entries */ unsigned n_buckets; /* number of buckets */ unsigned idx_lowest_entry; /* Index below which all buckets are free. Makes "while(hashmap_steal_first())" loops O(n) instead of O(n^2) for unordered hashmaps. */ uint8_t _pad[3]; /* padding for the whole HashmapBase */ /* The bitfields in HashmapBase complete the alignment of the whole thing. */ }; struct direct_storage { /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ uint8_t storage[sizeof(struct indirect_storage)]; }; #define DIRECT_BUCKETS(entry_t) \ (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) /* We should be able to store at least one entry directly. */ assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); /* We have 3 bits for n_direct_entries. */ assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); /* Hashmaps with directly stored entries all use this shared hash key. * It's no big deal if the key is guessed, because there can be only * a handful of directly stored entries in a hashmap. When a hashmap * outgrows direct storage, it gets its own key for indirect storage. */ static uint8_t shared_hash_key[HASH_KEY_SIZE]; static bool shared_hash_key_initialized; /* Fields that all hashmap/set types must have */ struct HashmapBase { const struct hash_ops *hash_ops; /* hash and compare ops to use */ union _packed_ { struct indirect_storage indirect; /* if has_indirect */ struct direct_storage direct; /* if !has_indirect */ }; enum HashmapType type:2; /* HASHMAP_TYPE_* */ bool has_indirect:1; /* whether indirect storage is used */ unsigned n_direct_entries:3; /* Number of entries in direct storage. * Only valid if !has_indirect. */ bool from_pool:1; /* whether was allocated from mempool */ HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ }; /* Specific hash types * HashmapBase must be at the beginning of each hashmap struct. */ struct Hashmap { struct HashmapBase b; }; struct OrderedHashmap { struct HashmapBase b; unsigned iterate_list_head, iterate_list_tail; }; struct Set { struct HashmapBase b; }; DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); /* No need for a separate Set pool */ assert_cc(sizeof(Hashmap) == sizeof(Set)); struct hashmap_type_info { size_t head_size; size_t entry_size; struct mempool *mempool; unsigned n_direct_buckets; }; static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { [HASHMAP_TYPE_PLAIN] = { .head_size = sizeof(Hashmap), .entry_size = sizeof(struct plain_hashmap_entry), .mempool = &hashmap_pool, .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), }, [HASHMAP_TYPE_ORDERED] = { .head_size = sizeof(OrderedHashmap), .entry_size = sizeof(struct ordered_hashmap_entry), .mempool = &ordered_hashmap_pool, .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), }, [HASHMAP_TYPE_SET] = { .head_size = sizeof(Set), .entry_size = sizeof(struct set_entry), .mempool = &hashmap_pool, .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), }, }; static unsigned n_buckets(HashmapBase *h) { return h->has_indirect ? h->indirect.n_buckets : hashmap_type_info[h->type].n_direct_buckets; } static unsigned n_entries(HashmapBase *h) { return h->has_indirect ? h->indirect.n_entries : h->n_direct_entries; } static void n_entries_inc(HashmapBase *h) { if (h->has_indirect) h->indirect.n_entries++; else h->n_direct_entries++; } static void n_entries_dec(HashmapBase *h) { if (h->has_indirect) h->indirect.n_entries--; else h->n_direct_entries--; } static void *storage_ptr(HashmapBase *h) { return h->has_indirect ? h->indirect.storage : h->direct.storage; } static uint8_t *hash_key(HashmapBase *h) { return h->has_indirect ? h->indirect.hash_key : shared_hash_key; } static unsigned base_bucket_hash(HashmapBase *h, const void *p) { struct siphash state; uint64_t hash; siphash24_init(&state, hash_key(h)); h->hash_ops->hash(p, &state); hash = siphash24_finalize(&state); return (unsigned) (hash % n_buckets(h)); } #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { static uint8_t current[HASH_KEY_SIZE]; static bool current_initialized = false; /* Returns a hash function key to use. In order to keep things * fast we will not generate a new key each time we allocate a * new hash table. Instead, we'll just reuse the most recently * generated one, except if we never generated one or when we * are rehashing an entire hash table because we reached a * fill level */ if (!current_initialized || !reuse_is_ok) { random_bytes(current, sizeof(current)); current_initialized = true; } memcpy(hash_key, current, sizeof(current)); } static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { return (struct hashmap_base_entry*) ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); } static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); } static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); } static struct set_entry *set_bucket_at(Set *h, unsigned idx) { return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); } static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { return &swap->e[idx - _IDX_SWAP_BEGIN]; } /* Returns a pointer to the bucket at index idx. * Understands real indexes and swap indexes, hence "_virtual". */ static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, unsigned idx) { if (idx < _IDX_SWAP_BEGIN) return bucket_at(h, idx); if (idx < _IDX_SWAP_END) return &bucket_at_swap(swap, idx)->p.b; assert_not_reached("Invalid index"); } static dib_raw_t *dib_raw_ptr(HashmapBase *h) { return (dib_raw_t*) ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); } static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { return idx >= from ? idx - from : n_buckets(h) + idx - from; } static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { unsigned initial_bucket; if (raw_dib == DIB_RAW_FREE) return DIB_FREE; if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) return raw_dib; /* * Having an overflow DIB value is very unlikely. The hash function * would have to be bad. For example, in a table of size 2^24 filled * to load factor 0.9 the maximum observed DIB is only about 60. * In theory (assuming I used Maxima correctly), for an infinite size * hash table with load factor 0.8 the probability of a given entry * having DIB > 40 is 1.9e-8. * This returns the correct DIB value by recomputing the hash value in * the unlikely case. XXX Hitting this case could be a hint to rehash. */ initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); return bucket_distance(h, idx, initial_bucket); } static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; } static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { dib_raw_t *dibs; dibs = dib_raw_ptr(h); for ( ; idx < n_buckets(h); idx++) if (dibs[idx] != DIB_RAW_FREE) return idx; return IDX_NIL; } static void bucket_mark_free(HashmapBase *h, unsigned idx) { memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); bucket_set_dib(h, idx, DIB_FREE); } static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, unsigned from, unsigned to) { struct hashmap_base_entry *e_from, *e_to; assert(from != to); e_from = bucket_at_virtual(h, swap, from); e_to = bucket_at_virtual(h, swap, to); memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); if (h->type == HASHMAP_TYPE_ORDERED) { OrderedHashmap *lh = (OrderedHashmap*) h; struct ordered_hashmap_entry *le, *le_to; le_to = (struct ordered_hashmap_entry*) e_to; if (le_to->iterate_next != IDX_NIL) { le = (struct ordered_hashmap_entry*) bucket_at_virtual(h, swap, le_to->iterate_next); le->iterate_previous = to; } if (le_to->iterate_previous != IDX_NIL) { le = (struct ordered_hashmap_entry*) bucket_at_virtual(h, swap, le_to->iterate_previous); le->iterate_next = to; } if (lh->iterate_list_head == from) lh->iterate_list_head = to; if (lh->iterate_list_tail == from) lh->iterate_list_tail = to; } } static unsigned next_idx(HashmapBase *h, unsigned idx) { return (idx + 1U) % n_buckets(h); } static unsigned prev_idx(HashmapBase *h, unsigned idx) { return (n_buckets(h) + idx - 1U) % n_buckets(h); } static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { switch (h->type) { case HASHMAP_TYPE_PLAIN: case HASHMAP_TYPE_ORDERED: return ((struct plain_hashmap_entry*)e)->value; case HASHMAP_TYPE_SET: return (void*) e->key; default: assert_not_reached("Unknown hashmap type"); } } static void base_remove_entry(HashmapBase *h, unsigned idx) { unsigned left, right, prev, dib; dib_raw_t raw_dib, *dibs; dibs = dib_raw_ptr(h); assert(dibs[idx] != DIB_RAW_FREE); #ifdef ENABLE_DEBUG_HASHMAP h->debug.rem_count++; h->debug.last_rem_idx = idx; #endif left = idx; /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ for (right = next_idx(h, left); ; right = next_idx(h, right)) { raw_dib = dibs[right]; if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) break; /* The buckets are not supposed to be all occupied and with DIB > 0. * That would mean we could make everyone better off by shifting them * backward. This scenario is impossible. */ assert(left != right); } if (h->type == HASHMAP_TYPE_ORDERED) { OrderedHashmap *lh = (OrderedHashmap*) h; struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); if (le->iterate_next != IDX_NIL) ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; else lh->iterate_list_tail = le->iterate_previous; if (le->iterate_previous != IDX_NIL) ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; else lh->iterate_list_head = le->iterate_next; } /* Now shift all buckets in the interval (left, right) one step backwards */ for (prev = left, left = next_idx(h, left); left != right; prev = left, left = next_idx(h, left)) { dib = bucket_calculate_dib(h, left, dibs[left]); assert(dib != 0); bucket_move_entry(h, NULL, left, prev); bucket_set_dib(h, prev, dib - 1); } bucket_mark_free(h, prev); n_entries_dec(h); } #define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { struct ordered_hashmap_entry *e; unsigned idx; assert(h); assert(i); if (i->idx == IDX_NIL) goto at_end; if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) goto at_end; if (i->idx == IDX_FIRST) { idx = h->iterate_list_head; e = ordered_bucket_at(h, idx); } else { idx = i->idx; e = ordered_bucket_at(h, idx); /* * We allow removing the current entry while iterating, but removal may cause * a backward shift. The next entry may thus move one bucket to the left. * To detect when it happens, we remember the key pointer of the entry we were * going to iterate next. If it does not match, there was a backward shift. */ if (e->p.b.key != i->next_key) { idx = prev_idx(HASHMAP_BASE(h), idx); e = ordered_bucket_at(h, idx); } assert(e->p.b.key == i->next_key); } #ifdef ENABLE_DEBUG_HASHMAP i->prev_idx = idx; #endif if (e->iterate_next != IDX_NIL) { struct ordered_hashmap_entry *n; i->idx = e->iterate_next; n = ordered_bucket_at(h, i->idx); i->next_key = n->p.b.key; } else i->idx = IDX_NIL; return idx; at_end: i->idx = IDX_NIL; return IDX_NIL; } static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { unsigned idx; assert(h); assert(i); if (i->idx == IDX_NIL) goto at_end; if (i->idx == IDX_FIRST) { /* fast forward to the first occupied bucket */ if (h->has_indirect) { i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); h->indirect.idx_lowest_entry = i->idx; } else i->idx = skip_free_buckets(h, 0); if (i->idx == IDX_NIL) goto at_end; } else { struct hashmap_base_entry *e; assert(i->idx > 0); e = bucket_at(h, i->idx); /* * We allow removing the current entry while iterating, but removal may cause * a backward shift. The next entry may thus move one bucket to the left. * To detect when it happens, we remember the key pointer of the entry we were * going to iterate next. If it does not match, there was a backward shift. */ if (e->key != i->next_key) e = bucket_at(h, --i->idx); assert(e->key == i->next_key); } idx = i->idx; #ifdef ENABLE_DEBUG_HASHMAP i->prev_idx = idx; #endif i->idx = skip_free_buckets(h, i->idx + 1); if (i->idx != IDX_NIL) i->next_key = bucket_at(h, i->idx)->key; else i->idx = IDX_NIL; return idx; at_end: i->idx = IDX_NIL; return IDX_NIL; } static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { if (!h) { i->idx = IDX_NIL; return IDX_NIL; } #ifdef ENABLE_DEBUG_HASHMAP if (i->idx == IDX_FIRST) { i->put_count = h->debug.put_count; i->rem_count = h->debug.rem_count; } else { /* While iterating, must not add any new entries */ assert(i->put_count == h->debug.put_count); /* ... or remove entries other than the current one */ assert(i->rem_count == h->debug.rem_count || (i->rem_count == h->debug.rem_count - 1 && i->prev_idx == h->debug.last_rem_idx)); /* Reset our removals counter */ i->rem_count = h->debug.rem_count; } #endif return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) : hashmap_iterate_in_internal_order(h, i); } bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) { struct hashmap_base_entry *e; void *data; unsigned idx; idx = hashmap_iterate_entry(h, i); if (idx == IDX_NIL) { if (value) *value = NULL; if (key) *key = NULL; return false; } e = bucket_at(h, idx); data = entry_value(h, e); if (value) *value = data; if (key) *key = e->key; return true; } bool set_iterate(Set *s, Iterator *i, void **value) { return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); } #define HASHMAP_FOREACH_IDX(idx, h, i) \ for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ (idx != IDX_NIL); \ (idx) = hashmap_iterate_entry((h), &(i))) static void reset_direct_storage(HashmapBase *h) { const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; void *p; assert(!h->has_indirect); p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); } static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { HashmapBase *h; const struct hashmap_type_info *hi = &hashmap_type_info[type]; bool use_pool; use_pool = is_main_thread(); h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); if (!h) return NULL; h->type = type; h->from_pool = use_pool; h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; if (type == HASHMAP_TYPE_ORDERED) { OrderedHashmap *lh = (OrderedHashmap*)h; lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; } reset_direct_storage(h); if (!shared_hash_key_initialized) { random_bytes(shared_hash_key, sizeof(shared_hash_key)); shared_hash_key_initialized= true; } #ifdef ENABLE_DEBUG_HASHMAP h->debug.func = func; h->debug.file = file; h->debug.line = line; assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); #endif return h; } Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); } OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); } Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); } static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { HashmapBase *q; assert(h); if (*h) return 0; q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); if (!q) return -ENOMEM; *h = q; return 0; } int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); } int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); } int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); } static void hashmap_free_no_clear(HashmapBase *h) { assert(!h->has_indirect); assert(!h->n_direct_entries); #ifdef ENABLE_DEBUG_HASHMAP assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); #endif if (h->from_pool) mempool_free_tile(hashmap_type_info[h->type].mempool, h); else free(h); } HashmapBase *internal_hashmap_free(HashmapBase *h) { /* Free the hashmap, but nothing in it */ if (h) { internal_hashmap_clear(h); hashmap_free_no_clear(h); } return NULL; } HashmapBase *internal_hashmap_free_free(HashmapBase *h) { /* Free the hashmap and all data objects in it, but not the * keys */ if (h) { internal_hashmap_clear_free(h); hashmap_free_no_clear(h); } return NULL; } Hashmap *hashmap_free_free_free(Hashmap *h) { /* Free the hashmap and all data and key objects in it */ if (h) { hashmap_clear_free_free(h); hashmap_free_no_clear(HASHMAP_BASE(h)); } return NULL; } void internal_hashmap_clear(HashmapBase *h) { if (!h) return; if (h->has_indirect) { free(h->indirect.storage); h->has_indirect = false; } h->n_direct_entries = 0; reset_direct_storage(h); if (h->type == HASHMAP_TYPE_ORDERED) { OrderedHashmap *lh = (OrderedHashmap*) h; lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; } } void internal_hashmap_clear_free(HashmapBase *h) { unsigned idx; if (!h) return; for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; idx = skip_free_buckets(h, idx + 1)) free(entry_value(h, bucket_at(h, idx))); internal_hashmap_clear(h); } void hashmap_clear_free_free(Hashmap *h) { unsigned idx; if (!h) return; for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { struct plain_hashmap_entry *e = plain_bucket_at(h, idx); free((void*)e->b.key); free(e->value); } internal_hashmap_clear(HASHMAP_BASE(h)); } static int resize_buckets(HashmapBase *h, unsigned entries_add); /* * Finds an empty bucket to put an entry into, starting the scan at 'idx'. * Performs Robin Hood swaps as it goes. The entry to put must be placed * by the caller into swap slot IDX_PUT. * If used for in-place resizing, may leave a displaced entry in swap slot * IDX_PUT. Caller must rehash it next. * Returns: true if it left a displaced entry to rehash next in IDX_PUT, * false otherwise. */ static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, struct swap_entries *swap) { dib_raw_t raw_dib, *dibs; unsigned dib, distance; #ifdef ENABLE_DEBUG_HASHMAP h->debug.put_count++; #endif dibs = dib_raw_ptr(h); for (distance = 0; ; distance++) { raw_dib = dibs[idx]; if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { if (raw_dib == DIB_RAW_REHASH) bucket_move_entry(h, swap, idx, IDX_TMP); if (h->has_indirect && h->indirect.idx_lowest_entry > idx) h->indirect.idx_lowest_entry = idx; bucket_set_dib(h, idx, distance); bucket_move_entry(h, swap, IDX_PUT, idx); if (raw_dib == DIB_RAW_REHASH) { bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); return true; } return false; } dib = bucket_calculate_dib(h, idx, raw_dib); if (dib < distance) { /* Found a wealthier entry. Go Robin Hood! */ bucket_set_dib(h, idx, distance); /* swap the entries */ bucket_move_entry(h, swap, idx, IDX_TMP); bucket_move_entry(h, swap, IDX_PUT, idx); bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); distance = dib; } idx = next_idx(h, idx); } } /* * Puts an entry into a hashmap, boldly - no check whether key already exists. * The caller must place the entry (only its key and value, not link indexes) * in swap slot IDX_PUT. * Caller must ensure: the key does not exist yet in the hashmap. * that resize is not needed if !may_resize. * Returns: 1 if entry was put successfully. * -ENOMEM if may_resize==true and resize failed with -ENOMEM. * Cannot return -ENOMEM if !may_resize. */ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, struct swap_entries *swap, bool may_resize) { struct ordered_hashmap_entry *new_entry; int r; assert(idx < n_buckets(h)); new_entry = bucket_at_swap(swap, IDX_PUT); if (may_resize) { r = resize_buckets(h, 1); if (r < 0) return r; if (r > 0) idx = bucket_hash(h, new_entry->p.b.key); } assert(n_entries(h) < n_buckets(h)); if (h->type == HASHMAP_TYPE_ORDERED) { OrderedHashmap *lh = (OrderedHashmap*) h; new_entry->iterate_next = IDX_NIL; new_entry->iterate_previous = lh->iterate_list_tail; if (lh->iterate_list_tail != IDX_NIL) { struct ordered_hashmap_entry *old_tail; old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); assert(old_tail->iterate_next == IDX_NIL); old_tail->iterate_next = IDX_PUT; } lh->iterate_list_tail = IDX_PUT; if (lh->iterate_list_head == IDX_NIL) lh->iterate_list_head = IDX_PUT; } assert_se(hashmap_put_robin_hood(h, idx, swap) == false); n_entries_inc(h); #ifdef ENABLE_DEBUG_HASHMAP h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); #endif return 1; } #define hashmap_put_boldly(h, idx, swap, may_resize) \ hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) /* * Returns 0 if resize is not needed. * 1 if successfully resized. * -ENOMEM on allocation failure. */ static int resize_buckets(HashmapBase *h, unsigned entries_add) { struct swap_entries swap; void *new_storage; dib_raw_t *old_dibs, *new_dibs; const struct hashmap_type_info *hi; unsigned idx, optimal_idx; unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; uint8_t new_shift; bool rehash_next; assert(h); hi = &hashmap_type_info[h->type]; new_n_entries = n_entries(h) + entries_add; /* overflow? */ if (_unlikely_(new_n_entries < entries_add)) return -ENOMEM; /* For direct storage we allow 100% load, because it's tiny. */ if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) return 0; /* * Load factor = n/m = 1 - (1/INV_KEEP_FREE). * From it follows: m = n + n/(INV_KEEP_FREE - 1) */ new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); /* overflow? */ if (_unlikely_(new_n_buckets < new_n_entries)) return -ENOMEM; if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) return -ENOMEM; old_n_buckets = n_buckets(h); if (_likely_(new_n_buckets <= old_n_buckets)) return 0; new_shift = log2u_round_up(MAX( new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), 2 * sizeof(struct direct_storage))); /* Realloc storage (buckets and DIB array). */ new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, 1U << new_shift); if (!new_storage) return -ENOMEM; /* Must upgrade direct to indirect storage. */ if (!h->has_indirect) { memcpy(new_storage, h->direct.storage, old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); h->indirect.n_entries = h->n_direct_entries; h->indirect.idx_lowest_entry = 0; h->n_direct_entries = 0; } /* Get a new hash key. If we've just upgraded to indirect storage, * allow reusing a previously generated key. It's still a different key * from the shared one that we used for direct storage. */ get_hash_key(h->indirect.hash_key, !h->has_indirect); h->has_indirect = true; h->indirect.storage = new_storage; h->indirect.n_buckets = (1U << new_shift) / (hi->entry_size + sizeof(dib_raw_t)); old_dibs = (dib_raw_t*)((uint8_t*) new_storage + hi->entry_size * old_n_buckets); new_dibs = dib_raw_ptr(h); /* * Move the DIB array to the new place, replacing valid DIB values with * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. * Note: Overlap is not possible, because we have at least doubled the * number of buckets and dib_raw_t is smaller than any entry type. */ for (idx = 0; idx < old_n_buckets; idx++) { assert(old_dibs[idx] != DIB_RAW_REHASH); new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE : DIB_RAW_REHASH; } /* Zero the area of newly added entries (including the old DIB area) */ memzero(bucket_at(h, old_n_buckets), (n_buckets(h) - old_n_buckets) * hi->entry_size); /* The upper half of the new DIB array needs initialization */ memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); /* Rehash entries that need it */ n_rehashed = 0; for (idx = 0; idx < old_n_buckets; idx++) { if (new_dibs[idx] != DIB_RAW_REHASH) continue; optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); /* * Not much to do if by luck the entry hashes to its current * location. Just set its DIB. */ if (optimal_idx == idx) { new_dibs[idx] = 0; n_rehashed++; continue; } new_dibs[idx] = DIB_RAW_FREE; bucket_move_entry(h, &swap, idx, IDX_PUT); /* bucket_move_entry does not clear the source */ memzero(bucket_at(h, idx), hi->entry_size); do { /* * Find the new bucket for the current entry. This may make * another entry homeless and load it into IDX_PUT. */ rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); n_rehashed++; /* Did the current entry displace another one? */ if (rehash_next) optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); } while (rehash_next); } assert(n_rehashed == n_entries(h)); return 1; } /* * Finds an entry with a matching key * Returns: index of the found entry, or IDX_NIL if not found. */ static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { struct hashmap_base_entry *e; unsigned dib, distance; dib_raw_t *dibs = dib_raw_ptr(h); assert(idx < n_buckets(h)); for (distance = 0; ; distance++) { if (dibs[idx] == DIB_RAW_FREE) return IDX_NIL; dib = bucket_calculate_dib(h, idx, dibs[idx]); if (dib < distance) return IDX_NIL; if (dib == distance) { e = bucket_at(h, idx); if (h->hash_ops->compare(e->key, key) == 0) return idx; } idx = next_idx(h, idx); } } #define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) int hashmap_put(Hashmap *h, const void *key, void *value) { struct swap_entries swap; struct plain_hashmap_entry *e; unsigned hash, idx; assert(h); hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx != IDX_NIL) { e = plain_bucket_at(h, idx); if (e->value == value) return 0; return -EEXIST; } e = &bucket_at_swap(&swap, IDX_PUT)->p; e->b.key = key; e->value = value; return hashmap_put_boldly(h, hash, &swap, true); } int set_put(Set *s, const void *key) { struct swap_entries swap; struct hashmap_base_entry *e; unsigned hash, idx; assert(s); hash = bucket_hash(s, key); idx = bucket_scan(s, hash, key); if (idx != IDX_NIL) return 0; e = &bucket_at_swap(&swap, IDX_PUT)->p.b; e->key = key; return hashmap_put_boldly(s, hash, &swap, true); } int hashmap_replace(Hashmap *h, const void *key, void *value) { struct swap_entries swap; struct plain_hashmap_entry *e; unsigned hash, idx; assert(h); hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx != IDX_NIL) { e = plain_bucket_at(h, idx); #ifdef ENABLE_DEBUG_HASHMAP /* Although the key is equal, the key pointer may have changed, * and this would break our assumption for iterating. So count * this operation as incompatible with iteration. */ if (e->b.key != key) { h->b.debug.put_count++; h->b.debug.rem_count++; h->b.debug.last_rem_idx = idx; } #endif e->b.key = key; e->value = value; return 0; } e = &bucket_at_swap(&swap, IDX_PUT)->p; e->b.key = key; e->value = value; return hashmap_put_boldly(h, hash, &swap, true); } int hashmap_update(Hashmap *h, const void *key, void *value) { struct plain_hashmap_entry *e; unsigned hash, idx; assert(h); hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return -ENOENT; e = plain_bucket_at(h, idx); e->value = value; return 0; } void *internal_hashmap_get(HashmapBase *h, const void *key) { struct hashmap_base_entry *e; unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return NULL; e = bucket_at(h, idx); return entry_value(h, e); } void *hashmap_get2(Hashmap *h, const void *key, void **key2) { struct plain_hashmap_entry *e; unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return NULL; e = plain_bucket_at(h, idx); if (key2) *key2 = (void*) e->b.key; return e->value; } bool internal_hashmap_contains(HashmapBase *h, const void *key) { unsigned hash; if (!h) return false; hash = bucket_hash(h, key); return bucket_scan(h, hash, key) != IDX_NIL; } void *internal_hashmap_remove(HashmapBase *h, const void *key) { struct hashmap_base_entry *e; unsigned hash, idx; void *data; if (!h) return NULL; hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return NULL; e = bucket_at(h, idx); data = entry_value(h, e); remove_entry(h, idx); return data; } void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { struct plain_hashmap_entry *e; unsigned hash, idx; void *data; if (!h) { if (rkey) *rkey = NULL; return NULL; } hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) { if (rkey) *rkey = NULL; return NULL; } e = plain_bucket_at(h, idx); data = e->value; if (rkey) *rkey = (void*) e->b.key; remove_entry(h, idx); return data; } int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { struct swap_entries swap; struct plain_hashmap_entry *e; unsigned old_hash, new_hash, idx; if (!h) return -ENOENT; old_hash = bucket_hash(h, old_key); idx = bucket_scan(h, old_hash, old_key); if (idx == IDX_NIL) return -ENOENT; new_hash = bucket_hash(h, new_key); if (bucket_scan(h, new_hash, new_key) != IDX_NIL) return -EEXIST; remove_entry(h, idx); e = &bucket_at_swap(&swap, IDX_PUT)->p; e->b.key = new_key; e->value = value; assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); return 0; } int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { struct swap_entries swap; struct hashmap_base_entry *e; unsigned old_hash, new_hash, idx; if (!s) return -ENOENT; old_hash = bucket_hash(s, old_key); idx = bucket_scan(s, old_hash, old_key); if (idx == IDX_NIL) return -ENOENT; new_hash = bucket_hash(s, new_key); if (bucket_scan(s, new_hash, new_key) != IDX_NIL) return -EEXIST; remove_entry(s, idx); e = &bucket_at_swap(&swap, IDX_PUT)->p.b; e->key = new_key; assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); return 0; } int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { struct swap_entries swap; struct plain_hashmap_entry *e; unsigned old_hash, new_hash, idx_old, idx_new; if (!h) return -ENOENT; old_hash = bucket_hash(h, old_key); idx_old = bucket_scan(h, old_hash, old_key); if (idx_old == IDX_NIL) return -ENOENT; old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; new_hash = bucket_hash(h, new_key); idx_new = bucket_scan(h, new_hash, new_key); if (idx_new != IDX_NIL) if (idx_old != idx_new) { remove_entry(h, idx_new); /* Compensate for a possible backward shift. */ if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) idx_old = prev_idx(HASHMAP_BASE(h), idx_old); assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); } remove_entry(h, idx_old); e = &bucket_at_swap(&swap, IDX_PUT)->p; e->b.key = new_key; e->value = value; assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); return 0; } void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { struct plain_hashmap_entry *e; unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return NULL; e = plain_bucket_at(h, idx); if (e->value != value) return NULL; remove_entry(h, idx); return value; } static unsigned find_first_entry(HashmapBase *h) { Iterator i = ITERATOR_FIRST; if (!h || !n_entries(h)) return IDX_NIL; return hashmap_iterate_entry(h, &i); } void *internal_hashmap_first(HashmapBase *h) { unsigned idx; idx = find_first_entry(h); if (idx == IDX_NIL) return NULL; return entry_value(h, bucket_at(h, idx)); } void *internal_hashmap_first_key(HashmapBase *h) { struct hashmap_base_entry *e; unsigned idx; idx = find_first_entry(h); if (idx == IDX_NIL) return NULL; e = bucket_at(h, idx); return (void*) e->key; } void *internal_hashmap_steal_first(HashmapBase *h) { struct hashmap_base_entry *e; void *data; unsigned idx; idx = find_first_entry(h); if (idx == IDX_NIL) return NULL; e = bucket_at(h, idx); data = entry_value(h, e); remove_entry(h, idx); return data; } void *internal_hashmap_steal_first_key(HashmapBase *h) { struct hashmap_base_entry *e; void *key; unsigned idx; idx = find_first_entry(h); if (idx == IDX_NIL) return NULL; e = bucket_at(h, idx); key = (void*) e->key; remove_entry(h, idx); return key; } unsigned internal_hashmap_size(HashmapBase *h) { if (!h) return 0; return n_entries(h); } unsigned internal_hashmap_buckets(HashmapBase *h) { if (!h) return 0; return n_buckets(h); } int internal_hashmap_merge(Hashmap *h, Hashmap *other) { Iterator i; unsigned idx; assert(h); HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); int r; r = hashmap_put(h, pe->b.key, pe->value); if (r < 0 && r != -EEXIST) return r; } return 0; } int set_merge(Set *s, Set *other) { Iterator i; unsigned idx; assert(s); HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { struct set_entry *se = set_bucket_at(other, idx); int r; r = set_put(s, se->b.key); if (r < 0) return r; } return 0; } int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { int r; assert(h); r = resize_buckets(h, entries_add); if (r < 0) return r; return 0; } /* * The same as hashmap_merge(), but every new item from other is moved to h. * Keys already in h are skipped and stay in other. * Returns: 0 on success. * -ENOMEM on alloc failure, in which case no move has been done. */ int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { struct swap_entries swap; struct hashmap_base_entry *e, *n; Iterator i; unsigned idx; int r; assert(h); if (!other) return 0; assert(other->type == h->type); /* * This reserves buckets for the worst case, where none of other's * entries are yet present in h. This is preferable to risking * an allocation failure in the middle of the moving and having to * rollback or return a partial result. */ r = resize_buckets(h, n_entries(other)); if (r < 0) return r; HASHMAP_FOREACH_IDX(idx, other, i) { unsigned h_hash; e = bucket_at(other, idx); h_hash = bucket_hash(h, e->key); if (bucket_scan(h, h_hash, e->key) != IDX_NIL) continue; n = &bucket_at_swap(&swap, IDX_PUT)->p.b; n->key = e->key; if (h->type != HASHMAP_TYPE_SET) ((struct plain_hashmap_entry*) n)->value = ((struct plain_hashmap_entry*) e)->value; assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); remove_entry(other, idx); } return 0; } int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { struct swap_entries swap; unsigned h_hash, other_hash, idx; struct hashmap_base_entry *e, *n; int r; assert(h); h_hash = bucket_hash(h, key); if (bucket_scan(h, h_hash, key) != IDX_NIL) return -EEXIST; if (!other) return -ENOENT; assert(other->type == h->type); other_hash = bucket_hash(other, key); idx = bucket_scan(other, other_hash, key); if (idx == IDX_NIL) return -ENOENT; e = bucket_at(other, idx); n = &bucket_at_swap(&swap, IDX_PUT)->p.b; n->key = e->key; if (h->type != HASHMAP_TYPE_SET) ((struct plain_hashmap_entry*) n)->value = ((struct plain_hashmap_entry*) e)->value; r = hashmap_put_boldly(h, h_hash, &swap, true); if (r < 0) return r; remove_entry(other, idx); return 0; } HashmapBase *internal_hashmap_copy(HashmapBase *h) { HashmapBase *copy; int r; assert(h); copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); if (!copy) return NULL; switch (h->type) { case HASHMAP_TYPE_PLAIN: case HASHMAP_TYPE_ORDERED: r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); break; case HASHMAP_TYPE_SET: r = set_merge((Set*)copy, (Set*)h); break; default: assert_not_reached("Unknown hashmap type"); } if (r < 0) { internal_hashmap_free(copy); return NULL; } return copy; } char **internal_hashmap_get_strv(HashmapBase *h) { char **sv; Iterator i; unsigned idx, n; sv = new(char*, n_entries(h)+1); if (!sv) return NULL; n = 0; HASHMAP_FOREACH_IDX(idx, h, i) sv[n++] = entry_value(h, bucket_at(h, idx)); sv[n] = NULL; return sv; } void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { struct ordered_hashmap_entry *e; unsigned hash, idx; if (!h) return NULL; hash = bucket_hash(h, key); idx = bucket_scan(h, hash, key); if (idx == IDX_NIL) return NULL; e = ordered_bucket_at(h, idx); if (e->iterate_next == IDX_NIL) return NULL; return ordered_bucket_at(h, e->iterate_next)->p.value; } int set_consume(Set *s, void *value) { int r; assert(s); assert(value); r = set_put(s, value); if (r <= 0) free(value); return r; } int set_put_strdup(Set *s, const char *p) { char *c; assert(s); assert(p); if (set_contains(s, (char*) p)) return 0; c = strdup(p); if (!c) return -ENOMEM; return set_consume(s, c); } int set_put_strdupv(Set *s, char **l) { int n = 0, r; char **i; assert(s); STRV_FOREACH(i, l) { r = set_put_strdup(s, *i); if (r < 0) return r; n += r; } return n; } src/hashmap.h000066400000000000000000000367131322621430500134470ustar00rootroot00000000000000#pragma once /*** This file is part of systemd. Copyright 2010 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include #include "hash-funcs.h" #include "util.h" /* * A hash table implementation. As a minor optimization a NULL hashmap object * will be treated as empty hashmap for all read operations. That way it is not * necessary to instantiate an object for each Hashmap use. * * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), * the implemention will: * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) * - perform extra checks for invalid use of iterators */ #define HASH_KEY_SIZE 16 /* The base type for all hashmap and set types. Many functions in the * implementation take (HashmapBase*) parameters and are run-time polymorphic, * though the API is not meant to be polymorphic (do not call functions * internal_*() directly). */ typedef struct HashmapBase HashmapBase; /* Specific hashmap/set types */ typedef struct Hashmap Hashmap; /* Maps keys to values */ typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ typedef struct Set Set; /* Stores just keys */ /* Ideally the Iterator would be an opaque struct, but it is instantiated * by hashmap users, so the definition has to be here. Do not use its fields * directly. */ typedef struct { unsigned idx; /* index of an entry to be iterated next */ const void *next_key; /* expected value of that entry's key pointer */ #ifdef ENABLE_DEBUG_HASHMAP unsigned put_count; /* hashmap's put_count recorded at start of iteration */ unsigned rem_count; /* hashmap's rem_count in previous iteration */ unsigned prev_idx; /* idx in previous iteration */ #endif } Iterator; #define _IDX_ITERATOR_FIRST (UINT_MAX - 1) #define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) /* Macros for type checking */ #define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ __builtin_types_compatible_p(typeof(h), Hashmap*) || \ __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ __builtin_types_compatible_p(typeof(h), Set*)) #define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ #define HASHMAP_BASE(h) \ __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ (HashmapBase*)(h), \ (void)0) #define PLAIN_HASHMAP(h) \ __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ (Hashmap*)(h), \ (void)0) #ifdef ENABLE_DEBUG_HASHMAP # define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line # define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ # define HASHMAP_DEBUG_PASS_ARGS , func, file, line #else # define HASHMAP_DEBUG_PARAMS # define HASHMAP_DEBUG_SRC_ARGS # define HASHMAP_DEBUG_PASS_ARGS #endif Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) #define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) HashmapBase *internal_hashmap_free(HashmapBase *h); static inline Hashmap *hashmap_free(Hashmap *h) { return (void*)internal_hashmap_free(HASHMAP_BASE(h)); } static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) { return (void*)internal_hashmap_free(HASHMAP_BASE(h)); } HashmapBase *internal_hashmap_free_free(HashmapBase *h); static inline Hashmap *hashmap_free_free(Hashmap *h) { return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); } static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) { return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); } Hashmap *hashmap_free_free_free(Hashmap *h); static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) { return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h)); } HashmapBase *internal_hashmap_copy(HashmapBase *h); static inline Hashmap *hashmap_copy(Hashmap *h) { return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); } static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); } int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) #define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) int hashmap_put(Hashmap *h, const void *key, void *value); static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { return hashmap_put(PLAIN_HASHMAP(h), key, value); } int hashmap_update(Hashmap *h, const void *key, void *value); static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { return hashmap_update(PLAIN_HASHMAP(h), key, value); } int hashmap_replace(Hashmap *h, const void *key, void *value); static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { return hashmap_replace(PLAIN_HASHMAP(h), key, value); } void *internal_hashmap_get(HashmapBase *h, const void *key); static inline void *hashmap_get(Hashmap *h, const void *key) { return internal_hashmap_get(HASHMAP_BASE(h), key); } static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { return internal_hashmap_get(HASHMAP_BASE(h), key); } void *hashmap_get2(Hashmap *h, const void *key, void **rkey); static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); } bool internal_hashmap_contains(HashmapBase *h, const void *key); static inline bool hashmap_contains(Hashmap *h, const void *key) { return internal_hashmap_contains(HASHMAP_BASE(h), key); } static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { return internal_hashmap_contains(HASHMAP_BASE(h), key); } void *internal_hashmap_remove(HashmapBase *h, const void *key); static inline void *hashmap_remove(Hashmap *h, const void *key) { return internal_hashmap_remove(HASHMAP_BASE(h), key); } static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { return internal_hashmap_remove(HASHMAP_BASE(h), key); } void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); } void *hashmap_remove_value(Hashmap *h, const void *key, void *value); static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); } int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); } int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); } /* Since merging data from a OrderedHashmap into a Hashmap or vice-versa * should just work, allow this by having looser type-checking here. */ int internal_hashmap_merge(Hashmap *h, Hashmap *other); #define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) #define ordered_hashmap_merge(h, other) hashmap_merge(h, other) int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); } static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); } int internal_hashmap_move(HashmapBase *h, HashmapBase *other); /* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ static inline int hashmap_move(Hashmap *h, Hashmap *other) { return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); } static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); } int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); } static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); } unsigned internal_hashmap_size(HashmapBase *h) _pure_; static inline unsigned hashmap_size(Hashmap *h) { return internal_hashmap_size(HASHMAP_BASE(h)); } static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { return internal_hashmap_size(HASHMAP_BASE(h)); } static inline bool hashmap_isempty(Hashmap *h) { return hashmap_size(h) == 0; } static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { return ordered_hashmap_size(h) == 0; } unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; static inline unsigned hashmap_buckets(Hashmap *h) { return internal_hashmap_buckets(HASHMAP_BASE(h)); } static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { return internal_hashmap_buckets(HASHMAP_BASE(h)); } bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key); static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) { return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); } static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) { return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); } void internal_hashmap_clear(HashmapBase *h); static inline void hashmap_clear(Hashmap *h) { internal_hashmap_clear(HASHMAP_BASE(h)); } static inline void ordered_hashmap_clear(OrderedHashmap *h) { internal_hashmap_clear(HASHMAP_BASE(h)); } void internal_hashmap_clear_free(HashmapBase *h); static inline void hashmap_clear_free(Hashmap *h) { internal_hashmap_clear_free(HASHMAP_BASE(h)); } static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { internal_hashmap_clear_free(HASHMAP_BASE(h)); } void hashmap_clear_free_free(Hashmap *h); static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { hashmap_clear_free_free(PLAIN_HASHMAP(h)); } /* * Note about all *_first*() functions * * For plain Hashmaps and Sets the order of entries is undefined. * The functions find whatever entry is first in the implementation * internal order. * * Only for OrderedHashmaps the order is well defined and finding * the first entry is O(1). */ void *internal_hashmap_steal_first(HashmapBase *h); static inline void *hashmap_steal_first(Hashmap *h) { return internal_hashmap_steal_first(HASHMAP_BASE(h)); } static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { return internal_hashmap_steal_first(HASHMAP_BASE(h)); } void *internal_hashmap_steal_first_key(HashmapBase *h); static inline void *hashmap_steal_first_key(Hashmap *h) { return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); } static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); } void *internal_hashmap_first_key(HashmapBase *h) _pure_; static inline void *hashmap_first_key(Hashmap *h) { return internal_hashmap_first_key(HASHMAP_BASE(h)); } static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { return internal_hashmap_first_key(HASHMAP_BASE(h)); } void *internal_hashmap_first(HashmapBase *h) _pure_; static inline void *hashmap_first(Hashmap *h) { return internal_hashmap_first(HASHMAP_BASE(h)); } static inline void *ordered_hashmap_first(OrderedHashmap *h) { return internal_hashmap_first(HASHMAP_BASE(h)); } /* no hashmap_next */ void *ordered_hashmap_next(OrderedHashmap *h, const void *key); char **internal_hashmap_get_strv(HashmapBase *h); static inline char **hashmap_get_strv(Hashmap *h) { return internal_hashmap_get_strv(HASHMAP_BASE(h)); } static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { return internal_hashmap_get_strv(HASHMAP_BASE(h)); } /* * Hashmaps are iterated in unpredictable order. * OrderedHashmaps are an exception to this. They are iterated in the order * the entries were inserted. * It is safe to remove the current entry. */ #define HASHMAP_FOREACH(e, h, i) \ for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); ) #define ORDERED_HASHMAP_FOREACH(e, h, i) \ for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); ) #define HASHMAP_FOREACH_KEY(e, k, h, i) \ for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) #define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); #define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) #define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) #define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) #define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) #define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) src/log.c000066400000000000000000000016071322621430500125740ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "log.h" #include "util.h" static int log_errorv( int error, const char *format, va_list ap) { int orig_errno = errno; const char *fmt; fmt = strjoina(format, "\n"); if (error != 0) errno = abs(error); vfprintf(stderr, fmt, ap); errno = orig_errno; return -abs(error); } int log_info_errno(int error, const char* format, ...) { va_list ap; int r; va_start(ap, format); r = log_errorv(error, format, ap); va_end(ap); return r; } int log_error_errno(int error, const char* format, ...) { va_list ap; int r; va_start(ap, format); r = log_errorv(error, format, ap); va_end(ap); return r; } src/log.h000066400000000000000000000025701322621430500126010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include int log_info_errno(int error, const char* fmt, ...); int log_error_errno(int error, const char* format, ...); #define log_info(fmt, ...) log_info_errno(0, fmt, ##__VA_ARGS__) #define log_error(fmt, ...) log_error_errno(0, fmt, ##__VA_ARGS__) static inline int log_oom(void) { log_error("Out of memory"); return -ENOMEM; } #define assert_se(x) \ do { \ if (!(x)) { \ log_error("%s:%d (%s): assertion failed:" #x "\n", \ __FILE__, __LINE__, __PRETTY_FUNCTION__); \ abort(); \ } \ } while(false) #define assert_not_reached(x) \ do { \ log_error("%s:%d (%s): unreachable code reached:" x "\n", \ __FILE__, __LINE__, __PRETTY_FUNCTION__); \ abort(); \ } while(false) src/mempool.c000066400000000000000000000054211322621430500134610ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010-2014 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #include #include #include "mempool.h" #include "util.h" struct pool { struct pool *next; unsigned n_tiles; unsigned n_used; }; void* mempool_alloc_tile(struct mempool *mp) { unsigned i; /* When a tile is released we add it to the list and simply * place the next pointer at its offset 0. */ assert(mp->tile_size >= sizeof(void*)); assert(mp->at_least > 0); if (mp->freelist) { void *r; r = mp->freelist; mp->freelist = * (void**) mp->freelist; return r; } if (_unlikely_(!mp->first_pool) || _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) { unsigned n; size_t size; struct pool *p; n = mp->first_pool ? mp->first_pool->n_tiles : 0; n = MAX(mp->at_least, n * 2); size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size); n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size; p = malloc(size); if (!p) return NULL; p->next = mp->first_pool; p->n_tiles = n; p->n_used = 0; mp->first_pool = p; } i = mp->first_pool->n_used++; return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; } void* mempool_alloc0_tile(struct mempool *mp) { void *p; p = mempool_alloc_tile(mp); if (p) memzero(p, mp->tile_size); return p; } void mempool_free_tile(struct mempool *mp, void *p) { * (void**) p = mp->freelist; mp->freelist = p; } #ifdef VALGRIND void mempool_drop(struct mempool *mp) { struct pool *p = mp->first_pool; while (p) { struct pool *n; n = p->next; free(p); p = n; } } #endif src/mempool.h000066400000000000000000000025301322621430500134640ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2011-2014 Lennart Poettering Copyright 2014 Michal Schmidt systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #pragma once #include struct pool; struct mempool { struct pool *first_pool; void *freelist; size_t tile_size; unsigned at_least; }; void* mempool_alloc_tile(struct mempool *mp); void* mempool_alloc0_tile(struct mempool *mp); void mempool_free_tile(struct mempool *mp, void *p); #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ static struct mempool pool_name = { \ .tile_size = sizeof(tile_type), \ .at_least = alloc_at_least, \ } #ifdef VALGRIND void mempool_drop(struct mempool *mp); #endif src/meson.build000066400000000000000000000036761322621430500140210ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ log_sources = files(''' log.c log.h '''.split()) libshared_sources = files(''' cachunk.c cachunk.h cachunker.c cachunker.h cachunkid.c cachunkid.h cacommon.h cacompression.c cacompression.h cadecoder.c cadecoder.h cadigest.c cadigest.h caencoder.c caencoder.h cafileroot.c cafileroot.h caformat-util.c caformat-util.h caformat.h caindex.c caindex.h calocation.c calocation.h camakebst.c camakebst.h canbd.c canbd.h caorigin.c caorigin.h caprotocol-util.c caprotocol-util.h caprotocol.h caremote.c caremote.h caseed.c caseed.h castore.c castore.h casync.c casync.h cautil.c cautil.h chattr.c chattr.h compressor.c compressor.h def.h dirent-util.c dirent-util.h fssize.c fssize.h gc.c gc.h hash-funcs.c hash-funcs.h hashmap.c hashmap.h mempool.c mempool.h notify.c notify.h parse-util.c parse-util.h realloc-buffer.c realloc-buffer.h reflink.c reflink.h rm-rf.c rm-rf.h set.h siphash24.c siphash24.h util.c util.h '''.split()) libshared = static_library( 'shared', libshared_sources, log_sources) casync_sources = files(''' casync-tool.c canbd.c canbd.h cafuse.h signal-handler.c signal-handler.h '''.split()) if conf.get('HAVE_FUSE') == 1 casync_sources += files('cafuse.c') endif casync_http_sources = files('casync-http.c') src/notify.c000066400000000000000000000024461322621430500133250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "notify.h" #include "util.h" int send_notify(const char *text) { const char *e; union { struct sockaddr sa; struct sockaddr_un un; } sa = { .un.sun_family = AF_UNIX, }; ssize_t n; size_t c; int fd, r; if (isempty(text)) return 0; e = getenv("NOTIFY_SOCKET"); if (!e) return 0; c = strlen(e); if (c < 2 || c > sizeof(sa.un.sun_path)) return -EINVAL; if (!IN_SET(e[0], '/', '@')) return -EINVAL; fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); if (fd < 0) return -errno; if (e[0] == '@') { sa.un.sun_path[0] = 0; strncpy(sa.un.sun_path + 1, e + 1, sizeof(sa.un.sun_path) - 1); } else strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path)); n = sendto(fd, text, strlen(text), MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)); if (n < 0) { r = -errno; goto finish; } r = 1; finish: safe_close(fd); return r; } src/notify.h000066400000000000000000000002021322621430500133160ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foonotifyhfoo #define foonotifyhfoo int send_notify(const char *text); #endif src/parse-util.c000066400000000000000000000104461322621430500141010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include "parse-util.h" #include "util.h" int parse_size(const char *t, uint64_t *size) { struct table { const char *suffix; unsigned long long factor; }; static const struct table table[] = { { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, { "G", 1024ULL*1024ULL*1024ULL }, { "M", 1024ULL*1024ULL }, { "K", 1024ULL }, { "B", 1ULL }, { "", 1ULL }, }; const char *p; unsigned long long r = 0; unsigned n_entries, start_pos = 0; assert(t); assert(size); n_entries = ELEMENTSOF(table); p = t; do { unsigned long long l, tmp; double frac = 0; char *e; unsigned i; p += strspn(p, WHITESPACE); if (*p == '-') return -ERANGE; errno = 0; l = strtoull(p, &e, 10); if (errno > 0) return -errno; if (e == p) return -EINVAL; if (*e == '.') { e++; /* strtoull() itself would accept space/+/- */ if (*e >= '0' && *e <= '9') { unsigned long long l2; char *e2; l2 = strtoull(e, &e2, 10); if (errno > 0) return -errno; /* Ignore failure. E.g. 10.M is valid */ frac = l2; for (; e < e2; e++) frac /= 10; } } e += strspn(e, WHITESPACE); for (i = start_pos; i < n_entries; i++) if (startswith(e, table[i].suffix)) break; if (i >= n_entries) return -EINVAL; if (l + (frac > 0) > ULLONG_MAX / table[i].factor) return -ERANGE; tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); if (tmp > ULLONG_MAX - r) return -ERANGE; r += tmp; if ((unsigned long long) (uint64_t) r != r) return -ERANGE; p = e + strlen(table[i].suffix); start_pos = i + 1; } while (*p); *size = r; return 0; } char *format_bytes(char *buf, size_t l, uint64_t t) { unsigned i; /* This only does IEC units so far */ static const struct { const char *suffix; uint64_t factor; } table[] = { { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, { "M", UINT64_C(1024)*UINT64_C(1024) }, { "K", UINT64_C(1024) }, }; if (t == (uint64_t) -1) return NULL; for (i = 0; i < ELEMENTSOF(table); i++) { if (t >= table[i].factor) { snprintf(buf, l, "%" PRIu64 ".%" PRIu64 "%s", t / table[i].factor, ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), table[i].suffix); goto finish; } } snprintf(buf, l, "%" PRIu64 "B", t); finish: buf[l-1] = 0; return buf; } src/parse-util.h000066400000000000000000000003741322621430500141050ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef fooparseutilfoo #define fooparseutilfoo #include #define FORMAT_BYTES_MAX 128 int parse_size(const char *t, uint64_t *ret); char *format_bytes(char *buf, size_t l, uint64_t t); #endif src/realloc-buffer.c000066400000000000000000000133301322621430500146770ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "def.h" #include "realloc-buffer.h" #include "util.h" void* realloc_buffer_acquire(ReallocBuffer *b, size_t size) { size_t ns, na, ne; void *p; if (!b) return NULL; if (size == 0) { b->start = b->end = 0; /* If a buffer of size 0 is requested, that's OK and not an error. On non-error we want to return * non-NULL, but also not actually allocate any memory. Hence return a pointer to the ReallocBuffer * object itself, as that's a known valid pointer. */ return b->data ?: b; } ne = b->start + size; if (ne < b->start) /* overflow? */ return NULL; if (ne <= b->allocated) { b->end = ne; return realloc_buffer_data(b); } na = b->allocated * 2; if (na < b->allocated) /* overflow? */ return NULL; ns = MAX(na, size); ns = ALIGN_TO(ns, page_size()); if (b->start == 0) { p = realloc(b->data, ns); if (!p) return NULL; } else { p = malloc(ns); if (!p) return NULL; memcpy(p, realloc_buffer_data(b), realloc_buffer_size(b)); free(b->data); b->start = 0; } b->data = p; b->end = size; b->allocated = ns; return b->data; } void *realloc_buffer_acquire0(ReallocBuffer *b, size_t size) { size_t na, ns; void *p; if (!b) return NULL; if (size == 0) { b->start = b->end = 0; return b->data ?: b; } if (size < b->allocated) { memset(b->data, 0, size); b->start = 0; b->end = size; return realloc_buffer_data(b); } na = b->allocated * 2; if (na <= b->allocated) /* overflow? */ return NULL; ns = MAX(na, size); ns = ALIGN_TO(ns, page_size()); p = calloc(ns, 1); if (!p) return NULL; free(b->data); b->data = p; b->allocated = ns; b->start = 0; b->end = size; return b->data; } void *realloc_buffer_extend(ReallocBuffer *b, size_t add) { size_t old_size, new_size; void *p; if (!b) return NULL; old_size = realloc_buffer_size(b); new_size = old_size + add; if (new_size < old_size) /* overflow? */ return NULL; p = realloc_buffer_acquire(b, new_size); if (!p) return NULL; return (uint8_t*) p + old_size; } void *realloc_buffer_extend0(ReallocBuffer *b, size_t add) { void *p; p = realloc_buffer_extend(b, add); if (!p) return NULL; memset(p, 0, add); return p; } void *realloc_buffer_append(ReallocBuffer *b, const void *p, size_t size) { void *m; if (!b) return NULL; m = realloc_buffer_extend(b, size); if (!m) return NULL; memcpy(m, p, size); return m; } void realloc_buffer_free(ReallocBuffer *b) { if (!b) return; b->data = mfree(b->data); b->allocated = b->end = b->start = 0; } int realloc_buffer_advance(ReallocBuffer *b, size_t sz) { size_t ns; /* Remove something from the beginning of the buffer */ if (!b) return -EINVAL; if (sz == 0) return 0; ns = b->start + sz; if (ns < b->start) /* Overflow? */ return -EINVAL; if (ns > b->end) return -EINVAL; if (ns == b->end) b->start = b->end = 0; else b->start = ns; return 0; } int realloc_buffer_shorten(ReallocBuffer *b, size_t sz) { /* Remove something from the end of the buffer */ if (!b) return -EINVAL; if (sz > realloc_buffer_size(b)) return -EINVAL; if (sz == 0) return 0; b->end -= sz; if (b->end == b->start) b->start = b->end = 0; return 0; } int realloc_buffer_truncate(ReallocBuffer *b, size_t sz) { if (!b) return -EINVAL; if (sz == 0) { b->start = b->end = 0; return 0; } if (sz > realloc_buffer_size(b)) return -EINVAL; b->end = b->start + sz; return 0; } int realloc_buffer_read(ReallocBuffer *b, int fd) { ssize_t l; void *p; if (!b) return -EINVAL; if (fd < 0) return -EINVAL; p = realloc_buffer_extend(b, BUFFER_SIZE); if (!p) return -ENOMEM; l = read(fd, p, BUFFER_SIZE); if (l < 0) { realloc_buffer_shorten(b, BUFFER_SIZE); return -errno; } realloc_buffer_shorten(b, BUFFER_SIZE - l); return l > 0; } void* realloc_buffer_steal(ReallocBuffer *b) { void *p; if (!b) return NULL; if (b->start == 0) { p = b->data; b->data = NULL; } else { p = memdup(realloc_buffer_data(b), realloc_buffer_size(b)); if (!p) return NULL; b->data = mfree(b->data); } b->start = b->end = b->allocated = 0; return p; } src/realloc-buffer.h000066400000000000000000000040371322621430500147100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef fooreallocbufferhfoo #define fooreallocbufferhfoo #include #include "util.h" typedef struct ReallocBuffer { void *data; size_t allocated; size_t start; size_t end; } ReallocBuffer; static inline void *realloc_buffer_data(ReallocBuffer *buffer) { assert(buffer); assert(buffer->start <= buffer->end); assert(buffer->end <= buffer->allocated); assert(buffer->data || buffer->allocated == 0); if (!buffer->data) return buffer; return (uint8_t*) buffer->data + buffer->start; } static inline void *realloc_buffer_data_offset(ReallocBuffer *buffer, size_t offset) { size_t p; assert(buffer); assert(buffer->start <= buffer->end); assert(buffer->end <= buffer->allocated); p = buffer->start + offset; if (p < buffer->start) /* overflow? */ return NULL; if (p > buffer->end) /* out of bounds? */ return NULL; return (uint8_t*) buffer->data + p; } static inline size_t realloc_buffer_size(ReallocBuffer *buffer) { assert(buffer); assert(buffer->start <= buffer->end); assert(buffer->end <= buffer->allocated); return buffer->end - buffer->start; } void* realloc_buffer_acquire(ReallocBuffer *b, size_t size); void* realloc_buffer_acquire0(ReallocBuffer *b, size_t size); void* realloc_buffer_extend(ReallocBuffer *b, size_t size); void* realloc_buffer_extend0(ReallocBuffer *b, size_t size); void* realloc_buffer_append(ReallocBuffer *b, const void *p, size_t size); void realloc_buffer_free(ReallocBuffer *b); static inline void realloc_buffer_empty(ReallocBuffer *b) { b->start = b->end = 0; } int realloc_buffer_advance(ReallocBuffer *b, size_t sz); int realloc_buffer_shorten(ReallocBuffer *b, size_t sz); int realloc_buffer_truncate(ReallocBuffer *b, size_t sz); int realloc_buffer_read(ReallocBuffer *b, int fd); void* realloc_buffer_steal(ReallocBuffer *b); #endif src/reflink.c000066400000000000000000000110541322621430500134420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include "reflink.h" #include "util.h" #define FS_BLOCK_SIZE 4096U #define VALIDATE 1 #if VALIDATE static ssize_t pread_try_harder(int fd, void *p, size_t s, off_t o) { char path[sizeof("/proc/self/fd/") + DECIMAL_STR_MAX(fd)]; ssize_t n; int fd_read, r; n = pread(fd, p, s, o); if (n >= 0) return n; r = -errno; sprintf(path, "/proc/self/fd/%i", fd); fd_read = open(path, O_CLOEXEC|O_RDONLY|O_NOCTTY); if (fd_read < 0) { errno = -r; return r; } n = pread(fd_read, p, s, o); safe_close(fd_read); if (n < 0) { errno = -r; return r; } return n; } #endif static void validate(int source_fd, uint64_t source_offset, int destination_fd, uint64_t destination_offset, uint64_t size) { #if VALIDATE ssize_t x, y; uint8_t *buffer1, *buffer2; buffer1 = new(uint8_t, size); assert_se(buffer1); buffer2 = new(uint8_t, size); assert_se(buffer2); x = pread_try_harder(source_fd, buffer1, size, source_offset); y = pread_try_harder(destination_fd, buffer2, size, destination_offset); assert_se(x == (ssize_t) size); assert_se(y == (ssize_t) size); assert_se(memcmp(buffer1, buffer2, size) == 0); free(buffer1); free(buffer2); #endif } int reflink_fd( int source_fd, uint64_t source_offset, int destination_fd, uint64_t destination_offset, uint64_t size, uint64_t *ret_reflinked) { struct stat a, b; uint64_t add, reflinked; /* Creates a reflink on btrfs and other file systems that know the concept. The input parameters are aligned to * match the fundamental block size (for now assumed to be 4K), and possibly to EOF. */ if (source_fd < 0) return -EBADF; if (destination_fd < 0) return -EBADF; /* Can only merge blocks starting at a block size boundary */ if (source_offset % FS_BLOCK_SIZE != destination_offset % FS_BLOCK_SIZE) return -EBADR; /* Overflow checks */ if (source_offset + size < source_offset) return -ERANGE; if (destination_offset + size < destination_offset) return -ERANGE; /* First step, round up start offsets to multiple of 4096 */ if (source_offset % FS_BLOCK_SIZE > 0) { add = FS_BLOCK_SIZE - (source_offset % FS_BLOCK_SIZE); if (add >= size) return -EBADR; source_offset += add; destination_offset += add; size -= add; } if (fstat(source_fd, &a) < 0) return -errno; if (fstat(destination_fd, &b) < 0) return -errno; /* Never call the ioctls on something that isn't a regular file, as that's not safe (for example, if the fd * refers to a block or char device of some kind, which overloads the same ioctl numbers) */ if (S_ISDIR(a.st_mode) || S_ISDIR(b.st_mode)) return -EISDIR; if (!S_ISREG(a.st_mode) || !S_ISREG(b.st_mode)) return -ENOTTY; /* Extend to EOF if we can */ if (source_offset + size >= (uint64_t) a.st_size && destination_offset + size >= (uint64_t) b.st_size) { reflinked = size; size = 0; } else { /* Round down size to multiple of 4096 */ size = (size / FS_BLOCK_SIZE) * FS_BLOCK_SIZE; if (size <= 0) return -EBADR; reflinked = size; } validate(source_fd, source_offset, destination_fd, destination_offset, reflinked); if (ioctl(destination_fd, FICLONERANGE, &(struct file_clone_range) { .src_fd = source_fd, .src_offset = source_offset, .src_length = size, .dest_offset = destination_offset, }) < 0) return -errno; validate(source_fd, source_offset, destination_fd, destination_offset, reflinked); if (ret_reflinked) *ret_reflinked = reflinked; return 0; } src/reflink.h000066400000000000000000000004101322621430500134410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef fooreflinkhfoo #define fooreflinkhfoo #include int reflink_fd(int source_fd, uint64_t source_offset, int destination_fd, uint64_t destination_offset, uint64_t size, uint64_t *ret_reflinked); #endif src/rm-rf.c000066400000000000000000000172601322621430500130400ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #include #include "chattr.h" #include "rm-rf.h" #include "util.h" static int unlinkat_immutable(int dir_fd, const char *name, int flags, RemoveFlags rflags) { _cleanup_(safe_closep) int fd = -1; int r; if (unlinkat(dir_fd, name, flags) >= 0) return 0; if (errno != EPERM) return -errno; if ((rflags & REMOVE_UNDO_IMMUTABLE) == 0) return -EPERM; fd = openat(dir_fd, name, O_CLOEXEC|O_RDONLY|O_NOFOLLOW|O_NOCTTY|(flags & AT_REMOVEDIR ? O_DIRECTORY : 0)); if (fd < 0) { /* If we can't open the thing because it's a symlink, propagate the original EPERM error. (Except if we are supposed to remove a directory) */ if (errno == ELOOP) return (flags & AT_REMOVEDIR) == 0 ? -EPERM : -ENOTDIR; return -errno; } if ((flags & AT_REMOVEDIR) == 0) { struct stat st; if (fstat(fd, &st) < 0) return -errno; if (S_ISDIR(st.st_mode)) /* This is a directory, but AT_REMOVEDIR wasn't set? then report it with the right error */ return -EISDIR; if (!S_ISREG(st.st_mode)) return -EPERM; /* chattr(1) flags not supported for anything not regular files or directories, propagate the original error */ } r = mask_attr_fd(fd, 0, FS_IMMUTABLE_FL); if (r < 0) return r; if (r == 0) /* immutable flag isn't set, propagate original error */ return -EPERM; if (unlinkat(dir_fd, name, flags) < 0) return -errno; return 0; } int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { struct statfs sfs; struct stat _root_dev; int ret = 0, r; DIR *d = NULL; assert(fd >= 0); /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed fd, even * on error */ if (!(flags & REMOVE_PHYSICAL)) { r = fstatfs(fd, &sfs); if (r < 0) { safe_close(fd); return -errno; } if (!is_temporary_fs(&sfs)) { /* We refuse to clean physical file systems with this call, unless explicitly requested. This * is extra paranoia just to be sure we never ever remove non-state data */ safe_close(fd); return -EPERM; } } if (!(flags & REMOVE_SPAN_DEVICES) && !root_dev) { if (fstat(fd, &_root_dev) < 0) { safe_close(fd); return -errno; } root_dev = &_root_dev; } d = fdopendir(fd); if (!d) { safe_close(fd); return errno == ENOENT ? 0 : -errno; } for (;;) { struct dirent *de; struct stat st; bool is_dir; errno = 0; de = readdir(d); if (!de) { if (errno != 0) { ret = -errno; break; } break; } if (dot_or_dot_dot(de->d_name)) continue; if (de->d_type == DT_UNKNOWN || (de->d_type == DT_DIR && root_dev)) { if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } is_dir = S_ISDIR(st.st_mode); } else is_dir = de->d_type == DT_DIR; if (is_dir) { int subdir_fd; /* if root_dev is set, remove subdirectories only if device is same */ if (root_dev && st.st_dev != root_dev->st_dev) continue; subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (subdir_fd < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file system type * again for each directory */ r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); if (r < 0 && ret == 0) ret = r; r = unlinkat_immutable(fd, de->d_name, AT_REMOVEDIR, flags); if (r < 0) { if (ret == 0 && r != -ENOENT) ret = r; } } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { r = unlinkat_immutable(fd, de->d_name, 0, flags); if (r < 0) { if (ret == 0 && r != -ENOENT) ret = r; } } } closedir(d); return ret; } int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags) { struct statfs s; int fd, r; assert(dir_fd == AT_FDCWD || dir_fd >= 0); assert(path); /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a really * seriously broken system. */ if (streq(path, "/")) return -EPERM; fd = openat(dir_fd, path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (fd < 0) { if (!IN_SET(errno, ENOTDIR, ELOOP)) return -errno; /* At this point we know it's not a directory. */ if (!(flags & REMOVE_PHYSICAL)) { fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW); if (fd < 0) return -errno; r = fstatfs(fd, &s); safe_close(fd); if (r < 0) return -errno; if (!is_temporary_fs(&s)) return -EPERM; } if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) { r = unlinkat_immutable(dir_fd, path, 0, flags); if (r < 0 && r != -ENOENT) return r; } return 0; } r = rm_rf_children(fd, flags, NULL); if (flags & REMOVE_ROOT) { r = unlinkat_immutable(dir_fd, path, AT_REMOVEDIR, flags); if (r < 0) { if (r == 0 && r != -ENOENT) r = r; } } return r; } int rm_rf(const char *path, RemoveFlags flags) { return rm_rf_at(AT_FDCWD, path, flags); } src/rm-rf.h000066400000000000000000000010751322621430500130420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foormrfhfoo #define foormrfhfoo #include typedef enum RemoveFlags { REMOVE_ONLY_DIRECTORIES = 1, REMOVE_ROOT = 2, REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ REMOVE_SPAN_DEVICES = 8, REMOVE_UNDO_IMMUTABLE = 16, } RemoveFlags; int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); int rm_rf(const char *path, RemoveFlags flags); int rm_rf_at(int dir_fd, const char *path, RemoveFlags flags); #endif src/set.h000066400000000000000000000100341322621430500126050ustar00rootroot00000000000000/*** SPDX-License-Identifier: LGPL-2.1+ This file is part of systemd. Copyright 2010 Lennart Poettering systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. systemd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ #pragma once #include "hashmap.h" #include "util.h" Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) static inline Set *set_free(Set *s) { internal_hashmap_free(HASHMAP_BASE(s)); return NULL; } static inline Set *set_free_free(Set *s) { internal_hashmap_free_free(HASHMAP_BASE(s)); return NULL; } /* no set_free_free_free */ static inline Set *set_copy(Set *s) { return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); } int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) int set_put(Set *s, const void *key); /* no set_update */ /* no set_replace */ static inline void *set_get(Set *s, void *key) { return internal_hashmap_get(HASHMAP_BASE(s), key); } /* no set_get2 */ static inline bool set_contains(Set *s, const void *key) { return internal_hashmap_contains(HASHMAP_BASE(s), key); } static inline void *set_remove(Set *s, const void *key) { return internal_hashmap_remove(HASHMAP_BASE(s), key); } /* no set_remove2 */ /* no set_remove_value */ int set_remove_and_put(Set *s, const void *old_key, const void *new_key); /* no set_remove_and_replace */ int set_merge(Set *s, Set *other); static inline int set_reserve(Set *h, unsigned entries_add) { return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); } static inline int set_move(Set *s, Set *other) { return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); } static inline int set_move_one(Set *s, Set *other, const void *key) { return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); } static inline unsigned set_size(Set *s) { return internal_hashmap_size(HASHMAP_BASE(s)); } static inline bool set_isempty(Set *s) { return set_size(s) == 0; } static inline unsigned set_buckets(Set *s) { return internal_hashmap_buckets(HASHMAP_BASE(s)); } bool set_iterate(Set *s, Iterator *i, void **value); static inline void set_clear(Set *s) { internal_hashmap_clear(HASHMAP_BASE(s)); } static inline void set_clear_free(Set *s) { internal_hashmap_clear_free(HASHMAP_BASE(s)); } /* no set_clear_free_free */ static inline void *set_steal_first(Set *s) { return internal_hashmap_steal_first(HASHMAP_BASE(s)); } /* no set_steal_first_key */ /* no set_first_key */ static inline void *set_first(Set *s) { return internal_hashmap_first(HASHMAP_BASE(s)); } /* no set_next */ static inline char **set_get_strv(Set *s) { return internal_hashmap_get_strv(HASHMAP_BASE(s)); } int set_consume(Set *s, void *value); int set_put_strdup(Set *s, const char *p); int set_put_strdupv(Set *s, char **l); #define SET_FOREACH(e, s, i) \ for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); ) #define SET_FOREACH_MOVE(e, d, s) \ for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); ) DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); #define _cleanup_set_free_ _cleanup_(set_freep) #define _cleanup_set_free_free_ _cleanup_(set_free_freep) src/signal-handler.c000066400000000000000000000032601322621430500147000ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "signal-handler.h" #include "util.h" volatile sig_atomic_t quit = false; void block_exit_handler(int how, sigset_t *old) { sigset_t ss; assert_se(sigemptyset(&ss) >= 0); assert_se(sigaddset(&ss, SIGINT) >= 0); assert_se(sigdelset(&ss, SIGTERM) >= 0); assert_se(sigaddset(&ss, SIGHUP) >= 0); assert_se(sigprocmask(how, &ss, old) >= 0); } void exit_signal_handler(int signo) { quit = true; } void install_exit_handler(void (*handler)(int)) { const struct sigaction sa = { .sa_handler = handler ?: exit_signal_handler, }; assert_se(sigaction(SIGINT, &sa, NULL) >= 0); assert_se(sigaction(SIGTERM, &sa, NULL) >= 0); assert_se(sigaction(SIGHUP, &sa, NULL) >= 0); } int sync_poll_sigset(CaSync *s) { sigset_t ss; int r; /* Block SIGTERM/SIGINT for now */ block_exit_handler(SIG_BLOCK, &ss); if (quit) /* Check if we are supposed to quit, if so, do so now */ r = -ESHUTDOWN; else { /* Wait for an event, temporarily and atomically unblocking SIGTERM/SIGINT while doing so */ r = ca_sync_poll(s, UINT64_MAX, &ss); if ((r == -EINTR || r >= 0) && quit) r = -ESHUTDOWN; } /* Unblock SIGTERM/SIGINT again */ block_exit_handler(SIG_UNBLOCK, NULL); return r; } void disable_sigpipe(void) { static const struct sigaction sa = { .sa_handler = SIG_IGN, .sa_flags = SA_RESTART, }; assert_se(sigaction(SIGPIPE, &sa, NULL) >= 0); } src/signal-handler.h000066400000000000000000000005721322621430500147100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef foosignalhandlerhfoo #include #include #include "casync.h" extern volatile sig_atomic_t quit; void exit_signal_handler(int signo); void install_exit_handler(void (*handler)(int)); void block_exit_handler(int how, sigset_t *old); int sync_poll_sigset(CaSync *s); void disable_sigpipe(void); #endif src/siphash24.c000066400000000000000000000162611322621430500136220ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 SipHash reference C implementation Written in 2012 by Jean-Philippe Aumasson Daniel J. Bernstein To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see . (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) (Refactored by Tom Gundersen to split up in several functions and follow systemd coding style) */ #include #include "siphash24.h" #include "util.h" static inline uint64_t rotate_left(uint64_t x, uint8_t b) { assert(b < 64); return (x << b) | (x >> (64 - b)); } static inline void sipround(struct siphash *state) { assert(state); state->v0 += state->v1; state->v1 = rotate_left(state->v1, 13); state->v1 ^= state->v0; state->v0 = rotate_left(state->v0, 32); state->v2 += state->v3; state->v3 = rotate_left(state->v3, 16); state->v3 ^= state->v2; state->v0 += state->v3; state->v3 = rotate_left(state->v3, 21); state->v3 ^= state->v0; state->v2 += state->v1; state->v1 = rotate_left(state->v1, 17); state->v1 ^= state->v2; state->v2 = rotate_left(state->v2, 32); } void siphash24_init(struct siphash *state, const uint8_t k[16]) { uint64_t k0, k1; assert(state); assert(k); k0 = read_le64(k); k1 = read_le64(k + 8); *state = (struct siphash) { /* "somepseudorandomlygeneratedbytes" */ .v0 = 0x736f6d6570736575ULL ^ k0, .v1 = 0x646f72616e646f6dULL ^ k1, .v2 = 0x6c7967656e657261ULL ^ k0, .v3 = 0x7465646279746573ULL ^ k1, .padding = 0, .inlen = 0, }; } void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { const uint8_t *in = _in; const uint8_t *end = in + inlen; size_t left = state->inlen & 7; uint64_t m; assert(in); assert(state); /* Update total length */ state->inlen += inlen; /* If padding exists, fill it out */ if (left > 0) { for ( ; in < end && left < 8; in ++, left ++) state->padding |= ((uint64_t) *in) << (left * 8); if (in == end && left < 8) /* We did not have enough input to fill out the padding completely */ return; #ifdef DEBUG printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); #endif state->v3 ^= state->padding; sipround(state); sipround(state); state->v0 ^= state->padding; state->padding = 0; } end -= (state->inlen % sizeof(uint64_t)); for ( ; in < end; in += 8) { m = read_le64(in); #ifdef DEBUG printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); #endif state->v3 ^= m; sipround(state); sipround(state); state->v0 ^= m; } left = state->inlen & 7; switch (left) { case 7: state->padding |= ((uint64_t) in[6]) << 48; /* fall through */ case 6: state->padding |= ((uint64_t) in[5]) << 40; /* fall through */ case 5: state->padding |= ((uint64_t) in[4]) << 32; /* fall through */ case 4: state->padding |= ((uint64_t) in[3]) << 24; /* fall through */ case 3: state->padding |= ((uint64_t) in[2]) << 16; /* fall through */ case 2: state->padding |= ((uint64_t) in[1]) << 8; /* fall through */ case 1: state->padding |= ((uint64_t) in[0]); /* fall through */ case 0: break; } } uint64_t siphash24_finalize(struct siphash *state) { uint64_t b; assert(state); b = state->padding | (((uint64_t) state->inlen) << 56); #ifdef DEBUG printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); #endif state->v3 ^= b; sipround(state); sipround(state); state->v0 ^= b; #ifdef DEBUG printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); #endif state->v2 ^= 0xff; sipround(state); sipround(state); sipround(state); sipround(state); return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; } uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { struct siphash state; assert(in); assert(k); siphash24_init(&state, k); siphash24_compress(in, inlen, &state); return siphash24_finalize(&state); } src/siphash24.h000066400000000000000000000012571322621430500136260ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef foosiphash24hfoo #include #include #include #include struct siphash { uint64_t v0; uint64_t v1; uint64_t v2; uint64_t v3; uint64_t padding; size_t inlen; }; void siphash24_init(struct siphash *state, const uint8_t k[16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); #define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) uint64_t siphash24_finalize(struct siphash *state); uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); #endif src/udev-util.h000066400000000000000000000005171322621430500137350ustar00rootroot00000000000000#ifndef fooudevutilhfoo #define fooudevutilhfoo /* SPDX-License-Identifier: LGPL-2.1+ */ #include "util.h" #include DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); #endif src/util.c000066400000000000000000001022541322621430500127700ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include #include #if USE_SYS_RANDOM_H # include #endif /* When we include libgen.h because we need dirname() we immediately * undefine basename() since libgen.h defines it as a macro to the * POSIX version which is really broken. We prefer GNU basename(). */ #include #undef basename #include "def.h" #include "util.h" #define HOLE_MIN 64 int loop_write(int fd, const void *p, size_t l) { if (fd < 0) return -EBADF; if (!p && l > 0) return -EINVAL; while (l > 0) { ssize_t w; w = write(fd, p, l); if (w < 0) return -errno; assert((size_t) w <= l); p = (const uint8_t*) p + w; l -= w; } return 0; } int loop_write_block(int fd, const void *p, size_t l) { if (fd < 0) return -EBADF; if (!p && l > 0) return -EINVAL; while (l > 0) { ssize_t w; w = write(fd, p, l); if (w < 0) { if (errno == EAGAIN) { struct pollfd pollfd = { .fd = fd, .events = POLLOUT, }; if (poll(&pollfd, 1, -1) < 0) return -errno; continue; } return -errno; } assert((size_t) w <= l); p = (const uint8_t*) p + w; l -= w; } return 0; } int write_zeroes(int fd, size_t l) { const char *zeroes; off_t p, end; size_t bs; /* Writes the specified number of zero bytes to the current file position. If possible this is done via "hole * punching", i.e. by creating sparse files. Unfortunately there's no syscall currently available that * implements this efficiently, hence we have to fake it via the existing FALLOC_FL_PUNCH_HOLE operation, which * requires us to extend the file size manually if necessary. This means we need 6 syscalls in the worst case, * instead of one. Bummer... But this is Linux, so what did you expect? */ if (fd < 0) return -EBADF; if (l == 0) return 0; p = lseek(fd, 0, SEEK_CUR); /* Determine where we are */ if (p == (off_t) -1) goto fallback; if (p + (off_t) l < p) return -EOVERFLOW; end = lseek(fd, 0, SEEK_END); /* Determine file size (this also puts the file offset at the end, but we don't care) */ if (end == (off_t) -1) return -errno; if (end > p) { if (fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, p, l) < 0) { if (lseek(fd, p, SEEK_SET) == (off_t) -1) /* Revert to the original position, before we fallback to write() */ return -errno; goto fallback; } } if (p + (off_t) l > end) { if (ftruncate(fd, p + l) < 0) return -errno; } if (lseek(fd, p + l, SEEK_SET) == (off_t) -1) /* Make sure we position the offset now after the hole we just added */ return -errno; return 1; /* Return > 0 when we managed to punch holes */ fallback: bs = MIN(4096U, l); zeroes = alloca0(bs); while (l > 0) { ssize_t w; w = write(fd, zeroes, MIN(l, bs)); if (w < 0) return -errno; assert((size_t) w <= l); l -= w; } return 0; /* Return == 0 if we could only write out zeroes */ } int loop_write_with_holes(int fd, const void *p, size_t l, uint64_t *ret_punched) { const uint8_t *q, *start = p, *zero_start = NULL; uint64_t n_punched = 0; int r; /* Write out the specified data much like loop_write(), but try to punch holes for any longer series of zero * bytes, thus creating sparse files if possible. */ for (q = p; q < (const uint8_t*) p + l; q++) { if (*q == 0) { if (!zero_start) zero_start = q; continue; } if (zero_start) { if (q - zero_start >= HOLE_MIN) { r = loop_write(fd, start, zero_start - start); if (r < 0) return r; r = write_zeroes(fd, q - zero_start); if (r < 0) return r; /* Couldn't punch hole? then don't bother again */ if (r == 0) { r = loop_write(fd, q, (const uint8_t*) p + l - q); if (r < 0) return r; if (ret_punched) *ret_punched = n_punched; return r; } n_punched += q - zero_start; start = q; } zero_start = NULL; continue; } } if (zero_start) { if (q - zero_start >= HOLE_MIN) { r = loop_write(fd, start, zero_start - start); if (r < 0) return r; r = write_zeroes(fd, q - zero_start); if (r < 0) return r; if (r > 0) n_punched += q - zero_start; if (ret_punched) *ret_punched = n_punched; return 0; } } r = loop_write(fd, start, q - start); if (r < 0) return r; if (ret_punched) *ret_punched = n_punched; return r; } ssize_t loop_read(int fd, void *p, size_t l) { ssize_t sum = 0; if (fd < 0) return -EBADF; if (!p) return -EINVAL; if (l == 0) return -EINVAL; while (l > 0) { ssize_t r; r = read(fd, p, l); if (r < 0) return -errno; if (r == 0) break; p = (uint8_t*) p + r; l -= r; sum += r; } return sum; } int skip_bytes(int fd, uint64_t bytes) { size_t buffer_size; void *m; off_t p; if (bytes == 0) return 0; p = lseek(fd, (off_t) bytes, SEEK_CUR); if (p != (off_t) -1) return 0; buffer_size = MIN(bytes, BUFFER_SIZE); m = alloca(buffer_size); do { ssize_t l; l = read(fd, m, MIN(bytes, buffer_size)); if (l < 0) return -errno; if (l == 0) return -EPIPE; assert((uint64_t) l <= bytes); bytes -= l; } while (bytes > 0); return 0; } char *endswith(const char *p, const char *suffix) { size_t a, b; const char *e; a = strlen(p); b = strlen(suffix); if (b > a) return NULL; e = p + a - b; return strcmp(e, suffix) == 0 ? (char*) e : NULL; } #if !HAVE_GETRANDOM # ifndef __NR_getrandom # if defined __x86_64__ # define __NR_getrandom 318 # elif defined(__i386__) # define __NR_getrandom 355 # elif defined(__arm__) # define __NR_getrandom 384 # elif defined(__aarch64__) # define __NR_getrandom 278 # elif defined(__ia64__) # define __NR_getrandom 1339 # elif defined(__m68k__) # define __NR_getrandom 352 # elif defined(__s390x__) # define __NR_getrandom 349 # elif defined(__powerpc__) # define __NR_getrandom 359 # elif defined _MIPS_SIM # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_getrandom 4353 # endif # if _MIPS_SIM == _MIPS_SIM_NABI32 # define __NR_getrandom 6317 # endif # if _MIPS_SIM == _MIPS_SIM_ABI64 # define __NR_getrandom 5313 # endif # else # warning "__NR_getrandom unknown for your architecture" # endif # endif static inline int getrandom(void *buffer, size_t count, unsigned flags) { # ifdef __NR_getrandom return syscall(__NR_getrandom, buffer, count, flags); # else errno = ENOSYS; return -1; # endif } #endif #ifndef GRND_NONBLOCK #define GRND_NONBLOCK 0x0001 #endif #ifndef GRND_RANDOM #define GRND_RANDOM 0x0002 #endif int dev_urandom(void *p, size_t n) { static int have_syscall = -1; int fd, r; ssize_t l; if (have_syscall != 0 || (size_t) (int) n != n) { r = getrandom(p, n, GRND_NONBLOCK); if (r == (int) n) { have_syscall = true; return 0; } if (r < 0) { if (errno == ENOSYS) have_syscall = false; else if (errno == EAGAIN) have_syscall = true; else return -errno; } else return -ENODATA; } fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return errno == ENOENT ? -ENOSYS : -errno; l = loop_read(fd, p, n); (void) close(fd); if (l < 0) return (int) l; if ((size_t) l != n) return -EIO; return 0; } char octchar(int x) { return '0' + (x & 7); } char hexchar(int x) { static const char table[16] = "0123456789abcdef"; return table[x & 15]; } int unhexchar(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -EINVAL; } char *hexmem(const void *p, size_t l) { const uint8_t *x; char *r, *z; z = r = new(char, l * 2 + 1); if (!r) return NULL; for (x = p; x < (const uint8_t*) p + l; x++) { *(z++) = hexchar(*x >> 4); *(z++) = hexchar(*x & 15); } *z = 0; return r; } bool filename_is_valid(const char *p) { const char *e; if (isempty(p)) return false; if (dot_or_dot_dot(p)) return false; e = strchrnul(p, '/'); if (*e != 0) return false; if (e - p > FILENAME_MAX) return false; return true; } int tempfn_random(const char *p, char **ret) { const char *fn; char *t, *x; uint64_t u; unsigned i; assert(p); assert(ret); /* * Turns this: * /foo/bar/waldo * * Into this: * /foo/bar/.#waldobaa2a261115984a9 */ fn = basename(p); if (!filename_is_valid(fn)) return -EINVAL; t = new(char, strlen(p) + 2 + 16 + 1); if (!t) return -ENOMEM; x = stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn); u = random_u64(); for (i = 0; i < 16; i++) { *(x++) = hexchar(u & 0xF); u >>= 4; } *x = 0; *ret = t; return 0; } void hexdump(FILE *f, const void *p, size_t s) { const uint8_t *b = p; unsigned n = 0; assert(s == 0 || b); if (!f) f = stdout; while (s > 0) { size_t i; fprintf(f, "%04x ", n); for (i = 0; i < 16; i++) { if (i >= s) fputs(" ", f); else fprintf(f, "%02x ", b[i]); if (i == 7) fputc(' ', f); } fputc(' ', f); for (i = 0; i < 16; i++) { if (i >= s) fputc(' ', f); else fputc(isprint(b[i]) ? (char) b[i] : '.', f); } fputc('\n', f); if (s < 16) break; n += 16; b += 16; s -= 16; } } char* dirname_malloc(const char *path) { char *d, *dir, *dir2; assert(path); d = strdup(path); if (!d) return NULL; dir = dirname(d); assert(dir); if (dir == d) return d; dir2 = strdup(dir); free(d); return dir2; } char *strjoin_real(const char *x, ...) { va_list ap; size_t l; char *r, *p; va_start(ap, x); if (x) { l = strlen(x); for (;;) { const char *t; size_t n; t = va_arg(ap, const char *); if (!t) break; n = strlen(t); if (n > ((size_t) -1) - l) { va_end(ap); return NULL; } l += n; } } else l = 0; va_end(ap); r = new(char, l+1); if (!r) return NULL; if (x) { p = stpcpy(r, x); va_start(ap, x); for (;;) { const char *t; t = va_arg(ap, const char *); if (!t) break; p = stpcpy(p, t); } va_end(ap); } else r[0] = 0; return r; } char* ls_format_mode(mode_t m, char ret[LS_FORMAT_MODE_MAX]) { if (m == (mode_t) -1) return NULL; switch (m & S_IFMT) { case S_IFSOCK: ret[0] = 's'; break; case S_IFDIR: ret[0] = 'd'; break; case S_IFREG: ret[0] = '-'; break; case S_IFBLK: ret[0] = 'b'; break; case S_IFCHR: ret[0] = 'c'; break; case S_IFLNK: ret[0] = 'l'; break; case S_IFIFO: ret[0] = 'p'; break; default: return NULL; } ret[1] = m & 0400 ? 'r' : '-'; ret[2] = m & 0200 ? 'w' : '-'; ret[3] = (m & S_ISUID) ? (m & 0100 ? 's' : 'S') : (m & 0100 ? 'x' : '-'); ret[4] = m & 0040 ? 'r' : '-'; ret[5] = m & 0020 ? 'w' : '-'; ret[6] = (m & S_ISGID) ? (m & 0010 ? 's' : 'S') : (m & 0010 ? 'x' : '-'); ret[7] = m & 0004 ? 'r' : '-'; ret[8] = m & 0002 ? 'w' : '-'; ret[9] = (S_ISDIR(m) && (m & S_ISVTX)) ? (m & 0001 ? 't' : 'T') : (m & 0001 ? 'x' : '-'); ret[10] = 0; return ret; } char *ls_format_chattr(unsigned flags, char ret[LS_FORMAT_CHATTR_MAX]) { static const struct { unsigned flag; char code; } table[] = { { FS_SYNC_FL, 'S' }, { FS_DIRSYNC_FL, 'D' }, { FS_IMMUTABLE_FL, 'i' }, { FS_APPEND_FL, 'a' }, { FS_NODUMP_FL, 'd' }, { FS_NOATIME_FL, 'A' }, { FS_COMPR_FL, 'c' }, { FS_NOCOMP_FL, 'N' }, /* Not an official one, but one we made up, since lsattr(1) doesn't know it. Subject to change, as soon as it starts supporting that. */ { FS_NOCOW_FL, 'C' }, { FS_PROJINHERIT_FL, 'P' }, }; size_t i; if (flags == (unsigned) -1) return NULL; assert(ELEMENTSOF(table) == LS_FORMAT_CHATTR_MAX-1); for (i = 0; i < ELEMENTSOF(table); i++) ret[i] = flags & table[i].flag ? table[i].code : '-'; ret[i] = 0; return ret; } char *ls_format_fat_attrs(uint32_t flags, char ret[LS_FORMAT_FAT_ATTRS_MAX]) { static const struct { uint32_t flag; char code; } table[] = { { ATTR_HIDDEN, 'h' }, { ATTR_SYS, 's' }, { ATTR_ARCH, 'a' }, }; size_t i; if (flags == (uint32_t) -1) return NULL; assert(ELEMENTSOF(table) == LS_FORMAT_FAT_ATTRS_MAX-1); for (i = 0; i < ELEMENTSOF(table); i++) ret[i] = flags & table[i].flag ? table[i].code : '-'; ret[i] = 0; return ret; } int safe_atoi(const char *s, int *ret_i) { char *x = NULL; long l; assert(s); assert(ret_i); errno = 0; l = strtol(s, &x, 0); if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; if ((long) (int) l != l) return -ERANGE; *ret_i = (int) l; return 0; } int safe_atou(const char *s, unsigned *ret_u) { char *x = NULL; unsigned long l; assert(s); /* strtoul() is happy to parse negative values, and silently * converts them to unsigned values without generating an * error. We want a clean error, hence let's look for the "-" * prefix on our own, and generate an error. But let's do so * only after strtoul() validated that the string is clean * otherwise, so that we return EINVAL preferably over * ERANGE. */ s += strspn(s, WHITESPACE); errno = 0; l = strtoul(s, &x, 0); if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; if (*s == '-') return -ERANGE; if ((unsigned long) (unsigned) l != l) return -ERANGE; if (ret_u) *ret_u = (unsigned) l; return 0; } int safe_atollu(const char *s, long long unsigned *ret_llu) { char *x = NULL; unsigned long long l; assert(s); s += strspn(s, WHITESPACE); errno = 0; l = strtoull(s, &x, 0); if (errno > 0) return -errno; if (!x || x == s || *x != 0) return -EINVAL; if (*s == '-') return -ERANGE; if (ret_llu) *ret_llu = l; return 0; } int readlinkat_malloc(int fd, const char *p, char **ret) { size_t l = 100; int r; assert(p); assert(ret); for (;;) { char *c; ssize_t n; c = new(char, l); if (!c) return -ENOMEM; n = readlinkat(fd, p, c, l-1); if (n < 0) { r = -errno; free(c); return r; } if ((size_t) n < l-1) { c[n] = 0; *ret = c; return 0; } free(c); l *= 2; } } int readlink_malloc(const char *p, char **ret) { return readlinkat_malloc(AT_FDCWD, p, ret); } char **strv_free(char **l) { char **k; if (!l) return NULL; for (k = l; *k; k++) free(*k); return mfree(l); } size_t strv_length(char **l) { size_t n = 0; if (!l) return 0; for (; *l; l++) n++; return n; } int strv_push(char ***l, char *value) { char **c; unsigned n, m; assert(l); if (!value) return 0; n = strv_length(*l); /* Increase and check for overflow */ m = n + 2; if (m < n) return -ENOMEM; c = realloc_multiply(*l, sizeof(char*), m); if (!c) return -ENOMEM; c[n] = value; c[n+1] = NULL; *l = c; return 0; } int strv_consume(char ***l, char *value) { int r; assert(l); r = strv_push(l, value); if (r < 0) free(value); return r; } int strv_extend(char ***l, const char *value) { char *v; assert(l); if (!value) return 0; v = strdup(value); if (!v) return -ENOMEM; return strv_consume(l, v); } int xopendirat(int fd, const char *name, int flags, DIR **ret) { int nfd; DIR *d; nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); if (nfd < 0) return -errno; d = fdopendir(nfd); if (!d) { safe_close(nfd); return -errno; } *ret = d; return 0; } void progress(void) { static const char slashes[] = { '-', '\\', '|', '/', }; static unsigned i = 0; static uint64_t last_nsec = 0; struct timespec now; static uint64_t now_nsec; assert(clock_gettime(CLOCK_MONOTONIC, &now) >= 0); now_nsec = timespec_to_nsec(now); if (last_nsec + 250000000 > now_nsec) return; last_nsec = now_nsec; fputc(slashes[i % ELEMENTSOF(slashes)], stderr); fputc('\b', stderr); fflush(stderr); i++; } char *strextend(char **x, ...) { va_list ap; size_t f, l; char *r, *p; assert(x); l = f = *x ? strlen(*x) : 0; va_start(ap, x); for (;;) { const char *t; size_t n; t = va_arg(ap, const char *); if (!t) break; n = strlen(t); if (n > ((size_t) -1) - l) { va_end(ap); return NULL; } l += n; } va_end(ap); r = realloc(*x, l+1); if (!r) return NULL; p = r + f; va_start(ap, x); for (;;) { const char *t; t = va_arg(ap, const char *); if (!t) break; p = stpcpy(p, t); } va_end(ap); *p = 0; *x = r; return r + l; } bool uid_is_valid(uid_t uid) { /* Some libc APIs use UID_INVALID as special placeholder */ if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) return false; /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */ if (uid == (uid_t) UINT32_C(0xFFFF)) return false; return true; } int parse_uid(const char *s, uid_t *ret) { uint32_t uid = 0; int r; assert(s); assert(sizeof(uid_t) == sizeof(uint32_t)); r = safe_atou32(s, &uid); if (r < 0) return r; if (!uid_is_valid(uid)) return -ENXIO; /* we return ENXIO instead of EINVAL * here, to make it easy to distuingish * invalid numeric uids from invalid * strings. */ if (ret) *ret = uid; return 0; } int wait_for_terminate(pid_t pid, siginfo_t *status) { siginfo_t dummy; assert(pid >= 1); if (!status) status = &dummy; for (;;) { memset(status, 0, sizeof(siginfo_t)); if (waitid(P_PID, pid, status, WEXITED) < 0) { if (errno == EINTR) continue; return -errno; } return 0; } } char *strv_find(char **l, const char *name) { char **i; assert(name); STRV_FOREACH(i, l) if (streq(*i, name)) return *i; return NULL; } size_t page_size(void) { static size_t pgsz = 0; long v; if (_likely_(pgsz > 0)) return pgsz; v = sysconf(_SC_PAGESIZE); assert(v > 0); pgsz = (size_t) v; return pgsz; } int parse_boolean(const char *v) { if (!v) return -EINVAL; if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on")) return 1; if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off")) return 0; return -EINVAL; } int getenv_bool(const char *p) { const char *e; e = getenv(p); if (!e) return -ENXIO; return parse_boolean(e); } void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { size_t a, newalloc; void *q; assert(p); assert(allocated); if (*allocated >= need) return *p; newalloc = MAX(need * 2, 64u / size); a = newalloc * size; /* check for overflows */ if (a < size * need) return NULL; q = realloc(*p, a); if (!q) return NULL; *p = q; *allocated = newalloc; return q; } void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { size_t prev; uint8_t *q; assert(p); assert(allocated); prev = *allocated; q = greedy_realloc(p, allocated, need, size); if (!q) return NULL; if (*allocated > prev) memzero(q + prev * size, (*allocated - prev) * size); return q; } int skip_bytes_fd(int fd, uint64_t n_bytes) { void *p; size_t m; if (fd < 0) return -EBADF; if (n_bytes == 0) return 0; if (lseek(fd, n_bytes, SEEK_CUR) == (off_t) -1) { if (errno != -ESPIPE) return -errno; } else return 0; m = (size_t) MIN(n_bytes, (uint64_t) PIPE_BUF); p = alloca(m); for (;;) { ssize_t k; k = read(fd, p, m); if (k < 0) return -errno; if (k == 0) return -ENXIO; n_bytes -= k; if (n_bytes == 0) return 0; m = (size_t) MIN(n_bytes, (uint64_t) PIPE_BUF); } } char *truncate_nl(char *p) { char *e; for (e = strchr(p, 0); e > p; e --) if (!strchr(NEWLINE, e[-1])) break; *e = 0; return p; } int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { int r; if (renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) >= 0) return 0; /* renameat2() exists since Linux 3.15, btrfs added support for it later. If it is not implemented, fallback * to another method. */ if (!IN_SET(errno, EINVAL, ENOSYS, EOPNOTSUPP)) return -errno; /* Let's try linkat(). This will of course failure for non-files, but that's fine. */ if (linkat(olddirfd, oldpath, newdirfd, newpath, 0) < 0) return -errno; if (unlinkat(olddirfd, oldpath, 0) < 0) { r = -errno; (void) unlinkat(newdirfd, newpath, 0); return r; } return 0; } char* path_startswith(const char *path, const char *prefix) { assert(path); assert(prefix); /* Returns a pointer to the start of the first component after the parts matched by * the prefix, iff * - both paths are absolute or both paths are relative, * and * - each component in prefix in turn matches a component in path at the same position. * An empty string will be returned when the prefix and path are equivalent. * * Returns NULL otherwise. */ if ((path[0] == '/') != (prefix[0] == '/')) return NULL; for (;;) { size_t a, b; path += strspn(path, "/"); prefix += strspn(prefix, "/"); if (*prefix == 0) return (char*) path; if (*path == 0) return NULL; a = strcspn(path, "/"); b = strcspn(prefix, "/"); if (a != b) return NULL; if (memcmp(path, prefix, a) != 0) return NULL; path += a; prefix += b; } } static int getenv_tmp_dir(const char **ret_path) { const char *n; int r, ret = 0; assert(ret_path); /* We use the same order of environment variables python uses in tempfile.gettempdir(): * https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir */ FOREACH_STRING(n, "TMPDIR", "TEMP", "TMP") { const char *e; e = secure_getenv(n); if (!e) continue; if (!path_is_absolute(e)) { r = -ENOTDIR; goto next; } if (!path_is_safe(e)) { r = -EPERM; goto next; } r = is_dir(e, true); if (r < 0) goto next; if (r == 0) { r = -ENOTDIR; goto next; } *ret_path = e; return 1; next: /* Remember first error, to make this more debuggable */ if (ret >= 0) ret = r; } if (ret < 0) return ret; *ret_path = NULL; return ret; } static int tmp_dir_internal(const char *def, const char **ret) { const char *e; int r, k; assert(def); assert(ret); r = getenv_tmp_dir(&e); if (r > 0) { *ret = e; return 0; } k = is_dir(def, true); if (k == 0) k = -ENOTDIR; if (k < 0) return r < 0 ? r : k; *ret = def; return 0; } int var_tmp_dir(const char **ret) { /* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus * even might survive a boot: /var/tmp. If $TMPDIR (or related environment variables) are set, its value is * returned preferably however. Note that both this function and tmp_dir() below are affected by $TMPDIR, * making it a variable that overrides all temporary file storage locations. */ return tmp_dir_internal("/var/tmp", ret); } int tmp_dir(const char **ret) { /* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually * backed by an in-memory file system: /tmp. */ return tmp_dir_internal("/tmp", ret); } bool path_is_safe(const char *p) { if (isempty(p)) return false; if (dot_or_dot_dot(p)) return false; if (startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) return false; if (strlen(p)+1 > PATH_MAX) return false; return true; } int is_dir(const char* path, bool follow) { struct stat st; int r; assert(path); if (follow) r = stat(path, &st); else r = lstat(path, &st); if (r < 0) return -errno; return !!S_ISDIR(st.st_mode); } src/util.h000066400000000000000000000556011322621430500130000ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #ifndef fooutilhfoo #define fooutilhfoo #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #define new(t, n) ((t*) malloc((n) * sizeof(t))) #define new0(t, n) ((t*) calloc((n), sizeof(t))) #define newa(t, n) ((t*) alloca((n) * sizeof(t))) #define XCONCATENATE(x, y) x ## y #define CONCATENATE(x, y) XCONCATENATE(x, y) #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) #define UNIQ __COUNTER__ #undef MAX #define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) #define __MAX(aq, a, bq, b) \ __extension__ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ const typeof(b) UNIQ_T(B, bq) = (b); \ UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ }) #define MAX3(a, b, c) MAX(MAX(a, b), c) #undef MIN #define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) #define __MIN(aq, a, bq, b) \ __extension__ ({ \ const typeof(a) UNIQ_T(A, aq) = (a); \ const typeof(b) UNIQ_T(B, bq) = (b); \ UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ }) #define CONST_MAX(_A, _B) \ __extension__ (__builtin_choose_expr( \ __builtin_constant_p(_A) && \ __builtin_constant_p(_B) && \ __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ ((_A) > (_B)) ? (_A) : (_B), \ (void)0)) static inline uint64_t timespec_to_nsec(struct timespec t) { if (t.tv_sec == (time_t) -1 && t.tv_nsec == (long) -1) return UINT64_MAX; return (uint64_t) t.tv_sec * UINT64_C(1000000000) + (uint64_t) t.tv_nsec; } static inline struct timespec nsec_to_timespec(uint64_t u) { if (u == UINT64_MAX) return (struct timespec) { .tv_sec = (time_t) -1, .tv_nsec = (long) -1, }; return (struct timespec) { .tv_sec = u / UINT64_C(1000000000), .tv_nsec = u % UINT64_C(1000000000) }; } #define NSEC_TO_TIMESPEC_INIT(u) \ { .tv_sec = u == UINT64_MAX ? (time_t) -1 : (time_t) (u / UINT64_C(1000000000)), \ .tv_nsec = u == UINT64_MAX ? (long) -1 : (long) (u % UINT64_C(1000000000)) } static inline uint64_t now(clockid_t id) { struct timespec ts; if (clock_gettime(id, &ts) < 0) return UINT64_MAX; return timespec_to_nsec(ts); } int loop_write(int fd, const void *p, size_t l); int loop_write_block(int fd, const void *p, size_t l); ssize_t loop_read(int fd, void *p, size_t l); int write_zeroes(int fd, size_t l); int loop_write_with_holes(int fd, const void *p, size_t l, uint64_t *ret_punched); int skip_bytes(int fd, uint64_t bytes); char *endswith(const char *p, const char *suffix); static inline void* mfree(void* p) { free(p); return NULL; } static inline int safe_close_above(int above, int fd) { if (fd >= above) { int saved_errno = errno; assert_se(close(fd) >= 0 || errno != EBADF); errno = saved_errno; } return -1; } static inline int safe_close(int fd) { return safe_close_above(0, fd); } static inline int safe_closep(int *fd) { return safe_close(*fd); } static inline void safe_close_nonstdp(int *fd) { safe_close_above(STDERR_FILENO, *fd); } typedef uint16_t le16_t; typedef uint32_t le32_t; typedef uint64_t le64_t; static inline uint64_t read_le64(const void *p) { uint64_t u; assert(p); memcpy(&u, p, sizeof(uint64_t)); return le64toh(u); } static inline uint32_t read_le32(const void *p) { uint32_t u; assert(p); memcpy(&u, p, sizeof(uint32_t)); return le32toh(u); } static inline uint16_t read_le16(const void *p) { uint16_t u; assert(p); memcpy(&u, p, sizeof(uint16_t)); return le16toh(u); } static inline void write_le64(void *p, uint64_t u) { assert(p); u = htole64(u); memcpy(p, &u, sizeof(uint64_t)); } static inline void write_le32(void *p, uint32_t u) { assert(p); u = htole32(u); memcpy(p, &u, sizeof(uint32_t)); } static inline void write_le16(void *p, uint16_t u) { assert(p); u = htole16(u); memcpy(p, &u, sizeof(uint16_t)); } static inline void* memdup(const void *p, size_t size) { void *q; q = malloc(size); if (!q) return NULL; memcpy(q, p, size); return q; } int dev_urandom(void *p, size_t n); static inline uint64_t random_u64(void) { uint64_t u; dev_urandom(&u, sizeof(u)); return u; } #define random_bytes(p, n) dev_urandom(p, n); #define _sentinel_ __attribute__ ((sentinel)) #define _unused_ __attribute__ ((unused)) #define _likely_(x) (__builtin_expect(!!(x),1)) #define _unlikely_(x) (__builtin_expect(!!(x),0)) #define _malloc_ __attribute__ ((malloc)) #define _pure_ __attribute__ ((pure)) #define _packed_ __attribute__ ((packed)) #define _const_ __attribute__ ((const)) #ifdef __clang__ # define _alloc_(...) #else # define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) #endif #define assert_cc(expr) static_assert(expr, #expr); #define CASE_F(X) case X: #define CASE_F_1(CASE, X) CASE_F(X) #define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) #define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) #define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) #define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) #define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) #define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) #define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) #define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) #define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) #define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) #define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) #define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) #define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) #define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) #define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) #define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) #define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) #define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) #define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) #define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME #define FOR_EACH_MAKE_CASE(...) \ GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ (CASE_F,__VA_ARGS__) #define IN_SET(x, ...) \ ({ \ bool _found = false; \ /* If the build breaks in the line below, you need to extend the case macros */ \ static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ switch(x) { \ FOR_EACH_MAKE_CASE(__VA_ARGS__) \ _found = true; \ break; \ default: \ break; \ } \ _found; \ }) char hexchar(int x); int unhexchar(char c); char octchar(int x); char *hexmem(const void *p, size_t l); bool filename_is_valid(const char *p); int tempfn_random(const char *p, char **ret); static inline bool isempty(const char *p) { return !p || !p[0]; } static inline const char *strempty(const char *p) { return p ?: ""; } static inline const char *strnone(const char *p) { return p ?: "none"; } #define streq(a,b) (strcmp((a),(b)) == 0) static inline bool streq_ptr(const char *a, const char *b) { if (!a && !b) return true; if (!a || !b) return false; return streq(a, b); } static inline const char *strna(const char *p) { return p ?: "n/a"; } void hexdump(FILE *f, const void *p, size_t s); char* dirname_malloc(const char *path); char *strjoin_real(const char *x, ...) _sentinel_; #define strjoin(a, ...) strjoin_real((a), __VA_ARGS__, NULL) #define LS_FORMAT_MODE_MAX (1+3+3+3+1) char* ls_format_mode(mode_t m, char ret[LS_FORMAT_MODE_MAX]); #define LS_FORMAT_CHATTR_MAX 11 char *ls_format_chattr(unsigned flags, char ret[LS_FORMAT_CHATTR_MAX]); #define LS_FORMAT_FAT_ATTRS_MAX 4 char *ls_format_fat_attrs(unsigned flags, char ret[LS_FORMAT_FAT_ATTRS_MAX]); int safe_atoi(const char *s, int *ret_i); int safe_atou(const char *s, unsigned *ret_u); int safe_atollu(const char *s, unsigned long long *ret_u); static inline int safe_atou64(const char *s, uint64_t *ret_u) { return safe_atollu(s, (unsigned long long*) ret_u); } static inline int safe_atou32(const char *s, uint32_t *ret_u) { return safe_atou(s, (unsigned*) ret_u); } int readlinkat_malloc(int fd, const char *p, char **ret); int readlink_malloc(const char *p, char **ret); #define strjoina(a, ...) \ ({ \ const char *_appendees_[] = { a, __VA_ARGS__ }; \ char *_d_, *_p_; \ int _len_ = 0; \ unsigned _i_; \ for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ _len_ += strlen(_appendees_[_i_]); \ _p_ = _d_ = alloca(_len_ + 1); \ for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ _p_ = stpcpy(_p_, _appendees_[_i_]); \ *_p_ = 0; \ _d_; \ }) #define WHITESPACE " \t" #define NEWLINE "\n\r" #define ELEMENTSOF(x) \ __extension__ (__builtin_choose_expr( \ !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ sizeof(x)/sizeof((x)[0]), \ (void)0)) char **strv_free(char **l); size_t strv_length(char **l); int strv_push(char ***l, char *value); int strv_consume(char ***l, char *value); int strv_extend(char ***l, const char *value); char *strv_find(char **l, const char *name) _pure_; #define strv_contains(l, s) (!!strv_find((l), (s))) static inline bool size_multiply_overflow(size_t size, size_t need) { return need != 0 && size > (SIZE_MAX / need); } _malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t size, size_t need) { if (_unlikely_(size_multiply_overflow(size, need))) return NULL; return malloc(size * need); } _alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t size, size_t need) { if (_unlikely_(size_multiply_overflow(size, need))) return NULL; return realloc(p, size * need); } #define STRV_FOREACH(s, l) \ for ((s) = (l); (s) && *(s); (s)++) int xopendirat(int fd, const char *name, int flags, DIR **ret); static inline bool dot_or_dot_dot(const char *p) { if (!p) return false; if (p[0] != '.') return false; if (p[1] == 0) return true; if (p[1] != '.') return false; return p[2] == 0; } void progress(void); char *strextend(char **x, ...) _sentinel_; #if SIZEOF_UID_T == 4 # define UID_FMT "%" PRIu32 #elif SIZEOF_UID_T == 2 # define UID_FMT "%" PRIu16 #else # error Unknown uid_t size #endif #if SIZEOF_GID_T == 4 # define GID_FMT "%" PRIu32 #elif SIZEOF_GID_T == 2 # define GID_FMT "%" PRIu16 #else # error Unknown gid_t size #endif #if SIZEOF_PID_T == 4 # define PID_PRI PRIi32 #elif SIZEOF_PID_T == 2 # define PID_PRI PRIi16 #else # error Unknown pid_t size #endif #define PID_FMT "%" PID_PRI bool uid_is_valid(uid_t uid); static inline bool gid_is_valid(gid_t gid) { return uid_is_valid((uid_t) gid); } int parse_uid(const char *s, uid_t* ret_uid); static inline int parse_gid(const char *s, gid_t *ret_gid) { return parse_uid(s, (uid_t*) ret_gid); } #define UID_INVALID ((uid_t) -1) #define GID_INVALID ((gid_t) -1) #define ALPHABET_LOWER "abcdefghijklmnopqrstuvwxyz" #define ALPHABET_UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" #define ALPHABET ALPHABET_LOWER ALPHABET_UPPER #define DIGITS "0123456789" /* This is a bit more restricted than RFC3986 */ #define URL_PROTOCOL_FIRST ALPHABET_LOWER #define URL_PROTOCOL_CHARSET ALPHABET_LOWER DIGITS "+.-" #define HOSTNAME_CHARSET ALPHABET DIGITS "-_." int wait_for_terminate(pid_t pid, siginfo_t *status); static inline void safe_close_pair(int pair[2]) { if (!pair) return; pair[0] = safe_close(pair[0]); pair[1] = safe_close(pair[1]); } static inline char *startswith(const char *s, const char *prefix) { size_t l; l = strlen(prefix); if (strncmp(s, prefix, l) == 0) return (char*) s + l; return NULL; } static inline bool strv_isempty(char **l) { return !l || !l[0]; } #if !HAVE_RENAMEAT2 # ifndef __NR_renameat2 # if defined __x86_64__ # define __NR_renameat2 316 # elif defined __alpha__ # define __NR_renameat2 510 # elif defined __arm__ # define __NR_renameat2 382 # elif defined __aarch64__ # define __NR_renameat2 276 # elif defined __hppa__ # define __NR_renameat2 337 # elif defined __ia64__ # define __NR_renameat2 1338 # elif defined __m68k__ # define __NR_renameat2 351 # elif defined _MIPS_SIM # if _MIPS_SIM == _MIPS_SIM_ABI32 # define __NR_renameat2 4351 # endif # if _MIPS_SIM == _MIPS_SIM_NABI32 # define __NR_renameat2 6315 # endif # if _MIPS_SIM == _MIPS_SIM_ABI64 # define __NR_renameat2 5311 # endif # elif defined __i386__ # define __NR_renameat2 353 # elif defined __powerpc64__ || defined __powerpc__ # define __NR_renameat2 357 # elif defined __s390__ || defined __s390x__ # define __NR_renameat2 347 # elif defined __sh__ # define __NR_renameat2 371 # elif defined __sh64__ # define __NR_renameat2 382 # elif defined __sparc__ # define __NR_renameat2 345 # elif defined __arc__ # define __NR_renameat2 276 # else # warning "__NR_renameat2 unknown for your architecture" # endif # endif static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { # ifdef __NR_renameat2 return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); # else errno = ENOSYS; return -1; # endif } #endif #ifndef RENAME_NOREPLACE #define RENAME_NOREPLACE (1 << 0) #endif static inline size_t strlen_null(const char *s) { if (!s) return 0; return strlen(s); } #define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) #define FOREACH_STRING(x, ...) \ for (char **_l = ({ \ char **_ll = STRV_MAKE(__VA_ARGS__); \ x = _ll ? _ll[0] : NULL; \ _ll; \ }); \ _l && *_l; \ x = ({ \ _l ++; \ _l[0]; \ })) #define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) static inline const char *empty_or_dash_to_null(const char *s) { if (isempty(s)) return NULL; if (streq(s, "-")) return NULL; return s; } size_t page_size(void); static inline size_t ALIGN_TO(size_t l, size_t ali) { return ((l + ali - 1) & ~(ali - 1)); } #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) /* We align a bit more than necessary on 32bit arches */ #define ALIGN8(l) (((l) + 7) & ~7) #define ALIGN(l) ALIGN8(l) int parse_boolean(const char *v); int getenv_bool(const char *p); #define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; static inline bool F_TYPE_EQUAL(statfs_f_type_t a, statfs_f_type_t b) { return a == b; } static inline bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { assert(s); return F_TYPE_EQUAL(s->f_type, magic_value); } static inline bool is_temporary_fs(const struct statfs *s) { return is_fs_type(s, TMPFS_MAGIC) || is_fs_type(s, RAMFS_MAGIC); } #define IS_POWER_OF_TWO(x) (__builtin_popcount(x) == 1) static inline const char *yes_no(bool b) { return b ? "yes" : "no"; } #ifndef XFS_SUPER_MAGIC #define XFS_SUPER_MAGIC 0x58465342 #endif #ifndef CONFIGFS_MAGIC #define CONFIGFS_MAGIC 0x62656570 #endif #ifndef MQUEUE_MAGIC #define MQUEUE_MAGIC 0x19800202 #endif #ifndef RPCAUTH_GSSMAGIC #define RPCAUTH_GSSMAGIC 0x67596969 #endif #ifndef NFSD_MAGIC #define NFSD_MAGIC 0x6e667364 #endif #ifndef FUSE_CTL_SUPER_MAGIC #define FUSE_CTL_SUPER_MAGIC 0x65735543 #endif #ifndef CGROUP2_SUPER_MAGIC #define CGROUP2_SUPER_MAGIC 0x63677270 #endif #ifndef FUSE_SUPER_MAGIC #define FUSE_SUPER_MAGIC 0x65735546 #endif #ifndef FICLONERANGE struct file_clone_range { int64_t src_fd; uint64_t src_offset; uint64_t src_length; uint64_t dest_offset; }; #define FICLONERANGE _IOW(0x94, 13, struct file_clone_range) #endif #define PTR_TO_INT(p) ((int) ((intptr_t) (p))) #define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) #define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) #define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); #define GREEDY_REALLOC(array, allocated, need) \ greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) #define GREEDY_REALLOC0(array, allocated, need) \ greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0])) #define alloca0(n) \ ({ \ char *_new_; \ size_t _len_ = n; \ _new_ = alloca(_len_); \ (void *) memset(_new_, 0, _len_); \ }) #define memzero(x,l) (memset((x), 0, (l))) #define zero(x) (memzero(&(x), sizeof(x))) #define malloc0(n) (calloc(1, (n))) static inline void *mempset(void *s, int c, size_t n) { memset(s, c, n); return (uint8_t*)s + n; } #define DECIMAL_STR_MAX(type) \ (1+(sizeof(type) <= 1 ? 3 : \ sizeof(type) <= 2 ? 5 : \ sizeof(type) <= 4 ? 10 : \ sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) int skip_bytes_fd(int fd, uint64_t n_bytes); char *truncate_nl(char *p); #define SOCKADDR_UN_LEN(sa) \ ({ \ const struct sockaddr_un *_sa = &(sa); \ assert(_sa->sun_family == AF_UNIX); \ offsetof(struct sockaddr_un, sun_path) + \ (_sa->sun_path[0] == 0 ? \ 1 + strnlen(_sa->sun_path+1, sizeof(_sa->sun_path)-1) : \ strnlen(_sa->sun_path, sizeof(_sa->sun_path))); \ }) static inline uint32_t rol32(uint32_t x, size_t i) { i %= 32; if (i == 0) /* Make ubsan happy */ return x; return ((x) << (i)) | ((x) >> (32 - i)); } static inline unsigned log2u(unsigned x) { assert(x > 0); return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; } static inline unsigned log2u_round_up(unsigned x) { assert(x > 0); if (x == 1) return 0; return log2u(x - 1) + 1; } #ifndef FS_PROJINHERIT_FL #define FS_PROJINHERIT_FL 0x20000000 #endif #ifndef RENAME_EXCHANGE #define RENAME_EXCHANGE (1 << 1) #endif int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); char* path_startswith(const char *path, const char *prefix); static inline bool path_is_absolute(const char *p) { return p && p[0] == '/'; } int var_tmp_dir(const char **ret); int tmp_dir(const char **ret); bool path_is_safe(const char *p); int is_dir(const char* path, bool follow); #ifndef BTRFS_IOC_SUBVOL_GETFLAGS #define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) #endif #ifndef BTRFS_IOC_SUBVOL_SETFLAGS #define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) #endif #define NSEC_PER_SEC (UINT64_C(1000000000)) /* Cleanup functions */ #define _cleanup_(x) __attribute__((cleanup(x))) #define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ static inline void func##p(type *p) { \ func(*p); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ static inline void freep(void *p) { free(*(void**) p); } #define _cleanup_free_ _cleanup_(freep) #endif test-files/000077500000000000000000000000001322621430500131335ustar00rootroot00000000000000test-files/.gitignore000066400000000000000000000000571322621430500151250ustar00rootroot00000000000000/acl /immutable /nocow /reflink /sparse /xattr test-files/a symlink with umläüts000077700000000000000000000000001322621430500232142fööbärustar00rootroot00000000000000test-files/bar000077700000000000000000000000001322621430500143232fooustar00rootroot00000000000000test-files/large000066400000000000000000040000001322621430500141420ustar00rootroot00000000000000|=bsLƖxk kFp}AZe;an W{L;(9J6QdyeiEr#^ wGso1 Ur%@wQ}{feגiHQʔb^aT=eIf0YP._ptm\aRM^\gG(yΓi^eȦ7idPק|e^In禟EVv,/ cP*Mns)#ZH>M(Aϭ)7 9:ij_m dXg}Ѫl+dL*x)O d'cυC+ .zj8'j H8JVJ]}%q9&=hp.q筿QA=brJ(N 2k@cG}и n&??q}UnUpVxA"zrLc +KY& teWJm{m4mM;4rt( TNhèD<1^eLLPčz"U A &@u ^yYԈ~ĤDVYxyuY7^H3!@̙AS-Z:~}YdXE{}~&~TT"!җk(!ypD/x.kWQ1K~JpiOֱrsjsFj?##^{rmaզ.'='13iIJW,h6pFkM@~r j2Ghcc%HPj7)02c,.Ùbim)5Z5|%A?! <ڌE&v7nˢaB"0#aV؈6-'^LY ,?ёH\Ɋ>wؕe"0̖h]hQO"}HT' u/Ք!zoXA-Kй|044{\Ort{ .)+$۝Gu:}e"'hHh,¹+l~x`\,˩{ 8%!O/^2XpC`&mRKcg?\yGev+Q6eLw Sf,ӪhzOLW(޿r* D[ w 0?@UnsN)1spBTp. wz}".J*f5IGDŽO>H^E3V5s øF2H!zhҭ0u!Vd4l.қ^\Zj4SN{["cv3xDxe (ҕ1$թ\+҇ICXe8BSt=:q'O\t+OF``KM؃5u7̃6P^w0Η9=z_Jx&;K\ә ~"M [;^1%Y$Ia!L!aG%pn 3,E%t`  fXRBOQA"Qʖ/xItt gԇVje$iؚe&bU 7ΖD1E@`ϺiUeW#%A))-h b#foCgX !5Kh+dž+f0n2)Ez1HYn]qddqY~Tt ֣tB) H8xkX$/80c# hY=).p+ۦf1wX 農9I?{[CE EICdѸǫ4L @pt0OtEWJ ڴRni#NUrJ~Eݪ8%PDrz\Wd o@pi1=Vբu $",9kϰ닇/3 py;Qy Qi2so!"@6' MFל0NGѵt oz=wcRjfx$&jS]GB GL#ܢϮ:^)Bf&0mU_-FBzWד7v;p+_tgr/j@;LEfTx- xqCsnzk%^Jfx82>wYO )Xbѐw/ޯeF/D mR`R:q+5O|Һ1 < 9dqʑ:T(L*z!y-&ix\ZfU; rJ{x8Y8=s ZꁥWΜׅ}!s2tMC@=4i6g旵nkÐLr LO[Ύ6qRd}i d532-Eͣ;ꂮr \зY6蕩uAhml2ٜTcbn| k㪮o4e$[0y}#fygI[o_ tj[֒W[eWy;[)H>QXXczϰ?R琥Nx-70AZ as|s *i!n6w "bܒ4Q|ΨȘbo4ࡦZ0χ&9yN>W佉jhmR"p F^lŤ8`$ߗ4#e34qPNxurLcׅZ%$Ԅwr՟tCfl&5o$4mFHA+!7ðyPw$M4wŒ7}ָTN41baOS"9YJ p*(_vPQϼ4?}wp#iP;fMVSo7NʬG:Ն5;C`t82л3PξZ'B,âlOj[- cݞgnYKx^Вl̶kXi;=płj'%E}coáyLS ڻ7fKJSLH,cZ3hlwDAt!s|.ر⥵;{S!)P KpXr 8W֘@f sKl/شg[}[ ּ-@ /K[Pun:L[c&"^wJ0[Y}8n-q2Uǵ Dz'?ah 8qք~Dghąƴa#G=zg\~)=Wή1WW 6E'=U)7!#zXPʏX7PٌgCY ZpolA+\pzAn ZNUFVuv{e:-MP'0; 6~ĵCB~`70"EL傤-LiOz[ELMӈb #@e$O3ݳN35d)idok>Զ׮OL$c;fc9CEN^ϪJȱYD@IG&|tYr!%b.b@(ďˢ/ؒ|#F챠(ܷ`͢=KI]s /Ɍ\Kf޹){$VR gjK7t&c̦;5f.:p"=9*@+m 9>,)ʩ(j_MRK^F-g+%" o3!j݉Y@Xffv,5р] 2W]댽 P̯[HmYu17#ꆦ+Ohޙd3%'/+ E'"*.3j,Z)B{S̩;@ Gfjs}Y1U+G;:a`~mn3JzcN\,7?Z s#98?jZ[A^HiSnLh2DYf`dijm5!X{[97;\P$@-EZ6|B,%ҬNudr]QJ/6Gr +AuK&a 3.>pAo\w n}{VB:],.&Ł2zyq v/3{lc .ףgrj[Rf1)ѧ nKy^(?i4spkJMU'mWU~9mծpEfvp\}&{hQYprվط͸~Ûyv-ilG~雅HDs)xf͝< 0ҨrD轁ک\T[M|O?a0fXpȤýTc#31;vd*s9rH\ a_kWDY7 cr @% f<2>*\B&Oə^\MIΙA0WJ,zH@vkezn0^4װԼON 3AepI_D?'Q<]Utc-8;+2i&6|SsGkS 4qǁ)!-B~8ZKpi>V4f?Z224*R0=Qֽ|کmS/:\g*Yy5&|ն rŜWlMJU01`yEDWh ǂD:D @[@pit\׻\ /b H 1ӝçFDڳqWB[DXg"3C鸬Ǿ!]S],3 S%·KԔ?ڱoWt^>6kQW YFY\~3ĕPԐQX\븶ggrOAXsl /֬J$)ퟚ\dꗦdJ+sד 6-=gx~9aBJ]l8V`7zFQ$}xkF'\{nBi i%42DJ˽:$3SC ;q4gS(0\z i݀IE3TwRd@R~IPJH7pYE`igVSS# Ær~ @q|F^0%UɷL5tc|vs?rsZLϐh3PZ 1M+zE)b[9lQj&SUI8Qs?=.!K0y}9$j2"Ns"bR=O4czؠ$/:֍g>qi,$>-醏Ֆ/uT0@Sʼ"\,nf|-d>*yvE`Nt@:,HS ʑ k*[mv@m2q AkWoΖK+Sf=o< c{=$5pA *`E%Um 8q5ɭLOǰwLMo}Nm3"+u1t" Oyx 4^[ExcA['3KN A`\qVqsYk`Ϩ;@@zEc$To & NTQX!b`o1z%~S*E$\U7$dԼ.v2ΖOJϕ &H?h_U[}vu8v\3Pnd /6i:PuK/4Q ЧP ΃N3mՐIr^mٹWM]S,Hy}/ci)хм%]0tExP:@2Cȯ9k洡ݸ@pU.Hazsn[\}ZF? Ҍ!M6FSP=~[n6.?ͨ[]3@ݩHynJ±fcșʝ͑k˛{܊q|s.zg)~A{ecC_xh^` ɵFdR-X䌜 7&Ӷ$&*Y'ƕ:V93SEu^,ɆS&.gR6@Lx0%}CܕGiR4G^/DI^y^CQWTӆNŒSE<|1>*6m;S^p XWxU d`y9Eh "HyM9K[RpR6|iN5B87I7@X ..Q`Hbdq.15_}ҏr\=%^vAvMy> G`ƿ \FYCVqvM(zCj~uwq=טi:TJݐV-\tH/qY7SvMSwW|PL*瓓d-TSyB,-ЪI_F,#4}/*yg[:#-W -K1S<[m歾K}}'>N_#t#[ $]x$gi bۣX8lD3',&՞.Ĕ>oE$/jYpJ8@Er캉(d@NO};8'(jn*b^8 wffkӉF/ n}8-`!E t(/QvEdR`67"/j֠ղCN+oLN)VdC@F"1$sXʬ6bzHVJv8l>b^mS?/s4=RǦ.a:m 50z/)Z=A:|@`9̢1kcժSMA*Xa}s{5Yv:^}{EJ*$eAK ˬ_``c;f (~|avs!mB/:8e1stFsOcRrɫ!n =QuS}v+OLg]k6GnầW[ ʗjVf{_ܝK?V=gxiB7?☩淆@F5'Ekf`&ZU`?Hbi!/<ȏS #]ZNW IֿUPe=~S.[ m)D4,=2 π bdLG]Gg;x#zX-iJe8':TFԭ 7X~Amg6sE V)K!p0͕:,_F-@x[D$6e/?W V-摇^yt-s5(?M1#V_/؄,KoR >0Th5w?8tXܮbXbc|wj(#C%)vv<| M){VJ`8x6O5{ T]ADҐD'W-hi|\i!5UkaN|ҽ`f쒿ƣaN"QEF?82:Q.*:={qa#cfFy},l'YCbr61* ң5h7X˖~mOqeBzǽ :?o8 e~!@ >*mv̎4Ѻ -3׮Z%Q'!QՁ8FƑ{5鏧nޯK:nZe~q^؍N%{8 –hHo4:J 4Q4$R&u2]zN=ei??DOS2's% о|5Žh;m{$0# zgalE?=}oH@KBE(Ɂ7\n yϣtvX"t0ӰC cNu&F0i@kvv#=4!˷yBo3)3mkDrNJҤg T z1גZOvyL@\9]Hj9j}>[f )!PL5qn xG&5<0RZR|vZ*^\> Y:ߑinzͯ` y] [q?SԎ| 0N"*c}Mηlo@oyeBP S*$hJ{(fI/EPc)]*C;P~FވXJ!tS+r*( I&11zOE%ڲgHnbwF`" Z -wu=(K=]:9] 2)KF_qp {r;|3N hy@b-5R'[vھafk1c-x1=~UR7BhIS /A #,j/P;xOp4+nOD_S'.,L'|k+pC~9: 5ά7}XS3!Qۺ ȶcH5:5OQ\DA@t.9}*ۑ} h}R BU Lwfb =k[6P s ퟞM\Hi2[8Qu }e'PǘUf=TH &JN5!O~JŒ#Rf:ͫ0-k Md9ڶq~uNn~FŘ|ΏN%"qpV ;;b*qf;\!#‡"deG,) =?0 Ily{!Ξb>OkmT=w5tQ4Jlm ~>ʱȑF&AkMN#]K)ykM 8>A&Ci&Y}@R`3R“Lu3NU=sc*c\U=!,[%@E0gC5*vf-;N(@!ҬFo\,/!Wnc{ &pyvK4q#K{EӝF9^czϬ.6MaCS`/uJ7&U"jUXM4( A3;DEu,m&6 s{|} Fv(rEةOӳݧ< Ϥ8<4<1B3CHy&GZE8Da Ge»>k|I8=i#Wd#`FXPwƘ*+QA&܎DNo&lԹ ڑ:ny$jsW~}yJ@N:)G_a1B,X#L5M*1wdBǧP6<TS&tݣ~G\fW*. `kcijqj_ũOjjB`WQP:;2q3ݏ5 /`C( FGL"}I% Q[`$'4u(Z cAUnP>(SNTnCEtVC9ĢH"&$ڍƋ2-IiT6dj# P_09oР6"P Z}U0ܶ3ͮ`jÎpwmr(ѴעICH(٫UGSX0gM'-FMxdJ`Z][:[B3G|&]!CJdf:r5KZ ď= `ȄbW|kh?ZHCSz(jJvBithB(.53\/sC&wQ w5nh48j 5.NuknŨbZQR j 6(žQxpL hgV XS!=[Gه|E1 C{D'05d:7Qv@jDW)y~ыX!SUYprȬ+KYJGMDҨ LaubӼ7~6SlʒQ}?u$RvkFZ#@!+{5r cEf<1 ;xΟtˢQM' -]zU9}?!=i%Ȟm7B{="b8G^tc՚vje|o/uzbZ'#SI㌅XqoOh t3e?K +Ty?<]2fŖrt7I$nq梿w6SB0Y̋"@ޘ D$[%욑lW/5lh *#5/]6N6Vڵ Tr Z*))VWq2 :3SW(&VW1Cq=M٬:rebvT f맸5YUǏ覹/?z;Y >غVjWÊ\ZIHS0{X 9!{٧FZcD/|D–91'4 9*Z*욧^Fcj /km?% 2^Tk˃IyW+P*HKq4586vW2/9Ӝ 'Νgק-f^7^!%%m1kr^gN7Ov܁@w%YI ku'ǫB ߥcH-Gs:aɢ.yEU!c_hKtUW@Qg2 :g$ho*RrtmY! 5P@=UE83s?JK `eB:`QgMBzZg@u= vejƶ4ueX+ޢ ;$x!5rr;V)],jÿ6+\ #n>ȳ '0+6GQy>'Bދ | %E  _|x 8|j|훉G1LCctK:@ERyxT\-i4>KyYL%<«eJ?єe\^'EtW2dpbV5X%tvͨ`?`:fn`7p,Y'%. DdgcnCAO7ԓn3|HSA8WЭ3+Ac;GeTw jNlos%o jFz()oX1/DzZ Vʂ.@QR {΋BD" 6n8H?qǿm[Cm(@h<kqBh܂) /#v7\a[+c^(fp`~ @ ԤSŢkw]󻘻=hDpFIH-[n?5e'tQIW.S=$,^/9%Jr:4@ʳBS71$^t({LITɟ1fNj2d3a!M %FPjo.xQA-TǥAbYNetu)JSџwlޱ*p=`*-埨~RTj\РA#9'3KZ]3E>Hm`XO,O>푔&ݞ9Oy͕C=D%lbm#/.^B(IPr#_ A T5NK# Ý9}4 w`s{ק/v7fGQf|vjNg+)H\~Y ֛s8 A]nh8鬲soKfnn560u6+մ7@ûe%ڒV,/9š9|tU7Z{xRiZx3H-8 `@23W~hP2Z:Ж0wٛ}71əWlH#8 N BojQ޽y(;n=wrDk<zHcH)+ၥYcjϛɪ|chftB%=Fj j"7+P<_vZ98ͻ֑=G/L߿ynxz# !-p|<)u7(  z^2gy7$h*;7 Ű0Vt(X3l,^ӔN'&g9s-27=U{hNSfͥ=x wƍtjF <+ʱQ>;6mnZ|ɑ%t^WTu~z8/.kH^ޕ )uf{O#k{ %jVGn Vk/eTmWi尵clxwx(= S:I^ &ScӮgorv%pc'e$΁97m ḶBfp>^iIqsH Y^QSd*"Cu4,mڤ"DuSPs ՙ4'>X9 r5t Lrl! LwZ_# V?O:C?3e =9_ӿ?㠈 Ms? ) 7Ol>Np(pEFaRPKh$knR8g f0\"4d@Mht Fv"Q>̱:ju vh#M@,'Z08 }- Jm?cQY%h˩;}cGZLdž>8;v2| Mg5&vZ/Fڒ^塞¯]׬<B/p:Ae!FUG yQ /T5G}Y& ^`Dxf3FS k"y*B73Z׃Yؾ6s*QF8[~KexGlNߩMIq|(%fTtI:8ʌ$R:-BD@:AQ`C2^{1ĖTdA8l0B H@ΰUԱf؜uۀHx,~ 銢bTQp̎Ep6xju g^wvσZ+ׄIe9j W $m6C3,,i*!szod)VWJ!gk/[)Y` }ٹ]jڏvd+MCׯm:`'GG //X ❽/^y~E j؞ȬttDs=XfS9gpk*4ˤ[YY"m1+ M ,2-neA?@C o)q "n_>&raF+(ywG]D&mዧQ#z \"gCV:f{NcI!hG7@Hdt1}jHqYؤWahT; ( +ZW;{2հiaEIt~l4RGD)`7uJL/1B^'%_+ SVE+*X43l:wޫ=j*5)Ʋ9)3wv{@:GvI1=wS,/[XS3* k}~[ *Q%Ftq?0s>K6g7`CZiΈ] :Pd9lZ:rn!"EJXGyIIRg`q!ݥ}oKmH~6Ԑ# d4iAiAfŵUsiNCBoq1LT'#;o3^EtǹpD7asZulc= UBɿC>!hJ%uW34 \3 Z%q6gi@e8%)kOL.gX@+S{q(̚<mv|wRlSk} zK*}z3#ĉFA1k>p[Rl%p޼eGD%u<_L**Qd9JuIRMN la!o<<⺹'gnȰτ7mc+ujO[XT,)+ouQLx@"p:牬DwƦ?RCV+=crTo#ceK#tR@|SW{<޾ :?YS;_; :j`e@+T;җOw aYIJ,ܞK;*Y+$lJ95%YC, Zk\#G#Y}l'PJ8ny$憪E%(|t(+xqPIիE%^ ک  |e36?l0S'b ]N{ht-JnFZY$jvFN/  dH,1.èjH- ht<N{dckP9\}'NzfQMO'Px Q Uz\$fcvX`jc7b>/β3C5+#[H:%V6p{=q\7&KSkŌ) m=S~riH-cMb֞\gWtE%!p飄 ~*{d0ͫ yMR$T7FgGKoië э#&hM6Yq O~(e)ګW"R /Ԩ+"6GO*L8Ebg>HxZF:R_}CxյWٻ%Xw8O ;0%~7~$NL%PwpsS)t?c{ 1 _bcx -k۪W;\[iأg5Dc*9Ͼ8M #R n]z]˭&dFXI9V4Y5*y>H?(n;G:ВH: u$8gt.~D-sUq)Y9sܨΪ}::؃*nrGp2CH<2T FLI#:OnYC4śxOSҝrGS‘O7J:C29V+YkPA'ȓ6KM'f=yUOn ک֕3CM3pzXC?Yܥfk}Ştifn+RXsitn_@uGNn`lK,Ӵ5jx|E~MejpS7hu@ M &j$"T {mrRb-y9FP\goʜy~a獨?҉fܝAI2П'ωXHQ]Oz9Q /ؽux-:T`S&@{~\Xєev `a OrB#joq ΂= XOX;?yۚjM32аrz(GI":1\-XWs#T=X)<c^M.e)nLڹN;7de] fB>"ˌ;~8q|;(yA U5ϓFRyeql^$? NVg'[QKK^HH?5h74u pIZz!] 'ge r.Է; ,]!EӼ'sBʈXTrV ŭ:7 ɔlQ5()U6Q $K9*CZ @>gqtR Lf5ިiO B;""ǂ{u<;ꆻAfr; Vz4%W!ΦM>/AO6%  uE7Wj[c %)5~֭r}n/@u#xvlO@{ ^_;ldJN~>Em~Ql])Qv 1dSY<|S) !Y F GbYѝekWGY\M?{7cӜm lt|kKezH$:8p8ѝo [ XAuL܊@7 `y0t3 taYVaH<8\P:хJ$7|!ّ ޖ#2J\=v/;N)1dgpdH:J,SqIn^4$B+XzQ!IexZ ;(; ן#;{PBG&K}L,^*V7wS*pP.+6 H/ ^ B#gz#9ln1@*:CuN"gM2σw TOxyTud]NU k~cj!*/ nps cЎ}ёwX#IKGRc{ jԇ HT&!=4 e'C\9Q ĚnW Reif=GImET1f3w1&Tv~jj)sl$Odfx2,JiD%:iþ$DqW"׃s⽝,d0bjeۜp[VYܷ yd;L権+Hg\r>I˲_Bҷ\ gm9W|ecXĖW#y2eI%y# <\h]z$œށB>q,/&.3l#]/Ж3p,π*]WL&L׀/Bu6LS+O 9_[1X3A1p͵plSI`VUg0o& 3_:8@63uVA$m*qrkk! X{@ڼJ{1σ7lŅ_?dqԟW~lw׋uaheoUJNr "'ұŸ6+U!YWF ܧ&m97_LѰig?XM4 ûްR0ނ ,0VDoN8ը}D$pY͉s.aTC5+ɺ?;nHk, hu(+?>X3 0x?qͱNnd}Y@I_`n ߧ$$*(??͗K1p-%ω>,F ͥ~4$:mmcG!+0gJCkS,_hv!\!C ۡSl$9g1S27jINZZd/"M0󈗱Zzߴ'v:qޘ\ۙh#|03Sl!p`iaD[i z<*)Z;yk{, =nc6!#/0.nrb0 aJ>vˀFEwO[9ޖMHJCԀ`RJVU}}af ]*1_+@.2ֶ_d9b… ko [R*0P&19J[3^Eq$P}뺚"TfR6BLth1m!3H}$͢ NyIqȥRm&c.D0ōb7 [~'< 8?U Dz(9!'SRҝTjn{hBY_.p3h&(#a誒V끬6ȣB;YA`HiMpDHxTjs~yf~Zj0(qj 'Ow.xPi8 j 04گ)dGbјa2|\yQSZR.۩5K_l@D Z-LNr0 @Cf]m(K3 [u[ f ˔sKM| a{P@/McQy%Sj,?<0.Tf($([`GStkzI s1$PNus=fz?&,mz9SdAm>Qu3NŽ#68O~'rS'Żmw,Mv`rt>fN*FXxH~%AڌfbڍѬ ϋ~ xJeMN-=5U-KSPE7rΊY5M Ru{Q\h&]R.<-`*oi~Ԫdx侦;w|XAk;"ZLK„ sXF YH.`)ήI 2}w=(qjOUV%qxhS[bhXO8{yc۴xڟ{SR:2O2|躵^`<) M#@~_"/1jP75xU.F{ zF)[od#Q8p5qƙoOb6# m y~ĐQy@;L4`]V2m%C64񋲮W[yO$]]^ 4#'71G |zڍv̛YWBǕJrU$LKK ֨!Xvc̛YWgZ?ȉ:-Y Kg1dGi+)F U#^̆{2 EufOĮtl cMUNlNڇ&P@#Wxїx ~9c[Ζ?fE_ {- 3njѻROdky( x9yy43^Vk4y lEIА \昦- s5XaUe8~Y6hx= ]5% ~jͰR#/%{V^˞m~\Xүg+:1f?>]\|%D_ILτ >+3 ZFMZ3{nynлGw#$}9^Rtel]=`7qYdMTm#2WKd?C@ R\S˿}D5( |dp?.'2$pyEƩhM t̍ A#YxNLΩJd¼*V$ f'\ 7もі$ױCS2Pȵqs*B6!k~A{8z]Ƽل.2\y4Ti[tB.tQIX6ttHݨf c|԰[/+UGNPtB ?8o{ƒˬ N Ϊ$kT7`~.\zY Y9_iO{0p{gQBwX 7eSS{ݐ'L$(A>o6Cn_ps샖6frawO$t~ =`=%b(@QbIs] !8SV&n/Zc-\uou\"T1R ZP N`AQ+ae)"[YSǒ቎!}LORq0X-=boڷKv:lzW877=X5=@ךv Ŏ6Xt((xޒ ">!tau+YEOn+r;oLޒsH0ughn:jfy)P(CW`S_x3DXhQvg~v@s2McU2QEBU=øRc#^NT_}S|M|+ Y^!WÉ\5C["J5YN;ƒ;JI8 .Ci( Cu6BLF!욼ChD{S5i]N6BY a٧v9H ~!Ĭ<"˧7< /eŵ%2֦n1P'ea80D`H@NSTΊPr Ma+/?.MՌ'T 5WcTP_`6>YXV1%zhu]e(*!f4t6Z~bwqJ0P9v$*-cMX]GSmҷ (׼@M+‘AkIa彡ZJAx5 Zx:ܻe#LX;kӏާm/n&:AdVW_';B7П}>c+*Ϲjc`b)RMr:7p;5m.pz%lH>*cU;[ie_",mlf?I '=23wSeYkȧg;'Wf|}u9|^$F4NQ)kI8@o|^*]x)[Ԣ]D#؀UTAOڙAVj#$k@V)p^Y_e) y@ccKv@ K+M淕57(ݺACmSXH.1PT*Mԭo&ܰ{k  9z\ . 6يg{o+= LrMvtwզx˜ BLTLR Q1SW:~dtX!SrfIr`P7-ß`q'eRH3݌4&b0Ե`!B3YhXGmAdPg:_ 5( Q&:575c|@]C/Ļo#nR!+d+Q|c)3OW'k4v۴S 9/n~O 8|lRJtGA>! R@<{^eIG|'$CdoԴ]1[,JV jQup'g"ubR?o"!ڛ2˂ePOOIArE>5;D½ !*ꞆYeX¿R IԆ1#?}" uT'.yUcT3v,Ŏ |= ɨk |%ho:{kM{NIi+x0iYcf[w~[gj2<>*`Ađ$CLUs5/x=;6. EZ6gd/5K 8+%9·LED}jlo9 ¶DY4eX;skG`|A*%Iާ{t6D+/ӥScR&\q=T2,B$sYR~8x Y?<ՑwOp)$Q̕y떝"ֿ7K׉:Ks`RYZPM Ju5%R~^O |s ^(YX-y0[DT-CF^mϿv&@B}֒7;ћR-&T$'O!ÂAfz 7{Eerᴺ^QUhpO_j=X|ԙT^hFunOϭO#^ U.ȍð1Gg!B6*Z?%E"ZUyT8S*} ^T/],QZmKẌ́ j>[Qt.]( ^I:~/eH}ƑӓYvv %9lJ]ZmhƵ9ޞZ9'$t &K /+zx۵,} Z($sިvbS4$z-2-EXʴTmly PGxR8/>t*hƽ/|h_y5v1YN3ũ}-ȱmB[Y!Gsd]}b(u]'lܢ͕+a1ۄZaD}>XjX"~K$k bVE|( #9nuo^Zcʎ%Tl eՃDA@57~lȨ7\S:H**9~wcJ^XDँ3uU! V/7;qqI!envn3O.ac`|G~5ft=O-NʍJ2K;irm&Klp6:N1Cmq=0l)2 J703ʇFK$ʠ0agziZ\FƈnJWi ?LP*fSuI~,#> Fz3i0N.c+Ft\:vkrJv`et"E!-㢑戮H{q7VKiV0,;KZ89j.4@fr"PW%`/S $܅o:7EmAӘQEiGS/_GDL=  T !YԘ&A:ɜN%MY6`¹ >ՕS+-Lo_wf q :iqۨB)zqvNsaiiܠ$@܁5O-XY3 {̞f_T\]Đ`YF,'*]IOxV N(xajH†/<I)}@RƑU_^i;\k}ro퍗:bf-x2^Z%F>A\T2F:u9rG-I"Wg>UdW)aY]Q.QgM)i e[#8Tftd\y }I?(T#k%π}<&ؐ囁F5v@J2M <#gŊ*By0:񱒑E'V`ޭO ߄I9T(5-bcU./]ًX_& ڧn({;\\V`%=+-XÅ j~wEՐ 4ix3'ouL;=RB_DTlgO aI,B|}AyKYZؾ)`Hhbcw-i7r2dQݦN*ξQQ%aü}WU}x4ϴ`ۨ 3g2`%4u$<źak&~φ+Hy_y ~bR8Iotc;aw ?RJ8U#Fj rƽo” XA&LCUbL1)E-,``8LO(P@$@ ncJM7Ui}FĴ;MzQ~>v%#[~'R wVAxQN4l*}4a4q`}vG7O.Clc9UGaPBk|,舛 Dw]Umhr0̚lK*أnlo:;'Q<]!MsvN~ŝU&F-TuH?v4v~WRR]^?FV|^xgISS0T?0$rLEʬQi>vы̶А~2K/s) v1Nn7#I%A&300Xɨ>.i7^!V𣤿c5Ѿo?}#RJ)j5OǗs@',q U R dn S 藣Gz 7jS8XcmWkJ<%BKz]QA"vEwpw Zsu>i#آ NkVV 3%h<4 rviϦtrW4 u1L[˧8L똺;kn4[G<^sG+yCL4+&޾ph@@ cEp/rejg$=Ei/FsP;/ew߃_h"R+}RCL }&'q_ˢx֨с'쏉\)]ѴL{FŬwղy1Jӕ i)S?v?~E1l D{b(^C8(c[Z~YQq-|1E7ږSЙtMSOu8_"3/zlc(ßd=a7ojR苿B @qG*X^k2e$]N^ZjCt 0C&w槗6*f ͒T 1U㱞)˰_ls=܆*= @ 0WqJ%4BJ1BJ)Yg>ZT=&sToJhGz;p^x1e8>VF)( 6gZX*|z=ix[[厀ŭ *B<.-sXHE; qvI$p咣xV[M?\R?$&e8hxTeN 4?1}Cڞg~Aʡ|ĸg rQYMGl+yծV=/9"sI~2P^͖7P3Qx4DPt\S* sG ЯGy'ܴRVnv8xF`raM@U}oˊJY߃R؍HVn6$W܊IEmҙ1ŘTk\ ;ݦ O8AcKeZ &p: RODHgT=+iz-j^(u@>ݱ!|H*RhݣEX[.˯̾iᡸ []W;ӽFIJjb8; Oȩ4&1w]f'6jpA6 tLK:tl')sb;<~[cŦՋF&{ˬTlEӤt8HM/ɫ0V 6bpMT`3Vׯ)7:Kfo w2#}8%6nnlDZ̐ N_}K.)y$KLĐB5=a&i oɝ.OT1?,AMhbLeCN& *uezɗP~==<-`ϩEgEsw^p *u>|QN.?t:\^9aH"4qMy|*k+鸡,h蹞|Ҹ>n2!q^zܘGVgYFdup`-U- D'wHLbxI';G"5HZ-QԷrk<]YӑkŖc.M߈B\? dnvC ).dXhkSyPϯ Tpл&> ldIjiZ)@"@^WO04؜dpHJ'7&ҊҊE/g5=޶&WIr+Or%*%իa+ߊ:7(-'BM[ܭ˹7g [z:+E\<" TO%J1QlP_Ԣq$ *pvfSCѠ,ǶQlApfӲҥFN~{aJTre곁L2WiIYkE͠jnGqݶ[R LNpHj6E 7uۉYZk{r)Es$!A?,"yݲ!i򧯖 ¯⊎zyeʽ3{}_ 9d~z"ΨQӱ.!|[&몜VU ElgfHK퉸XB$You6بH2x?'OѸ}.ÛpN6'1ۚ4ULW3ՂC [ⴝy9&ᑭU^۲A,!}Ky2mgȲ>KtJ4lvVp8(pKD+fS2K"pfZ&ђ.na0@@ /֨<9 fFgx\1OVnYNކdjݲi2u Nb3:)>6I֚*`U Oɕ#6奭Oo^4w~Ìz[ Z[VLR0_%1(7F<ÁʺeV`p;W<3iؔJ7/G(g{ ay: N뀾rNr kO븑** qFOzT`@\<#堸Gd٨{ "_5^I6{0Q=X(Ť˒\P_:|@Yĺf,ܽFT~@LjK*pA[>0P3##תAa[&"87n'ՏR\5ύ8TX:{p nP*jj# *2TvPK`:uj6j~{.F>@p%3|nbF!?qtCn{]V*sxjxQc!h;1 ,%{2PrJq@};LD ђ{_,oXFE\nxGV^c7m6AfF/1vU~R&b<|,k&81k괕6t9_#Zc2HT͵K(׫ԅ ȸUJUO[Osgjrv&ѱn2Yڹ^(î61<,*ݍnՐ1u2glC6'CK?*Lg,#49j%Dmzf̍酛4$G7o92f[a&<M~}W_̵gI~Of%`,3kjqTQyhPi'B9/m2P}Ic6~g5Ȓ6rC!#d`Qt1&ܭ'1I5V<"gě*㢫.ZH#@tIXieE7!2c)@V5KS}hrѽg(e~Q0F:[)3t/hףD-ta,Z]gQ@@a:>4E,^qj S,G5I_;ecགྷ~~K+Us@1nBAW[mzHJ&n&lUټ ,El*#'ĜX>VAC \YhM_3Lw?3]uh |I AE=3JUHaywT?v8]|ͻWY@'2}te[f$ T%ys9W}u`&O` EO!`$^P$s=T\*DPfLY/z,j )M\&r r`]@]JMgFޟ/ƺ/_2D(;[rN`n/AfPb @+"h" R,Iç-5wnH4 yנW!?{KHhrG<_ΌQ]HF3[vp'GKm{c36s'ĥEHO:OyP'*Z\\w }/*A<{ pNPd]"'D8S,.v]܁ WǬ{䀠An&KMxK{ }64_/t G}dz◴T-a$YśnSu 3̖y+7t@(% ƭ0  ÖuBR|TF&R[5~q+Z\3"telyS?da|NܴB^AMѵ^ ay F>J)!l9؏{\ƍM8k є4aQ3xuu~ ~{_[ 0)즩%fmp8݁sFf\=HY@Ap?{]`%rnY<{; f$_d=gf riィÇ!Wʱܗ򑖧 :݅QI볛1K቗q#6aMVC`Wڤst1X+"wKgcmPDV/C @z+m[sVaþ ;ɍ줣ppә]nyLTQ[旓u7T" }/mFLz^pl"32{ p4!l|XUcʭS0<X';W.>| J(1ԅFX:S9?4q%-H ^Tv_dg ?)қ^ö% f4PX޹ucPY.*K!EJ,Z3z|bwb|uHɓ8 sU _?LIr>z*!j~?‘4Dȝ/7s\ ӹ]T@~Arm\@YJvYgcHsr5"nG28C㉭òL.f5t&NyN!_#x쭍WΥ)8?ϥJȅx%xRIG&jGEΫ̃ɂ&pQܜ^7˫SRe\r;Wx llUSwj E:AZ9=_2"=}dbb…[3(<=xlHNrL:Ft.^V(Y8ygȊ3(8I;d[&iSoBCO9gYu/RQi U&]+@S. [Ϣv*|4#椗֧$%{;\l:qQ;3!K@ݟ_@VXe[4 jRlAd^)peM&y+)5_zOɤ7aYD(q#֟"@+`<ӆ$dU++ŲjpD8 OBy{ 'C9!yS6:Zήn8i`$&10~'38pۈC _l#ŋgk:4OF;On*61prr7|WI4qGrEMkYtH[m_,;+Dt!B9S%|'pz;%>ySMbesn.\k8FEVi]ʒy#. ^ j1b$7w5jK@`G0'_2)̝N/**)?'"tO7A0#v;{ֵ;h X2!֫8)j! 1 ~zW̼ߕ#ڼB\^ J ,ݾk7~AdBgz[3ky)AOcapIAdp)[-! ֪yK^|{Z_|V҃݊E :wx,QE\񆲻(%ᕜH]4LE# ;"=F̕*Qf,<,+m@c78z,=z#O9;zJ@Q҆F2$;KO:s:'A-)OmG*93!fR"4!mCR? Q{سj/xP]w,:p, s($Ǻȏ*;6Y,}? d.j]% TnEP;rА|xz?7<;}Ħ@ c߿HK+REbKޑӠ@+RN*+%o3G1h|8dgyT{#GK^]ϖ"CK`5dIdMDVMՄ}W~jm%WNɮȵ,gWд2v[݀;UUq+Ғ̹ҊXݝ)$rҒ؛a KeT,8J,Z=SUJPߥ& XS|ޑE{x Ry^6:,$bo3Xx5Y r1)#h_ r #sw)h0rdž6'ĴdKu9=/턜?nHdiض9GNT&MKNDy^lj<&Y rS8<ň]s˧EkuX9p40Oz(VbI>YVsbu{i]uXHwCOaO-Մlnm&GLe *>h{_?!ߘ׍0k~ei+pY,譾VbJO@Ǯ>B] !PkL/pWɇi+ݩƐ*n甩R}>"d}lP2VZ;g.uC䯆]\0Hy*&g/*ijt?~I-Mʭv\gp8pY[0(`=C[8bNy"ǀl+G) <.&mBj?lo] A0O[x =y~3VЌRJQHN ui{'poU476oAu=ւ2 e;FGsʽӚݪb,YY@?}JpheUaHzv*tY{{"V:UU@;գg%ozMq˖|h#lw( DcƖw.WS&sL1+{ Gxe.&ptϙ07OcoskMWg^[e}V(ጟL. kAIhpk!ހXay;"ώxÒ(مxa+J%SNSɄZ! 8bqzddx {rT0.x=2]ٵfb$,OCD5HoG:a>9bC`=*wi)X 9bhPrk#A02to_gy P*>@@F(ZlY%7j|d[MHAVlagf:U&VAw'̿K󍛌6Zk1J@)>v J}CrL]7COsw'' dS.}Lj7@EWi}&ן M/nja`Z aBW/-EWJ¯^Q5HZõML/lwIMpr$$Z> w650oTKQDvFh`)9_lL~f1CRQzi-}fW+*^OYXņGe98a'P^|ne*N<Q, ."Lq \PչM lՎʣfE[i=oȡ ًuw(Hxns$-ww.oBk5<%NhS+6m-uz*ՅH`eG6yr4vY>Q&ETؖtMy,TM 5 # ;t`vdbV E9Ф^Z0 Gº[>V} a+vFX98M%#mSd،[=TgDbZ%֦C `g PeWQ^lS& V|;@<Ů^śzb@4ۑ(u5@Y |%QӢ],bD9X~,huKdA# 4AR02pz'׿+qd&xXxXa1,kckCUsbϙ{3;yǞ߀k7Ơ_|Ԩ*bv'\)EUmZږ`֔:1qĕ#b=||16< 3}-C~ӄJ3fa*=q/36/3kP?x,qƕlLEw۳1:٠+όQĂe>ME,?40FK<@o"" y$idQh0V TU5ϮZ&_S!R* ]ZwTQD ΄? x0/ F < uGjwz7#)mA>:Z7Z3hS75&% <}U'8WjAc#x6?! 'U.dS@*[D`]nQYuSF0k7 eexXSr~ПB~!r0zW=B.|,~!+rO[sZ$ &lܹk5D)sRR[{1ì)/8 Jc`Ϳ.%~dOSձ[N3 cbN;[0MIY_{jk ͼ8=V u iEȖIMdd# , U)|;Y6B9ݴws,B g&E&DkbU=BCSs:T3>uLU'Gp/{r1ی@ ;(4VQ nf^AP8A-zX@MemLJp`κW9䖤ٸ1 鳭"fHX==nZ'PuSd(jpa$g CUYēO/W {?-tڲSpsCr;Bx ҶP3*o!)Cv!ܦ{4և݌0bFa\È܏:E؞Kټv}ZԱFXI2iHnNo 2Oo4ũ ؝:E2WcOW K :p):fuk -",KFVq:xِ؛{Ǎ;[y屻ʚR`GD-խ?+ {jE4#6s*,_raes 揄a$~b7y**m;X9|Pڏ F\Gd?N},Tf;ȆcD^  L+T2 V\DMIǬ ms5\mhX.T]~M.Z "vWu; ijNո9D{xunuYyGϻVyX?,ےڝf2L?d̾~Ǹ{&wmRoW/hֱzX*d3Þt\vWz[ۣ6iT@œ.Ԛa68,\]o N9ᄆ%E9&3.^2nJ9h %崌nƚ}a9DURFWZ}=:uUvtB$\_%fȧ[[` }c9Ï s w?<򩥜@O<<+/Gu%SGo0SxeDwSlhX$`&8xdw Y۾-2] ܶPBsU!|ZxB? ۊcZu|06q2 'nґ 2I XL/,4~Wؕ.~]{((4@L_u1@8 *QwCB!D_$gwFwHhn~k+Ncu"dC𠂈$KQU~'FO$ƿ1d7t-55A<s̮(“dxgVfL I@0^A%-\z6ԼŗS?j{8G;sST_k5L9#^2]8? M44ND=');/m W!Jג c tQ"H)TۘC'%tYuZÿdsWIgC:dNnVQw}}6]mٱ{(w.ru6t{ngZz wom<̠3=M!9UJ>}9EXw?-)Wᥓ?9צc;':*!4'߄d=*{uQ \.@%QnAӀv=%\f$ ڰJuQb&c;\am}z{ sB4-@ai"QC/]tk.@~=}h?ciJ=9]wES.Z}$"{Ժ^{^\$ƕ{`]Q|$Pa >2໲X>HEkR#gƞ8\*)DV1邘Fb~g V!f;‭#Ek=6dqn;#Rp<Gʞfzjcضmfd@d-u%%k28B}3)IfDnWg(xJTՙoѫC?-QcAm/:D^->K(tjT:E%R+Eac)= wciFC .)R v "mz>-ZI#zTM+T8]9HUNҰmDse\U+=թkw>N杖c֩p\aQoJD}9^oyx=zf? k+T%ģc]g^{:vB`qsOxഃaj!G"dgYIR={+AQxl:ܢ/CH}Ϊe_,eKz<^d.T +3gU]CGak;<%PtG~#]^!gСrjg|"kɷwW6N4p &ӹ:DjvoO=\ŗ yt1EgT@ROX:AhYP7N"ֈ7gmQJQ]n pp 3M}؁᱁~v:sL%uCW}E͓A'*܍t \jL„wyG&~W4qH޻M")\n]R{i ?r+zM`fgTDXwz MgL )`^ո a\24'-Bݡ}-"kVx9.кqf3!1 mg:i;iNq4SQ៴nH2)LO义.4q,ΤNG) ™0$gF@ :SeR݈ʩwz7EƓ]0yY[KZ vxUӹdlQn}  j*ːD*n"BPp+7;YKftpiܒomspE IobիZu4G*XV_-ά0Se񷭷 %pzoBje=og }1#/0u$*w%;XP TZPz2;AsK4)>BP< C!}ֲ%U/6X`e7A¯Bпs&y7Cr!0%%(ZP,?h'C ]/3v)K1h׿xs+OV߀ [(鮉;m!ɍs,/0zo0}ӂӆ,}nN){wIH|ym:D+yG2(WF%5mco+>:M'&>FIC ϐ.a&C/cle$gb'\3j <( k f)#3-Üz[)Ӵ2`ְh+ @D*L#8$\t2v(^g]L̀{m|ID㬳M=馗 +I﭂Tܩ$wo@*0܃K_*aB`2>;"HJ]ar׫['UUadÊjCp4_ u|@Nx왮l4})n?Ç\O̶;nB70ί̔u.*Q`uQȷJE-KS ƒt15yPUQwobs?`YS' 3^%uik9$XhTՄ3Cs,kLiZ D| эos̺&ID_;g T{;}vD|I DF%q(@{?~R nmZ^Wrr+Jw6‘xud>W"n@ý.Ʉ-g ~*u}"n vDQ'ՕmuFC,=K{m٨/" q[U1Kx ǖ"sz>\)۷';YǕ5w]& 3̸́(𡄱 _=B+4[b~9A(PnZlk`7Kfhœ!X"D$Ow%gAKKJ l9a _==ޗ&K3d(SsN Fp\zLJg0#A={a3' l俬~EDT KMa͝;O}|l>/on̡9Bj3KW6)Uqԋw=߱\ܖzcƭQ*G|G\>qenR q$ZnoHah ՞,y}DB3/]U9OGcJF;vWS*BNSzhVEH֤,'7Cs> B񛨐 V7jukxu4yǟwrcA<{pԕRlUNMDh̛̦_D p.`{+K1ΗpD9<#5#t-VTiqȾz3}B\;S~L*/`bVet ckUA"LbGƺw557p%\8ryrLVIk}ü]4F*g>ޒir@&o{&w2w=GxϲML>l _#=MSXi tsv2=`1dbPжR?E` Ze 5V)FuYjݎBGW O{bp~5`9{hPup` Wx(30@8+~ϣ/ Ffa+!be W!ʇx"}o7Qސ>p]'n co@.z64j|-Nug n1>&悅;KxrZ@y$)6e>4uxʲԙϵի2=`$Ez$c]}? 8 ?mB.ڵ9GGsGP*]I L 4|̀t )qzokr.hQgYW#ղm+ߤJONA7B!`wVPS4%GObr0z9_0:mn ueZ.HZ|@ue `V0WTqF]?J>4 ']<⯾s2֐us@HXd^dmvi;DfҏdE"^(yp[ͅdk~.u%ꏙQّK2>rI sxnW4Rr!hv YX@FmѵTBkt"iv'iBd۔"e%G6lk˵u\q̍pV9n\*HW#g;NLYȸa.꾳_ ٍU qg ^+mRPy?MLD'qd]~Bo3R,];7z18TΪǐJe9A7pmGQv}%u%/jL ?WJ`c:Ǥq;')$_{(8#_e(|$R`ktoQsT%פ@7"c0!]X?y?y: '63& 7L+{lPAL 6(N [;$ޅ3][bmܔ!0dYe/F}s |80aIL&UC;wEWY܂5v3`^ *,Cq"9\ z[9"TJ.OGڃZ%S$n%7N8n˄\>A%ZƏ~SB7 h&`ˤ:dya_T͵͠?a{\eyʿj1@\2ƥ0~7 %%=mAk5Cl e17ϟ+@;H#0˅`AdB^A&MDqdREyٗM 43MX0bE'֋MXҕ`N;j $tPUt b>Z|üOBIRGg_WCB?gT;);{/rzLUN?{TxQ7}BC0 &7[TDSQͬYرaӰf? 2Q^*J|xϾwFo/8> InQpN˼Jq؂49xȾ.m⋆~+suK'jUƿZOvjj&8c-'* AN@beDb{[4@I@@@9ɋn|5lqHL6 #Jְ[{ @)U$c$m5lfߒKPv&ʢ\XZG=at @tGjY$NIHTmFߞk[ё[!oR}&nSɀkژUP*x%s]uF H\+m_Y 1NJ,({5m O6 dcyĞ.{rg{Yzb^eݺHQr,5kcE(;/- xRE=DjBO;0[W4e NxmӫHX9߿{f Ǝ:Jo:b)d?MNK5o&r+ɜr΄"Τ͏@^e7ZDicx>W`ғud\I(5.ŔZ:}Q*5mMSmKtszJkhMP'Glͯ3ZX@]L Ȏ3OE4 wnj"Խ:8V[+ӷc@ ƇϱuO9ao$\|t2%][P?2k ntȠչ4n%v "BtdxyАd]u|EV5((Ϲxy3;M.#OcZuuD&SݤH xȴ5 d(rhԒP>C!&^y;8*;S#|b3j쥠nT/2B,Қ1A? _|OR=NJZ8#>(bx]k86ev=)z 7[""`%˼J[7Nm XDEhY"WtN鮵tCtKύ*)ODnA"/Y$Oyk &&6 ͐,FQE==~WHišL$9\/gBm% /@r&-ɓHoA0Dh?^uOzݖ}"=N[&񋻀Ƀ~- `"V+s skd9kFC-)3-g˗`ڶ,Leۚ/G<'K y}4kf>\R xbyYx]a`k:TN_g&)X-d*!so2P_bgT]ԩ9]B\ ?o(1Le,eV{!Fm[lQ{gDzߝw:S]Ҷ9Ś _>3M8Lr8%A*9Gzz\ay=={Ò?u ˗@Cr]d6yԤ]pX!vROo/V fC76\wWHt)u[!VQ"/} k|89y0 t662?zD7dsG*̒ PbH(3Gρ(E];RA$RpT>#rءJ[S [˸‡LqBg2At*>IGW5qQ Fm| 7 I&l^#XUȕhO6hۣM3 _EX{ah2~PxUK>}EէpV\9So5)jT&pF`Qfˎ;ў>qQ~:5HjƂq\\IZq0m0O k-_&ԣ֭uHLHUmpIeM''XG!;ZO3Ϥiɑ>oJ{,=}d&"j^S5B!ƩS@o *=yV$Ͳ~=8(iR&9P b:\&FWXkL@ԃb{^0smaJ2!AkesuΩʅw /Ux}a߶bFW(vCN[zL {3NFIrULn<"j<^~ef/Ynxj !}~j{N{T+̶Z*sZ4g'AT aFҨbar1D3kTk~)]T4x6Y^BZ/Lfx,: Q6x31Bշ6wUp 4%5]9HP(C&P\0|qӄoOg*}N齕|x';E Ufs R2/ޑB]׃)+)H|ZB]'DOKƯSQw$|>ӦWk=KP ^"[g` _vV4&^[8rr Nj^ 9ldhT~G@B]瀍B)P30([Z3Sk涓ompcL>_MBwI1.:ҿڞ>L7W!uQFG$)rBQU ?$B%z&CzJ ЖOJgaۧ&jzkޡ'm_ Tܺ)hW㒬Z0W; ǫֵu+қ^KhfKfѨTopuf\d%m)=gaN±t)Ԣ5JQf*tOBiWYf:*vUXdEx^'柚Do2s&o(b O{>(#A;tm]º }蠼ƻ hʂ*c6踉IvMteVʀc@j~! |"o{Q20ߥ9E?̽(>;LŸJbjB9MdlT͸CΏ6:_)gQ~wm׫te^^=/8 YFdшQ!j:cIֶ+(~Hx2?իl`G[k>vF5m._Ѣ 3%BiwQ@5>4.k["Q\ 1^o'Ρѩ' "6a6E3k9/I] p12חiH8OĦ"EOc`%5)\wcB޹":7ZV /s41؜1\JwnDo{g{D)4NA̗5lJML HA*ؖ*5H`C^Vpz- ݭ[O« =p]{lA.۷ߦlߢaQ RZ»)O5ӌR%3=>3KbNgQ# G G8ŜXRݻK@Cw:}`]Ȫ`{in JA{\B?:V[sb!T<H6(WdZQ#-^Z'wAbvĖh۬h`Z5ʼnt7UO[m߀d&?b! ߍ4ˣE1ɘXsϋc9,Dۤl5iXvkJběr5 cq"L%j%i8,7}Y_A+|ᑞ X9Z9SkchNsU냓mr9 #CoIr2m8aԖ9Da+M - 3 KBt3,[GyR1ї` ErKč4Լч9Hpr<a%e$$G@rҥ"ڶ>yHb~D5yA(`1wּHSZQK^"^)0OI ~Ԉ uF: h뮗65#^"׸IG"et(3;ru^"uo-w 5MN&);l&N70]6PS|Y Я9+j{R '?}?Zlm^%]>Āg6k߰QH BpB=L܋Scޛ'p.%9l_Rez [Sc:JJ߆I֟[K܋*~?v:go|Pɷ1A9;0qL5i.>ę@,B(`3/9mϜ7@+MX8ZT˰SʜmE#5K+]/b ZB:\ "-oCSr;gm'uIl'\ 7/D)"zWBluN{Ug3_?bI.BF>y;oaПkKYٌuwVZ6&z݉') %FB!Ǖ\,V }Bo רqhO87 mp]ZwW,g+^ g S0ZD"G4 '̭v(7ܓJ~= o‘E>2(P]~thG`, -_ ,bIj5^^(P>0p1~WEsE#pMBPqG:EDžߎ[m;G""nG)oQlZbJVX+O'! ;T3TN~ωtEVߟu^ pf.+] șC?Gb1_uẃ 4.j!;ӸjzZ?~T5}I Jꜙ?Jԅe^B>7jDiS]ц!0~_Y!K"Nayt,#W)VtǐUD1ti:5$0ʟXņH4wQV'jUB\N'Ǯϣ.=8Qs4a7{g)?/zŇa+ yB0:x*OŔ3&g LyUNmM@B2#E3+1īhR}y^q`qYS!Zp5`^$4꿂]24&`΁h{{ wE&\ÕhpY%f9ܚus_+*Rι4@o @݋F0֯5DԛA&ӧš0@tB;ZZba[B_VQY,>?ںépb <{s.*pe sPZ__TƗixP$ߦD XF6 4-,gD 7dd 78}c 2In Xn-1qY3ҀUu!ꑈ-$ 4&0qRۭ8.lQ- Nl,L:Z˧d;cjh[&.ŘpR6*34(mᎪRjyxf+OÅayք5>J=,c "8A u0@E Ȱ"tt]őBAt)[F5gnjMz֣!x .m[-ts^IœgOAv OIo!bU"߆K@4^Ҹ\͏ X\im?I{W\s}Cm5yH&[=W?^\x#󒱩,ʣ]0 Q Gq! 3/TS'DP [*#͛h=^ܝZ72^mUvli.%jfyJ|1^&G U1yćV89RiQ>V?xBK1׳xG3ysbBh@Þ`|ސښczUfVŵ߾0ABǜLflԫp: 9ikmM*~F2{UOaͻ%a'&@q~E jQ BacER-q x(F+[Ge"keu(u]WRy#zd:Ye>%\wT P7Q*#Hpˤ/y=8DzG"bY5Wc| 9;HMjgY>r=I&c MnlA[Nca6m{GyLԠ'L;0, J;ur^˥91a˟ =>i<0MRd%=-Kǭ!cUS?>*kN 5\d<0J Ї'@b|,ꪲ5kz?&,xt~S$'R}՜QR)~|`!O H5|e>PSZrߎ)5m:hQlEQd1 Mux] t2]k{yx8Ɏ!ceC UnzPÛJ)M8P+Ż#m7nQ< u^J5)c(/ /5/#묄*y?pצTLH5n?1 ,7:x9m606 o+W 6D ۊFh 3tFo ]dXr2+j^=}pt 1c*:!n^*nېӫ*(âGjC,2܀ -TSχ\0^$_:PIj̰lQ;@(M1x^4p̩ɋTtZrWPxime>ބRUzIc}-0TQрY [a!@=05%cw`_=\hx #_3&֑tbNNiS`f ?4A+Aq֫݋\'˥YxaHR-#R> 'N}W@4z^_9` Am$<~lu oLY@$ߑ/mE]t܋~#>Ī[b_ c '65#9d؃ńrͅnwS9$SKx||& ZfgIޯN65 (0UA]c9L <ՁK!P1hIxm ˫-%&)Im.d $NS[_S0o 8vg,-.z(-uk3!KEI=㽺el<}=]6soMyĊ(E#bS#)0zVY(Y TNf򹬉7PjɃg]lPf̢+b$O(r%ɤV6pW|Gl?ƻUv8~Ԟ`5/UL R}_s\'Z0~'ځn1spw Lf6]Py^o/B%Џ1(%ڞt=I~R#u#/b g5lfwq#V1;gX}v)W~ (x`2'OYK/],0xF7Qo>WE "P)fL іE<${`O9Htqq{)2HQ /TF/'g9@^fgS?` mGǂ[8 fs/DN,uġ׊fz 1x7(@c"_2A7:,dq/}~jѮSq& klzZă :\m!^­؎Vxf8r ۞_7KmyYe[u35܅\_^6+u?ގw6Cl,,&bApsC]N%n@Cʅց> ,̽E+nij􁛩K:M{WߴOf,QL2l= q! "&/ӺfahM} ,g vQT<9n僋кs1pYGUhO9r > !'\W#h!Ѥ]*[ 7 18nB!+AkgX,أ<*+V_YPEtnOOzl'/ǚ;RoV2"zQW$f扯ʎ]kUO)_8QQ :zn?wA490맨Nw)DإTNMA>a6-lMF31ѫ>S}"nx8v@Qz9]R b˽2*ҜϜ9V CbyV@,U"ӺȾX.pgֈy›q2w*OWPf5a At^Ltpw v8=wKmWKI9nLb ښ@dyDf'IU W4~l)z]R|ތgQ4xv$_N3^F,$jak^WÉn/JA푅B]kÕt_|м/1RסébL Bkp`O.F3lH ]w{EH`&EW1\~ߟ֮31ʫZdGu>1 yyR;6j]z4|'JRb]jgfq,+iARH I+4oyG>֡Zٻ:ADq\GGx=A͵Z=>J81uiKAF#ڶ* O)hȎۛ$@Ck)d~O- KQ7oce˒mz /ӈlds&`8vq=2j%]H du)a~Y+q hj "˴8ѬCjDeT$\pp5FtX-. W{ü5~cKa$gΓV&_s%TOI Odg_%:ߛ3mDCh3e虌R, ʨlj/P15gOHFO (XH=7掐"ӣgU$E}QKQ +B.ăHVbsP&"7DGjQd_@(]q?V{/OPi!t.|`k0W9@΅zjw8D~58*t5-_c<>5CeJ"YwPj_4@D[[[w|UjadC~Rru%Wn}#kvn 1x9i2}nwLb wU:ZB;恶~YMTP~Y6\Sn9Se =Y? ~0M=! QAAj}ƌIy njۚ'@T"=ycCj?^"{ u>a-!;(CD6+zI{Hw_ЍqQHt-Cτ*٤]u^ ;5k; ]QșjW $ &=?bt^>sHpc6OW*؜Oiډ# F,]̚(x.(k9KsAccw l" ¡aK:_?r-6ZΧt$]UfMNw_i) R a;AbyʅӋ;4+\G|hLoZ~XAT2 䒑{VFP$I{yv~=15!Zf#UHX<N;]ߥBKYz@< c, ':yg;ciE|T.ςFJOijoѩcUq+WWٔJ,?i 5^ ~Vee_ ҠV7XY4TSCrBVMA&p4Ff&r䌗U1/" ~`M YprWC F||Hvu7 iB ogl్8ʩtw☷=f8H=6r Mm\Ǽ6Z) Jc*fqqI]:sCQ[nr%4Doa xVOdY>GMK#Qe1~ؔ `O؍gA1|IyRE $\4zҘ5Px[zP"T0'gH:4E!~H0kJ: ŋ fyY|o.yf\8=9`$}ex?yS=U W9NnJ=&ɕa.B}FhTR=|JI`n`l?_2z=z"ـ g?=m)A5_qDn(CJ < ̫*UQ"7@m刓.*DtO+tP$=/9YEJ/SjBǥ) ^6|h5RT&GzUK&D/Ovt Ѩ, |' lh3/ 2ׇՉǺK jd1D^C̩hJ}dpBl"!H!ɘ0޿a@ zTPRz[2B[of:sBۺ7ȥX`(sοRqɀNgۤnZӿ%R# :JR|]ov1+++ъ֋#!<2d]] 8Z(?/jb2ʁʧB'g{OAY <Uk4umZ#IjXX^~<#*Ď/Ŋˆ]$]>#@ 8R3_Y(w0> @.DF-~ M溯W-8"nhfw8#}l:z s 92AtyUy`R컞;$%(b#~t_rȳ*[M}Tzڴ`t HJ-Edc|ܸ2|n e[b2(|6zϩyu" y/ʎc=6= #XPV裁3F37nx>X}:*B*0\:od õ+0,00QQN_|}$$ Bv:>_?7p.FȼePQ{zAC!ӮUspun,R_f`; {"Z= d͗muiR?(p*6u߰ ItTa C̳I)bJ; 4^i4K#C ^ZמiDE?[s ZEVG4OB$[uUA -Ql!GԻR\RKu jZ"-fNO:U6e O9~ '2rpwwQ '27%A5yN&f 8.GԊjGWtVrls2鑷'"u]?s<'ѝaR~^C] Ql56p"дk5#,4~8%DniCݥ@O|QD IYQ"ց_;oY@(!2\.w 61qUED)nĽ0̛3m'5} *ZؗXS ;p[H^52~Ug1hcj4QqJفx¦J7ŕJI04骽<h_aު:#"b{ Ԉ)mmB9?hۤ"%ʗ(Hq>U+r΄Ewf{O@~<rK@\0p&m^uswEQ]{il"=|NJmz㴛9XRv$KyGeN㌺)wK$\~L6<dݿÛ魐zsOQUm<<{,[? rJ8ty,;f%>M+]gף/2?D2/RDJjMS&]J? @wGVğyS{V~;QtX%WagALZP+v6)`(+wc9k P[Yqi: &;&2bf XptJA* ]*'/L`P &yP5uvL&ܻ)pI7KAo ЧR6t /,eBBA81?M/$+l+=c~ F b@@NsބC Lsզ1h#oewv"qP: :+9'D;~yӬ/bc%)-JS\Um 2z(4='z>ʯ,l]Yn>c؀ݙ1pS R0G. X~$C[)Ỳ@ii&ܦ9lS[k= 8_F!qW6t9?"KW@uxU-kJ,x>j9gʼnyO'?aؐ6 ܣGP6ԮVX>7 4w6c)>MjO(fxiA];4&#jnmL2?PImbP6bY3C1Cv|mYʕ5jBkPޏ),au!?ء!/Е <^*c9CfhLbIOA| _?vi0FsxwT)&v픋AYJg~?fDUn quX-,ԥt \rhi5@Dzȧ'7NDCR_L&6LFm Lr7/bE 3Q'iuIDLV z㉄_?V3iU C㚎G%H~"L eJ2wgb ^{!fݜdDuq(Rc]7laS:;z+[XtToh=?W:cʍ%ZnVc2{S-|{@"?"q;"J$2\o֣&C @k Ę); j, .r^ PaL7#Tq5IN>Kp{o]Wbf~\y N)|SlPYNZEZGdLUB3TslɌڞqЇ+mGtoD6mb{:{["E$[W/P> ,>a q fwRrq^7̿%j슠(\d6|*MQ̤Qu; 6,Vr V<*dWuowbU*Ό&Q EyL_Ԉf.%q}Q1M|t/6VV9sԅΨ{!o~a=KC?Z)$ɂ܋GEך3\Z ˪@k rgl%Vh* a"pnX8P/w'=xCCbM.~uXRRO9hncVę`XZ[TꪛuO 6AP~5 -V"%ŨHӧՉr:{Ogٯ3=|`CB|%>L,ښGWAN2BVaoQN&&EߵIrklnjJOs\oP[ ǮGƼ8,ФGGeoޢFH7vS2*s$ )N'Ɩeh2$@E ߃5DHB7a[%y[Kt y˶!^kf;: UO$?bo'$d MU{!Fk*'<~S9mAg4_YՓB>0g6SQri"9ӷA8X-fr={4~(49,uEt3e5f!(~_<2`~W [:E7`ӝ^Q{qSMw#nUn#Uz~ׄY%^O wXTay0ClV"{bW;Y-Uuw :U3wuB_lù\ 0.z)I(nW}. O:k *!*tz)OnP+ClI}0m>;A8V `u-gVe~WH [-xzڱLoΫ) ؔIO9C1 6,? Lql[[K! (")]/<:loڙCi\r Y{.VFLD3PWZLO{=;F;SflJ"nqAi1/Ckz>ʿ8TIċ$S ?*@ k|? d}Rd#K}%7\rcng1 ~3&Crf Eh:Q# :Um~Y|lYm%MNMǮ Tl>Xm/<=`ܔ5%N{Zzz0%4 Z Zbʼg#ߛ] &L$Y"3&w|7h=ݠZKQ24uIł8Dntϓ\]c0S+ѧSQc3 dnDb'A…pCiOl0N}9R,i$dc(;+sPHhߪ6^LM aCl8ҬMLeVũQ+vjC3c\B8dc i_i=|yԉVV'4{k'wCkXsPoLM蛞!ҚƗhc.Sy!N*$sE͝OQFc &8ݪҕ.zwyȃFaZ95GckL FZZP:7`EEA a+dQYcUZz:SrRc41*A'QC`O 1:Dĕ# &ChD鍚͔6[|;넣7E%-OCtu"5uϻnmE"Eާ_‰`!x8699wݜܽTyNH v39^q5bxWMyZ4:/\LGl2|Sm-$]deuмO%9v}#3Z=9ə U}]u믻Bvhs"v/SPĂ:1M~2>Loj'ePS48KYePpɃs])!4*L9u&?jC0]zAa^>2sĕ,?Q٠00l H0yH\C:.! 05YF~Cb',L.-h6lֲ5lh'T/XgH}eI:uՋOǻ)z[Zތ+zF Qn=gxI) TC h@m6Nq5[foS,U2n^!z]M@{ж:Af_.#SǼ ([7n$Zύ-C_. $ 0yOهģ҇&tIl,:skpٓN`2of Ø^T(yRooo=L٧ymZlh(\x?^TK4h*6z6ķ?`̖0mUkqt]A lY0j:+oXd[?;$.Nmo pm4fs&r#CK_ Sm\r*YQ#a0` 3yMKquCr/c]u5.ji{ʪCR4g!kPE+.;tTЃyJJ"^wz!yK<W$U-Ȍ3KS!Ǒ¢{ h k^6D>)+[XMfRNygC 4j?|Ԝ$6p4'd< ֋.BX~ᩁUJ)8]2]mx.6@~; Ya¸wvyW`ВupԬ8MlF7##թr tݕkQ//߮GOw_\`KXr<]ȴ_w 'j,Py2yԾ˚l9cl;hf+:+H$ OH)}bےFYﯺID}Sovb iC O.lXm~nh,3޿HnD3o ,!>lph?ͻSHVV~'9SfYíLDJ_)gh4 .wo$4^&Lk";׺? һտIK*LDqȯNE8׾|Z\@W.O"?:KbMQ^u* ?FirɎr{[10_ Qsr!*So%,:OS;ղuu=Ȕw7R0 )<B˸дPZ,拷`?dm2S0!{,Lp8nPC:pJj2fҊ0O IE UAS>+`t`WþYXz+X] Ir!u+;)!Ea Jhvp "'l ~ K$NIjE۝ F ^Ev485xU/EX1&4)WstmZ"-4׿m@/xI JGc"\D̦(I`q*`T43̃Rk`hBSu|и6NK P>^ ~#I&w |f%놤j1\Wob}jz1N̥rLO^TL+}I.ya"O@ 5@@[6y$f0ʁOoϠPP jꇽh,8kLx?LyTL;X FYR:4dK+V*g]gd؝rH=Lhb01u[CZP|']O)~GG;ĔݟT!V<6 +.tw(cȒdf0}ܹͥJ\P=$\tN^fn$cPD; 7Ó,MbX}j(L]}-/ཹhvn䪯)%u+X C忠V)V뮂q_=DyS}9:$t,6$ȴxj$va1(`<Y^ cx$ǿuۧacn(}zӖ˖POopB'GBIy}.sX%aFi=,2SmgQF!o{L~Sd]:wꑍj@ ]m{\5nȓfD.d=F E1;Om\]WUC³oSrɝ\EЗ>=3-F-5ښM $Cى/6 ܆ؒ׏:2kG^LwN+of& @5O oa c\.zIJ%8k@.c8Rj}Q:-G4JeMMO#X~{M(Tu)ܞ.IGJLOrnocoЙg{KQdhæҟfʑ;­0{L*q$3- @r Xbbl)3vSYl>!&7>WhmT褔W77fI/8+5ѫ΁*;><4B&9LTEJU3Y\1!0qccA)д)a[b06RM?hM|^IE'^+v}GkC,2d טx`:3f}ap Xj<#3H_16m+c_?B/hVrr0+ u}|fmqĥgtrVMޒn)ݲ{j@v(ryu!BjяjF TNLZAǵ*$%KȟY)W۝Wsb] ܭA[9d[W(eXLlp,?C r~2$r8rWpr3߮Qkh(v[/ojO #Qq\R(sW!+f⬞ãor?D\l 1o{xJ/*ҦuTAv6j^yjY/٢֩CŞPN|?홹hzߗdԃ畍"h>QEQ y6AzwrN1ffRijXbGΫr|B?RR-ҼISAo "Fj#h{#9яK)#\e٭MDBncyqp f5O4jsp{n9%%ƐDI*1<4oDxK&йʹtPUw%'TC$p, 87VI rlFg6vᐗYM:Kwt@熶DcH 0^iJ%ךKYCB *3v z8AdQI5n@O}5,=8,5](3lqD0 7 m'PwFMRm^pYCqZ 7 lS: ?a/U*4if;oU#r_/zB10m%NUO|8PoπEčK)ЩDh)?uJ$ܢF~~dьBlմ5!MςI&\3tTm**MO?'1Jq3a t?JJrI<٘WsO/iv_5Pb>$gRs68=/T*duQ ܯGܖ7m̪< *vQP %()Cj"xV!o? F,C錃9[ء܌弞9EB9B߻ݰGC,٭] *͂F xeY }"ӐwcS6ݫiTjD퉞۸o0z:=*u3:+B8VTœ;m>ɘCgOF`GdO9y{ s)y׏ןNTl>JqhWOa&{NAWQVsm% aܝJ˽ LR ݾP,_vQD.`g۰Wt?~` D@VF̿R$"MQ_f(hX|hե칊@)8lqJުD\?9F8e| =U=Ȯ P֞M8Lt>V1n]%Keڸ!,Cc9E/s]@L~zǬ%EvyEа% Ws_53(?MsV6 ,wՈ}ռi(^duܧƨ D Wed3o4C?N{&csuV^0,.421뾶>"UDYq7tW}fz~'ʈfb];'ďDϣ1%, H[3lG0Q6ӈC>Ts' ~aDCdv<т:1جtru4A>?lL'IW1\Rm.7xmE_/jP˂!Ld<@)YOZijo>O-e廤S{j4Xp:pT~# onFp5v1Lʘjh7ŏ^EJA;+(,e]a:5DnHKc+T9Bʓ*cC{Ji~{;v'hf=T97,z|U[Gm?+сqm o4o>p/T&lc $Q2^}kk\:}a?7/cDiwz‚"\}E [n>{5rePѤ7C5-!9E۽SAPQi #t0-P̸SLM ֗ fZ-l%n^,cibjӉ 20dOɦ2"\h޴2;P6`/ $hw9UCt!zL_!YQ)*[wHHq akA'm-Ny?l#A.VV;y9]`#HX%B#[{58TnU">D-1)uX%C=ZAw9Ci5}H E<C$;^I E2WlD+A.ӸZl[hNwO< }]Wah~y{>IV FsU :l+FPŎ$6@ß&!p!yb@g=t-xj8<+chQ0$d2Y %:V9I*eHgfU-v38_{%PnxEśCfaMOn"o/_TLMrkͧB .AB'B*. M@ӧb"ajmB#\E}&p~W!Js(IdZ !}_fu69N`soCr:*eԪ7Kn~~%W]#I!%:A D\An]P i=2i]ɬ1Qbe"2P-AwI_\hG3%ˊr67]0 }Oo!;UԪuJ{m^l~+2!{@p_1GHRsgPaiFi2ܗA*McG ֍tZy= o,U*z0rs)!<#jm*@XUR>qrMʽkC;_K,$=c؎軍+אXW,>k1fJ+󧬢}L'Ȼ)6oEt0:,8/A&⋖3w+/ vs#i5]Y^ugdpKa3wvlӵ$z]laLZFOo]ZF pu)W=`=SN# +$: I;q08E:j7:X7ò lU; ͘7B $SصAc8V]/cCy}B.~D*ͼ뼃0|/@Ežj|;>7SO@J y~E921th*qfVT3ɻ&u4.J X=\~<"wKQs;65%ZB\*>/=\x5J]ΰߕaI'6_i`lIB@dIB ϝEWe*DӚT'cUhwLl혵YR娰eq^&OJ2~]aĈwV5`ʡ>Z[ Mehah3v쏊1cX@@g!2:S^ `C( ,OJ" 1u] -9̮'KaNW,[ WE^CԨcX%r8 dO Ae>oԃſ(UtbkH'&eR_N~\V\J-oiNpNlG/"lڙJNzz^xKl^&BHNz')D{cݭrbVhӵVP\D jrȩ)5ʩn:)3G21ech0tP7kǴ6%\ʶ_95;Z!M}mo`oc\9-R3e4܍Ț* ;-;[ߋE<.2$ݦ5gk#}Q4!0iq\,ҹ@u 2 N$>1@}1p": @N2(Pluԏ)`)9՗x&C롗2MN6{hA/FkǍcY? `:#yy6'7Z3 ojLlIz[Qm5Z +&IuǺ`z:.ٝzᔕ&sf E!/uj .Z7=dT5\` -@R*"<ۑ^۫mSnڛɒ*)W|r@eBo"nIuhL(ɷ2n}wfV"|͂Iu4حcWk ᘙ9l8=#kK7V]x*tلL`0@ X4^041m|B.kYGN%0P`2K}9tJpw+$yn|BI ߪVc19WZ#o7?l^D0Z`8a#VJ}%IsyA1pvq_,v@r[ֆ22ߨFٻ1`ícőSoiք=[E")a0v+VgXo tF|~I@k#fTtJvin*9!2&uUñ njG;In7l=5(>hbn0q}iP77PtPvG؟"6]ܠČRpY"^a0qWoP8ΑZ';(fO_v+ߡ d h>)JEMifLf!'*PH:.ZVO\uKhF9y{]fL<ةF壏>]BkwH#>q\H)Y~!L O`5xG {@ix3 N'Ͽkou/°хSiS(kԙ|w#c?zI@O[(o[Yƀu.R*[(sm 98-uʵ%d8K0$2 ed]rFvȬ%vDqu \#ĪŬ^n m`1J }˜a oXMo0$} nq.$~!MNn2j@j) t2H{>QgGF3xHz$]ήv m ޔPv.qYȗ4o<μZ n=~+`39:%:m1:M /9կZ;_Ԝ!~)#{e69 AzLԖ:I{us+(#C :!/GA$PjwЍCEETa ߯KyIz%L4vEK'Ü.ʿc< #n/jۏ"J;0T;ji]ڟ0o4J[zxUWOl/ 7eRU~ $< hXqRӢp-2\uFoқ"ʭJQ=nٍ"%&3 B;'" `#{BO0 :eH!~Fnˏ|KB=1PyKH&fHh AˋKf[ vRxs6'qڟڟ[#\4]U7sWM̑q hAu;y91$ Sk/> =MGjɅ ៝c 牳%/)nKD+bUrM=XeM6Z| IjS5w " 1( j& fSՏI0c.y琗pGmgj=o(/cd%Mӱx=.X`f~W7S9E$;_] H>B2@4o%@ݕebՏdB~ca?A[Tr1.(,|](|b${í/]I ~m4WnRk$¯~Koۼ$vYᾣMDU !d≞AƧIR)/H.Сgz;܎7\ 4{s$h^:SC n׈?b߹? Smb^qX8,ږ@n5FHʥ} >,]ak+%d K>-]fU@^DE˧,;{/]g w8E[ާ8DqUR LLZ ar~VGs\P%H0N Q.uzl8Z5gǃ 5C82[W_3 ah%&TKR79 ,rV[}6C,#B')yV 6Dz Y G妓|E0txe||HeBDb/F-ݤ4U^|tS qDPc2~wPM7 1hYt;=^i8&/l:|Q%fyL9燭v[qv|C30O앑RPȋ wm1k)n!.Z>YU\ 0||3WԕRcnzo)D{dE ]54חҶ#/3xxA8!}ϾÕL ,>|?}OOY0o, X!d/k) :$'2˦H"CgKI.FXEvvg`LڻzcrJfw+Cly1A:![L%j܃ŒC$18nԿd;3ߛ2p[>䳁n(ci=` qFUHĀJb;߄@gSs`m݊Z$"eߗahmZL*Az &hB#$ʙ;+En~ 8#F:<JR#OL[w+^cXH~>S-9FABj;R!n > ${@^ }iy%.q@^%W.j /Dx|T * u6C{Sx |+%k.Cמ,su-.S ̭dGSwWM{1+bkT bm .;lck4^f"svڞ4|ݩ,,F m@"܅Snsphֿ͇Qԣғ,)-lpOAf)1!Ufup=zQDYL Mҙxr2&K@K]Qȫ ֘T´Kץ)$񮵽 ӺW` 'Eoz?ΈB<eTv=!%@[l[-2*q5kJK<ͽx%AA]x=|Æ=ޮ9%̸mt}S#V-Nk7hBH BϽ5| iS~p4-)${N}`GMâ }"-c2T26yʢ0H8cn!;LPr {- BYD,x {΋7C '&k盟V1\E2!6GHыn}ʛ0ÏAʅfpOo}U~}/@3Sե@0< \\0c ƇS_OˈE2an-lk3-[r lc 7Ҿdn_=dcOf}Qp?'1a˔4%B+MFdڏJ}Jm!iIk]2gUW@!;"-EC=%4'&6Rujr z?Z2-C ID*ÑM-Q[~rXFHt}IgQ\/ME᝟ ̈( lt+ \K' 'aZ@'B.OA1^ >oFm *RNXGk܏|n7ërgݼLߗvIq^s5Ԋ Zɨn7[hlh+yڥxTZMmˆl􆾯/C4FAiM\>,&W|DP1[Kxx&Jx葶7d싮\wBCcI~x)/-LJ{U='[J @E5?i՜~Z~èA e3DT- lG_ҖYq#~mpDMPSxijƥY*JDa~Qsm,=ZXaA6TG2JG #);-4|Eɸs?psLl)ĐGNSdztg]ĠSĹG։,7 e)u*="c67,tƏ3`kφUgnPO87~۪0Vx| u/K4Ό+[Գ2ANv[.Gb?%uGޥ V C!606ٯL9۔}#U؃P !Љ۵0/^]Fp4Ok^ !DXJv)`ǧ݃ ΂ӕ`RV.]Yu/^>&нtA[?̻݇ܤ5<&(np=iڗ"XMNzﰪ~3Nx=7o K(5?[ uO\7mWAWg)NGlTm߸$9y,W)Eq]B6xNhnjġD~VEDևnˊeS/UlE63] 2G." Z蠿D^Xw1=C@`ȩ c>pAȥZ8E'"1r,=t=qfƝ#?曛t+d8-A t*hѪ8j>YڥĦ g: HrxNҼJ;-Qg!_MU?(Q˾㨛49 'g&o,-@+rYm!9AlL B4R Q8P 1C,WڂQ쏗b"}&V{}AlİQM6JJ5HItbMi .-WN;K72Õe~佘<3(v*gd/f7T Ƌ5U׿ \tX~mctcv yxkbE yIgR} ܔѣά8ЁMrqg5h_zݾыzÆu43ωFBM3 ]Ұ[]] ͳKۨlZ6vcc]ϱƝ?0fؠMn$-mI(rJ~ #|"5GP\k;v%9N+fɧ75DwAE^RD["Y} p.7Ls#K㜖 >K#ФઈE`tJaQ뇙 Uޏ-y/t J)J J+{׮Qͽf |yov|0xاM2N!yVf^%<=]`Sd\0  ؓfH+>^oQ1@-%sڝOTrE_X6Sʍ,[~ bV"mkhzl^+Mt]Mf|_paoqD^B⯢ vۢ+ȉ.Z>5lЪxC1<& SP6p!/0giVX +E7u"`۫^qk,"vx'\on|Ga$V0Ql0gsB5Xil_7)!eу dT[~& )x7=qI8؞so]4DPfrt?K D! ^Na{%iD`s4-Z0KCyPw)iQqN.B4d.ajg ``x8$.,Ƒ)ͣQ~܏ٚ nȄA,KU/U.k@Hb) XepEɶ (tx*N|7'E:XA:/Y0,Uo/*dN:84{uWY8ޅKE"XTnԈ|QDi<@YM/hs;3K֧xɞtsyd ,`ɞ ±ٴD8pt>5۷Mp a O~oBS|%]7i L%ȳz|K0qdBr, `<Z 0t=4u ޔ#g+k2%2̙hNB60ca{;g%`@zڹqTpXOvQ f@%TL3!#ueqinU gkJiƴ1dk|LpC11bRC*Ⱦ r8, b}>y1wc 0,]FTXچ$ "dmv4%?@5j$]Q|·,/' =PiaM( ckb/YV4EPlztn؂qEiF\qob&&~44Wq7 MylIohD}P rFGh~M,A%)xWԸ-ɋi1L g/oѷm,ft(yw{9jr=8"'LT2)fn@h̹r"Kv#*58#ET->_u1yJ@jC;9sNO̙ORej| bK1qb7kDU]9Y|hŽږ %Kw<)ԹPrUm ia]a39hwX-4,uOf`nyK:*+97F;<D?ZQ񲩱JD*dD t'cbrhjEZy$J)8?JD_7b|JV9^mS8n _ { sMٿ#\pK%:?콑Rc%ހ̉]CI}=Cp_Wt)C^3Tv3`a7:FnEL'aF"O1P^FT7CP8YIU>D coڶ."7qq_w=ek}m\J oVo'AIǎC 'UzSC0rar]4&B"lƜ ])a:ίjct_ܘ[Xuc- EL<`*k9ܴAy}@{K* R-lmDPGd.nq:>gldhJ!N7l^Y=]%;Yҙ)uA[KSfIxrKuZZbW&ѯ'^H/ \5L`}A jt0p`tS쿏9heԂa#HY la$̲.2N |xת/ƭp{@z^yXBL!>f9/a}&072XWᯓb18xp^BRn3|I<襞'0OnLqH67i3ظEnMWRku&Y]d^~~ <0xA&YjjXG$݆r nX7$?;+?T1L6 ^ul(\u{y|fO'fk &z6IBEwţ,y7^ Zá&V9E{&-EY1V#S\C.{YXGKr%fEz8+'uqiX "QENdq 1K-v#z/{a>3~HB~7!K^[9WJ@Īq( EIО:F*? *Po<nm5ۆlyz%gTU ,oڀk"jΒ6th8#oynivƉI`r d><+v\%N:Cj*u.Wٰ;q`{9ISEԚW_ω(zRh$sWҡw{-89l!֠Gy G@ݘ򉎦>jKha~^N/zt=O69lbICXKcSȽ V$-al.+q ư !x]%'fHn$y&%NTΨϏ[Bdqb>D0}:ry~B-$ 4l[ F3[-RUC;ψ(5:m6٠Rf3Dg)JmvN$1֘dlTFʀ"5wdTF_])\d mI &(pk* v%&k 1@|[3[ގӆB)cRwñe;i>kn XJ|SUN ߪPj<҉WQփ$GAX5!^q:;ڦ?gyW$ $d@jj]7Zch``1Ῐf })R6/CtNS3TrPBCF vzY|KwH&@^M]o#ȩTOpEr(2F;f3ќc -)%n{U $DjǓF8vS3 31 `(diPɋء5haCy N .=byTˠb.ZؐɡQkTUG1$;A Yd M,-YCtxDLiTeu}ͪY[BHU660"  dԫڧj:X3D<LFNnS_. ^# ^1:8{ܺOl#b: +9gx{z2"TVʙ1%3F8 mL}_Vw-~kLe n;c4$pdDq%z}ٌ&Fd(+ނ8([;2&QuI/r $1oȿHQr֟ߪ qŠB;SYq|Q*5nʈgς!J0E)6%z\3y6>19?Tc_2ޏΛMz#ވrDaĮ6lCΆAJ+ќ% B컍Xmu{tfG^2I$\v b)dhLl HXUcR(=Ov aTmz Cr/{뿎3sh$uϚZ]uYт<ZWzW;di^vp v\ & QQ־;% RhP,,L 5bKސ^[~I*YltJ<@zbrvnt?("&f T@҅Z0soLTwS& FHu"Ff҈1_ksJEO~,${UӇ]dUn6eC" ~tMU/c6Q2@V }#`h;-hGRr!1acVsr 5=:]f0|Ej4q/$24`?i2~>/83v6ꞻS 9i9da6oE کibdܚ- $:đ%'^pXEG"hoc')ӭG[.b}X6$y*qN=W1*U;ej\v>C_8PXS(R¤x'Qo= #n ސ͐'?QJ3'&y|Nu$,,\C7)Xx\j%DU۴YPP6N#c\{M ,v->ۉk~Ǎxyq1-t^(lry}?g%J6t#<0X._ Xu1z4Fߙ PfVS V}<MwɝwmlhbDj IzF5í@aGCHO0n卶Jۥ*N{ʎTu=lʚ`|̦e/ziD1=>sF MxxOt^e2/+7v둣wRT,CܛkxS\Z~&0@r82`1z?ׂX#*p؂j vw+Q A4_f ɷ:dziE>=`327I[Url[tqzLK:gB)3FW3!SA3A*-1FrF+;fqJi}Q؈2M`􉗉YslBÄOqM߻8BۏwT:F0i;ZLlϟ+q}kI@Y%9J&# $Qn{iħ_$_;S 9lA>~͞XujosjZNo n9}Y p28 627'ΫaRg1L׃-3~Toi-sMAc M [LXTe\9F6UE~q0.=ΥQ- E SUW޲e%߂76MFa54҆;=xQv-28#Js- gBl<=p =t桛M6BxܞZ/a +ݦM|DMKoBD:ЌҚ-W =|]=m)Me}mV"ƥ 2y;N(&Sn?H6~J{-f*-Viɍ?qӁ_fE,?td/v}EGP21IYR}LhfE<{cC0^m3[C :s<3fi pgFv,ܶ0x!8ptMpE@\"X=g~/gfs(^)=9-ޅlm>ƵPTe>zCk59``x+DcYu(h#}/vɕ\ct:ܰ%R79Q|4w1Ign^P!xz^1w_N1ps?7c+\,?a֌{xQ?KkN yg^V}&˲1@sF/"H5ʧ9 dՓЧqg[DR=*Bs5)#!,̀TFp˕*ie!{tp_% ? YpS1#r}ω6A,(Ëx.kVďHN0 13cx }*{qTsHz =4vL!)g{ޯ7cN#vpZvћ ƔҼ.=3*Q n–,)Sbg7spkWefu,T15"5$rZ$VĢ-qMoapT{Djg40"dTXFPh%fD, YJzhE @*[{ʀ^‡Ƥ(YANۓ*"w(]s) !ѹ^mi-*MJh`m{bc$ըMx|MHK#53WQQU2n`Y1WKo }.zpؗB**ATΕl9'r y3^om+HҳM[X1`nE-Ӆ}0:k֮<,BtژR~;qs3,LP~ ,* ,ЯaϷ,b!ܝnҊ) )=9]`-oA$D9Cdp.B 8 & 5 6QEo,5r?cc 4-Üȷ6l-W˟Ve[*u"K `bSQx7A6NJO,ϸn8O>>Oʧ-&Nڊc7\J6ȧ~@%2hE.DbvÚ.<uU l pa9E5DUhx>SD TɈ9ڡ }zΕZOݔe6Eg#yivo P8]Ț}z+<~>lND^#xIˢ7t#Kp4[NgxV p-Hَh sksJh=pG3 ]E l9=N9^!=_l%4nl!-ۦjLϭky+PP#lmh l#3jr!0`q ^r+k5}+ӯ|MNmwH嗅ՌPفKu?PkqFX+/y>sho%6F ͚cҐvF :ߥs M?( gEcxPB-aum41Q]YackTQi&2+~.[^K(!Zs4Ѳ`,1\mycmuur< +#|2YYhy;>QJXt=a[Y=+^+..k:&n䦨|atz8#i4x,VHw\З~b#BNc6P{*Uu{6G<T=z^~|&:@] ;꣬5DZd3>Rv'0go~gY(9w8=N3,“&8Ly$s>Z&^v\-Xƀ@ջ˾O]" ]i\.K`H.QB_G.ϳ펢kCuA:T[}[F8fhvm 3U ?Ck@\_P,JM9 QNkH_8;I+2'jv!ޘ L.Kxq0:txv%y.Z%s@t(\VsCD z+o~)ޭlD7q 2<\֎&S*SL hr<y܂>셛S},Nj`|0("H("U)%ڜIˌ18( V).j_DrPYڢaH bGNϷ>[m铝Ǟ7d‚n,X)&ڐ P6pBݳz_>'#CoEEdp&CNSd6KHz\%P/V~'Bb˷@^6MR@VW3vlQB0/E4EӕpO _:ƘߗbԄVDI |?b ȯ \5zZJ 66ww;1I|Bx)GB҇o l>hfZj vecLvL{+Kol+5[;wyy>a:ux$YS!|gKU#{ a#S$[bBG2E0ٴ!6ߗ502Wqս4y:%m=[a zZ Kq 8/L[!p&/ "<}aLIFa |(=A3 y(9]W=vv"$NdI 9zXK(t8 d+wܗ:m\Uvyk,zvr%wn] Q@3\Ziv#ǸzN&XUL_Ux83g9j[VI^#ʇ< Sjb jTwIIAyZb6l~ǫ?6dacKKN>nr K%WVS6rE hOG*Oҝ+~Ԩd^D Q4AVU`tЉ*UoB7}ha(+xFW}QtP1=TզW(7T5rGa=*;اTݝb%dK#AzL]ON! Mm;&x`0q_#93/&D=@ u=)CG& f%&8x:*T)Q!Mh VIKU(`Pw;Rak=8s@j%}fhhJ}cioʵa#2Y`\p&>)1ʋ fɕ]ߊw7wc4*mKi7c^TM whOQ>5&K4tn, >:~z(tU:qڏG|.͚@+*%րcxkŀy|kQQ>"+;d??xN*Z J7Qxz{ĥ+$-Ge=%K'Z۟јe?O&9iΙ050P깖ʫNYK W?tZ*wn(lc?@jK"x$r[Jَv,>J=*#2-w;!jD!}1Sg5W[Nj}a!K ٓ*ǒܧ\^sOhro(EC]&dq#ݪf/o<^gdx հ^"2 W&GJJ@M/=`j;HھvEvM, _hJNO"zIXWe t³oʂU +"Ȋn̲N ^Un[,/bvnm5SI-!0]ߗSNBGeT>5t(.Egu=3O_KyϽc6ۓ2~$r)D7CܓqZX~5\A&Gۮ?m2\3Ws2(C,iwew^1s`MI,*^%,"DJ󃢩a.^ \F-؆ 1[.s]mQYܿufҊ\~_4Gq9RfJЂ xt=w ,,vfJ ށ B˥t3 q]S0IßCjiA_.Ʈk3d*J;P]-!M%Us:"2NVw4*I؂6oBк%+K|qWgdJ_3'QTqZ2 W5$'1=sW[_T tc.8l7:@RNưg;I,.di=%si 5J\=݌ @ ~M]o+L%aWgw9W Ƭ̎C8_̕a&'*VɎ/\o( (K֤E'ܤT;BC%۾ŶNaFk2Вl'K4jt҂`ٱ[JhA(L̢aS šqCF8 @s4GuGM(O9aǁA<zBř*D{O4-WU~^Qc |y 2.]]} W@Ge&dkNE{PW~ 1j&ep\ kX:fodyבbOS r[ dY>z%`?oXS=ڏw;ؙm4q=*qM@D=Dz2Yƃnd|7}P涓B:ZdF|X]=MF6% w$GN23L6Y=|[h5D9p2YtZ܏4nEN/A+{)פbӿSCΧ ^s bZE0(7b V~5} g/N?EĔ8Hn@m& ޤPA .}u]zUA(V9L!U✓YʴG~G gWs0&-Ly_|ɐC_@dσZ6+0wgK>0XGl#˩_hݓzx.\X6{IlgY&u'0FHӱ-f+?bR3rjq=x":0o |鉶؄K7kd,zt锃qMDDGvZUG[f?8n@2ʻn;^Z8TZ+cu8 8Ώtm~53g.8|Xiv]8endG(GEg4蠮&ͦ$,*\d*:V?SbK \W/ǀgNX5b(q2"!eknX>!ㄥ>7]&毫):_e:`jr=OG 5)vQ3o}q8.`h@I ÷0!yb}2hRmIAJ71^&?yps6D%=^cYobM1+ R79oiӠ]E `_4MspyY;0K/!0sK8!ak:=0^ņ!4ucl]kL-UE9["XDC>bįPmK2<9F;I5E:&;xrk-Ҡ,n£ YQ0Jt܌1}똧XQ J( }tB"vT_3N0rȻx*A۰ڣu`YK)wpQ0KNa1 ȃ B!"ֶ<@HǏ|*j kR%ް3o60IV TpRز'ܿmG,m]}e^vo""ЈrQgF*/~"h}Ob΄` dch  tV(),+5ҰD4_$uc~δ04^#b۝g'͵W JUiM 犬?p,miUUYHg&& y!Ciy3^7,p5¦B-pH}0%EC=z|m3Yk#œvjVJ,.Z.1dvvTX]Ŀw xgB?edIx:Ϯ # Yo S3lnƂ֞zQ_ j&uA"R0 @IDa{b,*oDYǡ֐f"_r} x%cSdϝe*Q CC:[;U4FcUrXiN 5S0qsF:7|s}WciOUV cLzCK72B6~ Y3aqpG'u@̀'< XkWUV4v+:zHmt$&E ?S_nJޜ{ ?&\Fþ m92Z`I5m<߆yC7T(?hGMs??Ȅ'ZWkiYu':MKa^js\JCG6\ ں5pޯ,ٙgba/ ))-qFpJ6;Z6Dipn"'/ 0!EJ}2\ԽfZ( %(Z R)i 8e c5I i<3Fk\:˾6-.rJ^Lqvomy5b>1o̧q/x(6&[Fxŷ%TISɖyY tw>N&g\]s3(iN@]BQvjvL*L Jj\8MSjd3PAh*e#jF', 9RM74wΫ7Ҥ|-+SJ_z-,dڟ\[iT_9\ ZUW7Sw蔴Dͦ3L{{ѓ!`< neVRF6ļgðY۶[{edk^$VPVi 3cT`,8LM %ٕ9)@ȋ_G6-U:j* 5wڌIJ A W6YjL=M2j7.Ãģe]7w1tZLeA5mQӆP1 Hnw~xXiaLBX!Uz{-!\df}Y5z=V{߮|8E?v l5!^]Fk{]Ivybrg25'9qh~QDJk9M@=uZ+Se q(އ!ҠI(U nE= ~]IjFq[551BJ7.cm!b.e=Gg%1x =f%kTsL,,T*mZSBkHXOgp\貕m2BIf:4ؘq)mݚ:MRpOόp-Q4u=^xů2HeHV{7nj)Ƞ9emBM1ӫ6] ɿ Cs+|Y'W'Յ`(1#Dsa5(!<'0pD6c%ĐixTҎxl/> ^Lh}AaÒXxiCٮw ~JBj )=\vmt0rF8ӥ$+>'x7 R5SrrBM/QSF1TvN+,XT3..J@_j *JZj@\pr[ j**3/sC }ao ٵI #< Ffﶿ[S8/#jJr?'Ka&@CRgahfKUʃTLTջh uAˇ؈3ʻ*a@`}gﮠn?Ho3P#rP5x]Pc-5e찮MtE8j2}f[Gc8j&c?:hgEE27A/1!3܌D4qc$q _mt-9YSZ ~9FcV KD6fXZH/ġӨHCOp~K:PҺ6٧~,G w?OE"}>$-9n kTKx- ktWY@ R5\>z:@Pqq!e焠 "[W_PTe] rD縉kq[Fh74Bvuh@[g0hh|QlXaʔ.5Cs:|CmqyE* E\4K)M0kt9 -'0liYny8Np28N̍DD:nmǓӞ(/GBxϴL'R7?cݤoxB|+{.?.#EF1q` K_ch.1jI̮+vDn fy]*#-D_LgNgpsr:>#8c9 cs'pp7vT< .n|h5Gx8]Y> jT/ q f tCsZ[bk!C.ac\E[EрYo_ ^$u^<YȲ'$. t ^ 1;~tmnM8bF./#m{ܷUF9Ule5ag>cO`"^##&] E ٱi5w*eJv~\p<԰$dFj$&])9:ێXvɂ}Z3)@[65CQ|͞$ꜼRQD/!1L84fhDS_"`[yɢ!e4 G3;2`9m^vnK+ALr]YXFy+$o-qr> D kI>T -4Om ![qVfL수]=_D^.jɟU.rwxۤ0jZQ)Y\]27rd:|^WS{ 4E܅ `D|@GI`Xl}RNOeOԲ}N/O}Ħ^Ŕ侨ZqGfA]n~x ڐo|pBa¨o rF`Ll\!ޝGa9e2};EY22{9TFDm'rxw%JnH!a9Q$Q Wݾ9pKFʭB- "Dv>)3g0bݹZbIFYf7ߍ˶tRm$> \ vJ$o=ZTCI,Ц& &`'c^f51W2!ԕCY\t$P+РCQ_3\/iݹZ[A}PŭNQ=F5 LKy2!J;OgL~kTY һ蜰 sPxj)|HY xl0зsEA. lI~E?&?0 R T p%QCO{׿u},q,4?'en0D;$Nm= / oJ1>JK RyID",t-P!IX%{]e^J}q*,LJ9I;Ud!c;56g\8VӃn}dOmJ&SmH+ڇ9l*WYS"ba _yB%]aːoEc57;-yj>| tNbQ 9r%ZCdR`AZIH^R 03ϒ: ^j,Pԙ}\iUs}QJ;\ 98R` 䊦r(w"52n俦5Ѧ{eaAC wi$`s4e_}u&CDJ> x̗{/2;$VnT H@RWTt=kRK+ƣ203VdyVה|佲-VG7qO E_E|?嶅`[Z3xv߱UBt9z-eIC]N&NRQ O\`;w' mYD WJمv!$z'NHwܾ4Y7:{~z" fSe F )npcpZ2}`EJHT,%$1ӤKAdoyQB *UA w+ +j#?i)|H| Uw!@thc=eoڸ:Ū;SPSoIa9D`+h}r.X/Я;@=Yo%co(a94ZW|XÍ'OƘ몑{0zRtTԼ!>\9D3&e 跔 Z`"rH'1zL l8/^]{%׵BQJ$ɒK=8AZ85eO2@+ﰟOpwcZ YX!z$x͡!bon8668XѤ{glskZLl7X-plY  f|7MTfÊ SJЕZFˈ[k8bxu<䨸'ֲ\}/njHJO_QLۋۿJ)= Aq}I5%80Yݮ !1Wj/%XiIN?\I6 V6|rR|7EFp;*4-!٢5Hiɧ ncӦq 5?=+_q uߧ;OG4qS|'I벱k6bso./' pi:}Mo.e>mIxpo,ƬsbekԋWB7Bi{\̨wGYS네Sm)1!g:/8?+B6^k9| x! nL=v"(OcwqIאE*@Z%sgP{Y=U=2}^^|Ne l\ ><<5 ڀf82cj⛁۔~ݪgQ@j3/p-^*SЕ3JT1lq6rb<èZLHy~-om"t#d5Ȉö\`Ը8gEoM,uy7u6gZڂveʃ1mk- ޭB0{L aVǜh}wQ+i 8$T}с~oaWLWnу22@q+Frcr\<<(}oOܝn6rÄbl-dH^!`]2o2^ D(6,˺ڟ6MIEkI9cϻ/X< 5SA[ }|.塕,n5>6Vĺ4L/N$RK~~؉sX_%}R"WzLT@5)bEJ.'$MA\o$԰qwo|ť}U^; ea.es{ H{ zrdv^ۙQ$s#UP ?^>`PGo2$1a$(?W`LNij +Z4=ߍz={=|UTצ3߃iҴ.т'F@8Cr֮W9ۏI^?E|O`K<f>]$KU2V>K&wV4DZGuJwwH;\KX*icK'#U b_xp+Gi3Ex _>޲m ;@Ǖ׫~KK%61(D[ÕY[ yNˑC>DCtfuOr-}pְʇ/]NqR/=oGʂfԨX.Eߜ Q+͝>V;稬I+@{l(ۛ2nY"!%sG,X,,CY4kωueXMv*R1ƙzi:\7Rhh[kPӯq6"+jqNj?QSqY,No>K(rj$e?2'"< Z}Ct6P/3Xp.'7ax.Ҕ<S3?Ə`u`"TޚKDF|ym^J'jxUU;-O'HCK@Ÿue}ZgV2>cazZ6F!j%=G ANOn/M_.i4bȒ">k lG@~$ ߺʗHW%q.gUT֦*GD1<~1*4}Ð*Il[ɍ]zNg,WZ Sg Ϫѹa^#{ngy>RG_\LR&f\0E E!o.hc' JOs5\TN)Fy[QV_9{$Yţ5ys% H5\tt獗㇫Q+3gHY% &ETbeK_$~-yk17BF r"bp!Ip%*ظtʉU2UWrk7Yvu xͿ']ʠ3]rZ\+`h4o]scv2cƏ ($2]DԶ0skФ;zEv5p _E?QV'Gf K0>UNCj"MqC2h,! po(~m3Un5 1ul[3ɇNz4u@&P+ThXSxkk1XeB%H\2\Y5Зu/* Gq׬R|ԕ};>|'H;yI,<} ԋo l_AkB%zFuaW٬Y^4q ݙ\Fc#3f"=T;m AE}5~գt+Ke6hbu B50E3F7ՙQ) jq}1e}Q U{ƃg5g#+@$cd獌=C*ћo"Qm֍lv=dj0r[j5hz5 CjhLFK]oF hFMzZ`sA)|zANE{k)~18X$2Ih%,{B z&&O dă5fKb(vOPUs$[oM7-(Zh iQޞ 6e|Tbx{]CuAeE؈J)2V3J:d(Q8*HVw\: ACOw]>7/PRB㹗hԦe <'ckP*o[DJ&FQڧ;E){7˛2'V4hYfZN9O '1eZKw:qz/ WXI]"R@ NCp'WҴEHE,84˰_=i!Lqxd:^ϤD:}%/"MkH|;<чW(#F&+8kk" N;ښ7_4,nq,3tU&1Q}d$CwQL/!u K!@Q$Exdt]'3^OFْ|!Hps!{D"vɅTP`紕sKkٵwkSOܴtf*D5£>x$<ڛݚU vbQ!Tj̤!8.Bm%۵o:dcsL &*sw4*[E>7nSJz7uVN4E7 ~M7_źW  T6{Q.#5JBz\FL|JH/R˒TE z9s) AOS10 MpYW׺:wKf_#^T\<BJ+2g Thϗey qpw "'!ߜY7zjǭ:R#e)(%|H˾jG#=ZG-(qb'$B.N֜=T>]$+fd) M3~9_.ΎI^: TW^l`Roa= alu,dΆH>ՁI74syTVswv6q\A[hHtOPfO(6(Nr[ĉЮKBފ]k!?e\-yvs>rX%S:ˆ̉J))ڈ-(/rl2=an(DqQ51(-c1&9PJ_[s"hF;[5==_b_${ \^Laʻ6}W՘m-9!fAwHFϩf)Ilۊ&G-a& $Цy vnZL'TDiH 4|[ВƺVچHTPuP3oIAQ 2">aQWg ,/̬3\m'Dd7D4{ӚmI NX\Z㒗?%U/[ǝߣLgů8;JBwO:FBT,>q9$;$.Y=&Zs@$X;f|1冋WwD6R7|s|£Ճ65SJYP>ƕⱘ[IljE{M/g>ąUB[I3Fm|tJ 0c A s恤"pJ=gmKm0yJtq\ZfdF>m V҆\g*`^FtH:/lRgtW9`}Sg}lhMtzzP(cx%}] |C`'d~_spD1(IwVxft o{M3U[Cfyk0X.IK"B("7HP,T FgwhQьYP>t<{&:}Vߥo1 6X۹-H9NzMe E~^/SzvxqH5lb S-d;CoL +|9WX;G?e յ> -B?I^/;6 to["Z.Kd@E ̣kh+E#9(Uۜ7LvMәr l+/y&k1VKm8{Vۏ&x#Rj⏞݄$}3'SȌ-q3pd7Y(L<AO,,e>a  v4V;13el`ѷSWmM܏!nzi.EW{:U8ՕzJp#KCg3#6Ϯ"n bkh˘'FP J f7yKANzW8Gw9کBVQ&o;xłVy bkү%ܚIv=!TYÿڕfʻKvD-"+O,|߬m1cy; 0*&oYQ3ܢ8&ʄk‘佌䪖YtgQ'*X 5?`okH=T&쉐ʇ`6I =\i^'yc7"Z|tVMk U 8 ;k3׹X/6.p/K.AQٱs[\E+uB\ VQòsKgsW{h-I ;1IA,PdeT 36FaIЪu= -P/7LzoEE~\Ns Zpb7Ϳ踞h }ݪA S@Wv>3teCFǧrD#dxeJRB?trX,wIW3~rG\Yu;m~7 a0$,cs3R"{(/z󵣯 UimeOݴ0pԹ-Wa6#SzH;5]}ΧRm( c&g^h ?*L}#Debaq%SO${ ݠ}Ju>1vՆ;@'}IGOPq~}WEAU2jtmp4z/~ Rڅ@r'w5,wkO~PH`d 䥻 ehOW8@ X {l0m%ē>/`yriҰd jn4]UCjpͿ*&BV"5ش>Pmr}?ԪbI606[OfR:>`ijz+_/aC[?$q,( s_H>Pܝ duIϦU03qFU#84\ ,jT#kg%/X.7~7e]Q r9a!Ϥ!Zd+;[&̈?Ά%3uTX|P*1d|NL) #p?rDBM3QqS{{|t"-$a+ '\A Yx-`WeiAJXpch_}4$GJ㓸{ї:OE~κ$-AiąY")vC:Zb4DtZWn>S~\;$ML nO${I?!fݖ~Ѻ V~zP^ Ï`ȵ\ -쾥zfih9@wѡRz9Xi<# 1g\ #T DɨGi$=l?>>~DLyDﻗ2Dcjs0ҀؙE۷otkss*͌с#c,@ƀd`ZPy /Bl*vh\TF~.oƐd? \M"PAe{W<+|_p"6@ v>z9P4bV5¤#޵%XW˿ز pS'Ϟzqq!f >lx5zDԻXCz84`ۋ'Q -˾!؅(Y ~6' +b[$@Vii '6odsRCbDR_Q_DGjPf]Ѱ:T yr㹑G%4^q<-r?*&]#3߇ȀAwx4c/F}_Q`lNR\oh,Vg_jFIop'.QnYc5w_.`*D$+-.5[@:?rzT2K(H_ [$nzs CE4aX`12Q4ֽ# ;,sptkv \̗o t|hay=> w2*᪫exGQ=.ra,Sbj,a+0w9@*g@nV`VJRP\K: /PՎ sJ5n&<+fUop!\$7$blϐWNzR f0*/mK;zHT2A-%6Hhi$vX'uc0#n'Z?(:]>rHӄضGwJ 0 ܸ6bx'6-47F7l@3VtI? ۦoH^S$q8 0@|5Q};X#t9 }WcN;fԋ<)g`&pK dS 2wv)Rz|dt=`74D~ug&nS*O:.?.~ed| $Mʜ}lLTj̦[Β$',g}ȓ[UfIꃁDS6!/VEX{Jk9dOgG@K,6n@(|Y*"1OIP⧪fF'7z˴Cخ.@wz "2.czoh^<}z)f@dk$M0A TGPgs ٞlbϖOm_83Hp``!7{XC_xDʼnhT}ұ!Q"kX$gJMqKMup ՚H%uv֝#jY⿖R=KHI ЈMI6y,%5k͓t-&B gC`K,؊cPXLʞ8w4QA4}于n&A~il/t@7/nK>6Р:UP[hOd.n8r0j^%;Y:ėҲ+d)=&[EР#q3B1i#C\ wn/}QS v1Ciw͞gC`*VQÂb(]nW)I`aDk>NJ5w62My$_ª\x ,ܪܱ3uetDZyezjX [˚G fIJOJ#a5GE/3?2]]lgV=!7$SpX%z "%]l@V&Vx,YÆ.oڑhաKAb/}zF/ 7IeSG"݆ 5mZS(mR}`R}9rBV'm0mS}}KPe <0d #^Fy jmAJbܮ Z7IQŕ BF~h^\0ai%Edt =:$Ig?'!fy M<鹍XF5DWgܵ0&97[/ekMu2CLB< j{m*ݔB 3W@@_WM^=tXhv #{1e2*!.Ae`FzV`/X-hg{qAZ=(yۤN3tx0u~!jΖtx¾BL)9T4`d:}uy&TnKJ49^ǖW%8̅h_Q޹7|nA6zxģQK߾-܈ؽHmijc` ~KnzmEAc֘‡fJdX-ɜ |(;ǭZVpؖ٠.N8m$l *N;=7"L% DBS=5dr$yDtg~RZr6<1iR@d#A=+ju25AIw Q԰ϲeɟT1ߣ6gG^sƟռ_Zl$Pj2oVe[L=Mf=AtOWr5m:ݜuIAOiL\VB/UےYls51 Bh3Kq[j uCltzݪ$rwma'C4B |N배2]Ku.HcJ\Ɋ> [&K`n9BQ'$d,1k;OG[zjd |?;鄡R`^1]+qnk[XU(W6:rsa1WaM3^-x NO=f;c8)A0OYc ُ"ƢϽ+d8\DUtjFO xtZؓyߌDF$Vii^gagm_`,`F~ _%E/D)=| `w7osP t_nد(>L~,,J> 0=z &/#<\d&D4mF*ȡΫ\pZYq]tS'ĦIاs:WA\|U(t47 P*gpWځs9":%(;hS1q21{qe1ʘpX$ЩD\Ga |Ǹ99wtlhSVҿ؛y>8qكșZA!gѮq-6@36AM%AD'sP0qz@YUj2=RV/Q4*e*#&o*|F 䦞^v@8)an 0U-v>xZV1c %iWzLبτ}RHohx(tPBہ_C. Ťiݡ2\mzXR8AH6P; Yc< @<=ou(v2=6=t!;__ȱ!/8k|m y ~PoPQ2Wo߇oϹ݀4ϧY\.+Q0: FS>z4r8/UAt7 'v;EcJd52bI|T!xЊp}hH;\UDLX*2V xW`o`> ;i;b .Y\It>m/U\/ĆPp/lEm:P _ Ņ0`cO6(ǒGr6<i?icY?Fl=aV}Zk%?bZ^]):0Q_yȇsS@`,f 2N%- }`^8RWt&vFl?^2 ^Ahx[e}g |&>-KGP-S#;|NmKL4oDHT;fXk6QĹO i"v58Km.ZT@WY?5;IWǜtg1reg/c`V hfD<!|gOWrRKC}9A7ԝ5ZrĽ[} oBSLxbactQ /^.Cl؄YckAgrPӀ0Y4h4B03X4gZ`^=`cG4z2&,`v.`mBT]MŐ<= r MQ)&{\+\$Le|55Tyi< ^iҋ`"=g"`9@lM7 ˙meGu]| |Bu^Ajp53m{sg|3!-v IօKK]p{@f[DV2aJޓM i-54Ob3eҏ'^9xŕ!sA+ e$aFGC5eyPwTx_7 sK/LLb>o_'Y\Q9%W6Bt*2y'wa?{:N|?ݢՋ؅CF~Nk}DodS9PU(ѐt/hUl"Pg;,UҲW!pC k  qssOSjG .ZO~gf7 3nqO_G~=:3H.b_L#MuYNN)¯ܝdLG{o%v $rAZ_gZPoyGG\A,&ϵtt~Leh $UH˿eDSW[q8J唬ڠ xM't&Wh@w­WЯ$~]a'P`n]y:,ѡq yq)c m]߃6(rrՒq{O )RvF=Y]OKZ/2W y@5pRm[jqKrdq7f/`f=;58y|OL4 j+d 04( F58d3;H+bg=nT;f߱#ArATŘUdb.Kx0E#tbU4n2$GAצ9),Pi)!%~ [ֻ+RuxX|:b*7FJsn"E3p*?G!/90m&|ASs|Z]Z,Aix"$c| |fzX*޳tIIgН&CF=lY)l YdQoiJk p :)w] a(ZDZCjeg]t_N;EdO ?gbliwZY;~)F]ANgMd V$U'ä puؐGdݏKt@aZE0nh̹JSƂtz. h*mekVdJ2l; $w m Ѓ  =Y3VlBH<:MI7iN Q`I!dg}'1_έK2qY{ %CO*&(ql&냕ZhSZ2:ʒi7ȊW£!dYR)ɬ19km!GCC[_]n(mS@2^8敢7,{;@eֶ2r--Q`v) [S >|ϛul֑vK`r%5:,^ᙊ'dkăXR(¿P˛]xcƘ(s|;&n lXI?^~t\EpYVFf#G4Cp;.VG9|+I3 oPht8prqrBIqPnxh`ۼU'(kh$B8߰AOj*"\HߋHr}s5!(A/ӭ0kZa37p=+iTڹ-h{,:gxx'Ewsx0!\t"}cj-{ uMI1dh]aQ7K-'\HSp _;s.3=w+FT F}9Mrw2Ak}>zAeFO>x,@L vfI PFU7)8%V㨭U.l_ģK);Z\KeݚiGIn#I3 OTwX0rhɽrlF#SPq4 cwHLU%T 5aI'i5;[J' 'mRC=R"p5q@gRG 3X:"G5!K`d#- תg)Koa)RI?Y-$4.CW:ALIs R3;v-!5םCif~8Ŵ?)qTQql4]Sy2ZWgE7ޢɩѾ M~VٗzkJh.Pj؂9i^fjrL7-(bD3;$/D^ה\Ĺ PG`݇tZRþ6M cx*G.# ; p@ ,3$@$E_mW =zLtUt[q9j`?ݜU ɵJl<<$U<bGJ~oZ1;&3?#]}}lcjw b:r'D8scd 㪤Lj2hj pK.}UBtJg.w&@jXx'#tːa$HgcS)~@f~@e[v{gs$=rD,ݶE~y;Yo>yYQEtlM oRَ) & # i{F9~; 7l Ds4\ѠD:L(bR I"0 ?i=U; VL-(S=|Vе9%&sƠ701AV{2%(OK\?UC<Ӥ)[EN]񓌅]kIptmrދvLR-]fs Q#.}l&ƍ[tCZuKBR5~,|tdWӕbyBRf}z9nv%j*. 3m\[`juadh2` )p`%n˻ bXi"`|iSthNd/)$.#Hm՘ o9XM=xAO8D}GS7&±c$RLEY |ŚFV(oa)aTxl4 x ]8a}u%ZuHqJQܱ{z،,2MF/L:]mSI*1 َ{HRa퓳 8M$Ov=:6Fc)-s^ &gPOyU)LW~|8p|ygtPU50 *YkM״k_|ߍĹ-VO`*ZO f I Vg۟qsLZװxk.\CP' O9,@ǎ#IcT1HɐA~]ÐY毛 -KI6t:iZ6T4e2vzԾ(fg*SFX: 2$CvcpAf7t,xˣ')Kb5L.k,eQSnVd2˱i&rii7&3@x+ٛ@dMb䳮lOa+i&js9#QŜyK>xd7 1tGH&<'nfK 2up1H 0^h,ńaYxQ&pT {j򡘃>I|0D+Km<$t O78׺]XdU)` Ibx5w[4WyC<}UUwAjfR+ǻ8Q EX'Dkyb H~-&:pd3U^^CVQ~w1H9pvlgwn?|L ac6˳JTdkp:84[TgdQ|ơ36L᥈¦.13Ec0t-R)1E7kk" puiaݮ ac|b@%C;zc {v F&=F4A«~wpd 0i뻕Jڏ|z3TbL.7?]0O*=g$ɉVpu 6e҉+zh@OcO>O@h&da .`ܱ=(3.\ssg$ @u)~\G}/~<uTvnlqHw#"$ ^u 'noYu}ͩuv`'ܥ*8EanV`H<{X5hm~Q3jm}K&HN&0Kr7K2Jz{oZ:٠Ub jޚ]e'<=~|NV X0)Wjsy_8SOL./u|E8liHʙ(QL&4@Fi ֙=vYl 2G h!AK>Sx׈~AMώ͢<갟 C",zVOVhE5LINPG^WHn,(s&ʋbvC6SqFp$#>/Y:lɄS2ZOc?/ { ! {d'$cDlKwRK;pؚ銌\O›Us(adh `9{nV ՠRu$h"m`.:R|CE߱{޷j|$js7ʞ -"ncT\ bcgטZ$e@#%a 1Vcu@S c6fŋId@pV/ÉvLPSvbz$DͤH`0LFIDe4}+>Kr`X!v>r֭ό?mYjc{9J}Jw`ZEBoY:ҲHN+ֆ:ar&H=7θsjΨjb q <Ü]xrTh &-ܙjr)H.YԊ{;tW]nk*5ˉveEa$ 0WZ-&+ihU*}L<ڣہmqA6!w; ,nLۻT/@2sԔ c ,1oNW?%fXI"bʌȦJ픖X㵆 ? , Bg++7pk; fU׷msbYn( RT'^JV,,19h(pAls|z4PL CDh}qJj7YiLqM, щ&r CGu+I n3~7SaT( @.R] U%o1*yzB0svD, 0 ݖ2UgM3[ݖ昣)bÁSҎl.+rĕ࣍_+rSM7uD"3%>֓Xw@*-ʼnP1M:D&vWҤV'JnXqsڞ!]m ?@W؛ZmVxWh}sO5lkn`ኘgp(T՝cq\b"vqtTz'.) Pei'QݲOCߛ$8.ƐCt"V@vBf)d Kk=@R]þAoţg'ܮ+eoLO`&I#hR(:&J|֌~1tyLVҡDB w?#@.~Zlm1(տgMZ$3Mt٠afXfrhgФi'Dhˬ!UcKR1g` 1hq=]Tܣқun"q!լ8QE`D<`i' 3);\9R]|{d7RIۑ 7KڭaTd 3^^ cm9 tJ!,%>JMGM~ֶKsǦ*RjvoU`+.R5R/T96n/)"x!~R'Z&i.IMB}M:n RՕk(,Qß"3X d_ٯP8)--pmaYUV0CX \/`=չ&s/5̡ʪ)M]AA/0hL9Kg~j߾xZ֌֡j [:? AoUd +=~^V;\? ֛\#.PWk6}H6/K fX^箲EB)6S2ɩb[#kfY^k@;T轰"3} 7$U6ua\Lr!-ntjd1 ~S $'P+6QPJqo++NĖ EBtƅQjӕ$lm@D-Y0 = gd3Ũ_LqǓx-\JK^ex&$["ikAy_'OsHD/I*nYN#:`5i9`Tl%Nչԥ='U%\ &z+ 1+Ѯw_%m:NN%yZgXP Bn=_J ҪV\xPrM.Aue`Ìj{ԧɀ-b {D?J O =.&qrU~`[*QaxѯJ*̫ 08&c":J~my;SPM] 4LJM .q i"*\MOJk7}"]ˏ0Un]ᄌ/A +@QB m<ȉ8LV/Hu'\^e뎹ȵy_?0$A 7_n>)d| jQg,Q0_ ˬEBW⸪S@sON2]k@G+„=Th4N;_D];]!ӃXs#)s:֟.nZMN0."V BרB1T$q.$F]I;<礔st&FiNa8fwySq&olӈ+BD<rp6Wae)-G28TmvHa | p+4ܳ;_~|^Gc*;;"=~=p_2a|(";QF"L7{mՙrP!p3E&$4ju1@n/=NkŤ>Ӿ#>3y<~Sc&ÆxwI#hH ~w,p؅N/8[KQ"hCy Exnr"VT.r{uk0)VݡO8ZL?m eJOִöTЍ2t_k4p&.O+'>a~zo@j!Dn۩1g3BN"zk\;F| 耗Ew3wDDH$f97C7{4k8OAYZ 1u>Tw^'Gcl{-}9-0MAhuA6t kAx,^lI?R`Y.Xߵls̀|6OB3SB`Sw^Ufyn#' z\<|R-eC kd{bP _hIBZU 2/X:AhU }CF1\$y* %l;JX|Ð!,bi*N=!V rd8ggqvޮ*[wЅWXc,iɩ@H}v۝{Qq\,[;> ?~+to"^*17t0Y  ̃UKqfG@m5rDdAx/^%#&2z=MzJ# s*AvE=թCF 7X}gOz1b%:1Y8`gNF~RMiLX ?Jl|=Ne' QhztX7@ǀ^˷Wr5csX hV /-#W?SOLPE&PL/G le)|p2"㿕'{1KF.۶m!hO_%0|J@f }*4nv¶;>X)|HHZ8ҟ80֘Γ{JW!f)|MLSb>6b.NOjG]`X&/޹Tm0b2)-j_^ B;XC@iiuU.R#),ɋu#j,l}.?MG{z6ٲXz_4`ok^`z9Pe%Չ+9@r-hL jq6YSH7a@mà x xZ .е9kb=; ;| BGĻЂG}ꨃ瞨) 9 ɖ D$ۜ 4髏gTMVVhMs&|/*CW:$ˠ{[K r6P7n7bmjw8_S۳PA'YYNZar>U RiUtY=Xeldp$$}W1^C#:`,ozOd.=;y긡{)5ԃ96k ۭ9i,1 /Qeh4%3meo~ Wx "9N["ht3ho' R=iv&s {od $pg]ddYvL0i'@^nQa˂~sA+#B&:Âθ5ŭs]k) -i!&?{R;%ztwf(" ZeP嚩R|n#j F|љTO"E]I2?upf\-~maORo8BDLo0sR:,-F쨫W" /3HB wuN-AkE\,3M*m"+<7*4A(e `3e3X&*";\FTiTSc\enrDچo>iφ eՏ{)Y'?9Sj6ϧ?Nl*cc%L\Ppt0G!)vT"7?ݘ':R<~D}{Qk8 TektQ3e'fLڪP]X[>5 d8L3ox ▁*7C^{\GM^G7ѩPNVk ZP5;Ρ>?o'fOb K GueRZhuo{XABØ@r7nJ^W@?}i3\.şS#RnhP-?LP kWN[tOm 3GGhBu۝0RFǚsZw]-$&'/=01DiC,ÅWC瑋yע} }gYkNLn4'[Yg'n3OY# ڗO’_m=JĆ> &\O%٪`VK@pF% 3#SݯviZGo3ެL{1:"3 r|}䵶`mKevFIJҗ,1TG(FZʾ Y;d< yLgZuMhZ_%3ߙN+vi!|#YUXR5}MЀv'K3X?3 XllVO`#akXcO_X ^@': %dLk|)͹1>/]FjSc?ֵ|CՓ (Px£š5heZUr y5ِ%،`~/9 h5v#Ϋjj9|eIG''q/H^H}QLKxs8Kd]ж+5AOp.NL2:S&.j6 \:0jO˹XV6zH=Fit Ez.isJ[[rW6HLz9>VcZM<5@dǏzU`"6"w]1 F*Zg]'=<Ķ ze4nG0w,<;;S0QgT;=kB,=}"wˆ,8o´c" u~n"_i[>0II[% H /Eia: [X -~)A4OY:ƸHִi۰~Պ6 񓤀, ϖxx`SxWU‹ޝѡ^;Wi%3/z;H<Tq =Cz@'Kԗz"L"ݨzpR~/a?Eyخ)ՋK;F}߅(,gYD;2r'd@DiIL!i*30/E^ wQ8¡`WQVj#O6@m}?Rg\د䴧 A0N˓`)b/ڕAǮ\ηGYDqwK-ؐoQ_[Qr zg! aJ YI㷡Ue)fX¨/r%P采({$qjԯZ1{pMJX{v5mܟ;|}ȩsl>h[2#_%8Rd<`ւ2mYf\%n h9d*H H_d[F51~ҁ1#nd7:!C#Y؈=hCw佘GmFN@Se1pZjpY(!]>Ov,vbj;̀%ۯmUPmfԵq6]2Nf36/kNC\Xؒvcf1]*_i-nLmkIc7po0h{6(H_2 r$p5Oq>կuJC޽fXH/.ou.M-@ msvDŽ#N*@{狢{PJ9V.Y-rF3+[\(%2*?ᩦ^= X{s/%;эԳ}V(:V>C:st4`D=HQGT5RN>? :knnmO3p]5c{UoS"\84iO)NX{|0f4Xz jږpH ,D!^tv6xOsêKr[\X;A5{14RqҨak ȴeq'NRsl>@!Dh]PyHki87m:d {0tY" sBQDzE' , ~9%s9g(RyKzQ qe"\'H!᧍ㄎ35/@K ]6dC}]evsce24.[d$s>ϑi W(tJ͒=9eDaFouYh0M jbz@͌,B'΍gW%''/efRNgKxQyD) #q%]jw: <3è9 OE*:rሊלJ@p 'ܩ-+n_h(~[pM_@B 'z녊~yeK!,-_AւV5e>^뒢qhn]ERK7Lg`ATT%<#mĈ<-V|SrS+=DBGANMӋZ3}|F60)hC`LC=`[ X#J, $Jbj6ְ^ZuHP2S=!6ͬt"暻ZHG2u]FkW0%TR@갳"%¶ 3r0`7UG T9z3K\HL\ku<[;D-KG) D$QZ1[ϔh<yJ<D*aviN>Ht$Ex 38UUrԡ֢ҞQ5zVL|WS_Fxi=T̋n#+2րXfJ\}en[nH-oh3U;BhM&VscO]^x!4-TݾkM6IۼZH"i'"6<3{ xrtvy\X NwaB_;hjV7ʬwp*V@ar`\dNXᓵ gBݗT om$hHj@Kw%O`y]Īu$vzw34>mۉ3ẜX^ZS˚~[i~ĥ$K-ۼ{6JsfUuSs-+iF rb)LbC|a2Q~V%g%hbN&H8=̣eO/h+l_aDH0EӱkeWVT >(kc&K8 o0毒#"N ΡNT|iʗKM1_ H_AnDy"ChOZIzHtzG^/&{)HhP.^pMaTt4P#ѡ*y}Ҏ,-cQViv{&5: qnFtmC28P<h# D@,~7h˜Xwp9a `ʉ$u`_O"Ù(#dHVG@@ l=v$v]y[pؙD.{?:{@s ۺ6r (clP /ԠNjuQB K̗4C=PT xX]E#an-:ۻ)1%*Aî0N 4gix)tԈK"RD@$P|~sIaHG;y Γ 5ȇ!'\V9OuY&x,+CA?HMOlXFjl#B)*qAׂQXF$X usُxO+ciȸQ<`mҲ%vܵXdLON rô6.|-6F܍=X<3s,#JAJOq9Z-A"Mw|SՅl}`6J9c) J,JV}sꤴge=7So-Sn1S+XQvQ*q;JʅT;lW됺0~`DwE҃biwb_%cZb|ܷ~=X?2 k4ө}t 3HnyrQ υHbOCWZ0!z{cy/ PGtOeU*Rof4kC !+Gse >϶d`7f_R$,nXZ"Fu2yދ[N-$MWƣ;EYdi#OP(媧I5u߲v~Fv*g 'hNxgQb`_i~$_IcMJhMaEY >/G]c/[J v]+ TVrXREí#Baetl1s6-JQoX%ʬ?ivh/Ln\67vMSG<*=_t< qTdx!B),~_e"lhйYKy$n@n4$$[J{Aҁ&mUT6'Q'/NPՖ*-"B\C@U q_Ѭ.w# n4  U-ukϒ%x ]c y.f\b T^4<@8'GU# χq^ۏ| f"зݣBc+FGRc\pOVB n9oB(]G#^&n4+A+'4Aj=9yσ[+|I*1Ӏa%݊EHL bZ)wL8Zu1YL/NuƘᚫMŗ mrJ+ s0dnD{Onj2({ !`9nv+"y >JgPߝ9}y0Ǽ[!J0=Ւ|> Be 3ў3.O.8Oɸ/O~(h27{j!x_>MpJ^U;ڔ2q4 a |ŁAāO8įnN Eu7,2$A`a)+;cohr]ʴJ[9̄y$8S^Od-@^8³Y_+k5I9 vP҂iVj+j)b`8.8د]Oz ' G:t?xb^pb?{A)Y||bVlxΑCkwV.mcf0o8# Q(Ъpwey nWWB1` N3=7$kԢGm'|s-ht0uIwuunȀ,L2Y[c։#%!8*V^TMMl}SjJT&{&s (Ŏ]}7wP\ w˧yt:r~m_R4ϙp:xp?8~e6.9D)hRFȪGWDw!,/x[!C;$I~B] @d&+`Ǎtls;T@Pgw*>'Tn=icl:"(GLZ{KuURG݁E뫴{I9xAɜnP\b=#L7vȗI]) 5a梗2>/r=0CȤCO]ߊϤ#$g(? (*cEfz|׷1|c&պ.TBʇF&0pZ`_`n$9@z1*YsgScf<d=S ˾ *%'}_au('6# JH(#s@Cær#ybeu4=t|[8cp'Up;_3"؈0tI]p2֯rpQj g*^'WJ.oFiڵI9_Mu&cݮ BծM{dU'p ަX!G=%x,'sG }ʥiX @ok[D|L3Sbծ:a>PwQ#wE^Yᅂ]z:Fc7}m0K5SJ~^ 52wgaA xx yakjtS!ǣ9):7+3[뒖8;dF{d-an oW`9e+=ߟZSwIEC?"OFe2m@MƞMC2XWd}{gp/@ d5߾tMAB4|Y^>}߂ety^)9POD)Ga^{q6oG2&0N{/*`et\̭ѓڮ3\m³By|?m_!La6/X㬈 c'ONW ֥q>%+SA DɋiPII ҶA 1,2)s'D4-_xŒH3ڄ7(qBXmm RWfVU"A2޽ۙ>.8('pR]mig|oѯ9#Cղ Sv?]~au^?֧1fyÎ_Aցdo!Vqr41s"c{b Z=#z%fl#-] ^u3Z@  [0!+ӲU ѫItc@#]ܜHXAf~z_C|WDJscqLEyʴ*(y%y? P%Vg97Pd0A|SGL.| 7]"UiBG -Jm5:} qX6**!0;F/TFs" 7 ]awZ2 Z /' އN*w) 0YztW8ίԮ9ھZwe5Lҷ*1Px8,}Kn=G qғ"Nc9IxO>.ކC+5JixF& O2Sf:ǥLOVڀ7]o"K 2ԜU&h1 K":.( *YEFd&$݋(("dd 6>9E\mvOrj?#ǿٷD8͖qM(یT1Ic֮bM oK6`^f)K7(G}ѧ̟%ܶd}JMl̙TJ`pJW(P"5'h nj͖R6]nilR>7*e=n9VB3?x?f>' c}7mX ؓRX1q,,'NXܕڪUۻBz[%-RcףNպbo m|UYuc/9g lh֥H\{ug%XHdj,FET{O H-1cڲ NNF13`B>$椽d]qա%N$$JMs~;G[i, ctywNjɛM+a:A.^+sr5Sl/oHt >P>&&Ȍlq*iPΜK|.=DAN^mQ{D{ˡGI'Ņ'*=`>B=xͭ4Q NN.,G*D%^ )$h]\$2J귭O> -^Ʌ(_TdSJ΍ uC9JCʔ;1H^E Gma=!NyF~K)$u*"NRŻ x[AX?4cn1"L֛[ހG|HTpD2ٷҋ:<](y*_)B?;[텕VU],2-'ٍ!;.لU+Dʨ[[o_}2qsB-xl C8w?0dISޒ72R`1vPv@nNi2&e%ۮo%ۓKV EN{alClE9=-zwǍt<2 F1Q^_tIq0}b9+LO(yh"g bj< }IZ$ADly?ַ16J?.V>+_ y7Ì?.EI,^s~f[-j [\'!ölyLjzGxN4,Q)C0v**^(yS4')Ӯj=]{>UkndطvFC|`@4^`g,SS>`(E9)#ͦZ Mçrf3f҅9S=V U~{XE;u(>tLB۝'G0 Ovʛ>9P^85YU$ǀ =뵄jP /A-4ď[l>-dK~CvjE "Cq4$fZ]j7z#CR$QݳàQ9TzOaV $l(ƒW"j͜%HB*dU" ieRj2>1ekvX~r6.jKwv5""G2`6tÏ&^Hq a㖻'ڶ1QMaN[w'Jmf ꨪhӜyIt[/-i`K,q)y3}mo9?ӬzAAPSxV'wI5Nb眕 Yhh wp8 RtIN,\ɷ@8comp8PnGgl e]Wx܂񫭽ao׫͢u\׎Wx[_ؐ[\ ~sfʲ\A1 ¯`9fO{gkT!< ÁS^n3@D?cQJ* mї-Ej&$})DB[d,>&>)ZoQ*&kvrq۬=Cci yQ^l2\;cmKL"vp,Ai|50%q<{yȪW0)o_T'./v^Ŧ$^Vtg~FuKPφ#83SH!Йݿ)-e׆v"rI9-(?Õ2`O\4(RM*`J kn$*MLX jE>UC,(亚]NxPMxgt A+ߎ, 8ZZ:IQXfYȑ|X;V#>Aѻ"zcsiPR obnW\hᥛ v{ <ַ? gt6׼FB 5YڂB85 jlˉyOjrF~3!,2kgp4b1EaZ#'~| 7\uH:ua%808oCߜ؎$KSle\7枤GY N+dlt9Q`cpypDYR˻­B~x=xB_x+_ɶ"nGݓRd+٣7ym Fp|84kWw"N}Fgyɻ KjoEΤU_GģC_P{:f?}'߆E= MW|%#Qݫc&VEHԌT!r=(jR?VQԭčh{s'9|SwRWX5E#gz?`k#[] |/d*D'bx'ljbYh VBZ%z@Zlt?,fk.aTxWKzfy4QS"re.uݖ@ 8g9 )Ag!$%c#(}__pU\ZӋoSSs59ȹ g1)(× 2E~rMdQ}~gP O0/5~us)w&c-jֆAqLq_*81륒ef01$X̱-[e˗!pU 9Xz|!9\ã)+n8.Vx/dڮY`%yYꆥ*(tI])rupyVI|B%"{DvQ X:=b o!s!b;[wrM wq" Usj'z\ٲu-ա)i_}qFA57ڟnlR%7̜!ǵ~(T{Md}}klo9(&:54S6zD?3Ojh8ߍa0zf0٬,aSHV ;w5l* $~ΐ u S-Mien\kb18$J&%`(xi]Ӂu+\u;̯׾QT`7`x'Ɛ6?wu{b}`;_}y%uM}ȫrJ>.5PϏXU/SXY~\, 9wF2zW^7>4Uv)ҝvxiwG۟srװUf<-ȎoŒiȇ9Kpi#@)W dd&;v;Gr^@ Lh`*p ""}Z :n3H˧i96jSm$j\Z%Cye ,QO&x BθQCU6H ZPBٹ^!+ϲ1DzV#"5)Xd ݁T`ֆy_ :SxU}ߵmtᯱq3W]д:f5u &;xٯ{N+>;V͞I}bc1>)9eKulC;k*S5Ӽ1@=]пKԏkԫ䏩xLrmf[1s}+M3/#c~>\ک <* 4-F;\q+9AXϋH3ά(R7vZ@Jx$e P;hrb&FiSIYFm{EbKOWcj&M7sFc*lܐǚ<]+Bruqcԧ?6pNRj^9 S7Glt[kJfeǖW?UrRt <`8[;;.Lrc rFQL1+@s 1̆B1)gz A$4-]Z aO^DNGk?(Mrs1PT|K&(3{1l79bC- Qy]}m;dSø84'RбudrƄ7}_]P3RA2/,{Mz{"Wd?}IOLTֺ)]_-O(p\0C9۹AgMx6􊥧ɬ_L):cR G0cohє5N. Q77B5Щ}ARI*)nx~,o$VqzY&\@!@IW<' 9pC˫H T6o=W: )稂8䅊}Laq>nOeTbh*50ᡦCor19n hy~c;mEDgR= 4$~m'䖛 띀`݅)'tUX ;}!%rqnD/A&U<6f 3J3=]ϾlP<;!)(Q~a.~ֱ.i$;ZW^ z>dcSC~ FxUviX)`})*d"9F;opMģ~tUbGp.gu^a%iX 뒬" jC:rgVE| m4 {}Y`LɣGvo gCPe4Ukׇ mbI&*iF y.I0gd_`Y^Vb}9*dP Q UwZr;ZJD%SMSSn:gi6]ooTL_=ga|474*p@q(+zQ58A*ϢҪM>UK @:m'-Hƅ jM iL5F֩{eߵv)*)Ű< {doq+eZ+l]MI u wvζj?v O%puj=X/B;j`V '@' tV>uo=64pL&DOL3Vu$ʡWu*Ef!剹ğsgJPu>V.jL*=;0<[X?T#֕8ӠN ØN_^# 2kNaFhbKL!ob- N#.Ēq-jOLőjNc+G i^^v%H,,_,Ċ&Cʇ nzA"IMm~cXxiNӍɏ")gJyNw|ɣm=ɢ! '߇J4HS{ST6И\6Rgy:ڌZ랭ZbUBsKx>c#QqZζd/'"IB)e!y5J,!DL8N@fk4^̀FB%x~x?E.J^FKb_iI =S=4&F^(L>OJK,W}Jl24\襤cPT9Xd&P8q \l9 |w0pc-Y`.G]LFKl3|g1/s}W bkcyFI5~88 9X]鄲iNm6O:Rb6:3g1$FH7enԙ@8EecqZ|DG4׉vZKj hxU'FC-݊Gb@k~q=lIBd-PtDZJmJXjZQ܌>:-qzW%l¦\0١8@5T(le VESX|ύVtiUޔƀMQC@!&O)t|^C`jR$taꫣ+qݞ?@:L{LT8L,-IqrU+o.:vjk]sSoCLXg`8|gC|S QFȚgY\T`,.V!4VVf=(RLj1*f@@Ciw怯9IeaTIXM6( !*Pc%SIPTOQܻ[N. hԮż=C-+A"`>[3'UMte5M'wXrfOzumFԦGm(%Y|98UnzckrFᾓ*ݦfi"BΨ")&t*_~>JOKB6 eTN'G?[k9)]~gKN6@ke6DS 6Cۇ# 8qLa >%"kk2*fl-+!U֖V V(o'}!|IXMZ}Gx(tU 9@F RhQ\t&44wokScw r+ݣ *+i(=XbdyKL*̧yS$)/hro7N Ȩf!gZ(ma祇̩xzi2j+j˄Nk!y D6)DL% oU]&lG}}x(mp؜/V ܪ)`7 \#I-e2e-3v>umoU3I=0"wKjX !C_#ĺѥ) g_+,!3=R'!'_ex -O>͕E+@~ьj]D;L2.k2zb8+q5Ƀ#vDGSWU=:S65SDzǮ-ـM1 ɡl$]_H&girRtl̤3\q0zor4y]+I(!Pİ$E8^rN lɾsiBZVQN 0-&p -bŞ^"Xh[MY&fa oEN.AhFUK0\j`㫥PsPG7 &D[5P PհU1?8bڋƪyg9ZWuH<a}h`Pym%Asɹ]S8.)fG84:r9Sbry)5 i>NC'. v '#i•JzoԢY -3%@1C:/p9Aw }%J#ǣZ$V,>%:|})eV^3\ P ђ9ޖ4RhG%jܢnшph6FVVi(#:wtFh@nen! N#[J!?"1UYir)Hq*Q]$t#̰{dDCЧ0tz;,w;ZH 5<~b;]0Vȍa6یdi,XkGx%f Dmʱ͠A@C~d֕ *&]?xktskt[rN ;i4OADXvl-b*DBjι1gOl.S]g]D?`-)ٳll(-ȭb'$T'qH"iQn=f;G ZCTNHF'_R(HZ;)s^wg9o~_ʤܑC *Pcs/F8dFwNk}Ȉ0%K9ckcgWҥt t⹝щT7v mQD3'c8#N! ;%9'/*xS;zLܔPT@.I7N,u2USxkz65$F^\+mN#$PC \}8xHIL ?r/X % c BEk7pw7-⋸!(3#3ZN`/»,80Ym\j̤ͥCÆ-J nNؓ qD&XVn툤Gi۲LpO@Qg4NKj==!K"ZLXTPm7pt*sQ奜(Nˎ S/,V.tݶɂkV.[Eݫ>a)-j h1Gά(ߍ]j4~ U {:;]G2/l= LHM][#ǐÔ F$+_Bru=QY*Iz { VRޑLJsOyJHDݢJ20 PZ W8iz(b@KzPtpD?žcSnDuG?cHe̢,#c Eymb,gb|D:`V^!`oLiZem_t*փ hDy3>Ex'IR`C">$0CdQ7,3sA ;F5|N[S%v:J  ؊: ㊴JH :j =,[zJ )2\'^-X2[t6+c uzf1C.Mv$tBbҶ #^B,x=BIo@F|up59;)kJ#*8j8^Dp^2[Gl>_C,휳ƪtN>f{{9|P@_sY;t[Zmη<8LD[|R UI#wEr&G6oJZ#<Ǖ'pzw!LTDE7lgo|Kf_tHt'VC^5zѳ(i: Adլ ([v]c fګ@TM(}D,dpH`.q8RN0s@Sl3QߏL rŨ\` [R(fZ=9M+!#s#IΜ̵yS,԰Ox\5DyUJ_u\!/_Do[jܺ fjFו5h! ׸PGZSkMf N=VZh=QBK{:5L=#(/>oظT:&;;Q9K]O7FE)+uMlO'V}[nǴ?xjnJD ';"噳Z*NHI0|e֐!j> GjWI_#Zanu9ƾ4^nVOa Hp ? I] s1רGx(+Q,Hԧ@ V#f oˌE;LUchn^qy?&LYS}"ĭ}7RZ==5td!0;l2Y}-o,A0!y+i<_N[{/u'8 BKMկKΠO;@3cEٯ$z#eCmg< ŷ.dݨ"O9rN&d>"I@lu>Lذ@,a'p9QR9ߌN&#r* Cؼ3am İ%YxtutTtUѾZd-΄4R-19xzULY8х%E4~3l; w6Kn֪KȹT`حzLZ)Q6oi:t-27ĩwͪP2ASceǘdI{=03C7gUQ)jgˣ0M-#zZ>% i]k0YB.a!oL&FX,>G+;xWoq'f<|h/Iʥ5#6"gG tCyssyRDNM(y)d 6R*.}\Ԝ}AY{a$<񓩩M ik*e"HeΡJE^~7n hNDxZ@j|Ws?&ʿ͗T֦ĔAh!s BjCJ' f:o6?hT/ 2]Է8k Ԝ; !Ӫy_x[a fb[!u4 ).Oj 1^ c!%+IkL6 ~Ȓ?!;ٚ6 %tHɹy.?K#7)FmΚ>X79bsMqT;YI],'D[UD4!.3_ ARܙo`ҁ`F,-O9"or|5?ئ'B+lLƃ]<{5n Ay&2W I;8 >8~U~hDkȡTXAם0PUR^NMye:_0.@8fViwKH)iG_b+Wl:Fh^|iwihA) 0+EӓQʥ&Amz0;/>ZZ5/zO3wYןFi93f8t(~Q@ },#󘔏TxFXd<'ޑ!>m90S5=d4}Q@i߮W&݂H2v{wgvˢHf-9F ][aPhH6 c59pЉK}TA-A٘'tIH қ(LbăT@g#;x5.H}`V[Bp5zkh.bPb(s- s #')񚋉H HZ[ iHx %r"Kna͉td~;Q|}BHy2`DftB^"-7~Pɵ0vTZ vaP}wX#3f挖 kc1gFQV?0&jz>c'&4d,__!;4D 1ţK AO ϔrPPJ2%-$e-39Ocxwۆc;`u[^Dr,MqY.cv.Ƨ?.PP!;\a"_uHe8Yåsd9XZ4/vHr~v6R =t}y6 NuA,h "SmQǸQn"P5Y%4zx"IMx zDZF4 (4"/k);N}7bο7Ɇs ,jg]Db,`TW_E4UjR?{.<5,#O5.}MB }360nUR9t'[M)+N~: R (y\ΨmaBsU>\HosAƛGt)qD ~f mMز ; }JgR1Z cƭΣ[hIuq__H3LVpB.uLۿ2nMZu֋qV.Mn%^`W^)0E#~{طm]͐;=Jcكq95s-'oxOfBY\>jP  C$_=c= P{ z2`&q: mĻq[%\g%gb_Gn%\i.g u\?K~xeb/R)ǡԃ!T5O}'F0@f PR(p1 ?qCpH}ڡU;|OxQxak(ƝH#|I=V*oFB SMҔng8CL_Y|k= Y>5" >z'&DӚ*8yC] I:(@7H9-D)z3@]p O# V%uT~s1+aWx߽\ HJR?){'U.B`yteaL8)+GlQX9*;^" guE8-l쬐~:DX/lQդvJ _ vѓfjHBx g{]-Z,Mvkl8ы D=bm8QWd <UC0t'BT^K=߸%uxM}2ԐuF#m8G ",tD \$<t g2NW[1.58ZKԎYi09LZ(R̃F!,z?p&&YE*B}([3ҙDIᓘѠ;$zr 6Tw=Z}Ͻ7 (p_>g7{WɔxD:5p /o1[ HOݢXȍ%B1CNvkE#~-}&p|iʶc20ؾCGϜwPƺr!tڀ>f;0v/8a*boͺS~K 6O6ei{2!c{\g9R ncjMrbsl {YX`_Ri!plsJxPɜ06xz' :&6݆ܹ4[|+iJP%NʺTOC4c`j6TJxQZv 6Popx,8rm є]TRVmMl%3|=\N ۚkX=Ҍ=L"*CQz4XFG,^eDu4@+z"M=!E7AFg%p(*9G|$OD=~|Yw!RuB(Y3:^;o,OkR[^@18rYy9~u]3 bAlAJgQj*#Jچ7w)ASW|ڱ)4BJ q;% LWK`,,Ms-A_Ek崨-jn]tTy?is*[g>' IO H 7,պtq\'+(' 5llAIƎr|i#/AdLCq yjs7y;U6O t6X'?|/ |%~T RwAԧR.GօUI?r(T ӵX1++GDc4;2B%j949.ܞ‘SWƐPxb>;0{+ ǼϬU5%@PS4Eꪳ da=A2QDZ ` $6*Y@ŦO8f n2T8_@R@y[Jj=أsf܎!^v;I_!o`E{ò 𶑜YlBk[B=׏1B? Md{)_*=SȚ_ؽ7KNRW\PPdα f-d6: $ Cl`)8!wT]žnUI7nk$-Q.Ő&OӒymug[YR5 tf4JpxLtb?赾H#5x5[5fGe`Q͡I]=3Eܚ# jþ%v7R]'YP"O'CRCô[h1{,þ7A1Eѣ??L}̓ŘZ dBd21O.)`9uvl+u;RNZ)7^XR ~HMj{#]g쁚-c)(r=mBn1]ހYUegZ? Sͤ!j6f۬YWziX9Kb n?-} ΛMAz1Q# 7V2SUGeAp_̓>cL%RT¬Xnwb%e\@DRi=/&yJF|ʡJY@ZNc@>Z"Yu煼 IOʯ)!Pq0:vo,=-'? ZSJ}d9s)K9RbDZb+." ֡PyyY!:MPпpԱ E0mfY?,o,0QTKROjgO}ntK6C)cGNg|IJ$5>!pT@cahb`{ yW{_hV疰em#YtS=.txw^_rmM+OT0 >\;clHNʼWxIȡ);TlA#sffBFutLMґy!aJg4G+*q0s}"u/9;)NbȹoSaGQN >YlsZqhC.bfS:W|CJ!>7'{\5Eg&f8oJ" 6ﻺHa8px N~w;l lCӴJrзvnШUe40+}'i&qH0u oI" NŒ 'FU|(R~/ԵZ͸p|=jwSV ' c^+oךD.lN$myMVKbXl8߭>(kj IxUmdž{azMuZNv⁞7q^ ĎhN ƍW2ČR*IQk^Ktv `mZ#|~ ͒8R&/NU'W"*E[G5JGRn~Ԯ U: -512^nuA4?7ϳRoIi;Tmh+o}Rr([aTdwݐHbv3QNO,ƧHHƩ7j,(tZ3*i}M#iL(%Ț̦ 2q v%(j]n9v9]0qq,\BW8Ϳ?aCWE :Sa ZJȡl|ѝAƲ髿O-IJʇ'}2Zh&~dc5 wͱOE= -eyY=.v P tm(A.ڌT~=&ߜGo E'[AtvV4wr<^f@M'8P?12D%/3Rշp|p+_vv孓mJNڃq|TpK%9^Q$,F~=e(Lq(I߽iKkU .'=,^#` d@dC.u)!G(p^VNa~:yXua$H@ 0=cw7)ym,Ɨi̦)hc2تV35Hê`#=gHXrWY ~MJ'#$FJuqm轸Nͬ.1K!R (Npn6X`-Y(HՋ(Qt%,Œ 0r\qJ#@%mSBY0L|s.%vP k 4M"X-rՑyR (j @fa:_S>Džhۭ)&m&J6$8k!n(J~r1"86EGap0-ܤLͷ()&$ ={2U `^Xmg_)1St9ĂKÈq~6*vg 6Dw!E\J(e&z..oýXjپrJS:<".g ΡQ߰{єE/P[1`@&(bNVX8n8fQn,zDo [rU>zO~-cARƖZb[@.y[*|'*U{ ?üs40qeeց-3H^?nA'c|k2D]B%#2 6&!_#lFDJ#13ELrRn,~0tdƤ xV\|sED3C]1'meW]["jf:ر0 3ڼ/9TH$qC)kjo_I?^ŦiuX fu6)vR5\$]vr6|%TYj^512:fȞf <23O7y2PBp"ܝ=QdgTdLTL/(Ӛ]lƪJw!=-w,ѸjXT"0q^bq,c !tmdᇞ(`UԸl~ mڽ_8Y 2Px=;sBųA^lG(`y6o}ֿ:8+[?X?WWh_#ϔ]JaG6 L(in@z\~ f ?{!^[Z)>ypM;=(b}F*x+' 6CA(U4ixV +0=W NM0sřL/:\5aJ!'[+c0WSHWQ#SYrneCy}&0x2'ɼznvo+]PM) :@*GM=Ş(-½m" x2rOftJW,Uv;Q:FUyĚIJfzC%ޚ֫ͮt=q(rfWۤ/ͬ%['FN=AU/#[@@4xզYMQ .Oyt L'@ Re*=tbD,#-J҃o"Q. A؊j_H,aRQz&S=v^蛄.Hʵ YF⃏b-weXqr4;|I[͌DJ<:@+4I$ NI9m-0#M95:AKͧXz`Hv]y|bUmi7[5=bușQvAҌc{rԊ̓n?8C'2V]g1;;}+ 4o1m֫KͮOatnQ5}-UFDA~S1I=2>xFl&%5%ɧa+x;`'ZgPi][Yj$j\[|j ^\JpA.YY\25(mY;+}*&bR?OʁlI[#x9/ݹ-_)Yb E@[t[i mVܵ 0/]Sb߁ޱg[rX~{H%gE!#hd;t>_q^U=/PQ3/Bj!SO?3QL$m^"as@Fg:!8[ON(:*=U#_K/@Rq2 o.@d̩i:zD5-4.CX-3D .c㱳Oj~HDY +3 !deL!XցL?; ?/p4 4>m.vz_TL&KoAɩ1݈Ӊt@hɗ.R=t)Ulc3OBu'3.v&r~)=ĶTk8A '>8XFWji`~ *j UɌwy'ǻ>|.e /퉬WÕf4' fؚ$P;6nC%o}ys{需{5.DW "-4u((>{dzɐG,ɮaܒ8.UOPz@Mkd,::2UU|H1AtRG5Uwզެͮ@]Psm ZޏF% XqroZ,1ܛ]&SYw.b'!>0Djb:6i@Sp0^oN /ZVDQL o!z'~&+T;}wx). VW xJX`ƔZn~#3왣T8[JLF5K7K"Uq]D0$ ȷd4ǢiP}RDWV;Tܑ dXO=D]׷2Kl,3j\īRZY{kTlMmW 9*4Dy|DeAЅR2`qߎoR̐$OGv5DƟT2\xh gP3q!w%]k&v􁍫,!]hX-$>)j elzW RWUlFK k54-c`Q<"1a]whVUj#O*YF,%o`Yǣ pxG(Y>$g.Pʰok+3ݣpT\p6 g,F׳&͜Κ#7@ rj#Tz-0iF6`.c2еtzE3.!Ib$ei`Pw,z(d0{8g'\z?;u+#A Nd[d%ET0ZÄUZN?:b_eX™.j~["K?)yQ?Fgt K޳"# u8_>r, O gׁ_e ,`b+ *-LLwd`a@4{ (h!ߖXs K i4dZ?[ތo$ lpvѦ0@ϥ\dL ghDZ (TWf ePɳ:@օXHgH4x'u{ $t;'5,6>{+`yDM/7H ?hYIo? Bc,8~;/BIUz67nXEdͺ{³탠|Wݛ>)=!`D']b!Vs[}}ymL!a6\}{Ϯ}u/Rŀ<(&?]A0#6'Ǖ YEs=7Qx|O6Aϭ0uL"bؑ#ɁkȺpc/]p)e:=2b )K=ǒ}`*A ʫ(xpިck*,UEK5$L2Fkj'oRn-0);)N_ #f[P{$.5l-S9y/N0TI"OBr+ s.qKgS1%})B 챼Qvn8. P#BQ;UR@Ww}RXRuk;ؙ: {!&]SCW{\;, &XSz*0QɭߘA/Ȉ C0˼h :d% vPi#fX$7h0% ř_IaUwɆfj/ʱNkWx,nyMwzj D<ȹur-7"]'^r #6K'M2I}<7?"#RcZ ( hgpldQF|$U}LQaQa0&T=.u'Fck ?DjxĻPN#6͛Wkei=J z0VW ҵ/[f8T 4IZC_d!~jb벷x یnٔg:dIIy{G='Ѹ}IdACAƎENۇuLy]\2AR' moRG?! C̳R͚A:'#?3x%޻$`]y}W-)g+ߋɤ"@v=MNVxwJΩڜ.s !^i6Qr(;V31;vJ\5t8(V4o{vˠ;dʬjRSp 裹^og\C[(.j?Ay"Q&$qGJp_@P&9'Z Y=4[X&B56bj*K8ua> U{ q{ i4>poL#n4%q8Sj5OPqN Pj@!?A'?ۦ]y'z- F&^M+\Q.9?a;4K펄ԁlRnFQ׋D~p VHV }bY]oVS_r cd'Cc=Q|ޛ!sk][8)Z""2E(T$ >'3 &'mW<`$^,lY`ߕϲFq+giuXG]Ҩk]8C'cJ= Isra!Z( `wt[{k.ʽ`cv_kml{SӨC,ȷ>cu[a[ uC dJNOk/~;! 0gE\`}Ű|\Sj} P"1`QSثe2rsGuJ(m,=OYU?^Kw6bsZ6YgaS!\nL;`H^Hg۱UGÌ%r J4ۦ}MᎴb*{0H v#RPڅ'j"q | 9f&v$PsAi @Q{Q]reYm' Ɩ} `%z-"tՎfLD>a-wC:aFsx~Ge|*clolmPoTnTא%ct{LHHaHҵ0-44h5`]{9Z>fV%e8v MVu&\^_Lt㗣M- C$m:FBo#;&l_uaއ`_5إj!Wpc'5{qBxS 2+.Pm|`f1NYtoUNV\H4sTqE0gric\d fqs{ۏtykAFYh1XSw%V.Q͊"ws9Y&ATWk%>[2D:8e6\h\Z.KfF>¬{%eY7S $NMtœ*un'͡Ke 3єsHWIZ_lV_!)#pec#M , (#|BiPdS%ʺcxMiĂ2tXw=`PHVG k5ckonB_v1 0dLAc失Rf~%ɃXQ Z@6$ - QO*@,oOT60D|sk0DL1{{{5>%gg4+A`u{l姚0Hr%ٽ<ւC:4^Si-P˪M_Xuk&bOSx|+/<94o*ռ<lO$P?2 xzs ֭ݢ] mfåMc|6I [lgw扮 X{U_Q`td*R].혜BvÇzJґ3#P+H>3R{ld + sB1A6,kS\L6pޔwEEêH’6܄gzK'/i7ܪ o&¸u8)d"1TUXyHa۹vHB%yc<%#b-ƏC߻қ{2UL{ h7Y)'rz;*e3i)F)]MC%7T5Ԅ3îx5>m)2wY[_y_JmR*3׍cC%ҘүD,-H7V:a̱LVWye&O}x>CiSQ^*ƾx 7Ƭz.o? X4%똯R.F @ANKXda+cnY36#0_zoUj!;єi]UӜYhH(~2 fAY:h+? |ã vLsj2qf5-~/##nM^IH~EUKګ?`NDC.N*VnVd:eEE` F00_`y.i.ب{gT&q<Vr{=dG-xL 7ƨB1V." +928]:ʑI[#0,Z!ɠX7{LHiDmBFb /]2S(?5sT\#NcVv"{]6r6:0~rܳ C/'kSJ0͊9+4)Nͮm nekӲK~[ ='Ct8(!$=FRPʩ` a\ZY.8Rbq\1VkR˩1 'bS3릴NlfRNBrhSZİE4EO>O4Dነ:?<(G*Tev.xª|[-[zKR_J>dRf<"!zN8bAYb:\dEdރΊ~+P%a(1&Xi.5H\+l4m;Wl" upI,W)MNl=SF $ 0ti~:ї0)kⲟmNCy/(:5|8áVw:D 5OhgK7c2Hώguږx $I/KBC@jE :^A5kSfo_ڢYQ.n!fM&DbKWنВOPasdh{(gT ]BC(b `Wp03fHg+  nlYYyapO%=wCYO*5Z4x^<[qi\8"L]a| {N^ya Gs 3L4;7g)n?c&Cy(F *BPx`4I1c[޽$WnK\ !5"RM 4IJƘ|nJQr&b\o+&e4LsS=1(?9x }8 ynlxr" (#Zө>qDue?5CXb-!{Զ^fnVAMN N͐71vdpB9 O{l.( mzPMHu.jDZH gOOXp<eĈȥ}wK@ dr-hfTbe4*X`L{q{ Ә(1EO|ΐAG`5J.KiL&7UȈyϳ:CoVSkwROUd`n^ '.Lidy YH+*O$PY*GVӧG $խ2a5bˈ+ fQ)TJ}}iRV<+Db, qu]]6WV'Q)~S< QS¥{jO)'ԑ~Y,%?+du;0:$:)XaV܎E#vsYgorx鹹s߷fY xM.e&s4Q[Ѧ' z3{HHDV1Kx<,}ީJOGxfkBӏQ5E+M0V5ht:jX]Gtuq/ߒj \ue5գ&rc 6dx47p⼊*l=W_F;YGpp{RՋ!жD!VG JU'zhQUII*aHT,Ɛz AfvŌU(Z/#b1%PfwXnł^f6k,Imn޶_gQdM-2D|P=l%L;I|Q 7 H4AX#FL5_۳ +dVO=C߭^7s%6T݌!Oі\'"o?I<+~'hևv#vC9^)3!{@3?%D G#ƣM!j/kGI3zYvil|$̜-f/5}lvN%A7l9EC>X>\MEX(X&ݤfK5As<,Pr5\Qh+Gqp"-z}{Mp1:d ?#'eMP# }.k<}m"or M.¼*C~s״B3e~!5 lDPHBcI>Wd Zmq%:뺊aH hsӚWyDj{niz stH]i6nҟ 1b%R ։pQz/׻ޤ6B3)W/Ιˎ3̅~&7˄S n2𷩁7L传zI5[F(eZ[WUNo7ؼ;(;12R3VcMcaШ<ۅVkW(FaɽמjXA\Nو}f(Wfn:9Ca_X~5-eӗXjh^ T# ƻ?x$/k&N6XbD]~_dO+hf1ЀޮQJEOPUO me{Ïq`/d^Z*O \jbg.yḆ}_Suab26Be -@YcP.ܸ9%DLKdrR٭YEiA3rG8Cʦq|s泘;*8U7+rņWk(˜R7D(fMJRد֧R;gJ AӮ#5UQpCpֳ"k5vy~a[})msIVl'S<=cɚ sg:;z8`O_;)2D/#%ú8=hp KM֨3n,7x2IVuC=f֙Ld-[q)6^wU)L`ѻEL+PfN Yx ʛN++-1/VZm{-*3!I9._>&)ӷw'K ~͘oХ*Ν,Kth.ǹ3f8&p{e4D៫+9?礫&ƨ1ޕ8>Ap`j:23y~`P*T|j-(-?{BJ;<*FDV6ZlJ"1 !'QkD<4 <: QZ;.9mЯXl![չ Ȣa<#GZS7* wCL@M၂F( u * f=|)bGe؝$g=iW:jcǶJ{ϵ7˹]fxx#>H?åǬ=?}Njۺd:y)[Q & }̨d) pHDf&"QGcONX5٥&Sm%k7hd*+('t_D%,,cKjmt}|h3뻄&z&fzv$j>|&sG0HGoL-#8f8)kdc|AwQk@9vV6ŘGHQ(%B,Qx sG͋ 3dmQ{1vdb%h49 Z>Nh'jpۑC>*c$ս<{ҫ +M-f"'j A)jYvClVfHuKCSBXaIyA鸤;r,puvWs&:J%E9)h=8[ 2Dͩz35z}eM.4IhxJc( @VBniPv|0'uMCDH%DV6-zS {IܯF,NA e T\󛊖{EN-TJNt::-BSf"T=j1_S춋9vHߕ1]=jFѱ@k|}7hݧ%k_ԅ%6Pdr'%,!ݥHL{ʽqt݃*[ j=]820S+%6܊9޷feZ P0x GM^ Moe$@}%i4)Z /)L#>zA^W/PTx* :@4 r^uAH,SչWHb kzD ĪUZܸDv~l®^`c􃦐.ԬUV@R-hsFJc4#U5lb2ny:!Y"z*t@ 6ZM')V u !/:8$%7N"G}Y>t쯆  hu]Qoy1E:NoYVc#mznO3$ )+E3+]qf%( &ەFtva튝SR$p{|:/} }4קttN죀&ˏ/h%!a`^EEpteN$ [/j"RGx \Bb?,}X\}.Q0Oh9ĺ,dmYQcR<(}ҝ5EE]߁y_KS~ܐjn?*enFhSTgU'VFnV˫ }Uy-~9vrݰތ4#5[`D_1cHJcC7q:O5 pgml (*΢l_=E콰)пUkaV' (L҆z/@*~*=cVʊ}Xv[K"LaIfZ$B P8a,԰gCyʿf9Kr Q x σlN" /&gOluf&T"H?9WO{ /bɻI2k}4ggݓxkvpz7Čt3&M4񮛴-ox?>7(1sf /lCoHЏz xJ}a[s{4P!NNVW2ؙxa N?T>hڦQB> 9 Q 4` ?M݂Ũ, !R,$ИLm;.Pyx R^ݖ#%44Q 8'F@tBtHEkchE*]a}ߪSm#Wg 0BdNMh 0WP8_;PVt0Mt@ֽ(jX2=  0eehP5QS뵗"~O S#l$.ͨbHGUX@ 1.W+9' urA x[?c"-~4z+KD=\k7m:b sJ%u 0e$M  <;)G7dV~7:w?zVǑI7$pWɞ>1zSsrgfHњ27:i*[Ǐ݈,oK2{}h.`bt'k]9"z1${8Z:ї M7&=7-yh%U 1EAH6RmFDjkz2z;E8a\(Q~1`|8xtDA0zi&*3 9zl02ljE#>&e ^?trȋaoڴ*~8֙% lar 6 (55}UUڻ)1\>RA04{tM~.VBOU -6cUaչ#0|09IЃ qKa7D4"4Z8 #R6'361Fl@C6[ãdi֋Z[!% e^= Po0զ|1B.-(w[gfM<_+K.dt79ʣ'=_U5{^a6iJOn n/ J+Wic+&K5Iy̲6U=Mi7.4pQ DO.t 0?vd'%YwGc1SkPv#1]:{Mϱ::YκED`ŝ< OF`D C³!#Z,kr.LHTrlp*kEUS5 Ӫ(m q0 ʦE*WB#g xL6cA{UQ]<215c곥@[dy]`gCAߕ/н`=zKc.5&vGjڥ%Jp}gvBۉ*[/} 9T\Gp˕/86 XFˀF8:s]d8* hrDğ]i{"?hjiAй #Q֠%5m>Hd1RH{m6ӷ5*}el&)l;nQ 9R .̫='x7 [m1KEIpBdXD>@8opULOH9$@x^ )$Ѓ fAlǃ9&m*E`NewU壹KN*k|TrDdA?gpm^HLR 0k ?ȴsRdx?&|gBwFASE{ $7\g0?q [D6+._>+|%x,/^RmM],Yblr{j1~)CtUcb 'FoUẉh(+H1^,)F^wyݒZM]4ڲ0!jk5 :ižcX,9{-FP;-ͭOrCn.>b `^%xB %svWZW+4U(2F`'kxb\GjF!jF k\!,QWwC,nW?h#5b䐮)nAګEQ = I%bS=Ō ,큗@ej?N›`,Y_Iz75fJFDuV~e6,>\VFD B A ѹ? \2YB# ˞MAƷ@&ܸ ݃[c>+@c:o4 pвEs\w\=6st,URlF冷D`*:>;GY1asbTPPC%Ρ.Uf.|GHaL]^$@O%x({ov @V|QF ^xsFxNj@]&=@Jn'c QKN뭯*߾ټCc_c uޟ&)m_Jܠ(4Su_yZol׮Ӳ+ׁGeA;%TQ0t-I`ƽ#J"w\И IM]wߦs 'F{5Ft%aF}lvY;>n+M9^&S"8?=?-Jl P՗y6Ϙ$xV5@gּDpVECU`V߷o'6ҍ͔Gv$8Ej C =z"ha;TGU[}Rj!Op:.I8MoGdBU/Y9:&w[7W!|vC%|k05gDN!wR=z&pGFLbws0J̓iGB܅BPwTz:65Ə.O 'Ř^/Mk)Ee$~o/R\-e0{1O9KEu`*j!$e: '/ mCi]EoJOӜEfS Yo/Zd9e?wKe7V`!)jFi!41*c _}n Z{Ll"ruߗpg(\Yr{UF3V."D0-֭ekM h['|t Ph FA|'DG{ޗ+ۺ1,f7Wo]sa,LR=x [sqg? g"rJ]N~Ԗ^쾐t`ɚ uRAa~!}xFp}]pirHN>֣ \aBc^ys :>A=]º̳+Iz,kS+z1'.鰪f K 9S;6P|IF.vxcX.p,[&*RI¶p:i/e3/$Qb"j{v7"ek][OamIkHC߻8H-N/KA-u5U\JD Fbu"khkw}(;5)b{nUbgq4O:Ͽ}sKq\'cCl^0a~Ew Nu+g7@wRk8#U]ٓ}9!=-ϡeD,@k^R||w\g jK [ ky3XTB/n>ɽ]1["& 8MdG8%_CsꦦSMnc4 VsnY w\0ݣ"INCy[!-d4' re-^<| Ʒ׉dgwhNkW( ߹EfѶE4:7Ʃp@Dv|Zg at{U?fM!Q3W7\=- ː܏#.}BTkKے`Hu?"0f&xtEG8Q:!!^pKY"~9! &7W+[D y  ?ÂMp m9 .'@M6'(@xb(OB)GJ7M&1c$MRmٻz(/N|\dE+n៰^&(X ,Do9t;{=#:pY3HmDu5>#<H?j1NAEJlF,vz]E굚3 \Lydfx*A!ϞɤK[d'_K$w;)uMȅ(!_9Y] ^t귿΍T"5̫1g4q4 z!Tړ0}WYеטs֬gszxrIŹ)ixֶ#Cŝ;%ppX%Fb 0-4%mH]kg>*;nyX\ zi|+ R;{u- !j{ W@L`pB؋F?ޅ]Ԗ]s5R/?' ^Zzjʓ ioI),[1O[:0ď|𛇍\|aP?# x~ r%C ] Hg]_]!#)(<Qp䖐5նq䊋͕ЙVA˿"ȶ,VO=,GOhA-f7MZc ٕ*8{YR'rFכ{sqp!d"kT$B9I:Wx"-5`I#|GͫaUb*[|R#Sjݖy[ĴyXOcdi0=w PBC!n1ߡ|,q+}7KQF hi4e.wmAXjsqI%?RԬFw,aik<0Q*t'I2О&h,mgV͌j ׊wFIAh+!a&Sj*Fvg#(ڛB7Q]<PaAh$dp֦u8lˈUr-p܃Η'XD!uSw Og$'F6LQ l{&xqt/F~S@:ɾI ZP㙲/)O7j}8>b"SVO:^9jsbP46F ԰M$b?z`o 9`Kʰul^TٷQ7fg&zoV_w cՅҍ%ԥ8ΡV\ wM1ZO-f8mN$!ils n̙)-@ZwUzyXhu?~RX1 7& -lz;"twr`rU38bDyB&H䝀ͷErs^ZAެ*@] ^q!\? ik7d"wi} *2;́20M%lJK.Z:DS0׸ۯ<WW l_)Q-[0q__]r&Z^tA?׵k{ypãH&F:m㠺YV,e`HBNo侉ºSNDꇡѬ(Dp\7_LLd`EubcIn)@Sc v B`"5\T Z rXY/豂"gԥɖ 5!L|Lxdt(:Kg?[i%C@f@5mqJbeFVEum2Q۠bE]3t%u&nn7|ww=@?=Iw0W S`ݑq8(>4~PK3NsM[,l~$2m,LG}#?bAg BS椯οd*S]6ҡqI1dOz;o6T[`7&ܔ|%H p4)Io $s+'l@IbX:`oSNH=ypFgf9݋3R_Om"XfuK[JԿNJE{}uD]EZ^G1-87=:RӔ $k7:.vj2:kl]՗\h+ &W k&mXBRv&m \[s&){r?qyl4#O>vE뿕ZmΐscW*NC74(n>\YC2ٺ,~)kc%0)cޘ4xdvdUm\,{e*u0 cζ&{B<# ݇ymyʮ#D²ތs"D5 ^u8wmg\b5z_wE}HCl8Mo" 6,ngҜf J;WG:8 6k;P8NY&IRzr]6{ volxgo~ƽySѿ[HI 3|k[^2lI\oᷛ`dRD?5T&cxnpѷe2/q&X|9zoZk*:Q`Hv`_pu{b)[-H WoIY}_p _Ր>+gMPU ? B-7p522(kfІ[ }8M UP#up/yKf*߭ljqS$i hCʳ0Yɤ)$G8Okۖ^H<዇H~ AM52q9lA> $΀ЊC=O2|3eLsLT]Lw=ʊ!SwNeR` /4b`wt#ɥb!xLO~G0 f:;z_ux~4$CVJp3ca9#olUWH.nTz1F&8Ps$ϧBZ F/"#Y꽠e%w FyKXeqm9o0[aqꋨ5.NO1 ك]Q)jM9utr\>}rF jV>-SF/ A>r6%Ü A/a `&p=]Kb aKŮd-~dc1F,K᰾3I$3ی_Y(s̫"rDkJ$[ŗ{L܎jE_ACO&_j앫=D -xn=)ȫCʍnË`|yӐR&*6ڏ;ouA:< f_"X }>$'ZHV54s*EC @h  -j')e2$ǙF"XDL:⭙IK<KvUﳄe\Ht:`~iRPQ38I[`]wI ~tkef6wO9+Z9a~1"MF<^Hr$T=3}6=B/^i[3D(ѽC LR[ί xCQ}=v@]c1ȩ.2~N'<ʆD:@$.)0ٌ K k%T\PWJ؛<Ν6(7~.ueԖ!CASă%`VLK?.,P<.$8QbT~6ak s fp-,a|+cln,Na##j]ܼ 6A}΋A]JJ꾁*"3X:XՔe~U֋L=c&qmzҏ};<$9E Gbc?82 ЋWwֺ iIϝY@Nt+KŒ 5G:|*' Ɋ?VƠ?*Ye >"#xa (x>lFʫ0 J|J8^{YaH8l?\>:Hd'ku$ʸE?aY&ZQ2B7V {?|X˦0ڱHa&JO1\)߀cG4_Ղ~!Jb M}Rd*_#nr*@jބlݖziF.Op =#׾SHPf`}?NoX[&U8lVk\ZNគA4MaFGt\#*yhqPM\ ,މ$ XvW2zӣq`hF6s]74*dkF[m1fbM8%F{g$Bf“#sxRM[˸IN댂֨x9Q#C.qh']Eڮ#M+)u} 9 )|jouR.fX}6b/#vpA-DCǨ,h^5jt*&ߍXel:!sdOhOk~S\1R5 4S' ;F쏳,.We~rtµeOR)}x*Jr_a.X3$-h~ӈ  wr{|(j/q7`NʺՂdis%|Ș+Ⱥe0ecѯVTF<;N֜EcNeթY1;4;o7mwzz¤0\榫lK95Ѯ;` *w|ƨ{DֆK@}j9Th gA4>!ڢ\eơ 6xu/]'b+ڕ,0qGHB8>Ҭk]O8]zyPT/O6ۈi!15j)U8++psXYlҏ/Ҩ1„fZ@31 ן^8cјJ&' (ϫp[ H1)t ^]P%Yon!$M~&`lCMhЌ=STb;:q` p]*-jVG<9z EV)$%na\E$ LG.<]],XS*B<>ht>ݱ`^6F9&B$[+# awE29K=܇ώnτHY{@8t _i0wMZ$o +Dw 2~Lz!Glׂ|#ڂ ^(ҥ&*ۘ$KdڝӛFl3 ." UйQ?3A`UfHD Ε瘆ۻ ELO-ht ʲxWxDp;縗ݍVwJ;!4۫%H|7/eZALCSAW.Ⱦ?,*c"]a%q*P"hPQ#މl\ J;I^-qB۸zڑzے 箊sK3ڎ |R̥ AMh],FuL4%^ŏz3LUX.*Ꜯc8jt⇱T!BZ#h\.(9Q`[+9PkY+VK9ٍsK(PHT̯ l?%#n@.\b~8oʼ`Z =:^|^k-Ƈ݊ek^)e/jcVz!qkP7z`K],_[nwL։H~*A3z 5sӄ#,!ƥ(*m 11;Ym$4g{UjDkq![kWe&F訂--)aɧPhE,6˼&1zm2 qu-V0\%֛Һ K\e ?pYl\ lIK1➕ąh^bג#!2>bۈŵHЋB-whd h5} =Ÿ,S`*˶rQ57+P=PE(~,$J-9OuvsU>l1qVgTx3}YuLszie?о=:}{6xHG󂹠vy+0eJ axxX@*<TAH,"=9$ފO .`rol&3^~-xw5֏- yY>ʨzkk$SjJDƚ<`%<^>8nFW" #vvE9hs+8.+L,D@g~ӾhdkB#1+A}.L`*kRw;T]Pa 79 K[b6 D6xq Rc4I/ NQG˟ҊJm YWCPߵǂK_@ɗ(cz&<;{vE닒 1$FqiFYLÇ)r5pdcuqi֚jjBa9]3kMDCu&0mB%ʃ& 1< +IRsZ4%F3 fH4>tpeΖ1?:=(Rɰל:lSrgQ[$ ǃ83I˚H'ד򠣕rWʝn^]yf q?M$Z,s(n}׭ KN]Rv5ۯ@bmoe^A<)6N&(eKШnQepFdEִ I#yZ^n4 V=UQ /cninBv=qi%:\Z4J gGFsF"9 +(O\,h`x}sw G=m<5$(6lrh8x!2 1tZ YO-VyWi * SgD4/{+^_&O[#U?Rt;,Gu_Ыd;oO8.U ~\8u*xXؓ*I|jHnT @n|㩦O"~\XHNB0>NT`-DMo'WB[UE%W fSUPa.tf^r-_ :zZ"B"[mrV1=L{~dt:kLf(R3E׿ -oNWx-2M|.Ÿ&kwCw.&ڍJ .y@jtFy4LS?R(Q' 6qJk/TEG= MP65BÇr{zPb$g͜gHY2-m??1?f+Ρw9xl[mO`kW:31"# (.fu?vzx7(L*e`E=To܇o?X)$BLHu@ҽd"ء6:~Tĸppac4 ! }`qs|PŒ1sg;| #8d e42-)W}3 %WMB,vYmAW `G=&n2z{;ߗ?8$S;!Ɗ/ٞٚQF6xܚ$L{k}bbYIƺJ+h6УwRn;Q.qdG7Kl`rnE=ЅۀWz{V~E$ZpÞFxV"&m]mDܫl!Ȕth@"rtNîFXz&^| i&%@f-UGk"T W_ާڋ6Ҝ$ckaRwR3kҞrt|ph_q`^])h]`j= l=.4R;%hH"a4-Ẁ @mq F rnIHDac:_*4?jdEHPxl/H4bLQi}utITQV9W{.p8Kuyhx{QF%3Okvp JBSiDÕ(3h–3B(17 vEA_v%!&N+5`TpINPG1F xƹW4\8xI@$44Z!jh,!䍓\$P߻c3 bQDvfC!+~zבstE,/ȋz#Ro_5j*<>GQoFx?zݳx=ңzfptɗ"#x|=l( MnEWyĚ2(/5S[ߟӛw곟޸C&1 {9Q4 $zgxdB^'jtm1#ig,l%ؙxHKGy!_~eאm'5GD5 _R>?@tnH3Y0*[M;2-qk _Yye"fD<W&KRP*=H†{Ӳ3:ܠhF/+8|vU{4~r/Ϳԭ$mrj+ض^) *?7EI GkK4\[څ1A昛gc+xf#vnkYE*;V/h6ц;ZkH;qpqztѬaV ILZO P]#1͡E,H+Ǖ1n!߃? SXb^ t+"r܉)|ݬzCogsˊCP9煪Q \m5q."ye5|RP>wp܌kؕ] Vbhȁp0!-8Lc#~e!qxs: M>NeZjwPHfqÁO'P7H_cQ jQ6Fr쒖󡿧/6pxPCsq/M.XuQAu]n"vubp+g8d/v&H3 .7$ҥ t5FO^p('̈́|]Iie=f7([ނP A}?B֍3_AnE~etM`<,rhKL|Y}=걍ZޮdiTͲ?V;q!Lq?9Y?XAo.?ڒ99Tͧww/ Tw"PŖsIv2 JO&AY텅 $Ipw?ԯ- #^iKBR|b;ͲG#Fp;YQ1z=I:̸x\{^tQ@9^ >5T9#)a*ͳd [Ș S#nv8&ͻQW,DR@4k fG[f%$Z/t|̢]ߒ14powجܥ#Ym81/6g)#2&`TPJ+3y [sILԭ0Nr.S::'l( njeYsF8aCC̕HM^ LÑ}h;9mY)*)2$F¿zFo^D A1Ѷ^e6ÑxKY?b6,Xs7@mttRNbߢJsƗr+diw;S V?!"qvYܷx O^(W,^7 JD {~DPM]N>THYo[(B0WUtRPŮ#5m7p|SqC r";'5i2YwV 3V0CՈZD;#X5?p+/<9DMѴ o12͊@`+;9S7ʓQ<,bFyEݾ>*S&WL z9cf!E[R2y0j"PEaf_JA?br͢agaJ#r`Webv3OW XHe?ļ뷉"mrizH7r9Ea 5,zZZ4qn B/=r#K^uw;<9yUySk5] OoxnFoctu$u_pGKEz ٭kѩ,8糹${X. %F~t B:hk tэY,důN-;os&ofy&z͑ԨDEw( Uki"F 8p$mI9uZț>HL- $&0^O觽!=iQZt)8!9qNr#ˡQwQ9 `  $&p-m ~`.H6T>MO-j2uGe Fˬ`1IYy݁s,]E`=]M CȵUĆN=U~5,RŴw?Bp,]u꽽lC~H%=\ P#dRYѱ0;#z {Ol[ 9/DRGB 0?22%7v7\[/xOA!ƭwԌWX"r?wP&N %'W4CQI$c͍~WSXy{1 m^< a[WDkʼn/! 6׭7 _o>]E7Bq۷}P9 Y/ A4*/A y9^Cn#i4 p-%"jY^9)_k3Ѓsin-vj+(B; RR.<Oo;҅q7uITw*lˉq'e܆#BAwbTOfs,QQW%y M,N)uz?M޶ (U.b@3Z=M"uZ =>c|+~)"? R{xX$rKt"."^YT_! ,UQ7&M4:#́s(c9i=/r1.W˘+.㡁D"w,Q+hyA4];$X,nӵM%MR.IRPAcv ,xM? @zy[%NأBIrSو͖OaIkQ{Dxbuf)Wa9|`k)? gDi/ݣQńFY_,G=eN$2=a K{9 kѺ?ȅ߽-<SDqd !-E^/+M]sRaEIЯ!𭆸\_Q]o[:(| /骣4adr1`%ؐuhVǚh߮ZKH䛗H6!_5G:Ѕh5=檭]I.@|=C 9,Th_𥣍0;ÔcoRbӥ dY꼖WČ [a4mF2 yW\ ih!Dͩ1 T}[7!nVɘD=h?$`rsu|!#x38}R#2\TdN~$dX ̠2LY!Ĩ*Z'zx 4޼ʿL5!Cu-Of x{!u:ƑMg !bSpWg|lm빭Åw}pEv3:oQN]fu&<Hzhipc%R$\q$R8m]^B\&NhGt֓݌ oRU 6W_RJ'o{p!YuI;c- G8`7֧HjZ68Lɺ>D;!^3˪?|ttz&Nx]j6iPF,ԦGSdFO4#=TũFEF XM:[u=ȹ۩o|F9".ePﴜ춗j\J &"!Ԭsyz(2c8[LK#zРf0N97kg*+y(J<%e:uRL܎YoT̺jVjG8Nqvݏƭ){F#qHf!TG3, 6L+]9-FYAtu)֐I&pf =T$~)u= fLaKpPwQQ!|K}ҥPsyc QqmqoӠءT'NwIe2bdSe hAcl;]hrtZ"m&F1XPYct n!B,h׷$֏5(͙J _+#C'F sH.\cL^!f!Ж8Zk+Kqj[42ZSI8mqv-tHʢN"3Q1zG.'oNɶ r7R3_u&-!G `9BYcaxmlzs/%#ݓrH}E&[gv8l6-$1:]t*ĚrH eE6^q0'u. n5iD.e]=EGyN9Zm3 iϹ\W;~qR n6wI2>H e0ՀqO):s/x5;^`s0 =&y*xK5.3PcziV9v-l'^AjVQfT(1Uyt0j@6$anB .BR!Ke?i{5Y ھ֝sn$ꊽBHyi950JRT;/H<,n $h,fJ/] m Lb%`k E\Mo>굿)d}G5 XYLXm4,jyecmVUh-]FC׻f{oF- ٤FAPS:PNa,\jirzYaG\v3Âp|" \;wC8oE`>LҠ$I8}Ow{?\r 뺉"Ok OAqMLܖ$x\(_PW*+p͐1B!Li 4Xt˟.a,<~#KuKk bNHuU@A;SsȕH6VbW+fHEcx${{ 4{g0c{PC4[f#F7#pD|Jˆ Ŀ6tݹڱhY# A-V]{༈PCf[ӓEW[:XŪE[M]FlKlO]%Ua ]=Rn%;^57yTTHrI\rЭ VYS [GwN|T bﴉ>a-r.`Cpb:4 B|_QS`Ns_;XܻMVBJ6oQءɤ*G1u2/ᯖrlr73+YO(50U-6'ۀ3knT„ ߂&,C[OMmF]@jWzDHE!RHO,Aʝ&taRSh(bbHluyB`AG- Xf1أqR<l8ez-Sm[cca2d>h8Ԩ"q@T=JDou 5rDMNXlPBPa0I s_ g6S2d91:bc>0d"#(u"̢L⮦wetUn&Lf|âiY n{c%ذ2Ќ|TgwS%ry< W$?\z |oEmnoRGL_BڲO, G6c>2 24dh r1-5!B`[x>UyIj `z{&S/#o;HI5COd̺֔[e:7"lKKd[ @c[5{Qr ՐN6UG Q 3+oC. mlj qgyz5bx-?EGTH5)᷷u"w(q?,oAZ&W7?gjԇ78, ʔ"ABh``*_+6ԼjDXIllSv8lx|ksY0FXS9.~tFT3 [-Cs}@\d0vdX ou!g3 T6B]24gzTUd@#yG1(P#z[3dH,/ ) $ 3BlVڦ hA%>(h2I(cqSh_'9NèDKFU6rr7c.v $ lحuzT1/FD7z*toƿ-rnrEHdʨ6::+ C\;)kt? VS\'I 7dML3H=1>K` .aYg8%2 iK*P~5!G?(М<5x\yϜX>KTRLg!T59ƣG±g-*}<|*ۆ|7ʵVxϣ N ^ mf/̆ {<ɯSXN˺Mܿo\sٜ-<'ɂ28<4[Pʶ o1E6+ |_zX#[mG5@Lge%If-< YjR;$PH1Zў6FJؽ`C1@Y?c(/JK@iW,ˊ!GmqZ/b>ɬSK9Cuo5!g E­/ϰ7ݓN,n;Z) ESLu<]Cenl^k}clP wD)A+F Z'wAV I:aW<@,ڄd~)2kR~ @\_؀2LnE'1]Y6ȇ%*t׎o珳jQ |;cPg;y i5X(LPcDŽJ[?ӦB.\vQ1Q¶њ#nxj4t`q#J=˙ S?99$sPءPeʇ/H1vH9ЛET&S3}@>@Cݭ{uv|[5 : IBD-Ø<.YA%[DT$9ʬsw&e_gvKdE8l3ȑ$e5H5\yf]pX2E׌Aٻ@53׉E5Hi²2ӳ X azw~`>qg)z ቹkvoFO\B>`ROծ$ːޖM-s.s#/w<z*c J5>ڦd &"ܒHJWt7(nvJ CL?Ba>Y CG^b#4b\HeW~DbD0..׵nFv1Pt`P'&CKJ&c'>;V0l.=]2m/Ͻu~㥝aż*P y>g+*5 '2ȕ3:?Kj 3+ *DF6;9a>z?f&בSQ$ 7|Lfiz )Bzۧq:|WEyhK䁐'|%5/:ŷ3)`,?UG%t&۟_Kͻ3Ht,<H}1_-`c͗H'库We)S빯q~WlL{hnų VoW{F6dV) vz5"PD daNq_ uQ#. h̐RդϽ1 BxR$Gmw};<0?mC9v[ H}9c Y=Z}9MJtxGV̸Dns*`I$a4/?40p0څCeЉy0(kKiwSt[d4%kѺk:1#VfMxB\DCrD"3"݀-{on8eC}^Œ*fht̖K1%z+a` 3qU4YWM9SBa'YH~{9\o[7MA.XHvyDK+KT嵎3aETugDH!R?6KӜ2;}? 'EԾvNMRfaMyuf5e6׸'o6Ih,к7ǙK2?ɒZj\|t:c#}5b0K 'rcuz 47fZC`=`gx߿k-WSG!I#_ oZl3^~An _TK[FIZeZV{=rLЩMHԊ]G+y~d7ώIϬ;@ӥ2-롊uN;#@LhWG% ގ8XlgYwZt vqF.De^ j?ˤApOP=sF\yfLy3V{G|Ġkeµþ'Bhv^؂斘] /[ AaƺS0NP'oB/:D3W)lLYtOKWue񎹶/j6.#@,ICmv GKxg_ʮiTEA&oH5D^nrʎaa!8 p;Rгc)`xs5γu-7ۑU8{>}2v&R~ Q?=^hDRAޒS=lULb0r-GFY;in}9v8nɚA`"-ȿ/BcmtF!A`A{ iåt |Ii 5|e qp8#y]&ך]""[0yfxf;;9805h$˱\jGe5|\WgG]yaŒYʫB2>HD6N0 M|G6ڭ7?ƊaKe9G)%2}Uc} ;cRX Zy ϰh)FѨiA>|'xqߗj 5\\i@vIu|@!Él 8.=S t $q5Z1؃w,M#nQw.jv|{_!m Ú7ik]Kd0 8xJ\aLپ(z}].<" s q? eB5a4ڮ)8UgtJΐNKz`߫x.v71 JN.^91dM"ciú6q[:аh}g3xP##e4,ٙU,:/H!g`,%zd]WXwcȄ(٤U jrq@z F(`WvS3zp'G [E6 i"7 ~ercgPŢp7χTKk'Gsv$Ek Mr }q[+Zc-X"CmwWBANf&xh8/|.DH:,bď78"Z0Cjæ@~t m]J[8r߈'tV,%BnӍ,wfz)*D\cv$M6o26bA ug~SkO_f5:]Ɛ&a="Vi`b$c / Uʯ`iHI7UZJ]h!>c2yt,". wmH#'G:i?h)*DQ>$=**ɴ=H/%/, _H~T,h EH-z9]8$|?v`;6eiyOf (c@o3G_̆J0ⶊ9S mnm4!~U ɇmkLv94pW6ł~dJ6)eIV(j^@a+pTvNJ) Y.?(81Nl(7=HRr38yӅ+[0_Mѣ4ujIOk-I!/G{w 1 ]NzZrf,R%oJ4aՀ>r!ak6Ӝx5ne|! 1J+hkϯ䇐ζ_ tRy5үǮ6A-`g-QI [Ǣi]`SKTE IXv>J֖/q(v;nE] B'T0g73.0>\kYjdCWTy+-su '( v0O7pHorRa.wAANYEwmU@%N lЧaD{ĸؼ MA)y` hyd=oRSxr! Di_T Ӻ;ܺg^ w Yˠa- Սm4| ˙F;H7|jRȞLȯ JrM"/Cnvr  !ZIhӷP*Y3gR[/[u(Su`ԿVJbOyPVsp1-[fiSZx W Y+Od9&Nh}~̰֡*'CKׯٓ^c퉽U@JqFo7vbU4*q{NǤ[cY렾qm"EXw>3]OVgldHj kNNhumʲV'?F!Z4V=/6 7T9fǺ+/݌nGWF))яi6, g{6$.M/MFj) L =b_Ǧ}B%qb!͠0F !$Z $t.0_`QtII4׵ `ʸ#: T4l3SկMZIx_1o\&!9P;Nnl&{|v 32r024W $Qr "؛aP\j8r_=":y_ʍ~Ǟ%\JéM}*}s_oYPƕRabFeZ+ != [T׭\ɎoML^# ء暁qZk͵v?ڄ ^f+35rd)0ҒOw !C}//k)*F-l?03ʑEi\ h=z{:p&V\qҺ!}FrSXe*!uD&'p8 wխ5'w\He1N^(G=8T-ɛj1&LSa Ln1ktҴx̜^kJ2K0{uOF*]͊v R>(ws,zfM~%ΘuJFQC;JdT<;^ٸ 7E=Ivc ˗mWo'ٻV\ 1@WUe $cE e~&cxwzvH5.bYG9׻ˠY/@ٯ)~m4dxH=K-QXrK-q{(IyWGinXi?&a*u<۶#rmheעT́FiydkyzK'X:ϔ*-=p^l.Lvdřp#wjcj_hrpSYx~2_رUY;Q: =DJ9U lV7d}#2EE|,:*{MRFsqtPWM߳HuykS?yʼs_ߊc4Ӷ:1_iǯ4δ^)mwWVb@P -DMAm`|aF<%k) pnOJ$̗.I0$"NSZdbOX ށBJNy~ a"8i-N/ծ+v]]FA-$UJEzd+-&ń/1рa h!VO~}YB ^xuB)iCx[{z!]54SQUǦ2#VXDPeidS[a] qu B",ZaU ,femoaiB"K?;QWUyHת;bKuC_+ %J4挬')v\r 7n&`!_WV 0SYO|ד]aL}lń;Lŷс;|@^+vHF7*b]`^7N6L.F4AXf'IwC c¼Υ"G[6V>L e:玔Z!iM <֠+mחЏ3Ja"boT-5z!qY2cibDh^OitYxɦ&J1C8?u9CPv'3_W݌fj=HzgG0y2Km(r1SHaN=IyհndvzCl=K[?ik 9D|r)C39a7~{ĂZ#; ӵcK 8:2GOY&5.YoZ鶄|\FѭK" 8C[REu(b53e/M+T* gY%%ج#z椔}LSH V]DlBkFo;H$Y .0p#&+ n'a+v5-9wzIv[Wf@n?Gpji%Sc=K2aUW̉2^Y,R$R]N}%fOO'.1 Wb- ,geeL2Tc]f#"ЦVFYQ=_ 'HӲrJP*n!854Q01[H6P_YE|8)Yhڿ@ښI{_NnT3el{DbXZ"6^ftJb. •VNgklR#41> C%`TNSQRRnlٙ_ ^Mb`eٽ*7ԶHC^,"x j$\{1O0-6kNЩ(K`ˠPцXwq+MIq9$V%&Gjcg5yZz8SJ;e-8ŊaDhbMLm8ލ[~n#"56zHi$Ĥ*,oh"|Oh:lĠ;LzgIZqŧr)a;j@d3mzQ,  r]Qgɚ[bZX]^!:' o&LW7 "9O! <5s> s>TqDCL݃: UE|dH Θ UdK(Oƒ5m] @=sυ AoT D\q(6Io*CK.Y ޛ8e3xIq$-[vj_ oYZxpiv_r܎G9836@b;/3<[i$χq;ЊKMjt\|:CLYم+,tm3%/P$~UpTx lpMҵvY~koڠFk}{~S|͵{*vbJi=$4aU6h5䚚>z#XTe/Z@aDhR?liM M9Wlsm=n޿4YsrA; uR=FqlH\ uHUhbq!zM%:G 3KD8tlh-2E)1iUֵS r'0NjQ%"!{AwayȿC8wz1F_h\DNj;%6R[\{T@KCN!X;^B~sJubWT)(V+ {s7;kvDx\Vܸ6;$f:kLy yL$ !|GPT8ۚ%|{XfT//9?7 lJ> `GUF 6 ʊa \5x i ^/!|\.zCAԼ;F%!u [ciN:F-8GqEpĂwe<~3&o`Uz &$+*F/, *\peZ?1#ɡTq) 4qc7}emj~U;o ^_G2 PRTd?y!pRM> \~eۜ_m+>TMmc_e#68D%=d[,UBtE`%)H0 @wdz kB{Ndt4şfј`Qp@]Ȋ}cJ)a>MhXc85~R+'&a1RMShC-ɭAA."c_Wh^2}os`E -lUǘD=mxSuZ{ 8@jenxQgἑrűH޻ {\]z,.#^PsKO[Ns8@@W,0F)5Gt88fK'*UW9ǎ8:= Iq[;U t=E=:{tYL0>m7˷!ڀW}^ $"IZütM|՗ S2<7NP~|LlS҇>P FY'JsϨe=5 ʹ!o}zHllvsRcj; 4̣$֌)Dx骵͡ܮ FIBU֜) -v>꯯W gU_6ӨAi}kRēCBkfJ{#&~䛧& NP9;:K(tMVZ ܟn-v /CG@{˻`VVNA*=@gAȏi#/:O݋}ߕRf.c.Aq 9s 8nR:̻Qf6hlI5x7aM16U旚Uo"R|["5 Ds[b w!L$>[ LRF^ f<=ew ݣK2IgZSi"IP>shB׊ػ,1pLazZ8hR##u4{@2XU._AlgO쥘>?U5&"ѻ,kg<CɅPfWҏfmv_x,}=rck+d*uShw>ҍ#LoJs0)5,F"zvl? !j^#5qbx7:"_u+YZX\)s,!B?Ɋ[)=-.M{iꂢhL͋[=A(25!6 _PYc|O`:Fn8ʰV;*|! s.=67Jp`w}D]6l\)WA딂uz'".NcVNH">~c|3\wH\ᇲ }TY|c!d ~,0. %1?C;\tNKg1p~}]׍RjݗS]*J"ر98s=>+S)U!(uDp0Ql6C_ҐV(J'?`˭~ =iZԛ49mX>zc⬺ 5$v=_L~^^n=( ZK s8{u~9)ݥDžȭq;gu2 c 8+^:ٛu2s]+̵ѷ!%1l )@\ƔIFMEc<6+ ,NbϹ5ϣ2)om(h*I} {j *(R)r^+ u}:lL"򧦬1ι¸(lfȗ7#L4vM+br66;['u6EğfTdVc@ر U=ڐe!ii\N_(3>*[D#KCJ/xia#Kχ7'ADx?IďxXPSC17EF%^CV_ qx,_SzZG?#J.:{w~^vikuu3hژ1%Xj ka,5Ii"qOPDF!JmLms}+ˆ?;0\$ޫ &_km'w(AGo=Ӓ EbӼp`Ѹ9EqK8bF@PnB:4} hEL*ͧ['cs-<|>;qabNݰְR2L}b9A5.@˛6^̉`Jz`ǀccnٵ$\23aI#5)?xOuRu8SԄ^(@1JȻɾMd>䒔(4'HzINsޮCK ~P<ٶQ Yvɟ:/d'~Eθ}wv轜-3kb ?3ٯl\&d3fa{wӪ-UHa7r&,DvaUc='?rY:z6gJ*k~Ipw7%0q?= Z+JHz`60etUslq-o|8/9?2ݠ}{Z,mŏ*#Hsxy2)Z$MAcs,دS|APw/J })p'R~|1KtS(y\y T. m=4% rNᬂ5, 9g -H)6hI"30ᢔ4-5%3}~DͲPKyrYN+U~'p]ayi+=}>dCZ]s WU ['EL{-22}K*&kP˼0Ն9ʟ᷄t{2*ZRF_+` p F`iV1'{Xe_;W.=̑j*a b' z(jY"4<$y{9R6,XUR*Kz£˔eX'|+u[>je`͠ )S4.ҐߎwM+!FޗÂ*x6{q2~EԐ&Ip|2)z]# 6R-?RĆ;ӼrbQ;y:،_H<O'L 7U~d4|Qn4]9𿛄'հ +ݫPrPTe%.3 FWO`.v\j05o0`r&ܣ~ua"<8==u>Oڞ `o6٩?H曌w _PhҋMy_Ԗ kwdP?PpBF$lkw~^j 9һZ|܏]7Q.6: NGѝX)SԈ}f6ax(@FTPHGjؙ= FwδQz0~*rwkZ̷֮>Tеd}<JAnt {ݫ:iiX+Oۻ2_pl2_L ڔ1Qe:"djSËC&|Olj<9)C(^1ȗ=`cȐinޅ-ѿfkC zvJO^}';8=^{i bp}ՀP"PVe_ U2 nhZ΂ejU46Ôwgh{s וf(ic@MjEǡuBSәkxU/w: 0vGAKUQ#6)e'?NE9lsT魶T= { / 4vbZ8n{;C;0jK z9J|P!qli+/LI.0O#h\…+2" !7eV''Ĥo-L oJ}s}^5K뱶PV|~JMjM@`qEtنn_XY"|)$g9d}Ңk]==&'ZnpS7V7;xjkx@1 f޳hAs4|aU`tgՠi4N=gEce"OYm~FQ|U4fmǫL,uҧjLj?]xk;zS@ѿmT ?ȖdjF'+\Ar%d ؘ{Fqxj-3Է|\1?+*X=1i?!T: f#=%@BaY>9EXS,h߀ߪ?h}8xԛI׎'ygva/u0(kP:s -+mկ3i:K欯b1lf<{sk\4…l:ܥō,@L>aHql>1" ]B\^*kM W3v2l-\ e?'ߺb?+'YG+'c?<coL[{VDJ6ñ-y(ȒIN tGo+]s,?h#h>anOme(rkSjjcU7jIGֳ{ED}Vxp3٭ˬob*00&I'jf)L쬚G.04WrE("O"k -fHB" AkPp-kT1=6xwY3 zv:1\k~8fC>WaU UW+^C/g NYŅd'/M ]}0*׊?X pyTWfT9.<'@P#zE+pRfk:2C$3I% 0$tNQƇ ؕ&8q\E&g\֨C.7xV}ieg(mNRihS%VcdjE{Vh8~OU$"Yfd6}O ^ Q VΓL߉:^sK5WntCK 5xzWF?C"fZI?ع `avΚ}}v/@&eݍ+iQ ҩl`u{НEޠ2&U ǰ@G4abnk3Gf67?gqENC\'Hة,)RNNm|-9JC>%eͥ}`mHӅb\zUa.W죍RuBН ;@**FڏW -^,_#J=]kSNy&M~CG}- MXum)7#^`S1;6i`!pPA래v1}3ݯjwG>6=-1# _ކj<0F7=MO7^kǤ2+$=>\%H\l\40`!~rȱEZ,TP)ou$#> W@z†⿱@Gt6fY# q¢n9' p<ǧZ`8ٰdۙU&Dt'u}k'==`eɇ2[Z)!m#w,L|܈U|VZ0g,L'd8OGGiP;;g ;dΌwNF-6s8_tQhh{̺!("Z74P-A>3HlQʀ8x?e)lTwpx汣ߕMq X/wcH[i?᨝ ˤm[3 j̬# }P^BM뇊fD$#y, TB)gq>L0n<4F32x5>ULcz8s+B~wdl4u:EzaRv˲h;o@1CD1~}j`b{ʢ,Q Z`XX#=D& .Fee%,@ )R M1//+ڱ.Ύ=͟v@p5XWPb0H)yoۭ\:f[mG== *S"m1)IeXHWR!)S}]7iUY0]xSB3S 9 c;Ʀ(3HUܒ3=nX<5[_M!:vbzJt10uefryw<2[⽗fFLJ[zr6@64+UC~햌'}7(|D|HjVwuNlj ?8I9^D\.riB& 5̦֡q {GS~$&pK+4Ӏ+y 0.eB0y& ύ(Ɏ4a!`Т};cf9F8xBC 'H۱^{gM'U"BX=@d&B' +v_om6{|TYFNT$un3WDHeS#CqusM~JrQ=ae&]).m󉣧'T(tX>~8EdjEcor _&=}bw͌xj^+=U"b)o\!SQ;Y OT0;'vKY߮xщ0B. b${H~}~5Ej0PQjWݹ9.#jrhl ;.e̹` k ̌8#2[uc J!k5jVPDgdf\GϠ՜@:\A(nV Z~)XqNJu~FSڙh7J<.s`u.PeD}5-M(w/9V:Ժ濜4Hd>S@O@Uyd 1H‘xSفWN9ICV.æ? *D3^/*cj <1 6(73bum8D>+VVN@mÏãfoKuƶFA @ig0_|#lO퉓" YV9.U{HV*٪ys8lrGdz4A U\9lT)eUN:o3DIi_|H+5XQE[YU@?n5AA=7v1$ʀ'.έ\5=D^m:a^ ԀVI[_DIҜ=H}@iu`̴W"Ʈ뚱h!v3j>!)*65(ҳeamp0GEpi 7D(pk{w9n&JV0ߓ3jLXц)ɆCKq \게xHz8f1![0VeVJ6򓨕B%1-w!Թw̴bO]B+'w%cL".lvg*90eVL^a{A,E`Q[tr$fgXVT!iv+穪 Ͳq8l=pz$DMӶ4ڶ'0m-ۀop(.k %0zyg sVŵ ڬ5q6UaaYXϳ`NXgH .D-zӧh.*̌܆^H9:3oKm&7V=s$ 5Mujq%,o;HnpTG rY(!CTIm HX!Z{+90W!c%t DgW1o6H'T & B^IR=Ǭb;^|>jKN 8 1mh5%RHդlГZ9,PuH575!Y00ءF'cU/*K?h!)-n6pFzP1{ay4q/Ie˺E ]F"B+Le-9~im>cu nH1TK*x>VP:'_ܷg͝fYjjLl甪v$ßm\ ,q"$,d ATw&D 󍾥>5 @!Z0-ɫJHμ1aqw0bb^5/4Б [M%2/#)A$nmThe G}x<z7y).!ӑca/G窑.1-[EBQ{y@)B Բ*.QN\Zf51?$P3V^*b]|'ҵ҂bmW`$k,ᄔ BEj#  Ԉ9r<"L`_c|Axb תkxNiHiB~aX}xVc&wK 3wj1?U<'B*<YDs!Cꖰgpx8r^ˋ䫺)j[3ɱ*%=C0ߧ)m>VʇI@wfH y9Xz1\PګIwm'i wmdPJS*I|zT`+3̞D ۙps 4TQH#[ #4Sw R/o =̅`Rux8؍Vl'zd?!+5rpk4ƶF@q+!j҉q-EQ9P!aB\o.I@GM@#7ג[ûl48_'.Z:ԆLB?ұ"wOM(m =WTtK&M~QADT~h"xW0\@P\t#!XHU]L Wi+AD g hquhq0giͷW]Xq_N7ZV6evh0; $#fe[lNAۦ>Y*2@^-հ ipݎ__~ĬXL yf[Ix/zs%hxe].+\ddBXꬼ{2X 5i>y&قT_Tna{^ \9J\/^.I~&39 I-m,FDɨ;i= ХK@2 4qF@oOM {KRҼT|}%E;BSJh}$;ܩWd~/6E~ 7ϟͪqO:+T{ gI\媥"2%^>Rg (& %~nt _Cb]\_tPGî &F5`uJ@HkddcgWvm՚HBRMSeM&XYr =_`Ұ%YrpM'3&ǀck%t 3՟[019z(o#mE1'&vO _HGi*+'^K|ۖfql,i.F+tnI"bN{̕r)џ6#}#xqWDqH}@]I8열uF$4J:ez-A&G|ib|یLf6ѓ7O+ 0V!}mk`jLDۼ9cM nN?@) @*]gn`96QԌJ V`@E&=?RV!7SJ O+n(%%yF~A4} 򈗻>Ni $7_Bvn{왉"#jdn]\;=ÑAɩ$ݹ=0* u6'Y0ۏ5!A:(`D$3Os+ W B] 5#{ D~(#t@pFUdĂϒ-!ٶX;>.V Pj'S+йx]EтLVC\U,N݂jjԝM Ʌ+1 YS*Y M9&M6O}8-ڮ̴,R}~5h͔nqZR݋z~˰$Oෳ/ϺmEc/8zvT4}ɦ>;!4ϟ5/nq9F.X l@m)k<5q# RNM,r34U 6Ohznܿ\N=E/y뉋d[ CE#ay?lHW@ˍ|(d}-`6U| dS1eϡ?FDt8v/,6_w!r@jK>z-e htQ_uv(ׄ/ `3.i?imiMm?=z=#hV]&%/~*gv[ 2da"[ySƗZ7߳ Me "nS Rs}ҀS?*O"!B-0>MtSc-9h`O~x9[}4W@ U6a܉G5ÛD^ ${nzv2vmOc J ŘA?&vIҙJ3p=Cij,v.T ?4Ռzr tbq:DCR:|T"p` n )M 4IB6wuץB- cTm"G q<")+r#'fGEO&S60KgҘyx WLxwp.Wb.XQ J->1_ +4fplX҃?BQw P[k`,9TxD;7'o#;s+~tb/U#1yam.WAEoC̔pt+&|up7BVYuD?;z)A1le]PmZ:qm439pڳ+ka ; (#N9Y %tš:ןz~Xu$h~laf&uoZ쳈1VasS⍎rbJF*WPӡnfL t9ظݢ^u7 ozU3 l{vs v6E=i7Vz]2|FETD޶~:r)7EH'0->ߥwwOfy>d/ܔkqўϊ.$yT"T7. B2ګ!*殗 :͠mqhVWSD.ST%^/wU+:Svq#330kƧ!=d\3Izp1JD,#%qQ|Bp@a<;݂CÅ Yge}Fl'f bhd}b.wg>& 6*d}amņdsUFe86c}c $Zp/S|3<͒Gfm+q*~bv^mRRgD?5fγJ=GLS%~`mݣ qi$:'>KV&@]ANO nM}ޚ1:xh~+DR˷#h>ZeuϪܮlw]>b.9G9uSW0 t/"Rї[:k,!GqCZt}$Oߤ;5}`a;>ڂ J[i|7;²89#y"@L{V W )JPd\m_.Dpu$`Vh#XCCR'J+_EKF&dw_ͷŦM* su=+b-Rt wcqbSNDka!HNP&-жkeEos;//:<  F ہt=} BSټϙx/,6dSoޙ$mQ? .w UK"KQ?Jq ZO*"S0zmt͘a_䋳_}]2rW{@70lTǛzsP6ROSI:H[4 1=Vğfad] f,%Ϣ#0(T1/[\_7Cp/+suPhYmj.z`ڷC_sO1{>݃0Fso_"2fx{7KeB7\A^ʇ>2iF͚eA#`+V0}S.3j9H] u-`qxȅ!n{\_,,IҌ_D3wCC'4D"#Kcwy+[ÁY'C87Bէtײczt s^?| @wH =Y[~{lEJ6,SPmxx(y²QUq^ö\(8\RS[)}oolil g|E6_=3D=P7n|!@cpb5TGڳȵGAy:ZPz>q~}Mf@&dBUIź r`ޮ{L%`ͦzA4h{Y|T*\Z9N .0tJz@e`3‹0 3uo .)~%2m+1n m[@- )o /P?='xSX aH0t'5~ M-[ skLgĂEð5WVBJ7xGැ77UTGܰ@6 UsCṞN/awx McXG1ΘAI4I3Ƽppc]g[_wowpQN&a (o&'euW](zkZE$[[11hLzTMY. Gx :-ZcY#5(3u>ah umŞșE ~ZNh\~'#x^;YG,l\g2.Ώ/y qKbNM{`gXx֜m%C{/Q"8؈rn!JtYЬc׬ONA'"t퟊e]1DPZ}x+P~)-e֬90V3 4SVFT4˴c+уlԡ޹2׋,]#/1_^8!Zt'@amDތd5rՃ] !--uo>&ں3"֧@ LAC须W)~#6"3˱y*K,]xUI !jŒ UBeqM*V'EN6Υ|MNa/{b ˯Mשi W@dv6R1-Zm{XIXb*_TA} b Vk3)Ō"--;HYUG:՝{x-jNlUqi6~ y`NtB)Y6{h7+v藧D& y.fBqPP*+>0=Bx7xWj >frl( E C^k-1PM|Kht;Q+PBhY|/M;.kci C1'3Ob'T]&ށuԢ% D]kԟU:T'q`j+v{H~f/tie7QSRk2@J-PS?uXP|Fh=qF^Β="יbmϰa11pq;0(9 x&%Ѹk| *Lȓh$;Lf"(L%1لXQ8 k! |$@'tGl a*ѽ6q:E 5ͥZpX82!#b=t)2fGNmƱγ'N꧋xvv]2?ÊBMܦ9^syux.G{'k0,UaIm@ނWyyG[fI&V En ZD>xn n ?S- 2 6?%@Ԣz \<]`ns- 3m[pP9 gYH7g2{KXSs[b >]&m'yy߭5J1G#Rw~:HLQElfIEZ*Ojf(3 g ϯ\es [%:nB 4@xH9 biO DT A.ntmhF2[XiMqHlNAvaBS^Z`'U\;z]j ͦ z0^TmU>ON礼qNn[ J.:ϡb(]fmv-k.QY]>]Mlyоٙ H7?Zp-v}.!bM>\.Ө |=K.E2zBJH&|, ґo-]'KxνB).OOkD`~ G9ÍgPf4>G# {^eC, S?3P%Z } )rZ)THF#jxms-; ۴%~DCA6Yݫ`txe# 8 ўlla:9}n6S5اm@@ҽ8PC&4W"H ??dO2 &Y?Ǝmf$>a{$w $8`ܹ딕(o&sOd_Oگ6;p?g!LyvX*E=03*HDH)ΐ!!xߢ"J`rV67.eA[o0cP"S4%Tti*R#zJ.(8{ QJ]JRjm\[EVF~ȹg!Ѱ`2_i"](ܑ7%y˲;pGD_;zfO"@TGD+Y"$:΁#W6٥LmGcXI ,`u&v񦨦ЇW7r{`g49/m8b&z&(Sԓl% j4lOY ex}УieoTh7MⴲǙ4`kwm_ŷAIZkg#5prN / \t͹ `IB ܻG aLڿtLi!$hcpRyAh-w[HPvkrRQbP$ΈK4zɢ[Jz͂mh߸=hv 7/ 濋NojDGˡfu2ogc-#Bzf7eoOrt*Fw7ؾZXyvr&TwNTm3R-aFWd-h3d0Tm^%jkkoynrY 3fCǴFv6i[GEt~q1)"dc%thz s=۲qŜ8 $sgVEY.fun=|mMi@p~ åR틃S.y"꯫5zۥs9ƿЌ{m#1)8]y_v =5˚F6>a!F7 3ot{(` 2 v C#AGYHy9PfMq9mݠ. MXO^>$(;B;&gW%kb=wE6PT ¶FE~ JG殹E5} !7gŃ4"Y`pfZe|hVj5JTvpLPI%x+gXԻ8F8*96ױhb۵ut#`|L21+,IQX1:q#~ cj0Dz#Ac*c>u}FƬz~to!P :d^&c <#S!ZtXgAC U[6Sg×kzyVmg<|VHcbS4=x`lJHr.QBS0`ojBD4 vO IrN&0̋l&]lzn1V҇Ш&ve;J5*FHȅ*q! %\$.)pTR jW#h}ހo%${_®]3ZYH3NMad׃ŒtKt`)%4:#VR{(}_hWG&w]#/]AFSvUI!G`MgNpqLv[ѐS]  b2i\bGeQ q_$(pbP !.qǚd_B`E'T:@$#"Q[Dpxon]YLB2ܯܦ\>K9Qjbg!QxKfV`.z(2KtCp)?G2\JzЙ2Z9$4l8nDjhJeFLYJk.PО(SRRV7b=XnMTP//}z0cp0[y'B$VDz p6q{=m7}2+CajiB~hB0_htwX֎Nj;H: `;ݕ gg w?p}H~64biۧOf\_$97 %?**0"l>lCӲ}c0(ļx,@'? :9!3uqYpq5c7#DTP ~PN(9bs"^^!Sqh\KܓP|` ׏8KXq c h#WWx-AU2sH_I3Vk ݘ7~hҀ)ڮ 6͘nD Hn\;7jf:Pb" GT7×.jOzDhSR29$MoCCRFbɆyh> G9;2Ie`<4Z{nZ.S"Ր[6k`R7,'eU. ֖ya{Ml1jG@D؏GE.$M:Ģl-!s_K$t`[W_"Gu] OSٲWqOU};x% i,Fl0zwkHb.Aޒ$5`B+F:{Aܵ*9,w]KH 9g$iDge h<30G.qH\n| 3mwb?i^j&O_'9Gf†ļ}ܑXJ U_o)+uWj4rx[nD5XjP;`B ك֩)}PVv=fa°4N.~{6qGd\PR\"ޮ[`n sh<$'Tg FnYD-?DZAbf ; 81mj'P[Oqf{peJ,6@ 3Y:7|it At[g|򒏡4[4D0?,HuKX݉fN Imm>V] e4EhYu V0m*0%[K|T?1&ljPҝɾvS|muzb#U5Bͼ:VAY["\z -(/ &jަA~` ʏ.1k}! }uPbY4bmKQM3y%(Z :{1DE}I䘔*XDJH;?quXL JsĈ Ñ {u# tCZ{숀WI5ў[{gsp0>u4!*% QȚ/mq_`y]&0Τ`^tQ,l=QB$/d?=vDZ a.tr.&W@ ,݉FGWD1<リI)gpZKC[zK #\Q"߬†-Rxcciǿ ~|$&lx䣿+A3K#Ռ.uwToz+ۅ ..ƢJʙVW1x)Pn#xbSMRzA 6-+o\iR],@9X(H4O5U^j| !`Lc[e X 1sΎL A0q }gM>x :(g _+Wgm7`B)=ro O_H J5w!D/sw\ ~ @tǺf; "C9I9A9ҚsY'+u 2\3Iȋd {7K2Jjo̞[VjĜ@G)%kM Kΰ% *lZu2Ӿs! < R+*ma{Շ v!~迿>b"A?Ne2T% KE#jwYq 7YQ(`q&7)3B@݂Q;`N9[c@TW#(X(ŨVt^@ۉ&U9vE4l=U2L)m?Z!;{8A_u%-}Wz7[G8S7V#ʈ'l'X?Fς+2';g1,|+ο_wFEctBR!c ˠ ; m끧 s F=IܟBTdY;'W0Ul%9 tt`](xkD^cgr4oRi,|/Z/dauhpl_^Po\tEQlT=qcG3.G'k[!4ٖ: m|"nnԊdzj% E*.+z l#=U KI=21dK[ɱtcd7`=_8 _) 0d`cb]08-D>UeD(OF _ҶFSMmqKsGhÌl93urUR) ZgƺeV2t]E8rDwfW˟xǃU8 o=t/>jf|\o+j')E#{~He=xoG!]yh!. FSO;ޘύI㘤?O=ާX]ǧM0Ož~qgмQ\Of.;%U4#pӤu0 ]p_@!R~XwQ/~JaM/E#w ݮ*9#@+dD؄9@B-~M=3YaԨ*,HiMgw?1¨DQ<ٜ<сIxX}64k/)@M* CiiD;@Ǖ/$< 74: `ɐRȠu 'q6Qz)|Q؊>Z1Dz~psȹ^~99ZXԚ lu^63g]Mq{a~ANМq4[%Ees-iLD$aE Tx[],WBp*/8C=4\Oo-%6(z@d!7l<1 Ox<>R|Y;)ˎI{%$&0ͯ. 1Gf NxmGn%.Yx_(EJK6rie~j%X3B  ;ב3~ *tqy32˹iMz˙CA-l b~5vy VDlh{Exa/#zDh9 !dzBҒ[.Cmn $RB3}};Z9R|Kz3($.+uڔۊ>H]È\%SxAz;G5W{U q_`[,IrD8"8Q@g"+C+=XۆDP=HPe JIX)l#]4-jKhJ7ZvO~1i@ &RDz74"d![6y,$D Z ]pX*?UcL}οtןW3`ȯvE4rN- |Oo8;Ă/8Pz*D WFXuM~%}YٗήT*0!Uɀ00&e`ZmÙT)4}W_^ l(Tct-G87b _yFRZirbٲrt8jIS;( I0ڷԈS:%Ƕ)4xP}nykIyG-ФQYW1]Rֆ_;;`2cζݲM \a4{xhlj1O5wYTrR'ZrNPo2b!Xww&&{ժĹ[#cW#flޞ^wuTۭ |qpŢqt-A`!{' X53 *eFaW_FKmAV )^C$A~r_b$U[o1d Id`5vTs_*B~Owi> a姸FrrΊaet%ʁ?l;h@4xr .~H3Y~v}g4ˆ_Lމ:bBG{NX+{ Zvy`JCڳ20`'q'C,Nޝa89 ʓ F0}gh!/@}L^qdU#6@IpqXq8H%tN9,Ы *t'Nvz5mh #3>"&\wZeb>fR#uٞ5dɝ 6f?ڣHn@JP d9vE 3# [gG-R??E݋ŵ4?F=>2zQ59XH'ʿM:|r;#o?+íJg!O%HG5meO.6g VUF`]ѱ.=Vk(Wk͊Ğ,ۿڿwP>:Mp7)SZySEyP)yrH0ǵƕ 4bT2[6(<[55GO}YP]*utѮ649[Hf8 Y2:I(Cd١e~ǧdoy;F$E]i ':ex_$ ӨaU|[+haU:8]D;I2}AY;}Nv F;؆zNY7E.k6/|$Lhƶtz&(24<ӑϒqB9]? :dGx1/"!VԂ‰#7%S{YtBN˖eo?zdd-[[iQ1a=8(BEjUϮ~&kKCӽp(:{THdU샵' >l cK@O FLҭe6a&{.03a'5]zq d >+Ydt2 zu(*հMk4L2޽t; ֜Ykᚺ_JxlQBWZ[G9Uw=*q37xr!p-*!zjd$iJT-CϤP&HR䩛$(j;kY~ [\ַG@~-ZN$vFּzȄ͉aFm>m*/~Rl *%Պ V8}&Hf&3A>i$b0A|X9piɲ0w GAƓ4{[X!F] Ǿ{Aզx./R3HYmn\Ʌ!(D| ~@U<U +[lMH@%،QH m2kp;)یyʔ+L9ec̳ uLx#R=v[sɀ ?y4i"x6;˵@!0~y8<ڪU>x-PaQ8nѾ tA(_`a^)<^"Ig S?,b- ;n`@hX؉ p|YuD| ed9>H7Z¾Isw=&Kg'*HDG9Ror@GC *r)pAYpXf:Ո.ՐK{hڮMpJytVGlCC<{H?5besj< @~Bo[Ozý/S#z@d Z5^-aa{񉎿 2]oȄٕ2<_t9ydV3i>odNh7q7P JR -f>%f/̖E#| 7;Ac%N Vڸ{qwփoe.y;r k#Wi=E7B$/]ǡt*3w|ИðܲZ-JBz{EZ㍱ LZB^` SLǺ䮆@j1 i~Pt!’ }Q1C o:|(=ǔvFRPN](H%EjrڞGw.?+荁eu ~# Ǭm^E0zQbEmB1#mX $' Z ۟p:+,AՒ>@ Z8}eDx=Oe`Aҫ(@!ŽG= Y ЃE: I%hͣ&Ohgt'7Ra]ݐE#1:6A; R[Iq[&L"7?VP0 NQhe"6|)Ȍ2@f#D|k/֛v쉷,3J g]v3S)X*6;&,4tsdGf%HA~"Q7Cbk.μKsf-b\Vp `Zo 5r%ο@DL:empMfxzyJI)ܖ& F9gHL|>yCxg$_4SZ' HvSɜ7y}-E6Nk<N-o(@z +"~U pPP9O0֧ieh0 IT$ՙMO*4P"Z*Y!Yt b"7$'^@zVZ9m?ⵘEMK8:HZNCPQ~ L||@"o)r>^ n؂lҵH8#rHFj*T$ew<\vߛ/铣.2[*Ǚ[gis_kPF |;A`g WoӴ! B?Q%ϰQkJ}xդgrM(r/ɀTXt 0BV(38R%s=CF3!'"$= >n`E\ YmԃKUrhK7jF]/(IUO*G>] )sǕ"4g|!GF(u\NU5?QRY ea uupuAF'o"wLLPh ;65)*u%ކfw}7g[N$O-Gns0$ψB"H*F0K/ZL b $K$5eiYSp#bVœ&6ʌ2M"_DO] Dbmoi=ɋ+1HrYf|&dа=wlaV:ƩUW)ǵ9Qȅ:-v_ޝ0 n#'pd#A8ic"8TVHVO\B"Գ\Da$@)_C: $掍-l՞bqC%$Jb{]-8Bwa=[i =6}%yuG5S@ߪldggdt"@HV;VɋFa떤03Ul@F)_I5g:!% Yc2eрc|ؓU@k:^ Hn(J"3=Hu"Ph(jb,|:YsZ$~m~-Sp{ЇFKVŗ^YM㇂V$O(]zwth +5sfIxKj{ ҙxPvŘs@8,6}(z|-f7#mTʠ,c?)Ey)dARͲx_1}Mk-nDtw#[(Q!K܏Q%=f۠w%L9ArH.teհLI`Rbl(]ƭrj:c+T6dk>ͷH_iL(a8"z4oB&j|Sl@}w/6)QW/sT:~ndVr5_\C|mA~∽E [)b%:Q.~<+\eu ףlH)?`S= X6ylү Rxc tZWgMs)猇֝UeH*O6k,l{{Tі4j]>+2$vDʪJ6X7N;hPx˵(F9i`^:n ya?ѻK,lѭ0=઒#mjڵ^Wێ-_ *`.U(ʮ6o9w-WPwU`Lbu/4X| Ы,׮wڽpc׵Ϭ}WjQ@~"Pl3S5K T=#į]REns:1 {$UmXUWD}W`X/t{46YH!p f{ʆLec?[p lysѠ"8c5#m#װ;M}t|uGf^DL80\M tcNFrhIZե)QJ tӞsmzOSf '}3 WRеuBfl:GxhYG* Ov}v: Zŗ n]WhUEkYʪ,ɌEr;9,XZ;Y~c,+D@|JTl`&1|'5=:Ez(VtK8mux'V_ߜ:fS /cǾ -4]Jdpoyi4ONC[PRH<%[sfGzE-pi}>{jzzly6*ɓq)Q{r0o|#r~(j)SkL+#՘/'(7הHr|).ItBcGl :=78E#{p[V|䗟|6IID!J4FGx$%ZOq7o} (M/pTKez|IR$L_jêqSI!A63f+}k,ۦW%ywg*ZfkOZZt00[$t`RM4sX%Qc" !qk/&z: 餌 F#uSq/ s$uwK *]/ۀXJqb az%xm+F]c rMMue8aq+|SPԫ@LNH#2Gr?J{+!@{3x7U ,#{琅?ulxwzLs_xFn^^m*q_0S)HC{+ۍ@8&Z&ل+Q1xA \6 ȢA0 K]#42|(局w\H3p9l_n Ì $ 6ސ2POQ/bP!{xsZ^CECBئ3Ɖ^],_*cNرaPI;xAPOK̘#Hyb<)&~ h^e3uRv8u2| Ծ4'PhW#3IM)~V6Fj刟!k"Դ[Ef `='hz:I0sdMXx/p2)5 yWm`)M*v̚_c>F)pV4#+&9 g(s˹1AE3KRF_S اC2 +͙CU} /_}|I`NOY!, 'Z8Ryg#k%sY6VjspYUE~06MU)frذU`)NX{IZvujuk(ؚp MmmַV7+U%`Q?Z+;>1 d/0*YV|r/s&f b+]Pย?#E2hw#8A/1H͠?IH yxxeɠx7d{""buߡ5FJqT#4{dUig! kuWKAM)[84}8 `| HHW ^ /՟IGzXu+ kJr]+8aq:b)3Y+< `QxyǟCcjTȵqPON#<^n5NSLv+3E:of͔7+\˯Bwlf>zb@~&*8[^$Kp B]"Z %&9|p^|}КoJ1+OzDi񅑋iQq6ΜԖLuyJKΚξ.7q MXtVxw.#jgᓯ7=7Kh/;VI B#|R.,LM; @!P%ͨmQ( ]x]3*B_(8u\85|d}lQUޱv:wB?6<#AU_n7^զ8ucv: =2fq Z^d .im NXv]$>w|c7"h}dpp?ʦ\)JOÜS[#?X`hHIB6u4mY%YfZ%}77{m 4rr ~(_jsmA"`:OԯKď)vZƔ, hͶoGu=hd=P{3ū.ɩ*mԛ͢ ! (L=lm[l\N'ܘR.`r#!/Df]vKoW}NC>0kߜһu {#!VͷhpAl\OH)A<С#`8ߑF4f]X ص%YKϜVW=Sߺf @$+ĖVk -Cy= pADf4~_#1$:{u2fKB䨝HEӢn&H =S'jrWfǃ!X !wP+g.RS:)#2SI :7_8qISb v=wbgk8 2r}*`7 T(fc~!r[eMK0 0ețID2.D]=S\\WQqm-;* +a#J.>/BgJ'o͡?CePmOggbb>oPCxGrk&V4~VJ1 Z.idtcwNž@N$g,YX\p\3R+KT'G7!͋Jml:eIYvӊcc@)pf;J4@]~8KΨ[hJts/XZ/&߅on0V/~OَrWA[5v)8 1R8Y{}vKVm̴8;~pxd?0'm/mJG,g5ח[(~3oLgp!$1Q$i2Tm+03NȜvg9t.䩑uA%\Wx`t@"1}}T`]dØCڤ Y@]g`Q˼[el(. x/,oQ2c5 ϰL ݖ6I_d 9;ީj-*=,$ =zuo9Ui@ljƠIj!U^>|yrƇ֛y;0ty A4GTG=j2d:.MJ3+N(IML Yd0%9C1;LP͓u3uhf0ONyj%+3&Y9XK'*\hɄ\#L]b8~4>s^*2]e PV﷕`Zl&V;pkEE.84zd-E{p e?cd+h]#G>EU^!$cK5 aћA#9L6쪄<[ =fVRnTp匏^蟺4aJy9@wbR> !q\vb0 z5 h=,.C>;ݕdQ֑l}R~kTC:CiRH~Ⱦ&^__8@Ԛ AsϞ ` v4)gn~uc{ kv1!0D#(ũ.4]{ (6;5QZe60bFqիL&l'T"y@RT%`@c7% {ڃS@lSzt|'n-N <\wǵu۞^v퍦gxpQvq,vҦ{u'7#|"Gxn@`wN`VAIsÄcqNOz}^W?@ _ҠV&HE]^PnOßS,:cϵ<'$D$j?Fm7]\} Uծ>,tK[dQ\ȗ%>/|]a  {PBR"2k xLHdo G˷z5OrC;J{ptZz*!+JlU}&ջG#ގ6a(C&0Dl5|ָ"yљ8W&aF'0c,QfDvS5WQB$x&֫ V(hط!; }6EY[Ʈim8~>.t^]s7R󻓑[/v!Bwo}ŰH .< []Cw n2}Jū ضk$HgD=0%jnY}+GI҄=NCw{ ~茕:mm`>j֩)8VwH4A,?0v㟑?=rhez1nG`Cv)OpOڞ}j&}Qb +448ǣHHa%FhDK%ijh5c)'Z7j.)4/ϗjNnź+QnE W*'I"{V=J$vsʎlK{O13vjlMjK^xQYZ:Vt%fJȖVO`Hx> `8B܄RG֐oZ^̩6Ey)GȺv!d=MCʕBcJ}uxwtj&Kɉe"u=qAB+ƃ3|H`LG'TX͉gwо.~,R>aKȦUԹ2jzP(Pou$O t8@~OYEӏ^doͲ&t&h1MkK@D$&OB č^r$_ Hd*5[{x(Y9QIf+@樫WG],gVՊg$De+)E's0ҿ'S[}:xf- Y1.pqQȅ:ƝLcq8v M3JN-mXccSV[T!_ |n0}q>J-X+#|p|aRhvӫ2_bAHY4riZn Omv+49{^2œ7FCo6ncȲL6rAFN{5'B`.hze2R|fJe8^QBDdg3(K<O9BюA}ل,M9\\}K{.&Kj꫹A&zU3ua0(=)؃n xBO ^<~X)fG&ϕH@hG;#*"H\^>Ug:/~آK<䱸`m:Bc'q^p"fK;LyNb(A=~JnܛțBL e,|SXuxi ReZ# 3JbہvċI5"joL+|]oDU1cǡ֨^Y8CM›QLR[\C9lfqb=*hIN>o>@Jܑ(.EuVRS_.'Vu7UKpxZ~R%5㱇)|#/<" F ]G傃bJvj;>'xNs4jXMj}}1k^ n*HPf)g4 뵠"F} fAğW:l"f}l&yzfs; IۥĴX9W]6nBٕK{mk.s~kɾىbK+s==7YUciԞ˟Z㼒ew0uJ+G@0h$6yXBM!hv9 'j4|nuRl>s9eGAoW9.,~vrKhdk.Ch_3I'Uvr9"/ XKԚ}˦.Ԓ.$1.٢]0-ֽ^>aWx҆god|,Mgʿ$Jg5 ^G/D*47mURE A}^N0H( '! Y9k}bi6ceorǑ=$IBݰ.9ide~_ lUvҿ<)/+ A*%`?aA? 㲔jJ&(I\f\|QuVvc<~Ujym4֔s#~X`4]AeT EF>4lJn/ݳ Nn.L T%s>aӍM4WT;洡zduGIls}ߋxPzZ,ru~OvtrM`*/2ѫ4@)ݶz4S( 02 㣆#QZ>|_.f9STԎe)9a*^L#[۽Ţ[& (3pMWV#²(0Gh]P$> ?1Lɇh+b|Mg9y.a͇\fCet0A 5PMCҵ&•y fW\/B@yW .-qX1Qd.V A ׺- r~OaNq:pKɚyas;Ұ\6tbOs|_[^/6[[HeSsx~BLuQqΓo<оx.`ͳTR$D/R8wSBQ-ʑYO$4-wp ճLVхMgXI=9tcg{GxP PN?Y!4eSew&w" n2Udb@S7*'cИr(ۿbQg} >E*wx5|SeiY+ê6|tWHzL۾M}j+֠>3mcpoP7EzBHGcQkİS pJtئiwMP1N < k)mS.ӓp%&9jZc>??/4I4g $h|A^E ;녧v{"TW|[A&e)qbd!Y[[;%|tRf..oobBHn0wb_ggjɛ(tW+x 4-\=_"߅ S#-årq}K¿*Gu8[\aS %ƎZW2L+h{AU}K)!+/uMҷXR āsBυw DLЀZם Ukq^ↈdTZf yL aWq*T= _nGdIAaG,6S@ 8a\ ljRBKҢ݃aBO HrԿ[ Y%u ٱAҥ##_}L0J:2u}GV g:Mӑe,oR!pOIu;Ĝm3+F6"<",pwOjLJ6goS6l3:c[4N+lMu>D "7{<K DSkH$_g1쏻X.;q z&FW7@DKMl:&ř-8jԝMvM"N)zw B"H ULMK'ut^#R2}iC,TyT%|P Se٠МS  P"3Ra:ru(46]-|` E %@f;y ٸS&b > ZTK砞)%z˯d FLpЮ!~R^ӛz)*nXE &5҂|օFFShENxg2t 3ܢ;Y=Mb Sip#C|._Kb m׈pfۛ~j>D : ɍ\t "8zhyoGLè+Bh+f"4nM/u'Yv.VX5`tHZ~f6ʶDv}bA1+koSc *rV_H]#~(u2)bmwG} \Ǚ:F ]Pds%|=v~a TcW},t8$SsqV<$ 1<fkH+m _UZI±J˭uډtN!"(\׸/UpX8ma.E~Y6h,YgWdM\1 ?\yތ0Mx C+ y>?s<#d{gd&oX SDP|aj]%'Qqg0;{=wD>Sa?ps|s hJg6L T@(GdJ=ġ P c3cKBkj {S&yb=;$6YP% v6>:?s 2D(n.5œܯ.,ۃL{n+l СLT[a'G#$ROW\A]msAyv~TfkcNwt3 %(scKzC6O+KZфݬd.f4$v*GG\? JɡxK+Fa2"I];27TB(2 O&ӓ;Z.WW#p 2~ԘH;*.QKuS5 E=M|*qɢzZ:g~08t 9r@vKjKKo9(< FͰr2mHC=eF8gE5FY=7eN :Hd)ϋUI};I$}2;Un "$XHq]}Ray'=dp1S&,fwdFj,ZE@ԁ'(/PI\d5, 'h!9ACĺuc9W}H55YUU@Q(5YKM8< ɸltp2!R.8x摲#Eeħeh)xxRGԶHn΃ho5q|g! VUfp_ID86)Y? t`dQj@;6?LV~WK$ݜTot-b4>xH  tU9/O-Nj Pm%>tc9IqP`/~[ٽvX!>@+}q{AEЭԤ x"1uV}Snը46. cWOl!z ÈGE7Qa_ :^L-uz܃e\LF(XxqHR/JɆquA4daJ%rjBY[YO6MElt1z6{75`ݖ7ٗT{z4_bnȼH)jkYkړBe%j'y&]Tݯ)zFVD@!./ 4fB?Q C?f+ Ѯ;ua X*5?`aNR[alX(b̍OJ:l!%ɟcT"gFd8&\Bzx!֘ -)}ɉDڡCBv^eBW+NZ |4IyHٰA鰲TDtm[@3:cT.Mԋ$O1J1s&Gs7L2@ !Y5q-H+LƞZ!5T < ՓuMdѐ$$,xYV5ŋ/5Eq4|kQ?u/94~]A,ܵ xdbL Q7C]$L?ų[% # Q+T `ҙv`=c639>6$פ: #(HH+?"ZtNKc> _ ԙ/.)aiiep(u "?LE F*/}zP%c= i7'I螈 a^D bO8*+Apnfؘ뼈ߋK2>&d͖bĚnfa! =K~}B Tv4g%W8|=ywed@Gi mwzn"Tgѵn4D*$? >ѻndpŴ82Y>װ-xEȶKR#\PJ8[yΚi8e6ʏs޺GBHӚ"3;eRa&B8JCh\q+-J[_G0"GM.SwؔPnos[~jli,!UNƢGd4ɥif;l{GRR=ő hp9>`VM z҅`[s6WSi.d@1dWW{G^]:u&q>tzV: ߭Gz 2҃ L̏X^* oY~ԄIMIG pЁd^E{.#^Y2 $E.H VvJܼЙvelOHkBlѬcCyEЦՊ6y(Q?%fhwG{j󦹓 F{+dȘuw̤[ih;G!Jl8ujt;'hcf L,/ =N7p@F/DfIl`(OpXm6a)G`oG * Y~C S3F z`,0er*mɼ; nF;` _*M?֝$ L # x=gkp/ uEw_\<،I&W!O+d-y軸?GO }b~15yˇ"kK{RǴ8KjWի|ȭrG5!<.a7}/Bp}:^L&.fT3yB$fb~LPwE|3';HR^75<^j_ 4@AXqbű4|Pc҅.+d6d%ax/Ȩ(՛ı &]x ˶ۯ*.0~4jՇn :A !\\2PL=1voQ~3Z!a eoe|-rH%6}.<+hd{d “K0@O) `#_J<:=#PD! p6$[MYT qeHZ|쵺cso@ Ebvj|d`nbJuKeoӱiAk  JJ{5&Dp Klj;&0:-۱CV Tx%(t}M)7@Fd'B:{KZ:RfܵϦno#EH'F@9cBI+>[ۂmBP[XߣƔdeQkMBe azdOlae0X(=k$/fIW=0Am].w"^K)e"_o jn%̉nt')g"bIȔ)(;x}izD=up X̟^ne{Q*`@ ʻD,0s4Ot$(!2@* )f.4 NO*rebp]@Ce1ZYo{-2>c,趪WY- LD;u`gЖ0'1]()O@ >>q='/;̳Y 6^h46'}\&2=q=`ZFw tDjhz^Ymɐ"Bx}cßHc՟#3H2m8rebSI!SCĪ/Db -(,aζ҂ ek7<%qSt 䲂˹aUgQ;FxyL!eզǰ@ClBq' 7Y*W<#W0CߐA/%n՘M] BC`&Ӱ2UQH)*_ޒ9FH@ Mkz9k|"O%FvV-wb1,KF %tP&dg)}oյ@桤ht^V$DDifL2ىCM贱VwxX soe%PX5-/oGK2A_sQBZ/L?i]?0NļW.RU5kٙ)s6j7W 걝H?5K|Uvi& }n]_a)bIakCt۔>]MQ@tsvJ#=sIJp1JsnJ~<9 k0bZ{C@C;$6 Ɏ 5k>u4u?ޛQ$KJI!Jb{^ߙX܈}\I&!&@97~,9^EZzqp͆W2,S"dUӥ"%)Oͩ ]µmnXRkp<['~pDf)e"Ox5nnz!QW| uDHǶ፺aKv1?\Ǎ@bΚT~6);QSP'2ܐ1`0'׈0aţҵ~G7ʊ0 OnJ3 }/8%Dx39T/7-N'RX(-$zU &oPRj/tPc:=œ9 Ĝ b-SAn~%G[D Ju i!:p:6eNն%r/ߵ8&{~ dnĉ)H5;!aHudn=oS)[¨OM|ǾPg!PAҶv^7767L^eRF.*R mY.@ Ҫ0 ~:]\p՟E;'z־6qx5;dҞт=xh,瑉)@{ y-ہ@u11Ǐvaeozzr5nơL ^m:M߈z( k4=Q2-' wuiH [|4%QAiP.ᴸѶ=hNSL&!5+S v,<*τm^%ѹ&btv $?Y`I9lٹ>ͷ¦{K܉b=$7j&K0uLv,~2$F]Դ Udgw^tܳxB(=K!)' Y /J7[\mVB$%Ý'>7& |5%EyKXXa%T~tVċ"A"4!qLT5ܟ:py^v3 6Y7y7EdpV=ňOx燴N/[FFSrՍy;[NȘa(R;9 rT+̉w& m)Gci>ڄS7 _Hy[dbKpB/]#$`{U+ xt9[081I:jNZpC蚻[%,eVם,F5ep`}KsGy4!  ^Y ZF+Öt0 yޝhQͱ"HTwB?zLnrt]P[c+SI}}EM'ԹdX8ca }З/]1Da;o<,)m\a[ XvuGeeNa5Lۺjf yRl{u3>{fbsElK[DYTS852eG@:j^28|U t1=FZ\i+Lbl$\zPe? 4EϢ\}5724vOҞ$,W0ϣ(c}/Ư9|3UȠu% lG2-]w`#Xz=<seqGK1l1-h:~9юw,@ RId3Ƌ;?^};'B(syz=JVK6 0ԭbhSt`Fiŏ~oaMZ:0UkH\R'mA-mVxPVxX"IO~ui?&:u("6&' hz6e (85#V/yGGm) {3ZPVe@_$k6B5 &s1,c?D>#^ wʷ#,I.摏Rlu:V/CX4^Ģ+T|qg)PJagZp;L-C̭/q4H {*) ~!'uʛ.&2T߬0ØqWX&>Q. sUD* jS}^ԁm7I^VD8Xi%P Dp-WRO(¥r-n1IPj ^b FՀIf7r'aP8?J 'UQGlyUS]̯Bˠ1+?<m70ĕVby$H% V[Z9~*"]b 4CMP, Jj2 vhQ/訄  8ZBcs:к,2zH-aF43jrzx *0VYLm oCwOPR4wg/&Y < Q,#ұޗ A{v$!3}1{[R0rߧh[ղ{wTŞ&ryʿ.%`$~[ A=aň3y?CK Z4'b?*:w_ť:DiiU-CG%(za7qE02Q؇<:-G*`p`JM I`&KJƕ2nEDˁe8w+-c0Dx)=Bȃ+WwvueqGN1x m[';9OEб4~KJ Ho͘Y#f*VVͿ*+ `dsksk92Y$:'LvK(K٤n,(Y.Aܑ'WM X׮yClC\BFt-8Wh +G"ub$*i >A uh%GAP10!(t6[~r M3s'_@!8p[Ax $fd6VwԦu2ӺscqmQɓT \Z7=YY.&ILh4)^?_v>X_ɢ qKRNi)V ǔմؙ$E$(n E儡+ 0pxKTF\*dVߕBH'cA#eHؼ'UrQ_+0hWgɲ?쪕KbUz,)]8 @%JEn *.;N§x7) FhF\)$K&P.8x*VoBk~OF9_o:_O{I> ]X3E.R'c|Xx.<6YށK0e% d';sik+c!ZYM&RmZ#6} H9]TS+yJLS *+@(^ z䧆Y hLw},WICU=I$X;h;Tf!m̯γZ.<WV^Z|'k'h<@aTߍ||1H>Z)lC,nJ]imp[>﷡5:kA81 9V >K+oP@E"Q8X1NRo)h=4L84߯2ݩ,Dy}NܢFYjx,yPퟋ*AjT2ñl&VBFq3 C+6_ޕQ<0lK, %)pNM9ZtW-tX{Ñ\\xl@r }s~"~>G3Q`0HN]fT1K2>Y$itWuTfDQ0><TiOɰh#Rܝa^~Fn[z]ծ֣1@v _;{PM%O !ǠOP* [``:17hsLfe``'R9nK3G[^g_\Jgr䧣M[KsD^m|$&CM0^h؉7S Ӌp@[osw$$ĉYk*PQjAº0b-}`DƦۘnl˾ޡkvHP%rpC {.%Mny{o]XP4il59~i[,F2Mf|ô\~#c܉z H'yY&V}eRs+Z}b"3%Qe8W-ga1vbUyLҢمجc+<hComtF_cO r}Qbe \fU)O0E_B[ JOqyAmRC}?R}w= o%9@p#;G|P=R~m-oթl2&v:H%+^N%1/lh+дTź@I@~'7/ SCzCXd\O>ZN-cI~].|>m`z&W.l`: 㗥Pba+1S]C%%ACiZ:G9Obl6L^*\PSC|1:=ZI? 25(DTK .6ْB״T3dw1P{|*&(gg#g> 5>q ng}y=M()ˮBB" NIgwAM3AY8Ĩg1eser'W?6F!Z^ҥ6Nfw|gÿ 9H<.ojÙg &?_x>=xNc hv hEp 7E"+O[d^kFw@t%ZZ!]$gaLB!q*lZ“ dC?ciر/O^O;DݹpQnkBH6LLL!m 1l՛! ۂKNQLmm(U(g\^"I$οP2 nBmxe ^OnFoPDӊm/F9S<;{}8َ*u8Zlw,b:ɸĥQ8>MK_jR! e^n !%ˊx9O?Ic:JX嘬-j1T~b]2|'-T&G󔪭UI{ 0L񧥟at |fz=Ɇ5+ΨS=O-A*G f5= T 2pI%X4?:ֲ VlbSȑtFi0s;50%>RVށŅ/CJ@R[br 3R@k")wwJs>uw=2(~(zG·4ȥilzȒΐJd%XÛLmĺ598v},߾?d gh8]DUTyvnHpEE+'Oq:߂ԯWX)g^I {?j-& wI0 ce`0X[S5KlFMr е-62de_m3Fz'a F^u1;}3c̃XlR'R+@}fM仾0 .{2lOxmI3CR =P@+wIC;l VK't(^*}e^dZ2DT$M *͢:qU[}c]"IEہb@!&BJIjbH32OiwŠ\{ˬ"1+Ӯo.G?`;-/S>h ~ޑpSLȭ$``4n]v20z)33␷p5OrUU|ɮA#˻5p7uWZg9zyA?sҒo $* lXs9vW 囝_N!o jnǦtbQ˜(_$Y˜?D} M)eWw1&1E0C5[dYDoԘo(.TvQp@C5TK/J !$9fā.N*XʄM2Pڛ"l#zBwX#M@,#-!Q=::GCwq;DD:/h<^DױrF?` d_i=Odr9BFSZ'l?'KfcDjU<-f19es9\*N2c+8!})<ҴC-ҙ"fՎ=# ҥnآ*,ޣN=iS%cuFϿfWGUDtG9Xґլuڮ,ߣxE#oҨJڪ &k|?r=ذOuѯG60:{$6`ݺM)¨O  P"ٝ ^rJ>x=TAH:oa_+~a>2wN)U#{Gbל)0ewyU, Wj%X'>@9F#*USHp7c z9@tA4(Pt˪ƣYC_fvjLZ܀ƒ4r.yL>P'ekX[`AmԐ Υ'U5]2B Uf2;YMru"/^GKk񀄬V|{h[?S,ItyEKNWiL{^hK20e2 L<8tZ6!cE܎v1(aC8o^Xǃ\T`op_]դO @\=ĺL%- 4s_c0M‰{~,n6_Whӽ5`AwjϚj_IƛM2$#Qf0[T\*;vQe_jxNv4w-mPj4Y+cj>\d~&.թy^Bt#tc׺X&v`Q>a#XQNY M9i9js˙ڷ4qbGO0Z@:5^tYUV`4Aei<U}&.&hTz+1f=e!f2 ,7?z'dLfTnu H!29gJ|cUqpNxR4./kS$[D8/5D2X"W4R蚿<'-vMx&J ^{"hy/ 1!tZCp1vk/qvˮT1@\$tQ~?7w_,uv`,%y/SWٮQJ`~>Ɍ ` KґАd%EaO^=ףz2X`:g]R;4aSS8fB+B6I $wG+b~1׿@x|:ۭ lA/'ea>+ 56Bv˚+qQz;2Nx{D =؉r gXqCdm'1Yp8Fw0iR/,3|`,GoC* mEA0A{΃OmO/H>Z,w-+\8nѽG<*s@߯\%,Fp]0_Rg'Nby5.7[q-: CbUYD*ߍBH=ͱvl/uc_n#M6-[~Pi6N qkM;Z𭮏,d"l.0ز.r. [G#6vk8*$tQ*_lKgAsLFLۍ?!7_ p[]:fKS0m_ &45qpT`v$(g1\%Fg K9klM1gԠ!r6-7^Hy3+n> /ZMt{/樘r5)=H*5zC`I!+ m̧{+k.< 9e5O[OF cM~D}q=F=܏b- s^ /=~wO\(qWNn'aiGET~ O{r7Ɵb;_b\ӄFƆ=N}`P }$# <"x;!d1V vF{4[QyyO8_?)MLSA Esn>3ȭ˺w{tA:jj 6[5& tPz=\ɻUi7Q*ك.OE_crcF΂F=\EgV )3۰X1 {!&.FcX$wOUw0v5R>"i1T\ =D5C0M J?zULNҀiZ L]BTbO{MHۦxfc3/tx΂qt3!LgAnΪycw68`OZ3'V{ylaB-j^"}ycqD9;$]p!K(nILWG&s)/k*~K1o"⹜Se.I[C`V?iǗ`98cgԦ_0L3B?-K#DjetF g ؤ&yW9?Q$K8$t5 ^wSH%%w?T!wjN&x?pސNب:-".gGъKŵ{j _k!<>c#e 7ٿS.Ӄ n튳dm䍷w[BomL2+FOLY0x%8ĉhXp{lPygy'w V[d6\]䂌p E1 QҪJͱ'+ h7+{vsvq¥#PAXk8}{R+ZsIi ZHkO'N5ty@\yvSMOn=Yg) \VF]p5;{U.:ר# +wpaz=#BjUFPPT<Т9Ӗ1*M<"ϙD^,} %w;]4I}8'i_zcq* 8sÌ7]ftm {^ 2pEd5EꆉK30bpg{qp\ľwڕ J!s?NT#%'ߥQ8NΒ6L[8޿ (.''R@v3s7k r~ IEj>>ɱ,)^wJԲno2ւ;5~Sig "g߀Ȣy{t}q`̛W[++q|L%& HnjNHqhd^|.eJ3h^UuQ BGx(S$-Od&vpugh~6jh-9 AZ4uXX &ОNU?sePaݔ=R$7$L~^{O}P96'[;$UxU[rvaŊa;TwMyB%9_f_\t4HWݕ{Ł>'=}Ir%ܪʡّ|!. ycȺHG1xpC+xt` ,~x|A?|ƽlX^Z.x^0zRek㿢JL`hrUA3W5 lθ7(vj6}eY@oۗĝt?@N6A+A7ڧqYMkS%-OZlr۝ Edg8r- wfpτot d+iqaqȏL *otu;pmh*ȲnaMlQ S"'vSriwt; GA臘._jɸAl/{XrM"DVd5h0C"zԢiwB>q^eDF [7]d=eVGJW+A%>fa)p`4gT/E{4!^X=?ת1`+  {EaSSdxI%JLPs[7JzӾͭ,eLqEHIzB+rKRd6xNb|]ZaY X\[-ڒlܹ:Vb:dw9W{'=wų" #65uIw= k-qb\]@'0voE,KM;Cn:Cj8+ˠǭ{yOK˴~(U^`̒Tѯ=<ؤqX$UU,q8˾$IjwB?Dd2GHVHcÈDP"`w*' ИW~zLp]L+Ν./bv4˴̚G܃H7{uyG| C73YaMQx@DO MWյ dsc zzV Aʆ>@aܿ'6SyDWp\ʿ"}niK VAUeCU$Bgh*MJ2LZέ0fyݿ_+پށkno]{T/kQgyB hEZdŭ xhAv]e ,SսP|y4,nХx$s-SxlL#ԗD%|E&e/3gR-^=SYßwnj%=lv](X}e!1k v|s85P$3%ئ"pyqG,7[Ȩ zT|\%*FfKJknmn& $,z**[ 8 @Ww@`i}eU)5՚l~V* ea!><e8a2:kM0ІFU~jQ; KqRZeוȍM/pFAAc [lνQmYٻųn~ muyȾ,S'%>vne 됺Q[5S W@ HKrKdM:HUa( * ͬ4U>![b6,Nwd~yc@;ڞ*Ĩ:?m$|7$y ; -jOˑ_@VP?UJZ"o"6TSoLi] GP!>A[*m"ZBj +Oso!roa/55L~Ӆf~N_pi W7}jFTd^[1 LYt1aYO.qqhvVIp3.y8k3* *MT?!OΌ `ﳌMlfJ>mgCGƊ,iꔡ{b5AXB1?govET |!KBۥEdX|`n t+V![b3lX <"%aV-?-̱ܒhv+}?fܞ5"t1(-Fx3qO(!Lƃ[e6f_}޲p\J<4?}lrx/i,ֿ Xw]'&ES{|evjvDu]˭U6FBuGi%>˸-K_•ͤzl\z<"(!FW^%ѡ+;W@ࣽE)QZf~`؅&*guJҘƱxCkǫfQ28n]t쵶#oW82t6es昄&UЏU@igK\Z5{C9Zy^'{yL*jMeEz  e~mm6D`$=Bl$2rw g Uv8NJJ*O ͐`-Rvv}\ꏸ6&!?=Mu@8%I>'^tP9싱6#sR?4>qe1eś6˞ "VŢ {1|yW #`RCq9;q#4,.NjciO L݊[k-jן?$TC.yA17Jq!wklbY;bIgP cf9bf69vg(=Q Pt%KC|*?5 VChjzCJ*OH(5N*Y@P9t>^bU|gXeJ Rɏ5TQP3j]P B w@%-.nj ÑH󱤢 YZڳ)cQ䟳w}#fuR'4r%r^4)>9RhɁ*ad,/r|6U"C"Tdž[_Na&8"4A1J\~'ðDnCL.vLXU8:=Lک`KK`1$Fa134/- ɦ+"=?Bg3Ko(SQه@{8:FW=PAsJ_XfsjqAdy0J@{'jҳ31.i膳d^3FWܹލQmQ b*UE0=i>"m:##Ŵ@5,Wd:sTa]R0߰,fqO{ϔY)I{mJFUC&GKLAAziB ':3izbkwn5 s,%~3ny`?B*:\aW0!UfR[|]4N 4_V* K*%3φmQ`g#C $u4ۦ嗸Ox }u[A=؝`Gg.BJ_5QޔxfTbBߴPLspafX7a[K#h~ig3g)(=̆AG#}=g&Ͽ |iIs2}HVM]7s>j ]YIwld'H=fy pcD.:yO>liz 9 ܥoj*6g Cj<_'\[u܋;h_v&=p/@NQIo Sr5˲~yYk[ ҾI缔!ZYJXzi>&(MJ~ӂꍕنqќoH*A?X[./Q 2{mpvW#aRJ%M4^/8HzNǾ˫o{,O/!$*E:qƹt-m]򖼄_|drROmɡ TlmopShgUڣ!&]Fm:=j4"Hk5^ّk0g(!NBcW8'$PbrpG⎄=尓OKAx+3NYP¯9uw6rWW];ђ6.c?i鳝\Ype_dCRCr ÊEcKGO^zQyfILx|Y62sg,oQ@ RgF܋=ӳ4O=C~%]HWY;}S XNtW޸ԍ\-t RAv,w1]K޼! y՛flԇ>~1du]I=_9-BW;Q"t叄0`소VzR'L6.*]3ȿt싡>~]Z猢k)ZZ2r bbRU?Z{$DFHxM7eGqKUN/O~ İL3f0,:}w?mZ\h$-:,{\ Ju")'a=QH4^VR9֬:dd|r蔝/,}y;˟Jޭ@szP-?Y(צ>6*nL.wSbdD>ۀ%*mw P;lYK!d?^.Z n|\Х[6]\0O<MG*KFaLJ1W/3u\ c ǰ~_IXp&k;8v$YPox"P|`YZ\ZFUxZRq3L^ D&Z򿬘LQSCGh_vUA?I>pN:"r'97 3Za=5#vY*]19ܦƯFVD]%z pwMAjSj0f / Pƽ#zz@BdxMg} +@*;*@?/L2"Q 5*0ّF|C7,q.ZFjg[` }H{"$TϾItT%2R~c@☓G\L緬SݖUulnYλcֆ"]/Ep&YR!Jea#3Ԍur~@rnKR~*@"ފ QiJTXͫ7=hb 7;-1 W `[]):ӸĽ$S3X $j(-%eVAem?n(|JvZDGdrYm\*鿍Qǭ4|uGEs9eۉXIJroouwɰWjhC[_Z̲ "Kx 3ERXÉMa?7DeC]Z6){|4!-Z_~ZC3sx=hj[z$0E`b+bC4 jYkq$JՄCV:G9W[, f?LKrc79N՜tG&1E_w7ϹAKBAq'H [,]F&zσJfO58L⿃Jg2\\ModjC?nڊ=п GFG*l҅[>z7`cW|Z ]Jm`j PA9~44D06xUU+,. gx;ꜵY|8j B!=d& {fq0Kc8)H@QμDj~5*qa(<̶A  > La݈5Hzc.ky r qSh(p_7L1W㎑CxLE%=ɻ Gūjk(ڶ8քbZ[ԌVb<<G ]|bCl{xv4'pަFC0SIź02m_:w%I/,llgĻY\+7-|zX.,(R]sqib{j:>- .vAC Ym^%|<ѕK W5Zo|p2&O) ; ]-?3<԰9WoSqVlX&̺{ 1vIl!Lȯl]1Z:o}#xxa0bIU!iH)e&In>h!z XXX YTr"D V&P1nmmnmvHrJ~{6522r]?gԧP5`*nF4zC .! JK ?)zlsch}֑x63 #^4L 0_ RF2-p"'W9ww*nwL5M.1F̞=gJWhwc؉ y!AЗϜe=RGrd;? @g[^/{,X !{@(. 8$HYagUU*ʼnː+큔aٴ⎲ e:`2&*.lbkpѰ4>eF4'da{I6.J[)Ik#з{\P'bKɝwoDVhr%!koaƮw3TTI#z6~tf \FȡuYjkBji%S癵=T~j/p_?bl0 >&Lj9@elGY|)o2L|j!KɎZldmR61}G&r@ (CamcRn->?`5(cԥ T0Ac]Y1Ϗ7IE>_&yZH 8(7Ϛ_]TP9`fwmcKuHo*ptW$1f쮴*@/)Jbd&X&7eݯm?a;h"#i>AAm o.y+8,$硛i;`Cg_v5mS0yT~6Ti¬1a,$HՑVxK&'K<]],l.G lLՏ_CxFYa|ݩAܰb\"=ODGE-a?yeWHoH0P.Ң+Cbԟۅ٭&W Hv CM: Yyd|oH5IyGӋ6TML1.20X_d4. %"v!!P/MXòUTqϧwU< 2%Y:SS%͓p!yEMf'3 ź޳#=+]xė'\QA+vOjvyhL2mRWUXć;FQ'0~VtXmm dl"؟ӍAm}n^܌Tޗ9No>\$fRu+NHmj^$x̦:/cVGk3[TM$㥌s~ʅP2(8G;/{0y*~;*GRmkhӇNy_']KiE#N0m); KUE\<@f cCگ)9MFȋU LWB2 *.>k:ںH$FGv_#H;hEB 1>gߵ%+R^+Nz{Tk"SµQsP/}cX%qU߾&}ɁƝ/+_Cozyd®ndP<4enFQ1JYR! q}`ҍEpFlLzO.5HTu-j= ǝ,O%#y%ZHܜf; Sx}Yje B_=!?}ofjݹ\z+i.Iz{?f2]>cjxReB@P]5}>E#1PRG*mFuiXe31["'+0t^M1jزJ n-Q 1Ql/AiI!/-_(),Xhkkx:.f'6w1bFr|ϴ;QɭZĺ\?8 .%߅{TCKr2l`$0b\e!b婎&n~4d:]v*5m5K ..I">Ul7R9ެ槑^ dP -$xn (8ˆfY+$+%iJ2pն=w]Lp,p["L*]>Ё>8+ΐӉ0gEVk~^SgWHw!횥 Y KOBgACQx#/ ("oD(\i4@ /YQ"x gq4VHC&iD9hu@a"zCSnvVA-6ѧt[2:.m0~IA]8GI8imr\ݎ+`2="ei#0Ayb@0\BA[Tmd?;Ok)/߬k:| 'mw/ejv](ao$@kg7y1'hhQ^OYF^)/,XLI8~2R4U6/]Xa+l5'JYh$E`룄+A<~uyX+P`)n$*D3?LfmaeOdf.4Zʋ"*"J^+,yr/Ģi#dn9X^6O30̀-XT/ElcJZ ϳ\dF=%Lcg1P):ryL2HrgݙrJkXnv3t*eΙ!D:!_҂Zp%LaOP2{9ʆޙp@*+*`q*Oňy+o*P=-W}-i}~ыwn3$$}($I~r@$w#aWሶZ+`#o e4S~YJWcceqŵ%ܲջ8tᢛ&ZY@RՖ H}KtF?n0xlIWӜL w-w:wm =B*h}hXGOX<@K~*.zbd½4v7֟NP>-I@p*iM#BJO{ZWG'`? *ץ|nLy` ίn4[$, *$n%X~aWUSZ g)=Fl !s[>Ns5.]Xv匭 eeё]H~aZFeOf9~WCX?0VlF&P@gJ8N *ph4hڢ N {l>eq4m̺8Qِ26f5:ųļtL "^ڛ1+5>FAGB7dtQr>tDE^+/*+BR"_3]bYE5*kJ \ =TĐ(v&Pʕnemi3;=h#(nPRLr!4t^cESմyJZDџ&EAM{( N ['rk]1Y㸦),yt0t yqG]?'Edp85 74[R=98g(Z2oW5@$dtJxQY#c-,nY@ٱ25.Hf Ģi[3D~kʭ{ɋ=gE_ZIIJ:6Js ZڙjT+7%xUMIğ,@&5V!4øGPPhmidžlnNyBH5H&Jҿ|C|=֭V!}݃\ԯ٩Pdpqs_۲{-ļ9&j6T/j]#u܅Iʗ,AxN֥C+r)R!7'?kD=)k4"Vl ϪM o0[q%I0w)9կ, bIc~^e]pE+DE>Բx1ٕn~ $!+{M˼o{ ?x\}$ad2V.Q-WBՈ֦͍ճ[U`K޻='Pm$8x*5MdXz\ꇫHw ŜʐA<|ϫHrZ[ as+{غ$_`lȏd^@{-679.yn>- [I s!9\vX%|[Rgm̀8ΕWIД1x8};i>Цcz 9/DPF[VXtӡ@-]\倅Tn^Z όeT8WV'c$'dBq))@CIaHF_'.Q"28Q(Gc,%U}.@-  VB8gҸs 0pgRլU~7| ~ j||OV]wR,Y!buWUIMmDq-2ǂ *MI*T`F8|їlw)6O!f2qyFP#MTW-vQ}&椘.JoqK]8*gNxnR(MOp]We_b.H4|D Ow8Tm& tyVK:G{*m&h(T&  ѹTxum ^SG*:ԟ *(xuR\&&̟,ه|w{#<`sXYB3#Qw̙Rc}d#6bң1+Xn`?aK)#rR,2!zbpbO q`WtG}볯]NHJP6lNw}AA%>㞇Ջo\6ӗ2*esrv6'ވumOr3Dh53D\ /S/=kbc Q ߝ״][gHޫ z-{x0!(Y@UM@06+ẇm)P.N=9H4obyB,f].ϕuNT퉮r[s`''L.xpB #dwD5>nS Bڼ66kBڡb=إ>.wygxۄ<Ee>[_/a}٫Pu }f 7ͨl4!ӎ.˳Y,l0-^cd< jCfmi4[@lm("J)PVc fy`]#d"M xxjZi07җ ›) U:.}5i +f\[!)=@n->| !d̳֭t31 \R;ο%_kp+;(W7[jnE]ǖ?(/C1()hPnH QWjV07n* ;|"P w,AP//2r{M^k|1`D?ˠkt٤/n@7SO5$O[1!s[O(F1v8XooQ󑷝 SjpHƑ~7*c' /x;gqGt1}9^"Q_ ް䉗ыz mmX}-"VD0TRj&M ^mEi"UՉ#@kO+aKdWgWN ? ]Dl őrZ5+-R hE^Z%|j9(.3HFV"qtn`c7fGx!<֢Rmвt*u^[pfǎR=B\8?uRa|E_YBw&֏ɑmXPܯSxr'%NwH@0O+zpy]@#9sA:ʠrּFD(_9igZ_eQ[=j^M>>zV麆Z7 z5\%aޫH?K_מc?X.8✙evjmZK`Ha)M#]EIHĹ $"dch4Qđj3|J;9pI9~xx$`zoLWCRZh(ːYEzWW'ű.Z旙N΢xt#60v~keFtN}l:SDl">5$YHkZ)M`qa o9||̉k(v]#7j3kW5T@4 lH9 4|3C1)9Z[h6ïj*J( GZ )t+pA)}HLV3Zc{eug+=}qJLT\Swcxh&NgbtsئdFG>rgPVٿ6}0m5NHh)Ԇ5?kܢKn6B<^jUkBBI\va1v/iJl&ls<\ D43դ 4y\i&X`_vj5kvnIIn|Rb +iCSPζ4m ra KahɟJl׈ PO,̶,ic{!苡mNB6t}ꎈ3S";('a5bL)4Q+UYʢ$&ҟ E#եyjRoft3M1QL Oem:7[za` T хj~p!ьza'd897GcW65zJi/e.ڳ`^uL_|E:#NP W^ \O< ж_D +57 4Od|s gւ Vwa2ԛflB_x14BT2"ͭ"owNf?d/Vѝsxa |s׌˱ m?dUC,(~.7_:)Yݮq{ֈPʜKT`lxuUz'[^&wRGe[}\E?FJ kCLbŪ ڲ2_WOS`)N90LPذE61憦{q ;Cl!; sVp}NT4 \w9{=]7{I r.F6lK<"@IG6S̄O*qǰrNlES~R`ʚ5+_WIʭ/lu gӌfj.O]AaMxSg^BMs';p~yϱ_3ʡ7 , 40_B+@,GtU|tNd_T>ND-Pd%ޞ~)u1G E<&.g++1dVRݐbb`Tr:H`g"֣ЈUpx}VJ=OR_uxX Ȋܹ5dj .b.&zYna';.=tˎb?K>Ƙ;XP&J7 U!'l(2v#fj]^Uۍ4>hURyE l-\xW?]52v`:DQ8H/t_>m'p}dQNV&!ERS&iYgfP V WIh䉐RBK M; 4p}PFXQuA?v\:ݎi%b!'GJtwv,rK.0 2A|3ӑa0tfHl #3&ĺ@]b޲6G^lAr- a1Љ;4W6~*[4 =9ET(๟edYz-هQOʶ! Gn[ .{YyvM-/@7K*bgO 63aM29ΡbuΖg:0<PTAV R~xi-Ml)TtDZV&|3DX|L)9pfo/Pᗋ/W3jhY6Xͮ/vŪLa|@. Z7⢀_ Nh -NCHīǃdR o )Va&…iƔjfEG_CTД+p~9sυlF \1,ꤚr׳ЈkCZ!tsj[y =kPǽѤ̛dW~*H>\e5fK gBݥi_+r&n28dR4n0Ef 𵴟[3z !J/Խ'THN6"ѵ~7o[`N'˨@u2Pf猕:Ue0uҶGFJx=f3Xrמ^p.5#*5PPGSڮaZ'|l8}^#Ds}tfy@2%۟l#9K7"5ӄy~/<ŀ|9ބovמP=}h֔ғ'9xp,a>~*5'f ђhAci)msD^66up ;"9okBCNifz(K]BQmR ʛp q>2rcO!Ude30Lj&y29 Kc"/­GY.WmUD&eQ"Iktnèt-j68w{)*[>TK2]îݔ=sC/;ƻD:20ZPqꢣX㮁;ךOjnl(6؝~ymC<%5d1)*-$^|\_lLh1,"tW6v;-.Cv;R < X{(FP!ؕ-^&Jf敯i 43$xoѴ/)&8cܙC;N}5`Z\O[7lֿ4 &XCߗ6T1%)[E 5;'ab2{\RMIx Կ}~$OZ10 91T{paR_qfh? &^5=ra+(Mt8G  - $,u*V7DUDpų+nİSgskݧĪPUDC^K5/jZgmSԲ4 W1Y4-b*0+ߞ'AR4Q?gB *ʦgGl+SC1 ԷrGCZll<-!g: $w4Gę3y):}f : $;QrhB0uݨ>B 6l`N\G0(PDTLDCkA[͍3"qqeLH@xΓ;%kFꈙtK Ĝ!]L?}Y፽w+h} ?Ԁfr{':+ZsNc>}Y3C,$boAO}q|ZnY[⾦Gw!p$uamwb 4Sckm;Ժs=Q@ ؘE"ebreR,SKT_ľӟ<@h+Ux:Vtc(hFqil\$f_kP@$hYd=Ӓ$n(G5$JAI9mt9Moᖨ4[}^BϽ=kcwuɟwR1?9]c홝_BuS(FopC〜DCy'd2BLUےW=]b{u-y:pL^J#d)Ⱥ v~û:JUqDL؝Dq+O_3=Ln rNf|2Wg2lۙ6H]1i q=Xl4iĹD}}8~jvgX0UZvd.t&zaY3/?ө'c@ؤ n>8 m9 ̷n?{SN눮#P /JnI ̊K&TQ,OØ4݆n|E}2g>%O >=r@fu]FpF7.a:;MVՌ }.R$|:VNݺSeƙUqޔ?&ԩPIQ w;UTgAؓn;^Ø`aMrD[*X7ja^yY[u^@~51[^2JE$_Yn~c꒹ b{Q&Xw{vUm4O9i`65Dz/뿏K?x;>1 ^l>;Q,;oeq'{b`s0E\3*\ʓ3<nCj,',;}F ޚIdR+!P ? Rɴ9e3}&ح15߭VMB?<ڔMyUSLPO?=dWI' kf7 ;'3+j~~Cv[  B凳ʲ{^#Ok3~mch8B8ևBMcC+ '} JU*l S4y{qj-L!LJ.)fw}G̟5{C3_dtqv".85?/wtCzcF"-nA= %A9WЦC͑ցs:hw"DŽ#k`nY$6jrLݠwѳdꮚ?l9E'R䟫u\ b hH"!/q#yg?fHfF`>Z0$0r.< 5~Vc l`5w/;"@Xw:AtV~Sq՟aLU,@D |#=;PQkqz؍-0iۡV]Uo\ˣQ%i/w@Zqf/ ~Ypg78*:9z$!UY arz_ճY@W[G@¼ev9TQOH Q~kkd 8qGNru~l.֒n.ڥtc`r !nzʥq3X8{CV$S&K[@e|`s$n?G?tQh z{ 8O܊l_F\4TD˴2/) 4v"%-[)nwM׎N8 y8Wl,W//(.UɬfO{иHլm&uL)p(\#7Q}@AKzc(m`Ʉcor$Mn5k.[3A@ #Ò|+1]]*Oq,4QRd/J;LF{.Mܥ +u@Ó,oŊ{wv5[ >HQjHAd?覆߳kۈ" LJ{1^yև >Hǟ[+%UtQ׽880o&UwM>iWcY*ސRWҧ+k(XHMs{-_J# ,7BT ̐W190v=U|rrT4`FۂndJ֞ɹxX-b(w[/M "/cq㷶傝B0}e \Wd|/˂/a;,&}.\NŀoI k$\_aLK`*XǤ+? kmTĀf!euɆAy{Kcҫ>YõnDw'Z\$Z1)O$U,[[`%)O%qĪ՚t cO򴌛RR%N)j2BnZbn2N_] qf"DckK[~`PKdDZz/E -uBowvp+ZXG/8Dqyl`K3kIPR @4* Hf!1du:LH8I`kGH8D . Y3 k/< sAX$n& 襪a^< " !N`S҃R*-ˁiw^/rWed-Xjxr٤ӁRMg5|U ެ2w}hF%љ%ݠK Ȑ}`?d l'`Dnh[Pܖ}Z`qdjJG*nêJ/+՟6E&(HK$.*O Ao/Ť/#=EIpm|`ߏwٮI}1vacǧЛ[h tu݈:<]GQ=\ʧMHD'kO dvOU*/`÷F$d*ܼ_ +FZE`˴G^6,H`ysv`cەEmAP%6$1uu_;~Z> Jv<{eu2Ď7(@zuF=p!ˬ_"ϛLRhD+TX-ɃTFPșE%;gԱ2u@@&(/p~9w>Bϖ`~/01'Z :w !M$gH[/XCFZ[ b蛲IߵfǏy-hN‚%MJeҢS]nYZA$]Ƅ({TRC9 +.J=LU2]xAYZOyfd miT9+9Qgm@en i9FXD`OQ4bA'\!=]O43g[V18w|8W,@+A1V75HB~noM*e ۏxb%om=ŕ]11t8?O 4Ii6_xN vŽZ%fXs5ˍe)]}g" gT  vhK_aC!eLwu#9φx3;yQx͡/o04J$\&:zS\9\>HC"r;@|޷UI ;fg1KwS1WTaQn@p'^XUDK&ћd' 4p>#hD /T t2y-.HKz=raq,eqlLmg}&1-pWmi)6Yv3kpee>KN;*_'J*OКW}f'+Q+}^!8= R@H~W(Ni;o{M"c P`K,%=`EO@=\SrD O}ubsV{& Tj-A}fҝ|Qڐ&X-f' t7 O qbʘBCU[XZPѣp~;VeXvE!=W4~xq 9|Mrcˡxߵy֒UXXM-^@ ESxd̏g+Ui :`,i?/ֶPW?H2Uq1{0C谣`:^4-?TwnEX#\FKbwWʜZXj?ܚG?ᓩx>]H@hѪs5zdr@-?iDܯ"ʩZOGi\1Fnj][US~,?GZ=_Y[#y~j1g6Ysn f@lt&> Z'y羬HC4~;nIzǝt6:'l9<19qֿ;OyD=Z~喘a /3g`p0_hv=f㵮昢@9DzzBݷJxl ߒXn^"N3NmGJYkOvd+g]AHs7aRw?1ko/z ע!jgf{;GíziLD[Af{=#]Kmc k2R闽9p?t5zv@{]0{GOGW(tE./$O=Mȗ G b)+gXMyOat1oCJgLf8q(ɨcfa#vM g-eZ }p=W6gͰnlײ%OZ7LyH(-aƁb8jB>H:SGmّ& 4]WН}tH:Zc&ޓ뙇$&#&ʥ:z.)Xqx02(Y\}v!h/l?6*隨fo:fkpS6ֺ.v>ȍ,A0` ȌxNRONiI.|1r;W'&[n.xъqܘv ,F=2g YwL S-O9(@/]\-hjO4(]x3q.]kN}7xn2DH䧲WLƳY E(*֑p415!^o_a m?@Zъj_0{ȡժh\x^J /|r\[wwAЗl&g!.߹8Sk>\SZ,XKIK>F>/<`P%sgMO=pX/ &\v' ʯ8)\2x!`aOKG%oKk#qSsy.t|dJÜPʇ gܳ=VGhy%T\hGfh2DxA4B8?ADKptb<C[YT*5e6Ƿr7Ѳ7#ϭruuT kiB%q [Y@PPXš {meg-Ui=)+yC͍M@RF;)8Z\z#n+@r`) T`WMWNB6+f|21_֐BTh@k;6)Ɲjlvuq},^6arP~, L>QFvH(czIg% _H~0qKz8qA.i&U1%*Tj,ZI^;dOVys>NphEc2(=o?L5~kE|oYԎ#H2UI7 gq$DM/$K%'\;LTczʮc.2aڶԆٖcθKN\y$),_j#zRԚx/oP9o,I6Jv_̴nP^$ޠڨwrq0N.E}8I%[ ]8M)yJP;M!حRDS  PߟJq bP$kYoW1ꜧl#05땴`YxR!&Hz.?̮?~pA&]NR^,>, *qepG3n.YOY"`l!:5aAZ2`jB&y ^2&9N]|GxySVFzsFHMW. &Bq~ЂG.}}OD% "m^lS+)iA,AkD-6X@ eR˧^3:bu'N2CNNG&@f=m@GɵWOcCFqZ(naq .\vNn&;p\$%sk2 }h\.Iɮy]H7ȒI(}}6 vO 9Qa}^| -`.5ztү*b5xhAG|El[W5]ܝ  /XbF0@^{#KKό#|J/u=Z'bFNn7ZN*I nc^\-|K$7q)߬=uUQֳTH"q>m@Y:n6aIܞEY:RD 4,75끷A)j:pAEuސ NvHFAvfTT[>\vZÝvbkwe䰢œžVt?P9W8YRcm6LwAc(1I&>aWzӡJO =+zL`/D]=IC#sG+BB4#*-[ZGxn R>r0ԙ5ւbk"]t("Q0ܕWM /ux^p)*ت40O~mrhP4ϊb%Ɇi_m|wt=2֊+iQ_?iқĠha%•K+:Ut[@GDsVCѣ?=?N"NS x3 J8 i?:{ٲ'ϙÍB1ۤ&G1͙D#Owp[DcH3ϟ9/Խ,6w;Bͫ 1Ɨ=jhWe3a}r:Is AOu2zzI7wPa>حU xv.>v:G0/W7xsm@ \\~XĀߒ)"[ȹl1NFj^>)?L#\D]TZ%p,{r%Y74(.}KR8ӗبj\:enաM dH?RL_i#zu1N-cUZAj@8pWL:{6K #hv 3V~qAq hZOO, ~ !%{#-rV_:gmOK fXAq^)E.kL[s&z~ٯ/<+ \Wyث&*Zl9ZR"%C콒5=8-Imq¬ʑ(*Y8P1'역6V84=m}X0XCnۆ.]Aࠉ(C5{{gsq {a:G2Zbh:bZ8Fieo\Iq9jmUB[.O5_l^nzh*R"wXiHBO6f]Dc|ЊIϼ,C}h)1ph Tm!F`Ȳ|GK- "c$IRz(1XrX.ژ[|'\_РAz,^(ؕ`3jf.ǫĿ+QZH#Ɠ&H;R4,mtx-h={"A?]=0<_1Pk=JCQsk"5%jotvW(woeubf?u*jɭ#C4mqt * 3VLʫ &K1qGK|:tyfgWdí!_]VrqRHf@D% _+oSi9z7Bl}O6x_t [98'#@-Fr88\Ѥ,*.)j! f91qq`i.;Vë'sr0U:9Hjq.?0SQNcy: r>] <^0rNYIJ|IpO=MbVY6?Lr"@PC6e6*)`Y3 N怜&]6L#] @kQT_@.G@ٷÜ1ŚyTș9n=w?hec$>2z]qiX)>EQ?QM;8} +ʪ9t|m9~UR"z}hkTɵHE\:- x yپOy#4盚v9iKF?~~wХrL,>AgqzѠvey.-n l5ࡌhx "z%5@4*…].0S},H/PD`?oaci!;ןPvm>lZ%퉪dܨpm:h-i"'tXLJx0CƝќూMZ#I i)9DZm#W%gWWHgʒgY%' YǷ̒DyC@>Ր;W`\c _=XŹ֡ < >@-=]{{ST\xZål+$OtTb T4a64͏sAxX  &8k::(nC5&ڟ@&>MzMust< Iא i|H6 $-c4]W2Rq-eWd,ެ혽4D?R}f\!:[VP&/2?myXCJf¸ROLnV7a"1~Mw fצe}xFs? 35>5}ӭ&6M3`2Dɽj %7مwM I94ܐ{f^4whÖ!Ią *;x@y&b2 =#sIdwȊ>S!yijюZF1.|&y^2s5qvѵ*;ԇzG4Dĸ0ˍu$&eFp,O'^}B6Ij6<h*oU $CPnqQ$<͔ZtM_wU12T*H̳&伯t{3W!h&&k.cOѫkS\ndS1AsmBcQSi?˟'O]A{I {3a3yJvPn"cruԐP$Lu-y.Y]g6caxHQR{t땳cZ vSәEj_-UaKYH vYSwzz p%ilUh4myJK82pX%}N6ؐq %l~&ZKR&~ ^/Ry2-բ $z1H탒Ӗ5.9SWY>Y#21g>b_g6iMј~'b٪_R2*øgSjs-[%a H\TFI0&v$=:f# #п qa3;E#{KhѾ Z|W7|DK(;Vo½* F NH%/ X'_VR=G1exgm_zvy[5B,١]ҕ#>΍aY,~'s&Í&#WZxbiT-R*=p׼qļӎW4qmX~a)1BڭHNP-jP t` ֊ Qb #m:Sp3e7g*3B)1IgE\Z>bVc-~s;{u!ʺ\V fPb˄uL?]C>q}pq"18ؔ͸φ#{e[cB=0=}t4Hn>9(I[Kԯb^e_r2.Hp7ۗw 8h&sZg̥{!*Ǒꃡl-0흰lKc'N''흌~},Qa#0;9o6K ]'Ɗ%;aZz`IpKƸ(޴اh5Ǐ4Y- i)?:~=Ɯ$%q3:[%( lQN1L֌9֭m7V~w>mVlěn+uO.k"+MV >I~ Ή,D [q0,$s;)ٝ[ gUڣfx|o:%(`Ŭ_ݑ&?b.0"g̍O&`imssyfQ1nA Wmx]! + J|U쵷*_s"`|}Y\Nn7 . auYiyaA<>4]hآ$H3?(X<@$z`DSt{#=^EZnYil݊<`JsG,>)LKI3q:%/}I!wj?=2>jWwhOߣ&Sɍ<+MKJW(??y4֚:(^W#RTLJlYyQs/zoJrm8/AIg/ 3-DswΎ얽IȻDzδK9r Ȁ00ALh7_2KUنY%sRsgv;(' bjJ";Q:'-w`~paoGIkTUJO&:PʥjMIj csP!EBG*qIFW -Odv*qFP_fl|~d׈lF ]LQ88AD;FP,NNT x?*B&]d{xa:y~%v"N d`W>&<8[:dva{P&Qf{L(0+ibnɾ"Sa el6D"LZꊄ$~ϝHƅΧc4فp׸%ToCq\?ە5x%Y'~ia{2u .(A Xjc>s[?d?&ƮfI\?j{voZYTH2)z=1 [ICMt ͹PB얁c @EvR;bh55X4Y~J@;DPyRᤕLQڇ=E0rFa 2l !z>.pŧ(m(ɱW43'Y,ւI:U2"fMC^] 5qaDJϨnPDD3NBn܎# 6)QPC\^(SmsBL=?7i!w.^kYA0iA_,hf=sWP:ݙUzդCDo1diz'% yA{cv4|Ko Wټ Rֺ|J{ q/ h7_s9}-VH╘CQedžW TW:<=kr+eP`ޒʧ?Ɖ L-m]AnV˻Ct9^6J1gMN6?\dP]Y uz3a[1{WOb, m Mjb`ژ/C# %c)N>ڵ4gm6ir|îã;B̊bựN33ۚ20%lzdG\~?Z/#phvD3[1B1hޫm~S.3/fP3-g,W:Ӊ]В _쫅wF  ڦ=P܅ǥ]Qo n~dp$ZkءT􂳖s+h1 aAI5Rv=Ͳ68ޠF{zk:6#srFx1_*1-s (d+oj]"z :i94.qYLqT) =6IxU %Q*D R r圁ʪ>oAԟԏ4"LӕMF QlMaܳ+ULŠ!xloJH?7?}-Ez\0(j쳑^ IVD87\t`cPܲaj[W`Ne=;il>jeDVP,7yGG({) LN{(5E"W\{J3-IVO8^T@d9OS4+ D׏Ġ. \Z Sec*=Yd]Ҿ)̂S?6kѯ`P3r4Wo{0g"u+5L}B#G¦@\m3 b^{c6%ܱsԚZJ=F m`o6qCWIGx6 dךy?$ңnT~CEf"FԤ(š:>'FkP `K]7;M|/㮟Sl@Yw󺧻7dJ7'gcqoŬ} 6ck=(͋J 9qD (!2fjӃ o?dRF ^ ĮR1kb(/ߪ]iUQn,ɡ=f 4r 3bh5oofG͂/! VbΝT` VaԃyC;K9NA"q[3t B/&Vzuʐ,O'(Zi/vyUB!>*LD"?'' Q9"0â~)yݵ˻?6kv_ҝ c0U#܌w/1L95 `,5͈Qhd1AA7|c5t,3<)Ok)M B}"nu?cf%vl{@gKPM|\_9 +i˜(-@NndbDôNi#2l.*TX|W!٫%U`u86QMI㓟qm6##"9ZKS}O5=rX۴-_Vl>I/*ѳZn{&R/G|a1<:x&,ZUT>ZsUSW!Misk/2laHH7$^bG+35[(I0(.B@SqcOک!3yFb#*`ⱖ ƐyQ/ 7wݎ4a~Îԡ9db* ӏ"WhKҼqVϙ[Q))Cնɓ]'0I>-vTp"@y;m(OŁ䕣Pup{airCޕ,vg,nR,% a>;3SX!:SXKG0]((^/ɢ lw"lޘhG|;EI5 bEݚKu"[ԥ Rų26ocP):vTq($ cr19/,(x^ߍ5KEDɄ3=- v1tQ($^m7v S\sGs.8N : ;/bԁ}YFEؼIWs/҉"?9h`@Y_F@3GB7#Q,hOP̧>xEI7rԚWXM4 <.I{PNyK^I7~eq#dQtfz4,JҮ<&VBX0_'|㔗0e?> b(З8=<WLL>N(?8պcʩc-=ZJ.t#!.U- ٴ*"Փp{ABu/!+GḪr )^< 1X?6]/Ki޹\M|жE ZIhXe%0ڷѝFζQ'kDțn q:A`2&B϶8aC2@IY٩Q'\Fчp0lY.RA7ѐ+$+ea0NM1{ђT 5hY6{T 3~oѝ0vDlo#Ǒk?8&fOJ!:R2 U 5m[ⲏ6]LN)!/ق`VCRrs'{e/Jyn=gy~aQdhB8󏬶3z'q| ?z}sN`C%* O.À4U3`M<*e糴匓[Y,# /RzaN=S/; BS BR l"r#|F1—ht07`؍2|(+~>9OQF'87kTl0NKB`\/U>1]`|-iZ?(.o]zXJYfd0l `mOwhz?]$}_GwM;D\+/|D/4I|-صթ<=PǬ5dAnDc%B ڄ47 b)XΑsmKw1XE []l-p(IջK]w}tB"!{~[H]w~{/IzBmҔ:1#34u\} i*xR쪰|{r~޲FmV8MAw׭ORZcbm'l @s[Gdg5s`w84@󷦭k{@V,\zH(bM[󁧰-` T ^6 [ĩn GFɊzBp Y&C:}?T3Ӡ?B+LjAӛf9kHreAN,7F5.zD1ȑH%דU5vlR YG,';abA(Þ˕淶k EjkU+y+ԋBʊ$h[??Du_B&;,i<(*ylȦ b;`壴E8"%wW;UH̫99 cbr%oTO`Yu:uhK9 Ar:^WUdŔ 7N~mKs0rI@(-:S93o,`1jWS9c#xJH 6[f煮:m›ɃmSY P(%n"Lј4zo**\ ˂#W䫣)Lԃt`m)r@ giG?&nzo-K CfpH hmWE+9.$lݗ;z:v򵄷k,=Ca-T)i;t%CY{, 8{p/=ܴ͊+ԁj\X=lvEsMٓqyLPT_bP"c.˲H[̾sq}i H 8FoS98zyeX99@x~9?26-eSM9v ع#xŦW\5DL 6۴F1_wa+ LXQ\Rc׿ei4~_7xx,4j6a}<=§kݪo XADx?e14.c_ZgxLtO9p%Uޘ N}+!Џ0b,jIP Qot{ c:Z7Dc?x,c˙ $.s('R1g%9EjkzByfCmuC35*QqDYR3u&2|Tv2v'%7묠뢕в#G[*EA ۂ%r,V˜^A5 (nA"x.T-8LaLYO%X2a$= bĈ꯹->aU.0ݺ'!V[j Ɩ2ássE.|{Ϲ(yPɯ1/DXF?VDO#z P~Ͷ/_--FY|BC;26[,#PV ZawҌި{M+5!["0U6ZtzxzK6\0SU+\)<ISĨ TCXzΥ\/DOV2EATk,US?h;` hBL%_:.=DV;@0h\VRGf$'7!J!ZD!Sm]J E?/2mT bO xR"rV/o]PdP;6Wn: I~98ƫ(|j]-Sh. P|vE^=$N=NdFgpNdPo@^eа}a`{QuG4*ܣ T6g@I^ZfˬR2v'~$.=L3 p1q(6A-{_F~j<- F??`rBZk@7u rPTN_޸ѬjK't7h-ReNh>`^pQY.W뻠M<41}}ȟ4CT0$g*x=@۵D)0R"Z\Ph QqFpz'IҔ]p+&&XS.W$I7˚mú,JN3ǓBJtT9n>_g1,{(0j5#(8 "}mr[,ϥBVIoLDkj2qjR]ȃ0jY5Cv50oγ,yt}`Rz ^gQ9L8E!~(AcP3E~`e/1~|d9\56̷&;U^R|tƞa%1$E-~o vՐ9@jr10W{?,`[ q[lں? 09Y}ttk91oFh*xDuط1hN`DȂ/N3l?']ӊDP[DNrOio\77Y)= aCRI4z(6`w!x6i}#$x:d۲r ;KBu%?i"g~OR/͎c.NȺ jF${kpYhk/:)z F a]<`ȱ<`a3;~0 |XE-T+Ը!2t͠^moaU¡H8C"pm-~d8p"!A|t+ƍ ?-`액V`l4f˺vyJ)OlGfķ2Kzkk Ն}=w&Ԕb3/d$uJK])LnO9zveR6VQ13Fw&Qvj]F~U^_\NL*F68H.d}W)8ű Wɿs_Cy^U/_+-ӟabP VMt)+q^.L^:ݺG#p+AsGtz;e`)DLպ65'4灲_s)Z{j$C^ *)w30ѬW{>pwa)=%O\X0M535 wKt൰=r_h UsS-͏hs(&RB(k(>"EnPwζ*.UP|rFCNX;\ԃ3HXCx E4IO\FQW΀Mk' [(UﰉQ߁g!f])n2 ^~ePf[k):-bbV(07t\k+T:x_RA^)Ke-oƛ0&r 0vlyDjHIk5TєL\w>@RH!܇/Gpoy D LzjoY5)ƶWMEѦZФjv_ϻvQO3Ziy&%Z`EԈ>$Wmw 5;een M"՚$X'b?I>%(``*.~޶Cӣ)M0s.^U js "wS2ٷIv gt-eslM_r iy(xE}[oyM$}PYkW' rFcי=묂!$ٚ3c,[M}&7_^45:U.=]\O)(?Z A=2Vwl̙d(+,kHUm` eȋY)'rm* qo;XUbR&r.b fq\W NzTKF`h)(D&6i;_o;UEmP6fpN-xUZG# ;og$Jo#`Ӵܻ}B4Ĥ] }U=(pO){qUXp>'PQA§-wQs Qi/m#2=t36q-ޡEiBBtk GQ\ښ+Kw_%1s3zۤg\^a"nȀ;op?l_#xdb0Q^hd5>]; v;(ϫjߌr1cc 7 TcGv҂ʁ O:^XDCy;ڝ4,PmHV_waUfN%sb!lǘ -'/d\ ΊZ`iceHIψKUm?i]ix+z H1@QP4y'(y5vv+Cr$}%jq^1ƀfc'5ss{kRXpeGg)j^×'_s:i~? "ܨAA}575v*OvͫQm{k|8}*ozkmӟ]*3HZn`k!uYBtm`tz.$AD>%?ݩשLG%L`7DޤND "| N.sD~c{Oj!tX֩v9–ML1d<~ fMOnnJm71-%)kC^BD< &t:7^lt%JfiA*0J0G2NaBDcZ+šlLHm텉/ƾ$~>-TK)wrЙG;OdˏIUڬ(6g }6&cz9vPdAX_ h޾bEgx#X.P?b&`3HIIV3 XfP/eXNUXNWPBEuQPS`Zҩ.*kX(}c-&9?%^B-ZBNĶ.WFRc/Vct`^#pB ^zٲXv"ExؚGM0-Q1_1L6Ɏk'SԐ$f#]Tʊ*Z-/ST_;݇-" ',1:m9->_8L=Z'"խ xPxCĿw%2څ\_N"E$|K=m!% mNnn\re l[e-hbXg"!˼qV8B[`mos1Q7Z1kSg+/פ?١XrvZE7sk.CbCEԠ-\;Skf* v{ \(d$$DDڱӋLlaێ$!ƒk?F^χHH~0o[iKR `ך<= z"8ETPXE[VlzfJW+$!9iτ8B%*Č cW@b@Rt'&26P :`SA`H %+Zץ2LB j[bMU ,&e4&|7qw q[ A7NF5_i噇PU ,d)E-o:M+4 . J Hj/ iLV^K؆<:.?mNgަ1gV >';GQrNЗH4儴ئēIfHy*d&QveȧׇP=?)4SK\f`;)j?TR]PYGg`g[qaLsOIK "6wkYc5a@­|=d>߫*DRfsL'^զޏy^JpD@@uR:j#_'a+S#v1[}ZJn:M@_5bfJ4׀̷Os0/xSBT:<q̠*S$?|Vp(U7=Q=$W/N>*-ъ! .o{R,m82P^N$e[RUiQEGƳ_8x/G,؄Śq }g쐽SZ;W{*z4*R$N/Ԥʝ?woaU5!Ȇ@c-ޝx LyRrݝ]X'%Lkm5 0eNu v]S( n!3][=ޱSߚ eɖԶé0=!ӯC_Y_0_ _= (y做x"GPhjZ\˦pR=H]٪cO`q&_t;+w<=3(fcL9,_=Qa  ֮H 9<58 rnc̍N`cL=V/6K<$!$8o_tY>F "#߹;Xd-^s_Z&9{С xyS'^ȓbfi z?]t xZ. =Lk"G>Hm$$HluX\YghZUC&O$yX.b M'2bXN(6)i4CXcD :BjBrCgWp+6eAHjO~X^?NXK_ej4^\WGM6!g;j &Ř67M-:(འٲqh.yKH֌wFps1jERLsvߨ{xa7%i=釽I jBzK"t]v}fEPd:gbTs6?&hЗ`\S!Kd=,pBV;Xqo-aXG$5"^˛z U(&:r13./e),.-%򼝺SD&WՌ#VPTyԍaQB0%}M{o܎=A\⌞I C kԆye l:^n.]Vn-P+7QYL72YT/rlRv^ {Oe$25]B`ASDL3 4em0uͪ`o-{u)<&o#dac!O覫|| 'lWIF/NLR&RUH"8:D-nxHluU }p2j s^11"8CT f \X\G͂i@uH/& 0bj^ds&j"yaٮYMWإ 0 7LZ&ߝ|uA3λ#c d6_ )4w b*d ̴fv ќ&!!bH3S: rUVVޝ/ WxZ2lI4=ip$mEdc0aIeKOFFéTt'r=Q8:6e.щyxg+c) )i*s! ҙ47Pk>QrWf= bHj7m0x4D` <9>p}4pdf^hݟ\ /"`+׿86<72KՓOGZI(lY0Em5@i9N8 A­+,c0*@ӫ6L$|#Vj?"Dm\pQ^i4%BFH8 /~+[y)%sM7B@)+T  lpQa62 74KP4ՈŕcY{]us!zkOI؈Hm0ܙ \4;[Spx4=12)Eo]7wEs$bmrfylͳ"[֮W2yV}JA FZ{"˚й俯9 vz컎Z aW3UtEI|rV?deLݍҔY<=)[a~gIX|,\eJC@/76D2Lt&E@VǗ_`;6׬LS|%Jps. ofh(X[zǿ%wJ-zfԸ\9tUћfzXMjf,N+S+9e){=iX@~cdivUqhh;@]Gl" %T/7;*.uTD͌_kג ?{sզJZG\݅](&8TWw~S7au&SwD*̳ЇuhxĤ$+tF[ݎ*JAA{0Z[p+j8ϛwhQx|atw[V5d!T7ߦ%;}@ ~A~SlԤƯX:ҔZV|+.Q; iЖ5lē3C`cazG?\lfiXc%# ::lIHն,W7,i@\uh4.yc?r߃RiTB?dN(Dqil!+Q>@`d SZ\_O-#K_'z)8&ioT%CW){gMˬKV09$/àHV G+KەA&[f;`m4}=俏P,{ږ/%`RqE>quO~<â?0?)ҋHTaj-9^E>5_Vc(S4m)G/roi-~A@h*J@vwR/i}7.\6B+j46>m2;Knՠd4@ϣB]IMwBUKƕbz) EÒNT|~Kz?,]XE}_lS9j+Ě-,XNqC#I{dR ۸YK-"rn-a|(3`A0 ضnC  ^mAFLإosU7pƿ<"k4}]3=qw3^+F(,0c,>2;]Rd!Z$T\3fue˸!7&|BF[Z=x;~S*Ɇdy&2_IG}LHXuA8j~0ƅQ%ȇ'sj kdkʈ1#`/hZH?”.k5u} :cx*l\dsٜt=O6~su|u$-̉I>qډб^oBsimFl4Lqr0xCiCdPRo0ςƠ`DG4 Ҫ 9C&"K- u-9 rȾW.['?YZ'³qFlV-)TT:| Э uI{:%@En(^z^oaYLf$$ޒ+qb_lؗ1FKa wN U]?qg:Gd6G +AֆuqK% TKlP=͗#EHtjR;Ujnuo ߞ*LB8Tqe ڷ#zW8`W-^wĈ0;zu"ir^!R=jڌ"UNQBopcd!ʓe 䛌PApnQTn+4p⌀7n 5ͻFkx R?ǧgnClmXeX'6jK-v1*@hX>'ᵄ:ke:Ǣ-Z} JZ@#0<96%_T\0ᖓl G,`QIh. wUGmc~_w}UlG܈)I+UUnGA "%l08u{79:! dZX Pmi莒+3Z Uu~($øc/ţm rB. ~h[x@QL 9xjPp a@ T PH.@[? U+_u,dG)C}l9xy6,wS*uPI $gYNZ7drLmk/T8ĐZ1BaE蝝}d4R߫ǿ\)c5f+/ #=+gzK"Vh`E(k>*ҤU q+v0ң'嗑%VMD\+?ɉ I\6Ú,]dyjsD~O#|3edsw퀈_V9W? c)Gqt[9V1d :ZyK}ޞGؾm+.Zw?SL$%* ,ؠȝ[(vŽfO[G֤tݔԁTۇQ]'k~ ;d;Gb1bi2[(R`e),-tj3>ŌqRU\"7!ʊb\ǯTWsz2biiPv\H`ۙa[o} KaA܏p/fF?h +0'eKSitQ6\ 4̵ Ӏʋ,?[maM9|!M#֌y{I*QͿD l'NŽP6xf`/}CԚ&JEj0 ؈%̌4t<?=k'#24Z=]kX:bbʇZXsIwwߜi® -n0z"{ ߧh #tstÕ|ٸGH l.\ſ³iMS`xB; 3]ޏ!UvImKMHp3cT׬tID%QDOSn\2tKQā`4GEtv#\w5>;T%f Q{8Zq#VشUTX6%H%[e 8I@@xgHɵRdqc_Z{|H|l HC7TBuM |[.MXuݷҬxCn`d+:R@@ύ3QgЕI[?CP^xʞ52C,!lp91^=qb@֖&ɘGF2hʖIЉҨDʚ Y(oU qX]ԨU'.U&{8,OwҬV$S[?@ 4-Gْ<^3)kϦڢ<-oGP=n`a8o@-wm\W-ft7 zs-Ev2Tw /V;þ二o\-LdÝ#ޯak6)g&KϠxv,Ϭ<w ,a^Jp*f vCE.+)gݒ9pI-NUZ1nKx(j_+5c<)G+ScBerDON:9MwOLy>|1cS7^VKu_R;zj 79mNp+6R~Eg*GQg;1:N!#;)bC@q%@#O;nʰx~(&6:BOL_}6teotƏ҅he⡁q*e;Cwi OǜgIwNĪhr%씬4=h[䯘,HsrnlxHoFeb\| ;SP*i)=(ٷqhK/l`, +U]Vh(,\ɴ4s|EzTX{AAed%z+нT#&="9n׸s0򛩠eR̦?b} TJ>cTGnpd1 Ithއ6BV—CԖ2 ;9 -ՄU1aB.IAs&Y'y{>_v30J|]QTAa@^ _ky]Cb~^^(=.Ġ*Gq;~P*Dڔ5,Gzyu,pk ZwJ-_PvC ɂth)B6 yF 0%Y]R|GY=5X':RH|D|::37791&*naN5՝e]Qz=V:[2wIL"k}3̺p@LfrRq9D.x-\}؂i2 efzbntuH)q-eZo4&v/{Oȶi>ZT=%Rw l'\}`+ܴ=FU6ˆwJn$EPkOʲ j!fU-UTc9@w%gX{lGg$ R?91NGqü;U՛[ԨYOIRE^BbS+]E%b惹\4864kZ[YgOսdE?5= ޸W$>a&@>?g-GX^)_yj(Wz|:۲` 6Vj |`jߖ6縘Pdi=_+zsp h(<NJY_}, ^wr?YEvySM?$0@ is𳝎ԃO*HAaGzFF'`L8n2 dWew9GBOiL \"IZA' &Zu>!2Dq~N uG,Z)a<#"qcèY=9H=Ƴ؀-p-%*\&TMyBŚ'g|E |o?|r9Y ds2 ~lQVqXW ޙ޾m%$>*톼97; L)^Q2KUmt A22s_(K;#J yye2UN~@ڃq4um8$8)+\%“9 TsWQ@RTt j/$ AAVZ9S5}LKL׀,z|W>݋&T'#N#o7f#Y}&W.icjUǭF:#")mގsY+DȽ_2|rjS<=1ғ7N}gZ5# g\Pxd+m"66ɬFD͔UYn >Oh7tTjr@ |-a\Ls?ѓ7>B,~}l>3V>5g%+[B%~Uk1lm \zvŋM#Y[ 30w~KB+ 'P*O!<ǿkTLˍvN'lTٯdY9Xlj ;yv>ᒁ8hjg @3hKj!w*Ҩ~QPUEM[ۡXRmf& q a4Wp;qF*x4hOXït 3[U2l'~Fww䜀#;m%/+%!$ K=S~xjo{K:Fۇ n|()Z% "T&7 N%3vtC:\wY$K! i5?$* hzU^5>]:O1 t_/k ѽQMHW4`m{A ~OL*c5y;Po/d]*8 u=x';zFEڋv.y1 ;KbuN%sVLX.6NfH)),7]!!'-eѪ9I/*f7VԭTmZaoW*^5:9`6<^SPX>A3މ mєRٱO΃4dtR Q)򮂠i(QO eO7NC$7Mn;D%驦 k;Uv焻J84l=>rTKc݂f`}&-<ۖAL!Ty*m!V/{9a^%(;0|8B o\ޚ|c΅caOWAJ,K>f_l>]>#_Iʁ#q( Vb_[,y/{|]Y "|dD0ޯ9BpЈjACڿSڬyE \o0uCmvoh0ԯsU?Q0zT$ K2@o$^Hu *[^kIf 7^[|st OQWG@~!^]S-nMI ȲBT{1R@8 Ab 2nUԀ>/pov6X> #YыL8ǟ>R=lG-z|a.o, ۼPz<G3 R%酡O L%9`֍h?6QȢoحCSL`V(,Ϋp)*/ P:^Aǖ%ΞurxkhxMhڍ9{th؞O0D'`x/j>'6 CD/j]^0>l`2Q]Dx?ۗXo.*i0Ň +Bᣣ5jqkPkPh 6z\u{=K3&n^`"]Aa^7ݎ`\VV1,A# ժo(F`S\#QkԃvԈ!6񫶇S4R3/Z݇4O Vf=0[nQͥĉk MUߩR\RR؃Vxtxzyj~Rmk˥%x $).ck!A/("W-RatN)(  uKLSqpY:LራU02ɗ G-J LTGƁxz/5(6:e&|{xi_\L{>yELQ-_p1UVnx6 #O#F3 oRdOxvP--D1_;n6:9co} az/ز&74KI2XD%pjjuC7 3#P3 l D՞XoleﱓrBGݕހ5?Pc-3#F-(j/Bᘈp$ۯlTk(=5mڬ$iFcD4W)*uJ!$bOF1=QP /\rLˈS=Dt&kTXݬseجoZd5%(Q3&U lqyߑCX7?1%vf7O)ox!ݿ0Z@!+ W^LRO^k7(n5V1q=:~Q8%<:A$⏷7G\0_tg \kٗʴzY}KYٷ֙299t"FkCk2 8ĎJI)+/E,yYaܐTFPT!ܪr+g8ު(hK3!ԇ|\m_Oұ”þԪʖVo݅ű]^km yVvF=窪S>᧯u'Cef;/e) wL@WP"j rQҠ$^01)Q rkskGAv6 \~WWM;l*%"QS@εɒ-$G< pvs<=Zy?#(D:N |q7y@кc{-qXs҂)N 8To+^5}ڛp!u1`#*DkBߘƹ!l:%==JZdfX¹f%%qwܲyT[Khe0ɱ{10!6xOThj6vԻq`@c~p?=(MDxkxTpoYu_x9x[;laq?tXտ ڝ; 9DY,SLJeL0P(F-=#E5H3>(~N)=>eV Κ&7wnSx'?+kQIَȱ)6" ^@C2[p:9hȓr~dz'CTg,ȉUvyd}1%2͊z3] QDv V37^* ;M] .fJu%$ٲenlfq^;aBl|XXv1=7G"4yΛt#|h:{%w˶0h!Ej,?'snOĒ1ٶD*ǷH( "-_у5l"ƦwMuPTRH t~ƶnq,] RlOO>#g`7 #4)BԌ?'9z  64hXB*⟇#,9/n ۝@( ߪޥS\2(5jMK)?9b$$'2` >aU'y)p}yyǯ@1ѺI"<->1 ǫ']i \%` &@Dj<^Ғ~("D7ӭ1 'gBvPVS"ĺQ 39#^./h{dg;"/xa/|ѝ.o^v7t^C;xo>CJ50•56z;FQnXE|26K]r |t;XIU'0NazY Z#/o=*p-0i rEk,nxa^A ,g=IԲ@)BLJ}* wv"9"N8)O[.͛.,3E?tqЩl)Nc`_a8y[rN?r.c|OɃ@RNd7X;#OxUaW}Ř@5APFIs ґWj - \氂5ʃ![).^*+U41U̔w^kWWOH'It:Vl!K᩠-i*ZS @ag/T{LHu#T#^'Zq'=g.<%rv`N`)ƻ+G"HINH[)áqY; | 3/%NS-Zn#*,2I& زp~ hmN٦M}.Uڤt'X(_}A y~J@ɵ;? ?dBqJuSNO;qqUpmIM9;&&hSCq }94pXuM+4zqEeU7M~>eAI.b]'N@nJx`!L/%:yvB nj-|N'`XWEmB{oٛh{S C }ͳ Wl-N)מ @l2'LO=u2C,RfF[RF9}3Z ?"ݖy&ӛ~OEu@n,tv'Gba9j/Dg&2՞\zFLkb N![ۨCmz0Ӌ՞(/ks;I^|BywCu FrA4הʄ⢸uK63G࠯,$jcHCkks;fu$ډYeR(ꊋ^:Ljktn1RC2X5 u7e$ٜi,sļZqb0KBkl}{*QxqQfڨqT'踯ʫmX4KU1%S`=s}h4,*B'yvy dK$kwezkjs^wCcj;K(N{? L2U'sjMt)Wrp+*u& ]]xkԷO32%Vh0]:CeB|jd-t%u[_=iޡdcL"m@."|z!pQp M`HS@yc}?E'ԓĒz0#MpJ@\O7+v ?Ug77Ƀ^Ñbi}F e'Mb e  S6={kAB_vs6EyJ=zd L̨)uS|xL1!|dx<.?Ut𝦀BBsG\k@U3 /Jq-zH13(m@yhUY 63H/;*HZ6orW9(bbUkPQq.7Hv!gRmtm7ck"x@$q˰ fE^FKL&ytW@-țbꡌwMxQP @8لx@N&,d ^X K.=B =|M]Wjz@ꪽ Bɯ*蠄+2N6H v8Z: `{!|d¿Qƒܷ y)=)R5S ";q*7ciw8 [7 񙂢y6 E1|ծ=/Z:'ԫvm\bHp~7*Zr x]k˹!t [,{9 kNmw,A;otUR{&-6\0/tgǻGWrݶׄR]qt=J>k _P`%Ͼ6M?CS~Ya`AII%Woh0,Z%ʯ#1XڟrFx}]9ULM;;|hhURwHfgXj>ٍIx|ۖ쀪LC* ml&/{` i ۫BF&\q!ׁ8? Ҏkt01g/_hԇ;I>,Z ͪ\z-}'x Mprv/OZ?8'nT^xq^9@(CBߧ'3(Zb0sh1-ѐItWv3CB\ Nr {ۓ(k5 FNQ]y˕px.>r 'ێ?N_Rx[ ?~(5ô]Dy"72G 5ӌ?|-yq3e#1Dvsཎ,fMARӻ6:OyX.K^ wn{뙫n"x ?~? N>p>zo!(T9P_? uT|o>@]8CA|\u%v"Csc }öcURrkkyj/Kasw\vLxnxuXƶ:e)WkE 5sx<@\P>Gdơ΃*g;b0+YW'_?268h[el xh^cv|*ڳapo7=.YoQx$x+N1!o.<]' 2;7C Zo(du`YpRN}ԚWRp hdqJ(jo {ɜI"is覶c.,[b1 JZ:_waI)xH6&d(9[IV͇(#{Q[!]h\Mu+ʷNbi")4_ƬTL;P'XПz&٭~ÊE W{-7% Jikkx#0PN܊87-`㉹NF8dՏͰ[C|ǞB`Pa5j0eS @uLj[d;GF9uiBoJo5C`>fsڮj?D@]:& sb9Us M^7O ]͵Lj E_p.NPs"b:Kku;+'SHS^Xco,-E9U] ɓ3*+L#.KmMFv< !?;:G`~ Lr$Vtt#Lh -a 1*~Ҟ0yMxM$aϻ9i5P eHYg[#tH:^FpSu8vEs_Rg0JAeiP2qk!czښ%DQi6`JEN$*]0} M gSyt4N9T+@[L]O_C`ɖ)޾5^>b)GUFkȟ<}>4&_[+π:B;lxÇA1Pt"6-`v @;( s UƥpΞڤ,=VIS0(M:Иӧj+z{9iId8}K7$p/ e\f*o )f+4e\Y3ZP4]0+}#PA6ch4A*q]6^,9v'/45ytf;wô5 o0 L!bc țXjxN oƼƇa /==5"YPT%qibuEo'e DS>>a귂N#Ѥf(ϑTWtl lYHF@ٖ:H\AAEjo@$|Ҭ#NJҶ)%e?H6_m;\NBg ?Rva(81Ja1"&_;.> #x")mPsBFbʿFR0R7F#}ӏ ܍bnAZe79c!"\vd=U +7Ɓ --F?iˊa56+y=f䘅qQ@U)େ[pˬ%v9qjہĖ2kk"euRG[Fi\i򂮂WwO/1CT+Q _ϣu1TYEIY cz3]3>yǣUAe^y$C\repsd~L=VeK;4b2AuoV0l*Kb' `X<n>և,ZnlW9/t]k'HOѠl7v<=xKM?TwdF Z b*  t,⣭Y(߸b !Vt^( m"8O\"6EQ_v_D*őU;3S>b^z>7I0,p"ptwh2^_DfolON9 9=P$aW9߻6oYOt*Or 93O(N}>?їqk+V9P3ߴe0gy*Տ)%Yj?E'yf/N栔p8LyjXcW-mg1óE*ZЀN6G9C5lI! &A/ Z(r<$OVyn}`%3KظbO6"DZy fAL w8U,"M 5_&H PLcvƜI z?dK["d,=?kWJ;wLEovlBӺ;D(ï!G0nƩgy#>Bn~P]&ל f:L//>??X#F=cH*{#:T̎{ i}3"aQRstX|g%t<`v?IS1)ߺ7MwjRBhҷ6sٽȑi@P3b<-F(Mˤ{7inկ QN:MikUf1ū)0ZY=%ߝ15MRG5$h[ʎ`m=Ja3f:,.bUmO8 kʛ&F9䏆DC%:z_^ۓYI% XI?ꪸ;kl6*P&T=Ri__ %Y[+WFZ26l tę^76 1 \vQւ/&VGO@ccFˬ, ,ܩ 6QdY|>[ 3.Wg%?dpMt)-WlRՑJ[deZJoxu鋇T>s 6 ERxָVR)$P 7X!ԣ2ڼhZpBm7hji)E" n߆s}Wz# T9Œ~w54 0pZrA45'%xtlMT cHDP0*_ 7%Սڨ |e!3sf,'ؚD،WFnKI:SIҔ@E4N8bbmlN]RA=/mD;|e q bEiH䃙&g%= kRb,,ImGe31L⛨k+8O&vkl\q!9 czpuM`aqVuC&KH/sl9 {-CkuEyuI"u5+4  dA"()+@^C6_I6 Kt>M*{py)+o)xkR[ťW3:<&ͷG&繿k21xiԯby6\ha0o؇|L9z&WxPطMrv3v^"`CAZ\{ \DaQtZG⥐N٪ <(g3D(3eE<&f-?_r`s3\qbKH cPޒp;db)@L/(!{ykA;Zg_b"H[#Gs܉nI4&II(ص;YW3|ȎK_|=f^% ~BSZ(ĔT b&^L $Zy#+O)w˖X$)!:v+5FSf}igS6zQ l o?& 14^$EdMT)w9sЯ?TLnpzxefw7SVI%[EL%E w ˏ ˒̖ЀϦz8*͞X $uU֮hG,c1v0BΠLd(iZsJ%ڔtWW!=2Q$QK4V Hw`^V GNǷF^:NNIYz,I%MKgzhJ"fh{=Z0׺8()]b3[Iy#?c8X6{| $4!0e!?.mdqAIg\y)Y`J>a3v d #]<5 TwѮ#7UTnnEN{={8?˼A̋ƇOM&SBCFkPQR#:K?Ҥ+eDK@6h]19|MuRT[ jLM~tŏpa1|3x<72*ϭaubJIOH'߳r= n#dqlB:5nW[?stKӎdnLPYmGsnF0kjKIU\ΕɌf7)Dm#o-vye|"SWA 5 Ծkr(W(D<YAfݵ=ȋ46EdT>ۙp{NnhW :wǃ{ח~H|`MΫȚM UC|К>`jﻘHq&YB+s ޛAč,-!F0o6!'E(D.gRȐpd fW*ş쪓s͉N$Pu1!g|;7Rlw`VmDWr띦BĵX׭Et+`_N4Z.[g6E]I` c @~En[[]e,K\ʼCA`DL aF|iы70bYъ@YGm8!=2JnF s9 E^ASki2n@cuhySu7wq3DŽZKؔH̽#į7ʜ{6|y SBH@k@f<^eGCڹZI܆ŸڹƎ=N$PkwDO孞Wg1$Gņǰ(!8j$wl៻c5*'_@T:Sع%8\D: h_'Dၰ&`T9Fa.u>esJKAyVGNßFݸoMAHj"Ǡnvhl꘰x [?)zxۈ8vvgj Kf\;VO<8w "W!Fa}~Pi9mXKisL" 7Oe)OJBj`1Mr>ykF:fzͦ2~{æ"ܗ GGmZҺ֌!'Rκ8B8փrsMJ/F9RK+g)s:o]o걡Iߤ !Ctk*XrXRBtI- X1PfZwh>_iSVZVEo1t$xl-$`͙EqWC+T) 6 g5;YF[ q!4I-n)eΉXL^]4G!̊)8e7E2?KMd3MR)lч¡!xN,S >qSfϿf)DT[{9ܳʌAky}lG{6N[[zyHّZSN4NUNp&&5.Y[9:h,;w+pT HD+ZCt[2"#gǢ/Uja;wuwd8U/ҔAa֪efA0+|ȊBFI@a<} bT^r68C߆I2,*iH^7߻墶sK#=V 5hcj+5m+5 pSZH{oT'2ȔV)4@W+ak&XЉ+:FXٰ.mO|;:E[Tu R߀Х3c@!*X_򀓜<y\fnL?b4?@4MBތkػ nWϠÊI>=hggnEgYmcCK*Kfa.j0~b dMg8} ƋW#>avf_EƓHA5_WR9v ٧ dPIgSLC3 ^3VḩB;p[|ٽmh[71@7ݜ?u4(&. PzC^q"E()Ye- O_6*5Q !&yBQ99دQ]%ZzЩ78B_`V+r' wk6~cjtrnŶO8z1ƘIHddm88 a@`þ=IK(*ފF#ٿynGo"i>f[K^6?mTݿ';nC9;[ES1G!MGVqZ2#_s^ o/y|+=򴼪`NQ\HNĝ&Mh6GIgD8e70}_z(Sov߆ T4a1ԩ`ɵB<)z/SxYkmY/ Er2ط0u?{J ѵ}dz.9!?r>B\w9R}JLc*#iuuゝWGP?/7_c>~/ Lj3cBo(TZtG#M ZFp֭}Fe=/f*b܂ gӅt4-mק^RM1 Fqg\@` dSؐ %ܞB3t-x"O`|DS7)uFcS0,/ jR^E_ Lyo:mX BT8-SʆàG L3鵣Ԣ OVz)/^GzÎy#qy"wsp,ZwRfn:Aiwu7&'h( >M#T*AY&թ_|P5 =x`%Mt's }qK6XThd]`A3 .gۚ7(nbAԗ`GNh~3hZ7|ڏoٶq,$({rfN[X>2"t һWY-9b5qMdGV(3e g5+(VzxO7j{ĺH?Rg{ hp/&YU v4 6E#bduD{M O6dCd<#ɉ#f2Tx\|Kjv8'x_Lb ts<"sCt_;q=va [i3j'4)Z+ nv#r`) lͻMDGCdƓ!78-Nog6\0)XEv}:`7Qt:[-2+-d=G>pڷ χ;o8U9| *tL\Qu7 |.z2)`X$RmԴoE讲( f #zg0@`*e\^B/X#cD>BɦHH5/I9[=NS6̎Q +h_-<ڶ4t8'YpK[ =5d0W-Cuw7DPtHà9>+3Fi i4?aP Mޢs/94^vR*{c"D+7Uy||,P{GlHNa9Lc"X9}1a:*ăda-p$R($ǖ| a؁pդϒasݙqS!;H{ᴶ!۔Z]drk&Bw|A֥iݸ`YIE ґU~/2+K|*RR7EE.YpiqX@X!'ފw[jf?OB~z}vH-43 Q"1kZ#)E]vXBB*?R˰nC5[r3UvEQIt*Zik](Rб[Ϟo0c@n^}+#=4)z-қEӟ_{?b9lC+] kEjbΟ)eN#b<7BWs2dcBE 8]Y{~u6EooGAG;1};S(݄o\]hpn`R0#\] 8ׯXSXG@^n)z>*φ`sZoӴYgC}O闠l7 鼥H R+P~tb̙R2ƛ@F-lih/v-BF~Cu6Q݇lMYwr3*8DHᄍI]]9h T^GVY4zjc@s] ̨ hyZvEXzABݨka5pq{9;O$=*H`-ȓ$I'b7O{RV X9d#;mY?5KemȤ۷ۛ+5VfCGsM\WXܥsrݹ~X"ޣR/( luxm{vd֑?f hj+AAu=|:-iƠ{cŃj(hvGxja#şijG#>yDSS|g+l5O-4T<$Bo̲)PU̖\uS[t+WaB]Kr-̺ ιQkߠͫMfC\=@|撸6+̆*'EDhߏ;%?._VI7\zƍ'߰9Zdڬ~^awz̉Ju "Qt7zvOώ?#Ӗ: M$;<*5|QWݸmۨp+. ᫂D毟 r!|Dp?uTj-G튰b_Z-m `6±xJ(ve!Q m>iM>1*/27tKL+=zgRӹ)`|I3z"]Bz0[i%mDAfO܏Z=>o •WVI[! vLw<gAC$_#ʄ6f`;X0O߳s?5[蒣0 ,FG^A* x6+2V/u*7\c*r yy=dJ57p|`Ϟ ¯F6TSXs;.3ļ_b$@5 {Nb-f?GC!+ltk8 PNͮ8>H=Ҷ ,"R$|2h|J`S&5hA d-k.x `q}­ (s|:hi޻&b ܰӄ~I]p+S|]פax|t\Yz<$!5?*7wb>Fz6F,*9l RSOñV者|>~A/2?@ U\naw+:76N$\f836Clti/d&=5ܪi28Cw  UO{"qYAeq$*pC3 o?gT_U8f)op#R Ks5iwr wwDxBYkh#cɢK=[\W7iq 6qh@^yʥ%|sU?}.SK}:Rڻ0j 憧\Tj0?ӈ}X_h m\K+f%$}/h6a(bPh"}qj;[wPCR_Qjc >J.D-3[Ad\gXקkEEoS`ﳣ2qѷXLD-ǬE2CRBٓ?4ՠTy7ȹf'1qfbql<_?' ޡ 3lû8_]KFxo%p-& 1aJـ^^B!XtWQEdD* 'Gu%,eGaChUW/Cuw:rFt0݃{sڮ_ː3DSc/39E ~]Ti&ML݊APEoL -eA۸A|<`|>ORj # <oB rkij|ՇKa Q2nx^Jͩ}*Á @8 r@Q!IRB2Ph=6/WgJi"cW<8H9i7Goi1n~̻(a$"NE)0qjKn~pVZ9e*Pq2Ow|otګLyV \& p,b=@S"]UJ~&<$FYUSGACBE ŰcYך ix1:4+SF>e—+Sa~F;Eɩ|x3mcq9Z?6+JOF!)Gcwe_Lty 0.*]I}K؎ .#$ N X? t t0GԩCJ]=چD54tԢ\gYn\J]m03A{O[)iE l671_VC1!4: .ǬoFVb$8w)1&_?ĖnuyJQ^ y@!q| /@2eQ+uh7 9ep#6uѮ9qC0o۟$IR1D^m^0{Pt&/vzW¯K Ebdo/s#xŻ,\{l]XV'aI1=iZھsQ%n}q6Eu0PqԈl( 7ʈħ04?qMH=11&>$l\RB5&vYtJ149᭼6`IlrOo8՜=zxϫܩPL$}֦̀}c[ZVQ邕/=|;bf۰&-)pV=瓇d5WO{d{mN'm16T?L^RjPYV [E(Vmh<;(#2يn wyB҇気O6]AE^hx;|/yuvh:՟rB $=,N@Ik c.:cmn}vFOZM?Dw5_G P$D풨9bcpՉ< ÛcNxgT톺ihv "TERmgnb_bAFv/sJ %SoȇEEDH g#&@ P5a<2j0k 0Losi}| G2iG?W찗X˒J>!QW.ڀ"U`LK\ou67LV ڄrHSf"O^ KhJ/3`=v9^G'v:k\%BkhWѼOL\64CJnyoZЕ U"1 Ȓ # 7${dقE\wpzӳ3-Ip ?d֥d=d,` "Oߋ^TSϻ_{{|YQS_NQ\R<6 Y5=qK䢌*9QqeCq=ip9eS4?yxLsĒOO8"rKyhSOu=vf))ν4"걓hu3,H,(x4vW>Q$Ə SAVsJZXG5fP obEYbԌ<Э^>TG(vQf8l@/䥲PX\eesηX^/ |` edXʹ4T?JΙ:/Y1;b-ˉl(f5cbb-k$1nqߑH=G :ݦK,{o6u{[5uAݦbfM ɪ)l58, b(!: BV+~e is BQF)Ǔ" OI6jczǀdղ8y(f @N EӚF$*4ZkAxN5 dtFX"ePe*/!X7-.(D@6wK["ٰp=%; Z?zԗ!:0R]T8  i)A$E,X">Sf~6=zi(JQ1!v@ފQEJ* QQo=WnndX.tXg٤B3|oS@܅s =KV1KzC.XtdS сb@n]ŷ v1iH,^yD<Ϛ<|\XK k҄@'lCgVd`wI&YTELZOs qS!x+)s1&ʙ%Suz?K?~{!k׫^؊df"Y6g$vr~M={sGT>"P礀-yAwii!Gcc &*N \+Ia)TF^ъ꣆S'v5[D} MFéi^<%`ڄƒla(fܱn ;nA0'h5.?Ne'3yA_UM8J͕izxPIȤM8b,k4lp e_ğguηCJx 0拴rŒ31xsݩ@AMV)\ qN_9qR̃`Ӎ@ BL^ޘo.Jo5S-hlAtV6QE_R,h\%ŜbO<&鄷(sn[\rDX[֘C\ 8 ? 7َC=7 ,E7%hw>6]uzQP:;& b{f}V|刂(sót^Zڡ=DƬ0ϑ_9 ;P]GƦ)/ N\Ҹz?h g4㣰[$c*RwSu_9n ̦*Mx?Xqm2euf/x>L7fl(l!Xz^]KJWq3$Ar[>:0qZRl12iX;oMBY/z{սAlk/CBM#QXl'w,NYڕD3i~^x^TI&-c9ðR\W8:YxOL?bWJnkE+ b 蹽j n GP_0䈧S"]$ =4[]=02۾'樰n5Ѣ/R{J>H&v [d3:~C?(^qX]NXhxa/nK 4 LeSͺx$K!뛍3Mrt2dBp nJY2S`_gl`]EJVX@XHbV.,;}'kkdj#Bhۮjڏ$A'բ۽d۵iES(fVOfFtlיaэ9_E txр18*R AV&*5nWP4Eiȹږ (rE?6`j^3Nb䧤uL\ W1. 9cbО]֒~.D̜'!eh(#ɢԿks?<쬬+zAv+b8ElGŸ .L'\3a `TY/Y 3%y==OqadQ7U)m5:&B7nh67 FQqDuwt9W;Ä&+!>/.)GhƝɋ/;W.#)?ۍ19XsLɺKHjH>da "@쌫;º G.yIʊ|̴N4u|߂)aqqsd1"~-x;7oYMxRώXm,(^O.$ n<F%`t[ur?D?Q:kҾ 'D-e3?pdWio؟o"͆ >"9o| Yl8w0)Zw+ӤI![DzݞjbX/.\ QH߈b16T2G֊ÊX|=@ )MI+yxUrXݣ]d_9ɓַAAISe0k=F55% l5,'HP(gƎg4vf-E`JnҟnIXkدp,MEK+H*蚈y/oL73pI){8 恵ywGiWE3cKS = /ru7Gn$L&؜Nz 5'AOA_W!GZ6)KK$&$kAZ; dq^xKGA.T*VW~a4\AwlHz3 :,W{{BY-IȻȷ "_؄E&p6k$oNK^JH5?qF/m+iZ8CiL~ii􉟼{/q߆F1 9r QJb%=!6Vr$?cGFbٍ~ftFN7zF׏~OB6#|?pBPB.^M9%^njg%R&;d47v̏]f`@3ݛM2ı Idk#+~UWtU7@9_hAb>x+-]: ;p|* M7D~\%M"hPj"G<*c II4Z9#ݰ$):|EʩOa3>p|P/,n- g&B+ uW֖`$)|ֳqH03*rZ"Ȗ/Ȯ'ڝ'8s T ,ėꛫ-Q y~a e؉ Q~;zu#Yq.,׆QwXc3p 8mFyZU vzߴp|*bw2g2DrF*n!GR K_G%ݑ5ZzQ ]浗SGs1XdԘlfYa8*7ݲ3ӡ%Ϡ}: pF {xʤ.> ;VPʉ}K/ƞBWs@UBЫ=|[2_^'`_&%pE xWy 㦀US m3fE .{}f?cS 3x}5ށf?ũ& ~p?|QKZ"l.zhr'&, rJ|ԙ{88VіōZɘ}g9A~: <d`w\,* l5?Vz'"0CXh~R}D%&N %j2OdߥN&Xꗕ_< $FD$HǟU)D2wNU(DDcT {v$ՃZuCٶ$ei\yQ/۪SWE0bxg^s5R*~fbl T|m. H ؾH89B2|*hgd/rm;qY{ׇe>cUkz - V@1hX~Hq!S5ɝdpmq&VZd-Rj#C4?X.sVclyKxQJ=Q)P4FXq9|-*1=%kaG DžzD0ZlXBP]'ju,Պhz,ɤQuʻh}x#dd#M#Py wzN( ADuj 7APu"fS1)N B0bҤۭ ߻G>dgOd\Yt:?6h)Zjv<}PM-կ4& GJȄR׈RqZv m޻\J@DU(6Dje#TX-2G{E6:eՋ>~6z#Ȳb-c* )GGu[-]n?/I=P:ބW^ ,V}$khpÕu^ɽ~qk C&GVRYۿ V}=bsw9xT%v j#95\/~:'M0gGZHii(Ֆۯ& dmo*0<87M5M`)i\neZ1G$kQX_޳s9M{*%*fh1P(ŭ&g^p- XI%iT!-(mv Ādj>Ճ_lΖB?pƤ` <ϛ)k׈ A#[9[˂vOkZFSmLƐqzӿ }Jۀ6Ƌ?mBR?{.s.Kz(n Ù"'w*p|\s(87hAy#j}Yhu ;Q  3+a-M̕XwJ`#}L'.e=X(%6iDtIȣqޯ1B$?Kΰ()gC:UmrB2x[td3H1{u1Z "_. ʑG TH/U,Rɡr$LGW-inNBD*Nj\5G 6rUtghϜc.g7ZK 9[&\i cw;-= 7bQ-iU” }M+1CJҭZk4;%R4/f,61mOBo4?_jxZ`NJ~3aoZhBgC- ,$~%6ےcip}󮝴cN,ل94Zc +WEY1 $q-Z?2lA=f,T _β4q(؋~&'F_,*DRNܹ'hZ9QO%i*k>\o1 1\4Z}pxd,p@& tl"*B^oguC*w,L1 [X|DНVGH?ӏjh9a`la"OS*&Tζմ⧠Ġy4-K";A֌~5q  wp|2:W힍#J9B7Ze{u?[slćL)R7<۶`)6S1xYVq k}=WjI[bOis9$6,ڿ&_>}ca_% S+ پk gd^f4L%t{ m`\&Ifn$|94M[(#i6o G%raqu~AU(ۻ2 /?K]W)nKK^ɛHTO&j<'9 L rs_s:F[OYYL7›"Q/L vIXΖgFQ<9 `t6 8j^aWL4HgS<Ę<n>e>ɗ \ב.֫tZz 3 7dK<fAM 0~_/=b\a_:U?bS6#e1h zv2z?np%‚T~-ֹoDLO9< zSaWWjF,+(.Q$$n(t, 5c~9&x,qcڽ)ݚ ]Ih,A%6Y.2-nrI\^gM׮$hk> 4z q"mY{)9<5dѭifDJT̺sʣ{ $xcG5o <p;'Ԏq=kA4l)soMe⺙q7h`QآHB7g{_'4zi#1'ZW5Yׯ#ȶ\olx؈}^Iꋱ)_Lk = t ^P+喍?7%z Gy$fI6[f}$-%䦽U͊7Egw|a`Zw)4O} 6zEt ?Gߩ 8B:7e4 B"T:;ndT:CK lJ:7yl5lqdFhX-0T)fk䄢k m]{_.w\r\b .8hLvd9 2t ?W>w!Ⱦc5(}ui[t (®#~ߝK `r2:Ͱ$TfakDR$U˅ <;4=Bu*$WJ zz{&h4Ә3]ϹJ =#č@:q3Eѩ}zdE+i%Vb*vB睋2εPb|q濫6qj䜄y`,(kak!~z­@IP |HGn jOefլcuz;@+m;M8:#4$V1* &0v3T9Z/7 '"N"(hU !PXej4f 4wO9ߍ \V*jwNL?Y] pAlFy/d`^pщݘBu}V`FTl;D*D5&F`IOu\*X]b0ƨomA,2l9:ٍ$xkدs%T8'Q-j3 'Wa>z# HQ8_zbN3207oAX[ IޤJMQu߈= .a2]K1=6.pF(2=q)}U@.Ь *s_#FzBH|gJa#=eBwIA 9 _]Y,o3z$q$a~ <̌O錼@B%-( 0aOEΔ aLmM^(o Nm|M&5Ts0r`9%cs1& HM6)Q.UҠ^9jI*2̷ yyoy=W4P`Ur]ݻw@(zP'~.X{HtjjM6 kr7_TzsFlbɃ#A -fZ_z8+ĉ*#me;$ mv W~h2cb1VTD2ˬٱuv >/aNƚ>X!4ŇaicUrxrgdޮqPZC4N!z3S`V; clۆ>{WFcI@AiI~׷}ȈGҾ6BL،898u%ӎg|՝3؁,ʖEEL*VI9"Jo5'yJ/<4|2m``M `s:"(vg TpQxR+7/E [:D2wH~ L^&PC p I7p@gr6y;Q_f/RkA>X R|K4ֹ|x>ϾC27Ȱ  2Qt}Ԩ|]fIiVh7lA~R\,{K@o.X&:̞PgK))n9^r{.ϊ@.L{vzEF9923+Np  jUX2HѺY S\eUAW< ]n 3a(NH-4 v?Y!؇i`h0 h&!FY݉_M)"S`%#0gf F>E_N|Т 1;Ȼ6C>H[(~ SGMRCǯuHdaGӝ_!?P8W)hw2zđ6U?x3ey4w@tVZ<)*T^7$09Ҥ=Fwp~td%,'crULO4W ]x|ڰ <9A+cJˆ zLOa]yn2Ieu?BcXSŘ@%[j7EzJ7OuPF"aA A!^tuFp)ۍ⇚PokF햋جt 9KeX!i~A=P0Yb6%mRͫ:LEC%.ӿy?dYs;J hx ηNnY`:2L?PQ  %i'͇R̎-Rt 7{If-t'ͅP1ږK1i Xy Ӽ#FC-e9)km9ty<mO*&r+@ ,ׅ9^Qə,lE+W+_^nz+"'Z!~A5ؔ)EeuښͺHwtiLebf _:br2D J&mVJ~TM߶CO^量L PJP@`hzظ5l9y/EKE*Q.- {k&j3ƉAs[b/ ez&{|9U o/O@cV0`C'O #Bu1V4ڐ׷9xpudQtv%X+TxmHdQ|#Np8&'7jm'ط9)VF,.C  `ec&(Epezo5xPhk;g!D,y,›pn.L.4$ȌqWOT;ȏɸƨKh+gm>U@DJ8NYjkl K%ԉLЧ0LH0\{uWԬ)@Mck\xrDS>0Ek7 ~<|7r/Gd87>L3U"e oļCjN>;pt-7uH[N%]XiVo[])GNjM Aָ2%_f{JeE h4s}cBɒ5B2x8šG ms`5 )rNCXUs [=Dv/D aK?s dFت3DL~6Mp:DJńYu0WƐ,=@|)CBC q*~] dA^<ר}x;*E}@VQkYư]0dU~nk`AjRI4^ #8XAa, zQQ-OԪmZq9J6۱7xP36KRZ͡3$Sc, Xn<[S+[w=|EAMH qMټIX&j6p- ANVJV##c-':X6~] jmR<(,Y Y>GI _{SII dU=wiu{ 4aK~EDG 8$ftMjhE<:jhhcvF]ȇ$YɉhnL…N nG->J35,VĬbp9Yu^aC"n`}QI;WGZ5<Ȁo> \uB{g9n:nH\ Tx3M~TI^/Y|u\JN &+=;BJ f"`ItYXSW$4P:߽mW6de؆p&z"5nj)ۥݝFky`h P Z+l ^BJ-i`NZñe TiK.- ЛU{CXѬ\UcqU6 ;$nhʰ>4qEmm$%qgRAKU[J;*Z ݁kun=~ WU4 s:_\d)*pLʁ٥mQ..\=8`(**ƱXmJSfXEh,\qy}Mr!=zer %OBi2;|qv[Ze~Y([I\PR)2aDiL6qnlB;oga}3 ]콸,Asj#kk.ߛK %C4Đ9`f:&&~~*Jzh,FF\ֶi.<4N|=rNnPγ`ЬȬoI uLݟcI"p1 { Fd&7٤|G㲒:vȓau)0A }x4*}rYs6!R6k &2Zq{@'p6qW/S;־vBǜTHw˟IOzi^vJ5d&f6#*8["LoLD$YJ>(L9$w8ZC.MK,83ڠCmag>^Ԥ1hϧCl{f?%#i5b3M;~rqY,~[nΪ;Lk~&kѲpHX"V~ѣ>ɿ4RL)NC]y{|"Q1\SlP)-")6jN>/wBV򨢅JX-k(Ƈjvcؼ(#\q~d`jr|`i{ҞI54{<֟ Ţ[?I_/ Ơ #Au.GcCˠ*e”s"_Yf,z1MMQohoia{]*2(ۺ\tOe,Ӭ>(r=[]Ӗr" 996 ސ6w {}3jsM.aO#|F+VVT$jșLx#Ц?ճC v`LϞq~K!+ǬG<; #ebZ>mՒ5V2eVιCQ\H6>ScW2K"P7lK |s{M&^e¡U6"ij_ZSWkam5B*OA3;ܸr"̐pH~sO ulMb]~Uyt\KC,/]q2>0OKǰ8?T!JB+c)Dti;U(`|úlĸfP1$D׮m g_}F Mhs+o}yi؋6 =vߞ91}%1i$Uv1φgbSO9tBbQP)2Im S P(DҡUQ^I QC?쩀iɘĎfJ+ с`i}V f$uFy"VWa 5hVjBJy\^F z0[KK]Fm,DbPCnGMeVդ.d uxMZKlw(f/7wP΁نOF^&_:NU!2NV 4a5Eg#CjՎM#젝CճXI][#`2I'_9p*I\e\9whFv 0_L\PI6cXb 4SۼlN#,\UӜ3ss]]7DƷP6{U!d&ոY5}Wnv/8Em`x2 H^OXs[Z槥 !*?N $) ŹN/(MKp / !22+-r-95evU99E1R]wL& ,كC]Xo E^;iXݘy k)gulH-ЮRן+6z䭶*n;Z(h5U/ƒ>fNrWybiĕ뮳2زqBz.Idq#!= +w?;ljf#;Шx0d-ib4}r~~۬IXVFPA-&rQjgſk>:GP}VGMk$]kAPhArvmWd0o߉SqcEܩl:=F#",\tm 1P50 o)!=uh|nY]5;8q/j0 Q^/g}NyzGB`B`kh^E!孵p.!HgAL4qRiFR翑bu hQ$P\ ֙'4ceA9f݅vCYҟ++S躖U1q^ rL$%H|ͪ`Tij5Ӵ"c~@28-'dt!h1ܜ󋶔)@FߺbF2*'iď:R ӯ`y|a>[.˳3L%r]ϪG^ ^Xb!赼 (n`PH&P;EI0>B fOXAq[H 4DyJ"U]e#89z}B M}4)=QWCFm9.-2ݫVYߤG^č؈i/"EJ45kϮMn} h.T}=/6 ̛9⦧uA_ӵ[‚ߟTi8S+?JL|\ =A6H^ "!am,i1fDn)$e%{ۛ@s-bPP@+ޫ[*yG{5XDʚ}j^; b;: w~4,X\]jS|TyE0 GŸT53 ^z|dn_U#O`ncWS\jRqrJL^+^Rm)QMy9{; )+\ĶY/r'| k 9=gHܣ7Rdh3j,21s eWd5˦SY]SV uye=$=M`~&dA* LgmLdC KkY}8tߊDV@㇭QKꂉu49$I\+!I~+Chת̈- '\#* ҉u*o6)F o?Vi1SYI=t!*s+"Q ԩ I\ hl=x!] \e 9 0ڣ=s( 7 Q8.]t?( $rX*3W}ݿ0AyC AI x:yu}lrE6HhOV\i]?jl7ׅ_O'+]SӹRt^ekQe{?.鷥]\!_Mf\ r-M~m8gDNs$QrFC/ A/aLѦʮջP&E`g8 ֍ur'$7g5|< (O {"~h"+gNQPܢy [EL03X#n STޖ WbXV͖d3oTŹ9qF`_!*/ȯwD n p\nM(ޖ3oa 0O̯jݲj./HU#~W%e@+Ci,8h@fqَ2$W ! kcn" L>|-g+J>4$N1hkZ9{dlk:7aHްXMhdLMyD^0t$$sד,~R5^d^Ĩ7pUD8iZH 6DuI{@F nDҵZplI 8Sc#W `TרoI&"ŀ+[Ce. ~.4Τ/d!q*l)61w-)DIZx2Aдd$$ǿ'g@V&, YJ=;oXX(+NQK\ Gv=μ%?DB tΑQALTz}DlFYª&3vQ!e&DUFqlV 9HB(jdԁ9Uo9,%eXg5QLQ|]Qʯ\KI Qͽu@$mG" Tu}^/2 {k1ɗK =V(Y| ErS5 luؾUB>6@u77aixd6,˻arP0FW3A[OQ)LOjMF%.oIw i)g1FZUc"x vh䕤^Tn&xb^+@>߀OZ"["!DG,!_콼p&kd6W++otn<1~3z(:Lf#L XVaJҎ*w*v\sW{Oipܴ1%SOݲzlJuěx+sLb竼35$@q杝!!wC&Wo239 [')vm /J{JIiU |g䟄&L-PeID/;|asŽn_tʍka~({*u`eA Q b N)^s^tRK!A里KW)ϙ %JӟcQ1d `rniE}E$W_+gc0Ao߫B:EWB-æel`▏zކ^Hrg%Qœ;mB.* LZ52 ){LTa)TB֩=|ʬ:%-#U:+8cp87G"RaZPoE)#cl29E0st\KdҐ/v@L೨wc{>S&p7n`zX2`y7ūG8Tngn<$?\^u(.f4CUUbCѻ@Bopr@oSO#†OKKU^io,c'cTf_HD{ӝfe<P7/rsVī]"|Kی|.My3 !m%?L_Ng(] <]B qO'E[>gV'Ԧ5}-Ʊ$PrS+FLnL0cPWoР^kPT"H."*CK@xL8@9sЌX8Tz #AvMo͉ cjLو5+h{ӎ72 kx[[E,q|hO\\(ސRTV {K66:1ʤ>MIԎvœV(jb\,1jv@q|!KA=P Hh$%xG>w&*pA3riy~w4A"]:h%7<*|;,Ur F>iV'v)v!i X~lX3f'ekMxtt`˗SȷU/xCB5Yd ,&G ip-Jqj w\:4{$$e{6~8\Y2^EڮZ&Թ6Q@JSDש"4oE`wmG߮eo)v[AzC.g5\*A r}Odu̶G4Msś-ˤxͼDU-1PnZocEei` "nz .C?Knzz3[&}J)PyvcYY@\`} Tb~. J]+N|7.E5/ksqbrK Z?WAY8 r/ fMJ@'aDŕ@NZk6MK͉XXUO(_ύlC\Q@^>ө(ǖ);όQ*zLDe ahbݤ^k1 /+rsՉslʺSEF >ex -hэuĞQ w#l$[E'>%JCR1m:$3xj?1/ d񆊅yrIa. -zLvٖ\ /uܛǍSNIHI'cA2J (R#7@[G5J2|&XW VUo y/]H۱1 fO4gޞ 0:x BŘ')Cva>vlM]x߸b:^aơU@^'Eאc @6x:b5'F~o,dQZl֭NYXLBR X!C^4xXn˺|F%ႄg[̽6PI$}NyC %"K3& :8 O#Bgl(a4m/45{>q来qb#p={i fp?ސ,aM]Bܧ}B>L$h *k Ia8l< /R$n@[rP$0<1?fIQڸ ֋]1P:z9ś1O(kZԎIUO,JI6Z/""Ȑ 1V_{A}w I|u?~%̆'A=2y^yǡe\gͺidj>NsXC`8Rڏ12fSʫxh5~ ϳ;#k?' *3e6dmJJEp,ƒN4 L?"ha͎]mwdخkje jsvyE;Q-c &/JΪs֣V2Ov;|g9h*x_o"G0Q1vќ9/G68P{Mg6IioV"*ǰ]DD'nh6M܌xv#S/}dRÇ#\kşROڏxT|}stfxn߱P1XZӑGn,0.u!su\e]^PL P;ЭNF9xlN]]b<uB/>;JlAH]].5ⱠV6 nFoMQ?{D7U~avC\7X̌ 7&@iH,TS/](nXĈ;vƽ<f lE6c"r^?16k,M+@h>ƆTݖ+<ʊW@F$Xdy{ #4pSs𾜕_gOpݓuh͔M(ȖY1Ҿ\ r/IՃ4[Aϒ6gzw$femp~*PECEoՂ];U1Y9z7Vx)BJ.⊨CMb]&Q^w(B vMRVu̇8F"/1EDl܃7FLbwBd6*?q(Fjg?o!9 FZՀx-/~v% #9vc]Q CM+ݼ \*ӪjdlJʆ]%f!2mvyـW'.WwL)挩b U& y80䑺 7w $˪| bXG"cJW[ncG|K:F`߷fi%0Ẇ>&șൌcV`YӗZZ+P]cBnc۵́t{7WHru'Eff4> Knw=d/-NZ1{6aK}20N[w"{ S @,?H*v 0"rvGG~B>&]hg;'& _ٸϛdZPiQKmhN](}狹`q̈`R5M>nCƓ'p'W w듏Q vβT:u@jk>E ({pJր#-W"96}}S'L5t!kmՖ}~+,8հvIMRDe[+(^qOkSIDk3[8a(YƌX-3ŧjP6 z;g^Te>&U"W* h+Aɦ~?RY)p'1fZEG@ p@)zEn(dJn$Cے_Njt!1%B<{DWQ5qm9( i9>Thx(p3B ⑂ED*ԳB_5o=R䬪ŋT^riȪEeC:6q`?t`}ViRrN:}VQ$)3j}Q46Yˇdׅ.9$'Ww6N1q ^ .{׾:i* )mwɩDe ЪqulYh#KQ;j!2~\/oz_rF6&((XT-h?p @&]2 +CՉAVL< f+i;xY! IVkW f &SۣOw[sILNﰟ}_-|es_WjRG)P/&yzKą?1q˼D0g3Apm24tne'vMnпbr.-]ZbF-5#҈hH EjKO8COJ6r t @ L  }@ Ձ4/kna֏쌿>1AB~C-v[('+?TR$e4t}+_tcA ɖ/A/4< +$^PK4!0݀WzC&kR[N0Woј=#E!Q\G_gYxGTjfuޫ)AGqTP9Vbs?&N~&a@3V`V# 1'GIkz1`}tD=o=T$o.g6ktO.Q[ ? Cm'kfWXm鎏cuUma.l#]M/K+YL,R@l| kL4;A/0!,RNL"XdB#Q1rT7>Rр#+Bcxxjr)Em ct? j6>u\"ħ'lwp .yX,{%IP.ݛ1FBax5fN;hDFf%| ԝNi#Ӊrc thod*)K1"`IhR+ *{z4_*4/MNYf!*טbAmo!x(86FKL4iA, &ARlpk&+O1: elD2C ǙhQT\>K+?Q%kُeq:}_9"2NA"\LGpS04oQ;@mdE*Aՠj75mc .#c(cYv̇jE՚Sk7D/-+շpu]4MQ8 KVU}&x<.8 aԲE Uݟq69،J}pV'@#p9t\G_<@̕wShdFV ^:gh]Z$ND pS ѳj{.20.6ގTsw"8}9f|h8Gy`n'^ :dtgx3L޴nmF# @6&͙1e|3[8vjvp֟([y؞ "!&e$0$`=WixdiC0J<)ba{2G>:X!T08h1vn{C[ry;u__(x()(>,BىݯUfmfy(^6sK͹r}ZJm^G1D,Egqr9  ocpmME@?K`ȢS#ML'^|Xu.=mT6!dH4w*U! T؟SBtg9vKR]ϲp1R3b+FQ-C1K=l/H=P#` pd':,ؒDC";fg,T=G$UWAny.Aꠡ7l3%%wSyx #*ƭ Jr(!zZmWl +0=fT%紽UMRJ"L}ϰW©4EA stu6BN]6!d5f`/oȻ|nu;ljmll3WmŇoN4bN@cȪl[+tfh4kh3oY?=;UVg3{ )B5q7O 2&+3o)[bgSmKm`X]TǬ$upm}J%A*V!`U N)YUg𱹐_FR` "1P޳^̌5 gz0 d7[|VOX~\s4aqO@v}4;v9o>52`i ,/,M& :{i*?`zH<9MF#F܉R"m yQy֟ d&ʘF"w@,52XۡT'782 6:ض9joH%wP' xm(|>%y9&`k6{{H(DYta2L(kF]7˘/l m}?fFAf%iIˈuq ܈ҟ~z5^K~@I%`c*FjD):)UqقgX;Xt-ҴEmV٬m52xGeAk7jq (SKƯD~ߕ{>M8_LzzJRFS4/sw.ޅR(޺YjuiD k@+n`% 3@["vn{`Z̒ڻwTLY4ˉDfSO\L7֘Nd8+):*8c[ ,L䄈"lX. "V~IB5L7ꂴjoPžgԯ/gBU.bZ<*PAޡq鬰l*wRⰂ<qfc2L~6.᜵7c`Y Χ烬 vD,"AL^rjq zG9n.=[br4 A6&2zzzdtB%dE?{DJ١jWTMi =mE@ n~r| 붉Dj>IN&gdsf9mEH=Z37ɚ:[$iJ1q[2" %; w:xY ٸ|o6֟a(|(1) @[s"(6poв-7&=vl205-42&G܂Dv *} n?=F[HHkmNݎ~͌{<ȭ {E%iOi$'?4U " ؂h'4Siж',Tx@ɓ8YIB["]g%;Bl,m eJ'd':0(gt?.= ̨V$Ee¡&K~!Ma+/fD"q@%= ?:|h<vSDAm ]FjoFS0' m̛a *g~2}|E:VpvMZdaI˞#Fy @>z)qX &Xt&;\ asq1\SW \3b"/Ղ[ OOHF" K#+6Bb|wK i4o$cBIZfu##DF}.lTX2m«;cX=&9DDX&IW ~/bXn8vE 7`c`4$[]Es? @CX n,Ќ CUpO}hY/HQ#gSR(@}dD t'WCĂEܤzq>#aXOc0veZ-db hph0e |TX=kY?MAx{c)a52'N. f @B 6F{)∶-4*HjMxTSrD74\bW$ǣlZB] ږBDe9h聏A$ gӟŁΧgaX5YgnԮ}P1GM:˶?Jg3/9Nvlz t3({A҇DZO5 ?4 q 0Sl29v6O+4gqF9l+ggl~3ZE++h*R$~=i<8.S ѫr/J: h>& 4LB8EO&ekȃCryN Jq' ڋ[epTG"C zwQ ڵ S#X +حw:2ILF!٥{!|[c.:'s3f$(Oh"nŌeBGj?4\l Ye 6Љ譡dŬя?&v|epj?PEj0I >A._Ϙd1U׿&j>Zk<[>骮KSThu Iry$&C# l=<EԚ=ܢr̦j񤀞w*cO:r Hsn\ CҊvZ|,%3< R%+<9OUIe/xK6 lJSNU{>O"YBx^7Wk;[Srl !.<򪖏HǢ1)@Shv5-6 L݃hK|dOĬgp03}Zd?:CBbj77Äz}[C6,AoW)!ꀤH!uOS59_,n 5OYWS5̒:h,4=0lbYx35)dwkͧ7o}"٤w/ߔN ԍQ(\wbkUs$雸0Fs[ oF*fӾ4eX.Ax.oŲ@@EE։+B9b/@{z e/y1`s r)u2Z^r_2^Gy+͆#(ԩe@[0"eBSl'KvB!TwؽePF}VF.Żf_հF^ /(X7yo(h|4]26rXzY'c0gΗ,aOwK|h/7(Kv2(IeA_#|Ax5RBnL}2$۬ڜFO_d|t `9s_AUkl j [|=~17%F=W $cJZ|?MCN֮o |mimy:\㓦Xv~!#Zx-*B+F/5(2}{XunoWXLSŨ:4hlA Ͼr H1(T6Ոk(NF>˚Q5qΠ'txmIMFEs{=XY_3̪`u+ S0 ż?n8Gfj &橌cP1;i/#Vzf6#Aۇ>{ VKFՑzQWoR~|Ha;8Rs#B g^ <yZ8KGqc%u䓕SFJ84F"B#RW0]P !B;RN}} N;Sڇnw,Y=;//C8ּ7=[bwf%~4ovV v_芉ݗ~zF t&"*VùcZP?8 LaU@Ώa±R$3@ ›nmht[E} p8TS&A5CBpC"#yZof\hc2@3JdE&ڒX])htPJQ |ߊUju#m# lO=q{u96=:v%Dmo~WNF8'Ɖ V\Rt!K/tdIWw ~4u O.cE5l)ma}%2AHTǿ" |Fz"SEUAS.L@ʰQF<{24kzW ݄̲Ϗ5o|vAF!8L"mh{뽆sx^Aa*wyݯ|>4T7DLݕ'?C# u/i'(Xa2zl[r]H鋧ٽJ7ioOAg 6;0gY~ mJv{[[RDoLgKS!c̈́CǮQ>$ױ_ IqAQ2&-Y jH~TˎL!D Uz#Z0% ۫`8{ ݾ#;nS-| D]{n2A*}v-A ʙ7˦jrMסrTc&hh?V*gֽ#Tz_z:a,btKb˽FFƥ6>a'p;5Zۜk.LtDϙ8?Љ#)nSmm$oQ 83ۊHrQDQCwwb,'Qsv3)O+!jy=VzKA"4.(rhMB j$X2wu50֙ keGrQN7v Cc~s|/J\Q=̆aK rPB ěSv"UݜK{z1Bt kAZ̰-jw;љ*cf ;&̃o1?b`tn W}) NZ@uz H-Kg> ~hE9 ,c2pv))0u&t25_R'~f^0:n?]J@QoF`EX%P ^!PМ[I뙶."Kv=~1=/+a4!ټ(?{3EUu3PF# dWg˜MH+)z~6шA Xk& fm)G~#F 5CZc2g`q2w8(֍ 7HGUCLChHJ -(X)"Nq)-I[ LgTN~0BW|8 v›?/pu o2I18bgW<>I)c^v|>G "m:|-ѣA n$,-%\K`L>xY r16d&I jT 4Qϻfh(C"\6mQ Z͗?U}~i G) O k>X'շWHi2p;ۑ=t@}#;uB{+Yŧ j_L sr T lxrP1zf9LA AZ0PX!f9aC,G4(V>VTD'C)E:^*sma}L] ]]\@li7a: %2{*kθ}oz{-=%Z\)ub=͢fM@˒% <:Aa1r+c8ȹbgTbT7n;9fF/ދ>om/(K vSBz^7/~B<([+f>6ƕ+Tb{qM`2x0E B]Iѧ]J3ߧL+mPO[~$,o:PڊcO*,QD9= #\0EG$1Dc܆*`)T*zV;BR!{5S`溦LɦjJ,T'K2j iFSr[*]JCi֟;n/vo}wp*?ߛ`vP44Ǭ 'S%ڧ.9ڻ--VpU%7ߛ>؍^lO{{a[jcxV(cg.}\KP3u[O8-y޹g}M@FIޞ~YafP+ўK4r $6n &~F*bS_{DUۚj|PW`So[Gı(A+ {9QJc6Y{]5xIܩO/ꁉzܑvq$P+/҇U-[Y4/2ȖLR/ y{DU$p4-f >[+n3L ohv(S7$|3ŹYz|ԛ E uutDCCmNmNIV׋bol2N$E쭿.Lcw W䮾{ r9qwJ.UK10Fۼ%}^Mo>YC  vI_HB8=\sc#Oך 涉K.y^`Xfa}"~37+é5zV~j^!iu]h [!mI d8af]OSA}dE1Нp(3g (%Ա!*$%8pR!Y6:ۥm݆f%7.=._! b:QvP˿uC"=-j Kŀ9F`,?_{GmĜE^z#b6~{Vb(V*;+z@#Nӹ?uTĖ8CaA Cbx%Q֚0y?#jA">'ɕJblCQ9juR䴂JnA|^a&ph{_W_מbaH#JBwQ]Q 1@Sß\ !5Sö8ەIQy+R~6ۮF3kB]e@ Ø[@Qo%@>EE LVZ&،̣͡mۧA͘dž4`Fߧ([DLhy5ۘOxH:Z*Ƅݲ3`ӟEW(`3;j?0|y\LTU[$R,Cނ>/^~uyczl->d2Rui8IL|Ed#$b*İm3n`l=?>]v8@Q6c:pS,O;~jp(O梪R5 Hi$cz^%cz (1vQRj#0 ý-OBnUJK*SHefSoD!Tf cL׶p9qԄ@N<ڎRH6ϴBIeXph87yu^B-)Lq᠝9o@7'9AZ[*>HI1 iM$_y eJxI!GO.UM(ʿ*!D%Ww*6R\/iRƉi8TRD壩~^Pw/v/mڌ!mAjOczI,Ғpj3&wԇgzOauG=)wA[t*eαS8-mWg}E7Rsk! ^ʜl݂S-\‛Pa$Pe@R~I[ΆƌYsOfkN.\JݖqfLFѵ5EGus1O*:9W! T%WQ~MfIw *ɚy.j)R%<r&{OfU>JT5#w \ k] %3nj{NvdTvBQMH! {`xʄ )1b4t[̼4@U\IJ,T<39\Ԛ{І9 H< ]q[B PV_n ^Hmt/wmNY-s ʔ֕~_7ή6Nhrp~: $Lt],A[&њŹaEeٸqQ"B<ȝ+NDOJÔd4`"*tG#K3|xuV~ ,D39/l|mѰ{K˙SLUvj5CW'kXNGG4fzF /MiO&/<]i|[0,Ar 9KSjՙ[' p3JN5tlE"˻W^k~=&? 8i*2_GIZG|*d B=N*LAOf[vXY wXc[@GB^VsOop|/MӾK?5R 0{864&/*]`icɿ]͛'i+?ٚ& l}*!fI4UzHm3+/^Tq\G ̃-L0) ’(2е)VXHT5(懵@p 1N]K26 K͑`mkh)?}̤ive mղ#BNUf#^*FÃؼlo+,Cu?xo~9L"ʳˉ|wZsIƐ%GVB ̨O~4S׾aNpPf'S(/KqmN:ls:ZzN+hf @VǩNնcںj2ުɴ nmJE$q C 2uI8Xm+f3XPђbPTi1"షѮ˪?@~Hۻ/,K;(٦EIS v3ȁRV^ r|spwGsUбH9ݴjqsw H}BA0džѻ^_Fbk('8hsRCӌQ3Is[@7?(?MJgX[;Iw[ t^ !MܢuDТءiS0= ~ ]ֹ MXU{28K4~y_El`| p6ϨA)R8#s@2THu$ 3ZShܧ`e{^O4g1f 杖']V=SS̽rQ;5;#;4/kyD#ERIu U+ՕKk$[\P8fŢJ&_)*f"= V?hO ?)4ux'OXԬUNjt@;Չ7X5-(-ͅ7]iLVץ7)fa&Ίqwhw`~ 1|Ou"O!$Hc rmmx/I \9 .XP4VX^ aK1U7aͅ.L9d MՌ@EF|E%.@Z=-_ðxz/vON#Ӝ[(-mgm=`b^x"7 Q S7ndm"B(S+e\"fyHY\ b{)ժr ;pqL F; lFƤ ֮R_&KLw hϵ|b DA#D;dIL.nڗwQ0aϺ^% ~![KyHRUr:. #v'no$)VjzI{lQs6}w`haƂ~~쟴}&tktVvgOTO4E8R=l,6mаdZpTaa@z*$/CNmgMG…LvZ띝>Aʕ0B;]9C Yf(\^1ҁT̴ I#!p[v!asrHPIѠ(BK1oM^[9CG0<qF!n ;FэfڵcGz{Lq{nlka,Zp Ƙ?ÑHf\zǢW}Sw_rڝ<)u{%IL7`e!}۰ ̆5}H*W] KDi~vȆQw"!o =8?L\gL!~:7{݌-6(]\wK6>BԎj- ǮlU$4xNBF"4by53 PqUMyV|-o†ps:ٜȉR8Ƨ}ML?U1!j|sR);: &ϗNbV6;l9l]'2@K׃ d lT +T qatYze\ &^1Ht;NwrU VA r*ܯ2`V+ x%)nSL0YSewn|spgmojUH;V-SѰGJqn.-F6 ^ 3\WecNڨFi}WR8d8cgJhSRC|}\ ݞIP 㓑Q8סOaO?iu"/ #˷uf5stW$\̉34&NjyR_ (?pQЯbd tȫUsDb=T@O๿f; y  &߃ɝe%Xh?-+j.(YB𼂤V$Gx"/8d?JKIaP^H߁xשy)oA6oJƅZNǗ&Ѓx(L0-ݳ"fsGV[c&m85!FmLƦ2υ|z}jB]/xyKݪm]Fg+U0vulg2Ds/&H`[L|bޛB#$16 2ekkf>p8鋞}-%zT,7 hh׵ 0^ Te~LM~S$"k0eO% vQ fn{g9] N%\@mѨ ڲV)Va6Z17P !i`m!I=]P CBHoA\Eih@8/Gs5Ѹm@zxio'O*3y(jbCq"XM((8=D% )^Yw^=m,UC$p>U.N=-Ǎ2T7i*$ər V5t_Dz+QABZZ(J}!Ntv%AnqGj($I=rwM ǂ+f:;NpDBtai,ZK:$n Q?jb&u5ER3xjR rX kn#NnjzF|F\;c2Oe);dcIWuʙo[)f I n3]NTo<NR ]bc^oC=g Ҙ S 2-PkZASYwRcYqSm p@iQIbY)NF8\@&kjud\] ±X%6wYۖ9EР5_f*0piHUm+AsZ"[**_-.\XLYFY0Xo8Qݕ:C(ʳ/9,RwYkv>4v|e Qp]Ie6(1|$*C5?mҦG:xG )PI;u3onuVMr3F,_R' Y` Im]yyq1֝H5C©#qD*ߣF߮1_PSχGƠ`6)L/1bTEjCGH ; Icw͊8`8(gDù/='Tջ4c\ gCHbՐU#7n2UElN"[%9}Z1{^ş2r@0QwM!OUCM/c5+jwgeIy^'L!xn}@Ϣ@YuN \RʵF7Y}1,{GgJ#/D|/-)dn&9|m#;!Ʃ\L Xq=a{l})=Ec059X9LKg3+|XhBMYץ{༴l$4*wk<Ŀg08yC,l}[`fiβ&%,+@}xa"^MDً2?Чm^g }̉ tp#*~÷B~җ=´e7I:AZfC(TG:1c|TiO&^cH}2<( ~eI!6e |H%ۻ+.%Nߑ wbބ }{>KS;\x(+9Uo0] eq 4Ò֞~P2Z5Б#+U 7h !QxMe,cj EY2U+$)~ezg0)0P[qrb"*F/~_mf?_L9 c/*Faw/5\ b ׷ˋ(XM*…OR95~ >O5r)JP=3>2v&Tk@G@e:Ue=ϵY@MLIQe2=I@2š y/h3 k% W:js20GDP'%/r rA&V56NQBaWjx(4WkRu7ެ.Ь\#&`wd遛k%uEJF6Z¢[7- MME.O< B sj o`} "w?5,LOK+pP=Wq`5H̓S45ʼ\Bȥa=8gY8(E]thuJV;Ԏ 9l']⿛0x 좂ZZ焷sKL)-tr7ޑp lx\1v$z9DAOM_-qxֵ7y)p~%:۱R֔20*H}@plRݨ_氬K{Aq<>OH2;!fVMWuzϧ{樗.[Qk&)9; TT! éC'a5 av[_iA~wz['ssh6mkn`Qjmez9 zxEf6E}2Bgp .bmjut (Uhj^ԡ`M9 ;[lIՋa-lW:&zAm ] < *ED{F澅K[䑠 v=V!6 D*xzjk4WQC}W+~:Y=q¾1I7L'+| ^9$Og.ܒ7~0-ʹnm| grF8\S˜Dd*%yZl*5YK/R,[\E5s1*H-.uQ(gʶsCvߟ1i3Vޣ+w|L̷/ǖ4ˌUʹ, %s仜Y*ØΑ,r~ FXQ-p[K7 U|lȔOt'v[6a KrgKaOWlaEԽ'HaeҚiQ-o5* zu C邒aNKzRKx$~x 0+xe+@W{?*Iyٮ8szJ:h_'*׹hxV1Ar^Hմ dFb0bQVjKg^a]-: >2epٝں^?)XnptQ~ Ra1]Rc[~``Av옮X%f;O*1`k\\ys w, ƊH} n5mEB.W9һL/J) ( f{ns$8Xt֋_~ZF`HtGBU)?ƍfyt@x]NT ct9hT\?$( dΑw=A5Gz}aG;ͲTj9gmO6 `F`XTm1.Ҁ4k)dL!cW>&,jU>Vμ.b's>g8*[TBTDN; %C*ūImomuZY @kč@6,PX!=PnK[oX`j۰PSf.t{R:˛ÖQUrGp_`I+p4𢐐,/pMu-@;h\SK LO@ s͡"z.|7; 3 O.>a#(44t~4e4QFbSlg|+8oA?6pc8ځ-P?X16Bn$0I8Z2,AJox~3a>}Ԫߥ׆^i3.@y&JA&|+W-<@$ q*{eY XF14a&<t4 1s30"pMNsiO:3z d[_7RYw۫+xBb(@ |*[/Xx .0ٓa3|$Wkw$ȓZgý,vJM/>F,"H"Eʱ[ KnAVz@vVᝮ4-Ƚ%;Iu:ߋo:''nEB=klKt|FXzMe.VPe Xȁ.Xh&3 }.=DQ|oD_6C-tikw6kt NV fUcѷzϚu+a f> ohS \&9]'Rڹ8^9,L{TKy ﰍNH Cn4ZmOn^3uqeƜnv Ermݶfv7W6B(J< gBa/N#&9`~)qtV?9ឞΙd pO4q4[Quez |fn cR=Nz@QB] ̞JE Rw^h`톽:CHƫ;VCLth.0vBT+ $ E_ J5Snvo˚NEgrq,Ki7y C4ODJK͸[[]!m1!rn˚%YXR&SZӘ):{ È,&>u :(vE\/ +'r4@O#:%jϵ7[-圮Ë3';1L>ZFEW S(8ѨuxCMڇGm73J.fiRB&Xкu8grl~$K8CG-PLHdvS `RQl"b}J\ʰru, ֦;PsjgqsBPwawA*퐑=cmTB?T^ܡ.3'37t2L5IӥރTBpiDq tudBˆ1_}$"38*J13' CRNh(%ϛ"8y `o|^SҶYdWR+c1F@7Y?`s).j2'K-KH^%Ƞi[jW%-bpYujjҖ1=̽[3!?!jmﶂ ȇ><5߽@G+%SomSn'o41(L(GA#0dWn_$=vBhKqF.Uxl _1dm}w5].;3݉BǹНgp5Qx™ЃkY[1C%~ ${Σ+OL*C'`ТP(&rdR)Okg/] W]a  hV1S# riV@.4jDD.( Ӟ ~@& U.8&?5Ԇ8 ͚/$ÑJ):S V?zS6lO"y0H([\VrHAF h.WoM4k{/Ֆ1Z˺H-ٚib}3G7AslKBR`=Y:uOU;~}NT@o9?\ 2w`Tvt$Y!.i$L:}B rdy+Dخ-YHU;' VA]Ր"ѧ@;PZ(L\Y; o Xy]k Z{=vB^gô1-gU$= KD=68'.| !//\WF>}Ro( (W5"Tan<O2艖tXK8pPU9. 5;`W!lL7!T_srr&8yGCkya'P){|殛O{{`^+1B?4)%h Q GCXTyO@.*((5Pajߛ?Ce p>`qNQEaD (2'pZW.6'ҿ ;.-tϿ98' Θkt"p/!nGG`^oe8ѬruQr",BhMufKR_.k;#x3qwnYUS KS|J{!"W^iBC^"0oA p:Rפq\XG8Y 4MD䇷DiRi)ȧ';*sv]ʸj^畾:׿H Zcs]@'2qN68J|l(-_!)dF⒓#rUy){yk,6krd;m1EgCCS"PxL/@y$E=I3j~ëG^T:l~rM J¨AdՊ&vJ5kޕ,f2EU3Fh7NT}+f<um,] ~ƶO '66SoRt|z2" Tdj5Y$j3 NEm+XjosR}d=p ANT5LӞr TWk)7* nd9G;GuҢȑ;.pK w_ҧ4=Mk1ɬtMWǘNViH| pZrԴ7izGmT|RG]Kc8`˱W04 ^d"Ϯo>p[)Ri_5yi<{]-Ygi3|ljWHbAZo%Aw#J"eAq? cbzA &Z~CZ|vZ4N9s*g uU#i/+ꩅ{@X/|>X zsMbYE%"oLSӴ/W>y&Ɗ;?3; %fFin-z[љ:ӏU|vπNƢ1Fq Ug15cPx\ΗA*Yjg4&_p; J#ӕP1r7c< H`&|sX ˆPxHx;ZTϲ_) @:trV6s}J G&ûy+,r?r@MP?|DkQЇ,o[xtAaiD>V]՟2h3ѲvA%U'0w k_\c#o\/VÓrpc{T%%J?R/!/.Iwv:u1 riJAA$` >gKL#mo hfP[ U4ЂC ǜNƪ#Bzk=8_AbH.xk߿D5owݢX DgQ,aa{N{\0lqC)i%ZOYJƇJfr U;1xzQCv*miɺ+&дk/'Gj 6"]{0ѭ o$%뼗d3rC*SecVNCƨ  ur/Ly`NBNNڶ)a WVB9;Y3<2̡:YMrAUJډ YF;GXqZOڰ vCkV0GHnE`X_Woþ&BS?9CwLݶidx æV ;` ;#z*hiMӣVS[A+#ˋHhb/h~\٭T}6@2<R$'ͭvm{M阂S4+kX3@,VYsB.i6*R+n_f*w-ӽ?<{Gc)ɸ?]RAI+'v#s>{~,Q@V\ EXOP@ega$ltЌ<+LXG[+3lnfpA9Ƿ.?DP Q袶tx 2MseJl3"/>;Qp{32XW>c2cu6ҡ , OЭG&H;nz3 Vp.&K2@&$I"B L⹖6e(D4Io{Q$ꀊ<*3> QMP\?ի" ZiŭꓲF {OӵW 0?D5jǦS0зݨؚEu#?+VX^ p d =,n߲bMICYԱ,-5ܷ} b2\e AltZ5WtWPҷ̬LV?(Md 973v*%j\QlA2' _5dx v0{ٜ&$Kjj*uוA pASV0bY2-l i?D#*4t-v2%J_Asa#:-\\1]«Ҋ Z@ YdN<$" B p8ޙYwݺ֞,PiuH8H"=/Pܛ W-ZB**3}h7!zuD]J;`\vGR4PL1jg91l}NPu %mշ Uę}/{e߻#mc _f4 CD2Y?B,u"  W43'0!igFR 0.v^(\mA)`P]9D`CWsvIU;0 ]E0حSÝLjFm0pgD7`K.yK-uƜI5[t&}{S ^Cͽ{rR %ԯHLvW7(8/iVJޒݝ\hXG8%9EA/O+<+ɳS\:%e2D,i'x%+ &Ojm5JWUko#S4;,:d7_l`mСyQãdn/=B$!^":W Eӭ3p'ױ׼WC8ž#q$C+CbQIg*}Ǣ"P+BA$Q TO\}Y&ST[[ģ$fMYi`(q?3h@Px)zZ1 Zϔ |Or:X?)1! P%[Xe)3 Sy@3X~)߶q|Y"M&!b:R,g۰0v1z!^z rgQNPTE ='tBK]Q9A8b0W%zeu#qǚ'ihkT%8CcH{Fr$|<^|:0XՀ@Th,ݟ+8肐޽^|IeB/8UL({w!fы˙>{<<|7uOc6yA,DZiރۛoL5+&, !euahdsΓ{KmuxvY2e¶L hQÿ6b6QA UA;,39~˂!kheDu'^@e6< 7I;$,w":: 8LsE*˧J爺 FܖZ;_H&s;V?[&e;(?vx$B?zmɾ apJIŮ/dJӄ3] JVc6 ~*2j% ̾R9REVdV4{cOߠ4X{XGߤ,L99&5Y8RTaᛓiTe3=Bm%b&V;ڨ_D1'8&CƲDq mͤӷ0~^2E-AV]__#Z/m^2 KA!{$l̬[Ĝ""t;733![6OGyQVz6x1=bŅJ[^2w߂\ȣ]KDxihJ\\кhGI @Q?7Ji,Lɹ W,~&~D,sjλ"8G'ZZmZ[<> yQUL|BC mln_ÃڤYՍr ]$$:8ѭ!+xuH݄c%:߫\(6=F3OJ5wG3IkR<*\a4}FA$y͜$  }J jlޞw-p%(][ԙU;ߺzSvgI"` m.HW*Ll)ae_IpĤJ[#B'ܛ8b#ChvWBZJn!#kM?Mc]ϮW3GD>V h͎NlzF}jRX O%A [zvW<7t䘌ȓx{ .L:6Xaz5M Uz cڶ,<}(S`7vpqK+O+^2ȳm iuCw5tCmwHUɭc$b>7ߺWFeC'd7=-qй׋Eo$>d"u8DV)/y:WH_]))U3Qud)x @D_Ebh崵҆Z%>|8^h/~aPz\6"Dd-0wQ<># JUΔZ.u.18: ѢIԇp 7VlI9?w7wg(8 w0{U歝vH-'_T[\: JňnH HO[]hz܆VYN q!+yav4&љ`"ůvhyH"pg ':) b#H5 fwm#WL5ș߫uFw?UqDvY[n{ ,&pl+ ~IHit*x:P9{]5>CиaYa%sJ́ms_ N[q{J E}·+Fi y,2ҴͅzpUqv0t468~^ sOWb7Us(r zl|[iϕ&aI҄OU;)vbz4"jk ŠN[d忀N~z qA^Z 7+݋>Z~3.k ]Kյ O/v8K080M{F&&у?\x4[K,JD/¬^n z]P35wA.2y9Թ;.%d FhuΡj`p0m/ g_E@*Y ,@A6ƙ;nf*[47tU=dx.wH8J$ϕ4W) 3 |V@$ɆP@Yuާ`8\]} x'/nz(eW?#5U|QܨBN܊O^%4N+%]1@wQa|3ᖞkfyp$zBpBGB4{m+΅a& ++}q a{Qq-RG>9u06Z@ȴ_5LTbSv pHNlNA5qF!$LW v7& J9U3"AT`)>̫Ca`#z}k+Ǩ@Z8nr7 ¹pZ5*{*cg+wXT71!*E৚; e~ "ei[6ݡUjدPHQ lY: 7c2+WRRN)Amǽ`w3ug~WNrl unw!)nOHof<i.9%;H r:9TAj xVz ZަNkW0`"RG,P{ hcN!cGfm[J.;o8 &U'S G$M6O}kV$'X lZN!(*Mw*|;`7c`:@y{Ul뒜#|M3WJbe:󱺈 ࠌdy4gSu 8MXoSUvA0LݦpxGIF? =43'QpւPrJoUsh}ղ/ vD"ye$'aFs/ 3Bl.>aRRo6_ 1p2<^|$yNߦ |w>E|j&[.]Ԏ[,Hfu1ܓco;呀" )!鎡y>}pbFSC \U܎ddD Ah(!H&J-#?%bNa_ +L'ANnkהIWf5F/%5 ;h2M%wi&Yds?lE=x 85Iz' YrU.<S3}!o6q>zzf'VB0 Y/io. TwT\.jMcbp-_FPίZ(Ԭ[pTLym򲩑VUhnŵ!HB 8 ?҂dLV)qidl~VHg`0 g] 2K/\wt@6M`U;ۥrI(0\,tbLgxlGn85Dk#N[8{d烍,9bWXQශW`1Z6X'kB |KMr/9oV)N+ Я4KOJ*ZNL9S\:1TV95=x_=J& 6vE|ߢ6׸Yz=?iDA/ӟ~;H2nWQkѺ Z.RHK&m#mvBrdG[h\&G;M @ L3]R$ul\ӄ4׶PEŎj-I/>ۮ7MuY5giE'2h"+o?|bvAG Kג7Q&M"K yP[j >: AƖTW ֠ Іz-?<(P\>Y|Or Bo'◊KNn"(or6IێK/ٻF}2]:?^ "çj<70jƤn~BGк;PA9K a7a-RE[uEuwK tQG $ͣk%W*9AC#03@Dg"Eav֐V_טEVu(s.EʢU0w%V}~K,]F6L躢܈fK_Z[rvJ*)[u1 @'F8~JI7"lGlHeL .30ᤂsbX0Fj sx(87iY+NWdP(A3OBA@=ErPtRP-dLEsG'-_]ݐ/ե _GyߧqrŸz20Yt̪^RD63آc1ɴu\ULzV !bA\8k9NKL Luk^jιH +h6$Ϙ%7;7;q4of|bu<86&_ Ln'cR$4apC5OAtahH⨷UN` !#<>WM]a}S'Qgɠ7YC/NQDſ&Vhr+($<4m͉,7b0 Cw-g*qz8GD@+GHauЏ8['fgojh~/ KI|eh#Hm lR͇` Pet@3WV<"a*0%`n(V>shUN%7f CU`"8 DadS#Ъ&i>Ωi M6LLvRQ1%-Eq2\tdlS|t{xȽCwzLAچRpN)VCW?0M8a 4բSY nݒjƕۼh5ЧKj _8"ٗ+QbX2vyD42 Xz w,Gp]Uƾ)D;2|6==$2Bʔ]g{ HzzEȹ5Y=j2`@s#[H%F@h% s :.ΕrNsE"jO6BAN oi7,WLo$XrS+FQ<;a=s>2G ]K`3mrV]~ 6dX~}?!Bɬ=qcFqSE 4 &ro;~ӿ?~5`-&Л'}r "*?stUQ硩6m@Wo#O7_?) ˔BMyq\5Vox%(L9Nff2=;AWY 'Z7Y=`Չh'q($W/pI5<'5:`[N:nXe>Lj;łvX"@lg}K 4Y{qQ0JsT?EBb;lTW3ژÖL ҃ +]bASXe 6\-ZbGi \9}(2 nKG,pҎMIJPQ+͚ɖW71sFG q2"l^GE̅i1S#fJ5pƩf\TPʎנWgYD!WxAtCT7W Qmb¤Qxuƹ7'5|ZpDV-qIOzl=i:Ux~9Cn|O#VmY2Ws(+UDH5p-ywapfw>jXQSVlR@sCm:x=kE'4u9GxVPǫ+uMjcG˔v*,E:E24\q ^*(6Kdy.Žu΀|7=8q0`OYσVվd1@-(XNel,/EVܵ7n*4gi˒ʍPɇnia{{ DsԌVj0#@a_+"= Ci:r.fKAt7tt=EV !XՉYcZSQá@w!\5k}OdN("kC2;3-B*I_y඼ȡmo-{g'}JZծ(e|On~.C,R$^IP B0 }8U=f.' g0{! -!OLf[S]GaQ?|ٟٲLE}XN9%19%稠e1KM>h@V ei`)ߵ20bˀ\ZɩO'6Qѻ.mąI}̤Wx^kkUeWv}KNFX,?Zx:l+dhސGSObw޺P'yJm-H +_^0EPzB/9?? 0|=@ Jݬ[ֈMaxhXU4fI1$'߇J { 5kG/mÔa #$laNa!2.9Y-|>4>|(wJ_&X;τZJ?33ڝ@ BN;K%Hϥ#=r &,)$5X->)CYw^nߣ)w(b 6ns O: H$ۄ r8;p&s$CR#LϼO-aWJָ͢'v.6}^մ{A~G2m: !idX=e@N:M^#o uF87mS8] fQtWX! m| aD8}ڥIyIeBp_Uj?eSm!: M 3>%Qcixk۹]1G]${,%e r;—ɂ>}GDC{1y)G;߽lMJ?@a6eϸ!YLf0N7~|_3XL&< [ŕ)o)wg PWkZpXJ(STAaa{R/xUB9`E-w=sf8 + 喭|oLkɠgw2V*EޢG @YJ!XzDjS0y|tS{)I%a)`$>utx> b]]fÉz/Y8鹝p Y:bU^#  Idt3`eXzcÇqH9fed T>pd!7uP> A@h> JD/"g{'Іڭ0&~l~z$hw*CVб0A;F{1Gt{ķwCW獲f#)x053x .m'Ig {:A8^XCtE3a+ Vuۤn 0%Uc\Xl ] {yY6Wrwe^{FVO:ŔJTv9FSONGh ,M ZE)+Y} ؿwnYw9lTA %encJ׭,9}WR 3n;zmndOc еs<2Ee?+%Y#Lh䘥)TX$[p#P/z "Z۵ z+ b |g46IATaN`dhM't?no\BE¦L{0Kɖ9E)"G,δ^28RMP~[qu*2HT4ۊ'3T<.SV!|-r-Hpzc7](6=}Xh0G8>0-x0: :_F=u~G|(OߓSbYӡ=f{rRiL0>gB3%lPD;CS)qhvJy{xj}<6@Aеx%L\ͳ5_'jV44s?-t͜YQfJ?Q+*2w'"J\_P':)}O3f<3-/54da-K1"jf36϶&{AU&/-'7@9mC}IQu}PROͮͮchET\ӗf ڋAU99swM]+@שBX٨ȹǯ_aLi!Ȟ RZ.e9 ,=ExffCzi>kD'iG=H/iAD]:9x$+-ՊuDB"߅87ӧ0=Ss(Vsr۹Nxo ߙj,K2Z9U挅VpW hG90y3mٲGDsR{~cMO5|/޺TObz')'™V>%[Pm uxIvZ*@Y% wDVkB3v_(Vb"RD%kWP[H]aͺXgtF{=jqQaṲ& JZ{:iKVKa7F9ґ?igae5" MU%8;VD[FHJ!6V'{Up2õѩ+2ћK7 Sc-"~iڭx1?VG$.M<u)-XɹpQNM\3R4̷םvEDЦi)’Z9L_JmkBEҟשn"is9fA&m s7t]3y]¦@Zp8C'(P׶%/^1[xRǙh"f;BH^.( X($Jѱ |~ <<l!8exʪR~o&kR?h ma7{[й*]xL D4_e@E jo#J[wqyuE|Y.`۔/2>R̙qApb;kRKgN;4=K>'N?| Ue> jI a-ӷjs Ta&W|k^/^R'E8e:qo#O`9 s>cPjR\4-,$ې.N>0M EYr_> S7uHfDp?VݩH)M6$ܨ=Mψ,Nkɗq8bnnhԃ`҆2tJ.X]萰qc=[pa2nMJF B \h=3q#.|DX¯ mkJ-J'ߓ6uaXL8XNcs#qYAPjmy+5)W\3fFԔgݨL >H dd pw9F31_UL4X%Sr菏e\lW4=5x5μ(:Un? BfZDel6@k6EǫWXJBtNA|A.)tXfCJNK?4UԪo WPR=r֙£\x] b"MbwW3T\m/͇m\U:0 X !}=~TfPk'v%_RPKJd|+=)+V:gnvQk@:dujulk(mf,!-'SzmbV1I*\p\H -"Wen(eI(#rb.0$lUR0*n""&.wwbi$n=b7vRȂU^ 0B9pBmjiC>!t n"63r{KL~ hW qk'G(x TDn\V[+wyݥ\b8"RIY=U{V`6d\fQ r_/OLR Pv63#,a~z`i*ʒ@|SqKz?Ń[㎶iR-@& ӣ{"0r}Vh~7u1D4"|Mrq&hIu4 }wHUpӇ{DfNa%z뾣OVCCK-b(gKa{Sʼn?]cM. SE]}%%Yqj݄ lJ +hY 2J7e4 6a6)Z~@ζn t\es9o>Bf^s5sZEc?n($_e䨴~%{r;VP{oz z켄hԛߣSs"Ȯ&XxQ bc"MC 8Acn^#)W$ @_hɗ OϧUG`G=?r^SR@F+jiySI wYN,1ؙETZ._@/\:LbvUqN=\:㹦BҼVz76;xӽ lRӟnE_{yg)Hz3c}uv^aSY L+L bωd"0yx&M׃A<uJ*8]2_c{*s\TG׋M)SMn(c2ኍ,fѼb'[[%`&= :pK-uZ_O+`f׃Da K8oWu#)|lwXM(J5p`'t>%e?'.6 j=Jf}X!æm$T»' IQC3ZCjߺ#=p̈́?*yCS43R sw=[즻 sBj"G1;ETMJa~xf+U MjՖz34<=ttB[0d׃3-S +-(U >&:c4G?@7$[jc1> y8|!#w Wx(W  ɥ]#_58KIo_G ~q^PhN-l y"='/.ѹ7 ψæ;r4W!mG:ъM1Â38M. =Ty/5FۘoV L{xE[o{1<3U_n}X<'k|6/#}Ln9]I-J6? rF7F(F*Mp(+EQK"%h" 2! пc݆ z?l/:A|!2yx u|wnPc27NZ,E@!Xc }Gnp0O))aa6T<;9e"s3~y&tpu@(N%W~RB=+8LJv3,)o=P-\Fix}P!Zz <)ׇgۗ>I#Қ#ہ;Enj@#tAc/Ma3[ݚDŽ|z 2 +ˡl!$4$sB;y~1trM +GKj0dyF7]36'Jn73ͷSessL42"rnU&mߎP[~AR[f1YNȽsa%ܯuww\Gs@C IP35y"(r 9`o!$Tm p>/%K;÷LQcqi9`9wMOegø֪kKmyD}+ډF@QnRꉤᮾWOCфq~L(K2VtWdL%FBjD<h vE&@cj'O>ZP"Sc4$c\g[G1Y֥{EJt޲8^svИ# >XQE xq+q=<1Nw9=JܩVhE"4Weo)#a $ECi&=%f”\yfD_2I3/l4 T]͹; G/VӱWwJ(Y73]1 5^!xTNI Nl{Cܰє:8w)dC$bqI]FᯌT> G0hݲFә8Un֢Μ䁑+Ad{2P}AH~d_z{lP\єbʧ(a D~[I{coˤ8+t =_bC"$;3ӄ3zvI=,KCi+= wx#ܮEtQ-k t.ܿ2ĈuX[rE#\zx f<4h[zQZcH/EDɖ*]%pfIrq =>S Ex03q8HbKZ\Gx(&yO" )ļjHLӉFҜw|z4uM֯qC4VN OW鴙}#((=͗K9!Gz#Ɵ??4+*'Z9g\`Yxx{ynI#!'E9GNəjȍg-=.υLT4%E/m;QFz-SLsӣ Q^:ʨGBby0Զ1 jn (*zpv/l>QUVeNA#>{Nnî4_$8Q"^\PcLkpCV1`RGvo#slr !gE1Hý"!*Kv8ˢ ia:JPɔh&!p?Xen'~kmN@\ {IrlkG7 %`\d?:+ɭe2SMLTHj_˨JoSh*dF&F*T7:_D C [,g'PI ROe&Oy X}gz=_09%@._K.Hn;ak' JU!,, ~_UE/^5Cg><I8+.5عx'^~CkbV'51I/GƁb`a|Lq#CD?B4̧y|R1)cǵSh{ yL f1=Y E\3?V%dx # WC{ZpfuǏ܋'3 J gWkiɁHQX(m ctdJeL9OwxKUҔ^ uJ`mSzf8ɶ"ğ3%5DJ߆|Ižb IF̛I 0ҟu](#ɈB Y,KyŇG&>J{/iEmth |F$RޘRE K-ɕ]{/ gӔfi4̭G/d-dĖ)XF.k NdWρKpE K'0]rM bDm8x n>`\R#r"v'uiѸ"ך'[5B0y%ҭYhX[c<%,FH'k劄|Lc /[P c4|GM8Ft~~hBZ[!Oι]LWW'2\q'v YYXIϑ<},T ёVnŧfr OxPi!Y[M3ͭEM*?H\=@67ѣr}J[(r5wG'N\/T{AL { B׏7.`c|pZfgo[̗ R~ʮo'xOkJX‚'D~[utB$`\0ĸQ0CϮz,Lyٍ;˟9O)t4n}S imCIӖSȦ 'y"،vFgʸzφlaNM.霓2Zhdȏd#aæ؛/h_\_ؒEŢf;F\E=̚?hYn-zv6{ *o5dc.VpDC TbJL2TR X,[ô04'[l8f1Jl9p/ޓ%k?cµ` bWs"-U(t۳ $ϚT8@'Io4߽M%Q`(|O,u+m< Q f[_n5cB@þ,Jv4D g_3!L36Yq x{x ,eͲ/ BoHGT(ľ,3^A _ͅS4tcQ5J{b.[{̖Į'ѐ_abxtaXoSm*Fy@fO{= -q>xM8p *G2 ZXX6ċ)بx&_.D} eECk@DNWb{(nTusy4 4.՞Q,v$pq{(XBDe;%j'rɛU$RlqlK'4dEp;֚ nX.X. R{g2"]:Gs`knYa< dHu!! x,UקާIvB fR̢OFƑTG(C[]lW(2x [ݷM*ֻ mN[ 2ㆯĮ8{ d HW؁y< g1嘂FeNE± }? 0,aĶ7" BaҬ2 tΕ= ,~$ Yn>m.XDqc@(_Q#oup݁V tFNۧ%3YcUb27. BnSr jיt߮v&Y|O[ e+k}`.;rYr_F78^xTn^4Mp/\P͎+H)>^ B ωiJ|{s#̩s7Snmdg HWeNf&G2-T|o^L]/:{YZkJDWD)eֺ@IsAN'euWFgZm3I=~"J@^K C.+G!~Z`TE>=nFI$vtb$hVd%f:urė\ctz1ձ+莻/`(fSʂ=#G;M V/q*ʨZs9<_Bָ)(eFj\v=ݔE,uyD,ihYsӃ Av'6HP{O䓆Jں2,I(8fIr`d׀ g4_.y yOM$ECٳ)ڸ0"Z ;8ї{ (Y` L xIf=/͒_Yۼ燢iш՞6 ?.$ VKE;B7S߼KIg;e ?&kl'Z+xɶ3&?flGMIw8^l 8Pa"vy!Vz٢m g"mr=C%tJ\`+{#Wеe!b=HEo Xg:)^i]!pMrhTXiVzjK3_pNy"hv,EGZ]6kUSѦ{lg XA%,LMϖs~SCR[sSE3~K% Iq"Xw-(gAN9{U uC o :r1P*p:-)JܚD8|KzC qe:UhO]$ gN=ϼ *^,\I_-pޥhV3K+R=z{";Y'#h[ 0V7؃XΏYy0R:B`kX "%fLD]ʔ6*'yMV(-E]ԃIRΜQ[mSF2 bq'99?AD#+ ݦܳˑi_UL)VE&-22|7/Z, (UY)0q*p({IX8.rC*j`nxTh|V=mmOvV";hYY!No.Jjl h[Sr,Iž\@PdSqHH Fj^w09ZK(Tb7rŌ:LqF>9>'{&uqHIPn/0 7aVk2(K2˺bxR4"Ml@_2[D VI*YB޵E|TKM;h#d@ k_*X%cw0p[L"/|TB6t|*Bu@*'(5i&DUid̐4R&wje4zZPQ_ߠl=&_, "7?1K^ Da6>klaaۀzY8]r,s|Ӗ k M_菺)[$mnjSJkuC}mcՄ'*ӟila(4R/]35q}KyӸ:À'=W"9Dh?J+C3ipa|ܙ(4c&&g_3Uxuk֞ lasOl/v>O+7Aߞҗ&ޭ3[UlM]mMr$t{ zJ$L˜8 PԌZ͐ 2BkiLluWH ʒ( ) \H`5[!M7=dJMuA.p'ְMQE%/fDwNfB&V2)|=4X H/dA$Q.˄6 ԱaB8 q=8bӔJV27hAb7;IԘH<%ZXn[є4c=}歇'4Ɲ4MlauϬ &-|܆ Ó*k#]9lP+A8{%#s 9%Mo˞C%- R%4Vھ&/5*r&;xs}'VșS^(#b*yM3 uW\bY0܃QoFʘ;O[%ޝczb3t fd8sNx"w7\"oZ +CޘR@A%clY-°Y ,Y(,==ߖhf]_f1p*-ׅjg[-'Ҕ1U @f6I9P.v{I8T骨9gL]s3nY:_"bv\%3…VJC?׿v2jWm+f,:fd,w ymӋQ5>8ANli@#巶zVM%ߚSϱ^yI!/ٛs~E׼߁9YRL$] GL%9Je~ϔ.vU;6 )2Q&ABM܍[ "KQǾtPbզXzKV<{qxp%#Nՙf(iA8Hl͋bB%]1RjVS2u$޴Mx `Tĕ$UV=nza\ !t"x 8x: ,oP@LvH"&Ge^fc¢¿ש϶?Q^&+p38K{f|b\#=:; ?)[쌱.U;skZ. bç/j.ii>?g]cK] n$\|Y/z S 9B8i ݏ! .,<[e4%CZ[H)b8q ڡ2)o$6֔xv3d fěZ<ĿȜ3$OB-S~'U'zX-#sm{ݛ`| 0I)g׸¸ci}x7g( +I;k$S>Rm~Ts+m.46nj'/;,OG49iv?+#1Vx® +* l$2ut8wS}Bn4_NDe✸u4xwo.3TZ0ph'g_Oma&&)i|@ h +m>?JMdaq@y6 'a+@2CE0H ,S/:WL+&mv[O%8Pp-Mv}Fb6p귡m"7 IqH[5sA *Y픧2J-RMyCGqͿ?2 $AP ? ~dLw* 98ZW/n2h>3r]j(<ړyFJlcm]t |zTf!}h ]a֓$1;eBʷX$]7力'?kH-ñVRWQw&XCHG~A i}GhlkދkեHU#V Q pW 6:MCu8aӇJMVf3S ĪA1"p]luEn .AwNy*j*Zȼ<3u@r׼!жj dh͸9zHdb&sA4U+MZ yڶ/mb#â j"yXEēn;ZA/=Ӣƍڬo $<ҏ Ƒ>@& |^vێ(PFq҆:,mERWz&2w'l/LJ柬&|b[aV!š%+dOmc8A(ǁ}..j^Qnz[1žLSGrh@d dhIU{aD·}~feN~*Hs [zdPuM4^|mL; |ɀݗĺРrRh.k8fK3J!!"!H]Q!S9,94\}65Q)ze[UC݇NXF@EnsWؽN%k!v_i! i+gSlF-4C=Қ @".(?(evBխźY +}RIgMvB,'5lY'qSQuI\H5!(8b2+T `/|=q VdM_|=6 pd_(Wo8v1Zp W Fu /.;,6Fwᇕ8Q4, ?gﲷ)Vv>Z"Wy[O{ u쒾_:Zv;jt-!0 4%^$#] L HƇ yV;&;]lUay a1, A&~CBiB(CյpC8Quc(AK;p7u)H}T^+k`XHo욜 ~+jƱ` jބi%(\O֥X> KWgGU㖖"X퍀œtqDʳRpub5 X}jp9a^8Ī]s;,.QpdwZѰ @=G_P9;MT [A| PhMa8h[&[o ݷ{6[-⪛ S*!&5?Eء)dPLBdkpj}Dz"`bzWZxT3oB7˾/~sbǼJ*1ȵb2!X2RlMvʞo6JDqL=W9N1 V)>v )D\)IZ +2fnώh. 0P1Σ D.r 8= ѣ0fXHͺ&YSpjy7xayP#Xj8@^cZ!n rEvXTU eήۤ^ubo#!z\Aͣqq|b~m | aiv)6x#raa+B6M2$2 &aB16/)yZfGkѣ] -׊<200t)KDCϮaE8Et5U>V2  [v8>gz0$_Y RsiN]@/BIH&zl()ykUÞq~X;[TۤmLc,9ȌXs8AL$rR"R| (V;ʲP =)tIF/Ը9"\ehm0`$cYMo=]#/8M܌oKn0plTԔHدφSضEO9m3Tש~?/!D=?w1|Z;[WDf6iT}/&g\  'p+ivˤg39n^G;u R-`:X̣~dҩ a!(m~ܺHcynZ +(CFL4,"Qz<r1 zBpI(% j$$b,efYH̪MO]{Gf܄Y2+#V_q3j\͙?0^BQR:OҼ_?iȸ!1.]f4X}urYA>e:1o] ]2{W\i6qE#cs> ܠD.GvQn& &b)d&;%x74jgcŸ9.إT@[nƍzmz-`21Zq82n5يe!"V]"sAo1?Sƍ^ۋv?,;â?hd$Yp h5At!lu=N>WhLd4w:W:̠_2`΁i+Upt r Lkɠ`#AOdFv`-o UDº|I}Wއ=et q鳳!ojXjŰ>z0L0`q(VFS;,ͧA>yˑK@ x>?{(oJq7Eq=|/w/D]KH/يѺ;I`ޮ{4yrΧ >&iv]d'3vp܀[$bUo-V&)G@cA)-Ӈ8/Ψ@p }]$ >za"-MR-)E; 2:`UAnmκB .3jaPs/d۩Y{''sb6sq`޷+C5m-UtVȃ.S;o챣=E}YDr02*BWTςAw/`1ɰ(h4L<`[XJ:nlC]Va,zHKMI ⻢#Gfe@]ӊl? dNA[kU)Xנ"X)bbdm- M?B{BKl/0fgS> 2,"6N_1 Hܛ#oVg<@iGizD kc~u<%᯾ /HAx} *|:.l jnrZO~I8RެU@4EGmonܠEq,px'};ƳE~@@w`q47k!omgXd!Qˑ֡41E#u5 Umrtmo4W{A7DMs?L$fE\C*>oH%5Txb8y1L9ZA`ПX! >ebO; ۉg FVV4fhg%DOzsun@,gf7ϩSұ <0`}s4UGEcT_C,A>cW-㓨U)R cS؞2tlگ[w{kK_1`fώ;H+;?";VxBh{VffԔ_Bb!C|v+ Qk(OF]o uѬ_ʜ= { 4ADE/8kmn8așs:ʡoJei>ufԺ<x'"+T;[~ũBE3S`| +뉂t4[;(yoG #׸[dz *#AfFyF/ef-F2XmJSu8"ݺI/CcmjT?H(ٍJ>5 97]ڑ݄UrŚXv_ڹ7!ˠ{AT馧{[ Zio:0NԣZۘno-oi/:8k/R }fͼ4,z1033On5)E ^Ώ&|qj|E4"8Q+QɟgIZ+(ȥi>O;^I6[ G/}HE1I6| 11ꏬNe>ѥUJ8kLR|񏑅(ߐn|sWCP!o?4P[h[(f,a{'_ǴŌh_unႥkhy^q6hv>T\ˁXb_k`ҜTPf+jldmg\gjcB5ypJ OofZ¨!'P6;{WmZf٤r,StSiɿኾ5 3'K:m*}td}L WY)zij^N&E&h{}[3"JR!#=fOM%;%5?U'ec 4S2flLw>Ӳ ܟ5mb/[L%jpbS֛.( anvT%RسEJ #@),XR^d L O|p5,c4zLVF8dҞlD\xA}qԆL'(NO{*zSWa`@S;{z2ӌ$|`iN˓0^xZnɆX`۵TNL͋Xƶ6q/QsQd7d]㗀E<3ԟu:[3ˑ$6s]a]Gl* %P8Wɏ:>|=alHBѕ6.[Hth%ؔ(׶.9+E4n<3v@ $Zf9؟9uqeltX N;EU7OQn,P ֢{bnFI|3M4l</b&muP<8w؉?6o#c[lJ5^]?>ǿm D+ǖ!Ӄa/&‚nd8jcuC̜f|yN+񙇺3#}dHiѣqZm%?lBMeqG'MKq{<1&=Q}y,&& zu!^,n1B5$in|~Pqj#z{ w`,^}ߺE֨Gq-ӡ5]:*%NOVV` nYY軡D8&2:R|n|V6)A9 4HQI~Fqz a8·`~XX%GIm2ض`2gH]aShZUDq-Z(޶RU2лނSٔ"7Y)Zm9"p:~-iعA('ѠcHv5C11u-hk;A8,2zk"f?NJ(߯G>Zv@F0sThiڟRSH\2:^ h7t1g +tMI^0%59'7[RPlZ85hSt3C2>;D߶5"zq$ ^SƫV{ho)Q ¤־"΢ 3R^t^K&q.¾| UÅ.,7DM}DdD}uWJ +wI|'cݳ[ CFQݘnʰJ"×{ihxж] ~A+s7`)+(O+눲yOs ]COY PBOb uI|+MÓ mfRߒAЮjfsl!갚К 1mYa4tL'6)weVf:HT-C7ۗDG} 6< J=;(kY}ZM#(dE"Z^l-iՆbscn<2#o ) wD=@:CSE6Ojfw;$M>Ek%Ge˓TɼNdeƃaY]m U%\㘞[jNu*cd+ }VC >2?@V7-zMƿAZd$a}?T2HX|/zB-_xLs1B$;=rtHPEa'mLE_n)i@ 4h|H |fU2uǖY`^ U{(4%r2iǦa$1P!uFJRI ՟SkGGuClSC)W'!Ӳ{ ?\W~IJFBơb.e  6 l'eRDyoyg }v:I½&o~c,`DQT=Ir"hђ: "{Qo !Z 8KH%l]fSb (6*SȦi~[ 9rQbbδO-tZn%R2W.(,th)4ŀ,*Bl\Lgf5fR,&o:TIjgl7 S_mkVi66P0v@3n7rbU=b&,UCk%N1UHx+C j-jbZ?m9^',vM{s600mGC,XWbtR;ѢB4)y؇CNm%a$U0ʴ=`g(ؠݾD( D 6S>%4g>|Y2 (ZWfFf=[B\BPf,ʺao`^,戇S$eOޢՏic1ʛ:8LKp&qdKqC(cv*gZ]>IZ3WH_"0Kf+r(?Wȍi vl-o݀chLħ󟦽H:cUyCڶa~Q"&T qߠ/-옸tR>25ĝzқBQm/aII kq,746ݸ٭z>~L=@fr~Ty7?J]کY ծb(R,IH/ I}֞l1@jMvfJKZS|e6 Ƨ?g冓"gK@k%c,?Mޑ.[`P\<)GBUvŘD"jo<Y, 43hE&ȶ\bNC3P@ 3a[$$;0T ٖݛg$qITѵ95{dM6aLO~RWbl 7+iKQpz~-24":Uh(Ϩ_v2!.x+=PKX5Kh0<2[u`/mX:|.MW.3j+ӬyYD)-0 ۃ5|YLwm?N^I/3m+_c2)^c~0C E + 2$SnJC‰Y6--K^Q=7!Z_z-8}xId+ͷ+3dyo5tVBxZ#"i,c-082UCrd1A"Yq?{_%]btJŢ {޻reUD]Bq ?Ř3.wSE#D<].{IbSa@I!΍͐ŁQ~dj).z18I ki7Sl6߈ [8q :O|ТX};-un߸Bw[{x5 [Rb`H颐6h !VIC_w9C.GE)d'&3bM5j@!zKMcl[yT̜q)+\-,LN2A:iTŒH۫/i佔$p}47gZ Pd릔!+ޕ|Sם`k2FUrI.̩rz|gv'쿍l8" VJԹ8xuN*<͟. 3UB8]^IZTl'*-:N1Bi:O `^*7"1#ُvޅlW5?󝤞ڹF3K-]-Rhͮl &6s@Uz]Q6ρt&eH `eSlĦWnNRk[ g:Φ17 u}_l#` 7&e봱aҿy6.!ܾzP<-NĖ뵗fHJ@OEI &3x7Ы'DzF&C> 7l" Hҥژ#p;ƐUk *U"_={@q=!wR{&|;p}#Wd=s;Bvx'xDH'y ~W]Ѫ2<1)eMAG¯sgdkC:܉; b1@ijp2B"2f.,4`oii.ؓ]pW "(k~cjl'pc#; A-E'A^7Eޞ⪵+fw|D|p qɓG}$؊U*d@t8VfAn^]OTߓCMͺ\HUzxZiYl&~8pt@d(?ǧN2v#/?u7 E^znVOK uq…e,; ;]El%l>:>2RPYwREзOΙ#Sq\K3Yux°VU7 xCܟO tMp6w3A?Wy u+Cni ឫ?q>;Ÿ(ŭaDh)al"O4{0_y}F\>Q@kڔ;9'8(FJ$LzK U|+T+H$:}oB1; *}[%>O}}Cl6<Fu4~2`oA[lOE_UAeFlҍF_\w~*MԔ5YTL#R ׹}~!s}]p'3 @,XǓYȈ 4@g]͇ˈMi]i t$7;V#&=ZL!w+.tӏUq/4;uXkɔz5cw0MrytfNJڋ+>KALF҉kOZ$a BkT@E1}ɎLǞThuqmx[V=X"u8 .\BW7qjGwp#GuDp3y` a`苊f){eJ$?zs8\u@ 5ł*4n}㷶T]}[NuUcFEl>p@Y.m,Լ>6_xT/>(~1^$5u] !{z嵕88M[P9h\ɋ'C_"ny<\PyG\nP*Qʝk97R_Uޱ9$o&@VaYQRI.(}t/W%`[d ,KsLat6I$wK3rb,#IKr0f5E= E3|v[K#@>3gMI 3,7K)(i^`*.3 {v1 AMjJ[gkHįѩ1t0gOKḦ́Nf緀:α9s,L LMQtr(AnЖdrLzs}?# 2!96c.9[2,W5*2l1e;.osi>kbtSFsor)DWX@+O':x{ے|g\E%8d #U%]&)> Y;eUC+M$˨d|\rpȨ,<<w'DKcnPjPz0иWQl ݢɋ+-1X\J87/P^3zw#_G~a ٮ. `ˌ@`, ;fzer_HR;[F9uv7]ڠ2h)YÕ߰l溶=h|ْhjn ‹POH)W):As*# `gթoMݥC ?)d}],*_aEq;|14*.1%)p+H%%uc $beV0*֠4|7Rϑ3twqFxWW ?MlvU,rKAnp$2"j5jky.u2p4A6mߕxa,JLAʙ3ŷ m|fÚؖ_ #Zo;Vdw*uF V d#eĠo=Y<L7m9^&[j6) ۹ZѠq? SنvJZ@m !_lˮ&sSut508% WF; >^;nAX2+y{!pҪSղ$9e,ìt-(yR C{9)jH}}JbRj:$-߈ʓM2lՏ0x8g~ަt@I{k?Uҫ# -c4c݇)$/Җuv"3KDe_;gD=pGCH:d-Gٗ`Sڱ&=ujuNs\| n^!m%ͮSk셲ƳR#+>)crFmv+-+mH]Mf|+goQq ҵr#zP-VvbskT(^a)3}+k5L;Y}{]RnU%J2xQ vH~$j$ aG&E7o4ǯ{n!pʝ_n۸hrΨdc*C˿ &ICkH}X!J>Ta>A'FBŒH2>^^yw:Zjnf.cE1lJhKYUB.\+4E2(t>31~R;M6f)ϏsrI\rF>\1xFa^gUe foytӝߒn=.L8AmwfKAB3KSUOi' IPC`:XqD]#0R|۵\S||f?~InMf싍K':F Ztu;Z9!1\Dn?g|TXYA?TO.tZs<+!t{ ?KVv܋MܣPTLI JN|if# Q@M)oXh0:rWR˴ݒ$@V#*C>4D/㪘F?MŒَ3NQt&=u6*~#3"9j=LJѼܡF]G &-ٓ0K{$?SBb3R߃,:L-*ࡲ<6.c TX0:9kKUVCe>Gz,ԶEl,)w*[t>oN_3+-XMZ$UF2ZM ^7qm+M吼Ɲ|ɒ 'o; Cc\Ҝq@"/[*Ĥ`F5*udyKQG\lX7:&5xW+b8O7szGw$ Y/9W`k{rrNN>OO:$r9wOL}}馵δK?Ombm񷞶]$ހ)3|`<5-'FC/ze|/XJ[{mw/=8Q⟧2itSԞp%듵BGGlmܘ4-1-;@럀 ][1>)Ē-}ZЯpTf"J/Rl_/c"4Vm2fCE׋sl8?8 "ɱ³/}:Kvw ]Wڇ][+#?ΗďpX"Uƌ$Q4x)VZ5^+lc!/Vrj_vH}(_jG*nئQ ^=}yQ6kl] i> "_$w"-.*ǐSꌨGeB+orZ>V}Z!Kc()Ns#4Dc'mG[ Q~&--ib)2B 2F 3\S`M>4bHˣc\[rq/ LHCƔx=0'uuH;ECł=\B`2P潯[0UhY)\2lf!Ѕs]J/?$m zYڗ~P3;odDaȓLn9.;r@ڄV(@64.XUTJmMݸ/ &kS#߿Ƹ5GsTVAgXC}bdԝ?O2$^"S}!+ٯ̄]`6r5,OU{GmEQ1p,di0"zb`>x(Zlms~#6$LoQ-OCS$빺Vz :'/Y|cO7x{_ĔkZP!6qX6Xy6;NQ%_P3Qq"Ux^.żl="s>Tٹbq6%`g1?`3=zq z‚E\;<`_Dn8ܳg[@mJ'OIJZ {[;`g < ^J3,т*ђ={beB<0>-_EʩPI V?bJZ)\}7p{驲5(H!:t-%IZuBϞL|5;F$#OHI|B|8yiKwݺ=a}oؚ8481Gr]UUosmslW( 1)kIbF|W;Ա&c>sH}N#̢RqO'̈́$]rtRlMDY/[t8ޢ|5`WXG=-{bEER|dDVy3k\z'[Pd;ܘvGchm Tʱxs>LkE0#pdFw!,Y !n.B _JG`_?i>|g%5 fՀ T .VHW2H䑂\^oU_.S >:HTS9W N9] -Ì3KCmQv>Zbp$TN/AJba1eqsBGsick^UX'o 0z:@='ge|@V)g*2ֻ]"op {s (yW9||᫸TPTg7l}z9uZ8$<ZM`*55^D&nCW G89Uw_:7PJR{ic/ד P y< M6j{Is^8Uxl#{2N8y=Lw;;%mH`rrs.Zx|LΖմowBv:R}!Q )rh;=<X&B.Zd;7>O=1vv/<ϒ`}GRNBhLj6?a{{JMFy ɍWT1:7fr:7 SHN ur1CM{Mqg7VkK"iD^3ܸPx2G.g6*^XqyS4n_܁BQ|M))J<>T5?Gs3:FIH7#X|>luؔa]!"Se.thp4rnz&<*e=luA2ki;[r )$(2ڮ? !;5C+ӽ-['u%X1Q-F?~%@b} Mb{=Ϊ&B8f\kh'S _)*dԶ^cP zxz>m\ːhbCIUZ(U xGC 4Ш\"|%w5vT{δ;lhZ3oS$8\88DZqH|ug\ ú[o"Ԅn?vC_Na,jJGg7 5} S#)e wU3n)ן2RݿXc !k IK=1דNPs{K ]SE_r-QʫAC(?~#.Si"LȆq 4fEbQ%uiߐxu6( >rc:D14XeC#S& x4Gx݁pՈmU+BQ\^q:䫷CS٥1jE @GOpRyIa)L됲4OƐ2UJ5M"c8KL@V+wa cw7YX*PF'^K*toHN{U\ 4X/&{ bU 0:9(M_L cܺ":ekmb&c7EIYhNm2 A~ ^̢;~b1Ƚ39rpm?&_kI2-2"3yaYo=\CnWګ(kYxbVJ貂 ?| i> ჰTl>Ωwf]r*x9Ƒ5yfS[#yQC0J*|MߥI l#g׵Kz5oPJsʆ,8"T ʬ(:D)C\q?1WЅ]Gy{jr]@mabVvX/ݭe] NǠ.+l}(;8 =z'Ƨ{:{wbz .xԀ\;WALAzZl@}:p5tPª N=~ P<9xMZQ.fkoIC/b(YU F>Z/eޖ柢ɮ;C7G wLRMۯhMƂ H0Gf4ӼO 6F|E dj:֌_7Μh~#^[Ú@Ѹ@+0j.D붻4@Z=-Dw7\"HG"πvO/XsJO,%ڹg2jQr"&_J}m GFk sҴ^2^լp-*r^\2ds$$ `(]J@GO K/V hBeB|t8.38\rDm3_Ola3’z'$ŦlΛ'p:Ev.nXGVL&)EGkH,N.~B{*VwbR ̱d OU'ҫB-rL+Tn *NEUK ewyBnœ~B.&.aT C>I0/ AaSe5S$=6sF`HҚ+*8cb. mPs:v!K @TTk&O`JNgveuEGUbYuaM_c7?IBr1TQZGOg䏚pZovF3 e[m)A0Dnj7HګXu BQaA܄E(Pltן2i*KQV#6@{Qm!4-i6iriNRx!U^쒆/枣3}yֺvC&v D{xxyuy6*!,$awupy9H;P|1/w@X?0y7jI>/- i092P\Z|ih' U-I]rG4.AZ$mN[ IbB -+ M9Þki1uΞ`Лb 4ן/dk?ֳ EluS`c4)b}Uӥ5f I3" .~YR6;5%d5g69CY9jQ|r$} D ObyQne1Khxҥ]׸C̀HOr21JpR6χ i?ƭ} "Y1/p>'x.{*>-qeU{w4ȅUH$XRx sGD nCQruYj-:oP \|n\TJO\&u :evF;pnA_}>QjhZhV6&S F28\*YRVt mvj:J^sqZmA]D >aX{u >kRykC*ړ(9Cn*lJy}Qz8M-dUBt欪%*ǯa|QL?|a4 u-5_1P #(;-_GBPPL[꣙zΛwI5E8&ѦX ț*=[ʻ8hI>GY4rqI=ܔq}[hqNN4wkDyDlGYrv(arO< ];f.~ub4UI_&% t8Wq9c@s*Tl:PU̫#?v[e9vW.ݴQ6KOj07;S ˲8\_9u)%Lk ؚ1]qHw^*\ %y*C^GfW.Re0c'Rk.w G]7w+q9N(%wc2{*a%% ĸk92W6ӑW9Y^XC+NE[4nlƒ\i[PmaBBzkh< dH&H<`/24re BA[7bj7FYѠpAQT-eZųF[ƻ0/8V•n)CQ:Sq: >w>j?98 ":$:;[5!JR;9YR:n=ʨٿ{Ópg*ŰXύGeRCb(kOBםӀ19`r/8vg:tY}*0U#x1[Q2L恖$Xa;,T3к Ҏ:ӟu!Y&k67k!>SVa0>˹>#GɇEE1|s=F1{x4 D91w|y&ua W޻s?t5ͩ&5I'u0ZoiS!,3Mު|e[Gmlk]L:A0+I͞ID=aMZZS _[**H*6WxwrT,\I2's#mq~%/ . ?u62}DgZr2'\6~Xd m"ƆUlW\`@?qCa:X3e0#=&Fe*=>@NV\Jr4šl wM#Y0:KϘo7i ybtզwlvKIrz&BN pA ܌ö:q287lrocA$*BFPÍ9y ǗtTSԭ\;OėЭ-`TJj~XN AփέOԨŠRNԖ3eڒkԩ|zuN FP Zꪻp1el4>@oN֯5yTRe>hb-+Zz|>F$OX Lc պ;XjэXhdy~h1N]th8vPJ$jC(D}[\}:s^[w$-G3rfАsLg1Pz`xe8ajj $ה ώFeG^TZ u[nʕ&zI- *. 9D۠bF !/17sin)t GzdLҖ_ܡ0NYĨ-L\_lAM˸dIGD2a EUG,;s/dݔ7,"{x*$nӅK} vpFޯŶQy4ƛ3tG2<%ف;Z!YƱviB{(l)fc-w'@ )FmZo<.y?/HKC5A OZUOT@@o?s;`ԙ{,;>i\q {)gi[!ػD+a{cBmV!5;"`CR_8uO࣪d; x3Tr;"{󱈖j.@,߸7at`3ŭ ɗOۑK7W5jsXGѯaӏdzجpX/fbKuhq-ΟeAe~^'1M 4r\Ci#9;=4,љ+t~faLp 9J6v,.DXu,1d"a:QH_DCczȄ4*%4 t7ZQcטY^7wLQBu D]Ou<ĶRW!SE+s)xM.&L>,ڼk1p'q'-mT9=YrOBHV ?M3<@¨ͱ|̳o=N~F]1) kC%$S0"Hfw%, v4wvc% 8 AAgȰV.+23m c` dQJieYbR7yptC(Ha9 Ųjv]7rG!!3o_ᙼYX9{mT]7O3UEZI%Gawp^u'k WfҰysmh0Zʆ y3EڭhFO!wH8_Yo9֓<qHڜGRpp<5 ߟ$ CϺ|/t oUyyOQ~, N0{Dw }j08 ՗;f"M!ETQR` %)0dH7cr^7'y5 4ADZXGo/$CLlMQl|QO15k qIf.gb\O^V:~W'ZT))!cB}]9T~T[%&gveGό- J+}I9$Sqjk#SsXQ;p]7؉ÄfZ4vB~"ڷbU de$iQv!wTWE2/f=vC)!'x2ɐg?20 ZT?`0##d8):; ĻđN>a2 l'ΊEwx?0{ >πqءE@C aWTj|AbZFp Um'hj@'>jڎP^ k3?dfj- M@j'1/;?u9=!KGK0GP9z=D(&T4&NW*̤TVr8]UZ(Tx*!)zm8k~ GOLӴw@v@>QUQ<Uő92_R b_J2 |b$ ] ٶ87$Cn]`eI|DLo K21Y9 )KNʰA9`1^5( +ޠsk;%yj &,ĢsXd47p\T}kF@1THp5h}D<5 g͉8jeJRL~PH k:,ZM-7m/+KcčTc1e½g.V41#!3J*_I'=e*B,+,,FBPq?C6. 2̂u0ந,nl@%iLwWq(Y,IRv4+m0S:S#li &/*L뮵eC>^+> B:Dي*)~J,H9'dr>δ:fG‡Q sڮt6Ȝ u; a cj76&nOQeMh}݅I~=/|\w[d5ˍ Gb$+/(O5MrTx"^/\="0,ybIH+0q|Pb0_ʅ>zhv[Zc7hXnzF SvUvHc:+뢽:r{aMH0mVR3|ZtBmԠ" Ȭq6p1>츮J!5›NNZ;EhL&rXBUƉأ7K*x&-d.tBa0ՆWݾ*K]tq8WOc7H~>i$g*ͷq$价'+[t2i OWIS j=.^ "ij"MkVx.j:f %^ 5r#$5Kܥe!% y#A֑~8ΜF?yPR%=ЇbO+ !Br8>ख़ۊ&P8~{: ?s0|S&T|(2{oz5ޜȮNPyX iRJ;rǏ=йCz :R "IB\¡950{/vw.*Z|Cv%Iuk׈r%W^]m~^RqU{ul'#Qe\n#D6-( >sIpRhC/ߢ ifZYޫ9Begf0xu=]SFBI!80V0ZT{yong%~rV)0٢_4*/T ="W tdxg!]c(u73|6,w^BObU} }Mmb+IG6"#j|"۱ ȺBm4s.A:tVWiK#KUMV)T [bU*F '5CTPKI#gR?uaF>ǰJ&+nWσ4U-I4:f(r%ɸ^}cs:Rg=Rۄ캪 _ȼ3dl/diS9>V"fT2zojƆy`'[[t~ Xo!z"ʉEhZ m4G līES*W[V%;~6L¿SҩtYdBsT[O;GU5R;N)RbM¬[DbG_[E\|N1v qI()U^iM67q;zen( 'a. d{jHƠ+!yHC0-m80FŸ$f۩ |,wi kX^aItuWh29*ՙui|+vĩŧe]pAodŘ[-u\^U!7jVO6Yr֒J.@+'Kpo Z j[7*V8`Q[B;p@S3*s|#lU$§uڈ0=z-1v>ʳJ%Z@},J8k+L^Z_Q Endq.Dy>]lH" ]/k(Ccxg&2Va9Tr&"E Px6Mdb *&u0}FqR_M2t coc4'7uB-۸Lêj4g:jQh`78Wg[",`R0ع)Eh[ӵQ94o5RbW(j|o;gxyVSZ ˳'*<=ƍSP{"ǎ[L0D/ 98(;C&||TeG wx?l/)[gD=żŇQBM1(Zr\IeJ`!^z}K R&&-YSBg³x˓ޚ_=y12# %Q~5Ce BFH 7| LR_epW8P^$@0 @L1:.5ap;ѡHoˆXxagrMo_tHFhI-LzT;w)%<0KтĦԱl~'0D "l-S7Qb6CܭfhBΑ'Ť :d@ϴL%HD,-ǸGU@#]{~ ٛ1PE %}o YssD|D(.П6hS[D*7T|!7 ΖUYi\wB >\D |`,c-gws]Z=Zιdg63 x"$0" c8bwkPLpy̒_#N|#TvPg~WFH USS):"|sN,Ɛ~*2oI <$+lKO>a.ݧNzdK p6i4eb_)|M .A~iWWpEBs ju]'7!笙ZO,31N٧[^zI8%%W5}w=1r/ K׼FTo0i&BXE M#6B]4+pИĬaɀFշ"k<.Ug ˼f et6Z\pϫ^GjL{a480[゙UFX f|?s '')Hd_(嬋TՀǹM]͉1J!"Rjph>h9\>xk%$w\Y pV#cv{Ąڷ=j^qk/UO޵ ?_Vچs׾:RxYF_$ !-ni3D OT3O3Ih4`|l1WBv.PEqF`;PQHp}NƒQF%R SH%-.Xȼ~ҕ `֊Rjcņ}`Tص=22ɤ)oQH^jw`p`ޯk)g%sE$T|w%ܶߡFl//?K P_T ~sNnu% TDL/o&pP_\tŃN-G{9$}h6k_CY;W Ixa~[>sL}CKS/=i3S|_1`ݗiBWQ P慠[uQ>n~P2rv3#f+)#6kɫM@: Jfoƍ$Y4C X&-N|'kO:<\.Bs}5וB,#jA:ZvܑHbQێ70y?Sg_~գ7|Q_Nev?cpVY{z2fj+g_<P?nNhȞC}>p:2ħ $Vu\#c,:oF5j}N=Ro^ܴ\A@C" Oynh !/HL# iWvʻ M]RDU$M(o"x8?\TVxG9!Nl)1nT%֐;KRS=QH,*/з&Y ‘G_0IFi)zTU0wwY`}?d7hZ@KaIϫl&1(2AL_: = A# E3nV&J]ľ_R~oȆohE&jw&O%)—%X<֎"W4k g QbtEGqA"΋arb^磆D!(ԁᯊi_\@`K4x ![ ='bCsF@E7d}'SGOAE%24U$ӱ=)lĿ`d6GdɅlwX/G9rU EI-x<5Nщ:l 6Xvk`o"Cfb`V`m)Vv7?;WnG4ʑkE vHv60+YG"Ԧ@x ZM5C/Ŷ1X-6Yۙv*kKSW۹Nn[$ <CEȇPپMԏЅE+g\W)ot1vLy/*s8DHT$8g,gQ;CK~{t|@(`s'+ձ4b]䭉F#.b-/)ap16Z\4[hb쩈yC ؘܘJ=j`aS "䛬N2kDڲ2͒X.*WR!~H޼/Qz>frދ8,? 3GWZ]ִG!w.wg`طM aErȊYw8D9J/ *jNw.l{CQn&@VRZ-TvIaoJxwRT+a^CN,(6 6 ]~/:W Daky@y9MB*N^p %5_Ƅ J(K2ExI:no.@?64 Q\wMS/ڿbGݎB e}2봚k@n=h, *˞OM9tc&(=f;8r|Y []}@vͬVJYU  7S'[: j?LͲ {/AO-tAwH\VTwZʦ jёo0s\6۟Ԩh{3ɩ :MAW4L M=u[ST=e˃Aw{w9'Ƙ/_ﻧ'1{m>)mH[cU9A6CsBGHbLMxYT~fc;|@$QRMrrJcQN:wlk8AJ퇄s6]:ȣ`z? P\>t(ꄄ=Svԕ)o~Gܔ}f/k%'GM,4ϰbf{sN|Y:^ƅɳC,Z{ Nn"l.ugҗNyt1Jx-}$,@ju+(_Qh VYIg#8hᅐF/O$)Οm-M7Q+ *d,0YTm 0ZD.ҰҘ| j U%ra z؟`N F?edZ/"n='É-F{Bߟb5G?"NiY<4H4&N{x7)Q3 esp'nܟ2tCдRJ<K'YrC haE\w 761R8겐z& ɮN7Faj wђlUeNr~_6X,t3Di1 Etiv5ME7ۜ.=xRQ,־e(C~AU3 kk)ftCˆ`~n7&;Df,q^%5C4H$F}0BI(c(7Zv`aaMF_8!UW=sJEK,-xv>=-|g!ML^Q˿mL!AJuPhe$D.:5U]+0:KO٤yYLdY<;YʕB8Xbr*Md׍T*m_/ɇ3;3[t3 lJu ùS00HLMmWA* !}we:L_cQ.¼2b{59 --|`pv& W,WT??)clb:Bd<%ZD =#4#e|O*Ƽ-%[Rt)' KlL$rEg-4$3QHl'%3% mڼu%% qUhǣز4G0+B7gO+/;Q,WI7-+pu7Nt{1\_RTԡM_ԭ se?E+EQ1@d4S[{0Js+XrfyTF iUaV ނMT8Q9|v[eY%ݼ)LKtjꄚHn&~9'skkг `{mu.87҇2NFrm!8T _ Ϲ'3:W(J)3/r{2pj+Mbjv|L>+)FHچۖ`S( dK_, p^tVfZ)Sg˷s)څUDpkma\싧޶ۇf{v iI9bIm Nfi6&+( D@7:|> Kw(Pθ4noүM db@Ю1N"mQ*ʲx(!IY| O|ۈ0kڣ:(_ۓ GjP #6:s3@իj^)LQڛ"= Bb(}R|LmV}a;-&@,[I#DQCR̠.]^xW.jSIl{ңѬ+˝+T1(&~&/iL? 1@y9@0A՝y2Eiu8DVlAjȇ{h9;SqF l,zc @:nJ!qgNk,49~$ JwKonbYL[mX{rpOк f WNONa F(#Cて? &F4X N#_<(3FAu0)J a({RM2Z޵H-Wmp/z~$]zmGY?,ařNr<|]\z]徧' 8r:Vg,u ,Oք FyމX& ;k>O_ՋXrg|WoO`t.b^{<(MNl¨KA%#QL^no}%#0b׼]?7cXBxnțدa4-/su?nt Ug˭wJjG9ݫV[,(r#^]V$?]T wXJ^o O!.XB:eBPArH_!D,$Sn:tL.ХQW[~}@UeY2(/)D.qZ@4)p򽨽T}("~cQ.ϥ̼]fNBא6.eA9`6|=gcQ_Hw_X>@D#tS}+= Ko4Ti8XBK~ov+Z^zz()_D{pͮ%M& PeRwIو o#}S9a"2<#m2,6cS+cQj&Fat|P(\AM];Is_DDuNsQE!r7\)rLm9@GF\(u.FY)AN 䶂GFE>BATKx^\pNzI=&ڟ3 (À:N̙Qb(hG \p-&F*VkE2V)⧳-\ГQN|gkA4}-u7_W쒄QQ1GwVXV!(f^ 8P;%gX]ՙ4xW((yxm1>V0r[v[=uc8Q.\AL rٛY䙸׺|\ϔ#YzV.=M'ϐm%tb22$L OX;dJ.7A2f:K&1f:~9ͺ@wtZ{X; {"ևV.Fi1SG$m~{ s$1?n\a'WКj5׈lo-).[q/^S /A~{Yq3A!x( Z+D]4ltkbwxYCd+#.,h-Lmc%a.~ᙀ_q9BJRz4Sdx"r䞬V#VBf8r)GQW+s-rfl3x.@/&]('9SygdBy}}+9;z; g0xN)낑t?H!8كGpE2}V 8*H \JfsR@QRWvZ.pǧbCj/~jT>hH]>bm@uaroqӪ_.y TĠu8,=saӻ\Idlm1Yi[VFUeW8 }0DB`^֯6[,P^~ -`BٷW .-ltj(?o3F $ `|nLp,kɄ,3H+{?+[?,&ky%Q!GwθTLg]kfG1}2x3FEepJf!y>=)X;hPݭ㴩O2 QC~8&XlYyl}:z{W:@m1)nJk"/^HS+['f~7'yi%X6^0 L{ {)3m` }l:z.:6`pVΊ!vb[{sWeה^UEmxb+>⏚zqm1 Z'RDB-l@J.z=۷9o0L*L9| F| ⼞v (xm嚞ίkQ/ Ar6o?=s[t'7śpNRAG:.,;WdSG%ta wƯ-=W-]lRLOej nxr@K:8]!˟C(+|$| w3+[¾Y [LN!Iar XY6_y0B}׭̧1>.em%DDe T.NOBCAY5jrZ뭥:Er:rsub'z?>iR~uq/gw!暑^ylMac ZN n⿤2״(˺zlu{)jsn hK Wq#55LilTf 8.^DDD|`ɲRp&U؋ cDM+D焥Cu။Z)-vzz?cu/b_8PH7fj?5SX&&*\ t DwU ?zω:!xPy. ܀OYbL XkMbH; uu24E1_^t?375I%A hqjAv~O5%+|P a2y,h.=@ʺtb+"%Z9/X\6P#Ncgi9bfI.K\wigpdAdZC[ɍ2wI6/] 66|va)|Q_`a1 &deP?5z ?/\HFjʠMYj}ѝ0Uc"!%\C4a.xίژx*OP#C F0&v *@C߸:*{7kF2_|QϼRu&󣢺xzwpfV0CAmXk4GPusUg|myX4Dnpc|aI5 Q l)n,Zt!*܁2'UGcgAH򎱻%UT'#2owBAk>LyU|]8eR:PR 鱀 M;jrh]f '|8B"6+>YB` LA)[px&0^`$J}{ɟ$cxѣI]qM s2 }I[x,yln.B6x7_ܼO> |\OGm:2%r U?n*עw0Qd@ Z@bAKc`ݩѩ):Fu2vdSfLtɵV}|z鴧1]ӮPy,W-jy^ ,&Q!8›1 [?[Qş:A 2ZZkmUOVɒB@";:KDAbgLXxUXGZ0BZ.4$ 1>p[F%=Q>GJD6__.H.c͚VL/'1pDivm֧F]LTtDv(Q P{`dJ6k'(bHm*cawk2zBP% ˙|'ztGy4CpRnL?dS~ÐJwa=B0~P:Xǖ\53e:*˶H4]ɂ:2 ՞#Ği7๷Ϲcr4cE~6\58ǔ &f銁eKmܳojcP`ndE⋉p ǬlsxVqK>;]FK Nm("ϜF$#xc_Y/N,J(t]і}9RA&ljMqL_vS[l[ ,uqH_4 GPًo8_pY*fƸ4k]},EvE3(U퐎3ӽ(\Sc6Jl,|<>m1yNoMu(h WbҚ'/*lBtbɸكa0">6I%#Bh2t |ɲeW9"QYf:Lħy|\J{K`=OY5L8q@}-jh{'(e'dg4Wnώ] /aZC{c_m |ĻL*p ɛ9ƜK88 g[=g쾴}l1é!bG&؝TH $e%a_!1arrZ<.c+'&S2X ĈN)~u[cB|(0[oNiD!)->;$xD}TpD`$wB9S^x): a(DoqՀ Q;l֏jw@ sTYBX V,P {/`I'1yyL*$S'@*)OMm;&&tS܁iҋ;^.&xsV+=4( fGǤ|&nNt-R6\-QJ6کFW9 ϚMkأpx.v T8[AU#&U1GA9E'o2'xޡߪªn$;[|gU3i4Jan5=>$2kԅz6e+7^|߫Y2N kz$U#X | NfuR_  FɹdEdiKC@%ʆEpn>?Fe)WUꜼAۡ3VQ0 '=lus=>=k>eQ(|F8+lT<>'nfNCeyX*S &Dž".ę=?Y1n٦6e_W ZeIt\%gc 3L5+L`uy8 ,&&D@4Ť7OyO謋 |<,™Ƈ4tnZ.o6^qUe[Q=b 8ՑSj 6; RdNW >U%S(n Ӳ]`c2 r@5')`shMiOL ~l7`Nӧv^}4O;h/`r?jO0$XJ#vQЎ㱫OnJVcr|c} '(=F9]E+/~]҃l<, |hwB89~"[X5]ɔܔj̬?+'5HT,|2 ǠsK!d>#0!U@UeMd-G]g!m-3~v >77EnDv*:Sx8<73Bw!4Z F}:{ϩ+k8ѱKvGPžk^,;'ko2$RGڨ{Kt~OD&Q^@zls=]/Q`7v~0:6|AΒOj[bq~ ~ksk?  c5r梨8M\ڴL ;Ѱ({;=t㻉vr=x~,4-1]Teap3#Po\4;~Xc.Oo|Or=s;/:+St+r̚XV -;MYj2~t_"g/h.ӓ߼]_ )K T F=ݼjA2>W)Fx2^,0P KWVV  b8D7Fl \V@TVЩğRN/%JO5$KMͬ+8!8[# 3 ppӽwv?jM{P xù,XPjR-O fZNJhTvXa? f 3 H3d}sz4@Dl'}r7y݃| !$|OŦY=L0LOƣG w_ʡ mg}0T؈VJD!r h m1uO+|L䁿mdYAF 1sjiy' 'Y!ogSt=);W / Lʰ1,YY5N"3rAtbjA(*W& P4rϒo:a8&uX{ge3[򢰈tɆj<iǩ8'֖-A!DZٰoYDQCC[M]WVY]-`Af07%S{Rmtqos[OF[2WG') |>+^x#D9)x"L|L u5sM6 3vn3X^QL˥ q[%!_q2GZ5<ȒSޥx*Z2o0̘zI)jr&uLpYXF2psMv D(ʣPXc'-Ϳ7Cȃl6  .l2.q#2ыf8pR[2b("z1K檗'-h)@:9xN$AX4cS='Ύ'J j"We:,$w_ކK5D`8ά47m2 ̾9F~6t[cr9{^<+n e@%Oiu;)dD8ymQ덭EԽ[x ~^l7T J] ۠38Ki{6&Fbu)Bdge)evMsnkShk- GQ!6&u+34@/?-1H%izHh5:(6K=wkílz m=o _C 9$dk3*lvhH''tNQF\r;;ZtLa gɥu2M7.%$Ӓe#Kuk32V:b&OoEv${uc% 'îaKծ}V̴U|\I`ev*<(0}̱zH*z8\$Yec:Ь0klZfۃm5)MEv iB;43w?>p2+g0>摦<%B1W5\V3 w<%r 㾺/@Tj~3M?hl,|I[;,JHc'jߒ$mc߲l~4U(M-!<?T}-q-dha5SsA>,Ir)OiNYKqDXSJK4]V1)CZ8'fo3i0{iFmܾEԧ۽G@Lځ!ScAEF|y;ߛ񸦗`uzU>-kTSRg;U\F\"|A 8ݽ-[X0+U+f霑VX޳ ڴķ0Esa*UQbZMi /R?&u##iF* p4#[Z8[?ЪOobS45Rx QCq>)Ky W<7N?WʡvtK%4ȒUrXD8 taz N! F=+c&0|݄`a'E =5UT O>qZSDC| ] KeG"ah ;`iM5o1$ 52Kvڵx%m}[ y'Hn'LQRε|1sS\N`sY& IT!mbϦHdG:H#(ýH;S!Ye'%ZT2>C^ ,v+qR;.lVZpO~\S Y\ ź_| YQO_041Bi~QN9e7n?zM<~ S/4ë7n%ޚbvP ޸;OT-e\C^%Pq ܽa4տ[ģ[xp)R[MKp;r tzSpwPt޺QwMYXk%4}O/M#{m&I3y+/5h^+EF_2=hkeؘ\JAdI/1 E[|;Ijx'݄kE! CWkЗF.i$<7VuM yXS; |Yv j1 Ywh7^ ggr*ZIljl,2u%uُ?X?5-< E6o?RUu KN۲ Gc&b@r+ËhlT]-Rw=ES 9&QHzۍv9 cc'kJ=oh(*rGfrWM3 Vn?mGK4bG>ču:V߁FQ֡τhލ0'$Urj-8?`XjBjٿltE(?ېWS+񈧧XKpɢרlČzBkK髩o!q( ]{lYCD  'Ż3Pt스[A9(EjcÎ͜NU#ǯ-:1lKFT3 T%;$ǵ?4̨K6d ZXq4) J/:"ЏƷAJbCE"pLLSw v^viFB1᪐J>צ78#-+ id#s\6+]>=(GǚCj dnԈ|ܮJ}DbH'2u۱h3 ZG}&i3rCKJc1Z^Rj`δ?\ԣz)9X `Q]uӆݲw,ˆ5`r,KB'bn}MB?QDJ6׎}#>4~`ӈ`JDxRZs[WdU4w |x`]XB>yʰSϽɹ fljvP\ӌXj!z{#=E[P:/J^ͫb}n{\yIRDE8y=_{+m[O 녛9 V{cPx_Ѹsf {ͧ .aBG+/1՚^GNw C]?"Co-QdQmنJ(ՅP]n4յZt`<Δ96 _aDKQ)/qRn^^ysQ,0{M?w`f#BSvbS;^/St5#@@R'N)-\K!8>#x6"]QEF&vD@k17>}}E~S™'K ^Yxԧ"O0Ӎgvrl}_Dc&D+.":9Vӱń[M D#R{Z]2X#!m~Хu1f֔T%;6( i ͝L_ݹ|.kjJ.V uA`GfQ$ׯc98"(esuә 9 IL,>lh|@($NI.H0ai:թs%FrqPݿR[otBdHese_(+$~Euʰ7VުJCdV2NY3bP>[t*\i2Y9Ҹu؟!I uQothK/s ~aR] ۧnާ`BO 12aH_hPŪ̓qTO/i nh uwn"&ρA6u7+ aY`j j s.esQ*$$`̂(~2{i()*~Z ]j.9fVE/ gNcf+!e3\Eg]>s{/NNcMƖ1r5!ؙ7ִ8z]zփ5Z2 }ӎ*Tqq@`,[cpw0alH (g+JrF?:@Y#SWr){-{wV2a} 8]f‰;Se ͨ,|eؒ|ܤOeaL/2YAcYƕx = uZn__GZ!U/_L|kFȻ1!71a 7hFLR~F;Abɬ 6б~b7D^}pWS]A~}v%򨋢;ֿ+HͫOݗ?xU15.λ/u'6+]>s|;)Og}&5&/O}$LD .?n9/\KPP`g`m6cb3bNNBPɸlY*1؄ޭ.Ihw. SMh,4zZ((:?v'[MS!_qyM #Vi]ɍ+>mcɯtԭC;Yk.S|֍H!9bc*S)5eӇ]CǴFP*ڽJb[bGEXXKdo$e]gGlH GXͤ|*&cƇhivZ[l!`WD͘N.klQPg[Yq<8fd "scs-вrRRp@e2줟V~e?O;d|WT]- xL7p8e0\ sяBhasqVVDJ_Wg;1sō-\+Æ͸EBYy (y/̐_'mbc|T!|%9kz]?P.zoBwY"2ظ{$N=0.`Qdvŷ V^ĒqG'hBf9ۏNJCsh7c ,Uj'TkC6TJ{``=E,(Kఌ65xV Pd#jd䟞E]Cf)/?Ӿ[MOar\5OP2Nd1~c TС-h..)h|lFxϹΐL|_7U  Љh&'Zj1 0(6w#sCC|9kr+6wl~ee-iĜ,r") ><,[oB5w"ib*AlD,Eaٸ#)TfW2vg*_ޜώ#쿸g(2fi*;zr/grJ; Ӳtw-7CSclZ޹y?/ V+('* Bq)2C:vYR#~wI87v\ůÙ[4>\ 'Kaov g7G{i#FF ;@J9R|9Y i9;ϮiG:clпnI'gҰ!n3\0 N|RFtCv཈M 4gfz (#ޜ[}[]ؕpFZrjcSSURGpaќUR4fnýK;Ջ$[Jx`ۭɯyD3 ]hz9"7AH$Vbbw,η-N2ƸDrisW`1(cq! 1c|*vLap8]خYn [b9)Ǯ1u3pC]5c!gF >z֋\AwM*wE}6>^PP{jcNX2T/gK64:=ֵ%mkg#>V|>4%*ő]_up=& Rrڴ72ld 2{L=N|{N&A{bjxKwیP1YՁ 7xkї_+%L)PB?ڱ+$Q\ 5< $5RԤM*h-8|? 7rNךR7fagQUP?@K[4|1б14܆Z dAT)q>'3'0hKBqH}U-zy;rx?[Y"OFT "drl&"gGgJ3{Tv q 1eYx 7)}OƠ\{B*L " ŷ7 SS>XN.WyGOzSQn-`Hn%e,P'N!lY\|%qR.* aye<|gɭnVY(R3[IEJ*!XCHv]/Mb-lG';yb夼;4ԐӤZ=!:QtWg^{wNKՌ4[bݬ]ObE+8UIl 3 UGIO0\$~z4_%9h怞wRMtVL"g$ 8WsWZldǎKL?XȄDz"OkJOh+kta,\9gsü|Cڼ$,g *܅=\Dq36~1مL N>H Hcd7cnWڤ K9.=u$7Da Ln )?w曆r:3"%tNB+C6U!rS,mm>A'k~o.7}WG84~ `NXɓT%=rznM5Wo 迪8UK>x.GO9|l9fC"`n țth`rh,z:Y+6N_rǗD(6̉_EQ=G+aL; Y].Q/|Sv$khvZrXG pH&n\: 1c;11rBn")ߔ!7(XE m;ń}؋9U"_ZKۘ,^*';;NS~{?^ImE+dQ/6wY'Tt9YHSuޕ7kEp#inPs:e-Բr Dc3M@Ý! R'>R> ,l38OTs̈́vSATRDX+L3b35~̬m ߭&ۿѹ(މ;A'(9|7]KsRWIj[GœJ*5;0'zrF2EREJ,@ġarݔZ%R`6m,P6M 3=gç"Ly][B³͉U!Y@Nf-YEq Bix$n-rltmlGD'Tmgl%p E%4BeTN/Kurq"lbmINϓ{*gxfn1;ݻqJ8=Q3?W>#Z+"bJyĄa{˜ݢG/u%')nl# 2a%,/useb<Sҟ?d<,"Lo5q ]lS3)Oͣe[Rɚ"lgۼ6])[3y R&@D!{yFVJ|Tw#Kr91h'Z;5@l\>J=XwIfp;Y(d_#b/$P5 Z)?i& Q˩쫟 sqeޭ288l pYg9,PTHJHmKD<+KR;=L @gxK)2RTJ /jjg7w"O|12DqUc+F*10kAuˏ?F?S01=rKSp|MmkdŒZAuBْPI*I, h Uu3G芪i BN;Ĺ _/EB{Yk1ٽӥqOѸA>AR'lk?lJAKXe (nS2/S\/R t]? RkP`ɬм]F \|Ks>bůWOkmyI`. &mU.hKosUYpuDE(nH^"tEp}n"ve4~n[WF4^gD=!J=oZЎ*y_V`1k}o0X4WVqt;q\d)\bb  rJ =%k6N )#|D՝`zv ⋵J\Կ7-_\u@j m9,#`:JA]|n!udtS;O# a(ӉRE"GnTB( dʡ]c589U07bnu9bۄXya=]1T'dMdha=U!zhhrW{70֏ظH>|'o9#][zow)+CJ FV\FPK54X9)yr.4*YAP'qV g坊xzمl]T{6x7f3^>)zjm9ؓ4yl9PUQFSЋ`nbs8$k&V&YrH +S?ڤ8\&+h0'Jrr{d J3dek}O#8;N1J,\$̃@lk(FlveٷzjHI7h8@)GWsԦw:!a:9j =#Ee%w@.֩ uaކi> G \bYQ* aaiՍ@E}neg@Klq}#Q.(Tgդ嘟 'izgTrÑ]de?ݵ3[7m.|6Xz>r:y"]]ՋBOj4yc_@qYpl/9ۆ6}FU4Nu4L pjbh1#U>g$}WX( ^r,VQvޯCB־^wtcf[^DJN#uW釉4I(({TX;*5d%6wty Ҡj+Xya?!aF n|^`R+ҦAK=lgjՌ˸sJ|0R#:3s]_ē;S 6BuܴoG*y ْlwX{9^gެPٰVn+sUw\*7K?ǐ<3cSM^'`q ];њYٛrbyʍ9;d|0I)]ǒGDN_O} k}6HzG#?* Y0h}d-' +HO]ѓyuJuB*0!©C67cvkԁmN0Tɜ"MNF;AP3&zaDȋk@ay!Uk 9T*4{)r)z25Ct|?v 9+-h)dw).vD% 5 Z2ˑrL [3%o:*"$aΒb ܪ?U`X(Fqd4!N_)gף(2d"?A}Y>} NϋW H Hl.@ ̺ 0j@v~1+Z٤hAiޞDz4A!{j<"|<`_9ySR#uc%][|\ґkh?8n*v]9/h[(\G`"a1>к&4-_KpK 8epdICݦ+$"!SqIYT:۲_(wOx4&^jL3Q*T+bss= -xlȜlv 1.Nf|ŪShP$5#1WP(u,RKn9J=goKݬEPCIbJLfDWgb!jS >%ބvgEIqW"]X ΅: B#m^cW0̦ n$~&o˾2T&2{P Z`, Ilji3~w8S4G%H\m}"o8*s-zoWm?6@̨=Jݽ79YJ`lHeYO&B<i5T-[금川K\VЫߩ+wbS4 kK3#$xǗD櫙35@ z>pQ7hIzC!Թ 5Wl-n@oK[PCNHE.K8Q j F\83+390]s|)m —D@8 dUT7l1-Pٓ[^=Wca`^NKYE2(z #!bk2W[,gT u8bDjwΉ|pc qblNH"g޼YIYS }ʎx"oOΉN3iE0Tt]s'u~SjN/PDGh5+.<:4eR)-FCӎ V{гgHhۍF7Y̕.M#^UB+ȅm'#0f^=c?xKhT ~x%=\+Sy|iphvB ĴI\I+IU+( 5r6hu/ҳ:*ss SQB>QG H?FJ?ĕP-2rp ܜkLhR~J=mA˭X:_cg=Stw. +4[v BO}6Er|oƀdlbJG墺oމK"\wtWSog\_e/=fkPhU NlѮF18P>Efw0R,>}`PsCOyrE6òi '8'kaIhG^v*ڲ IyX&@*|aKepS1Coy&hŲZZ!OB񥚊jly%?l=Bǫc*dus׃bEyWkM"OMڷ+)tMC̓ijT:~[Dsش^:%YH T8fV߷?=Ȋ]ӵ՘{6F_ENq# 6R%(!NFӆGVqd1-M]1]qzv`M2&:|.cM6 Oa_w0LvFy\. Tɻn  " 1$ '^TIE1NzAf*Sw'-ODG&(EexqZ`{NQ4ZWCseҠ +%3?bϛ"5$uj}z+)./y^l._LƠG5(@jcuܬMmxWM^+>|#A]qJjB4~t^eNRܛ"b*'R&MJQ-SU}qrxq1Uj9 F\~uxRٮb⥓|2fz;)KLGzeCVM(h?HKwM@t 6A[G,a% `)=V ^T0|l b<3KAb[&qGC^1>0j=g 8!eXfzJ#@`h]0(_pBw\i~P \w9%lWzdoTҾقb!s»(vnJ-:B Rnwχ#'9*˰@x$S}r8 "hI=`Xi=<%-qk%v"HVٿrVd$*|@\1 kRY(Egd e;_Y@Dɲ蓧*`N;7mƹݨjo^st CGUŨ:<9)W":ѝ'#`yz1R0[b:ϵH3Ơ nivwX-c7f<½õjKWX dmg ] U`SSaGCY"GrT +EJ cv` HR&S?_9[@41nHEe ZKs}DXf Q-"OoGG +p .Xj9UXJ4RJZTפ  F7Qrҍeۜo w- 2mFz0,fFapv(DiGݳ]4u[O5tY]´wNjTTp |15>Y\%|,]C@~d;ʘ)JByD8nfeC.gQCG(E2jjfkZm1U3m>c.GRn[][7ʔnj Kl"%K&rWnZ00e^UEE_ pðSI|&].E/v’jf Jqz)̯x 6 X ޠ&F=O:b5i#{wmێ ES@c\T;~ݿN1stV?V11_?_p~ݧ{zpڦn*7? v屮%5hrNv :TiO/Ծbb;3R62U|fG$1^C#kPgy~M&EgmQb-92R؁I"rkk_" @l( fo5#IJT }9f/S{)ۏ%wssilV -)^Z1ѝ(9a( NDlv[eܶd9RP5TւG^ 7TwJ1NX1i҃<+2_ oP^\@^#-j. bS[cBt> ? =񊾱iu`" _ejq* ĚkX[yZZFGTtL9?8@ )Z2FHJ@х)-yp5VVSVMdx(qLC;11n5;>q*JoT/2+,rtىdM&P4r|8ZєAr pn3,'<| QT407 H)1;"}ϩZw9&W P QYjS)H>DQ9 !{3#_)D 7bj+!Q8aVD5Ũ㈐Wd'Y$@zJYG1oy6<,2 W[J|P. yN>b5ͤ$TVf6Z/Q#nw?t{GO+BMp}#j zЌV@4ș6E^~+Ç=tZgGj[~6~Y=vCeZzH삥"rnQ{] Qy!cLgW6VqC%L^._rSIRkFcMekɧhˢΓp- əThŏ VeʝqץR8yOlܛK뮻b9q}#uenyBQ4H믓 E^7@hX?\H'NEVS?ce:gׁPt߆r+𚽞~ئryT#T \rii]4ߔf_'IɁE\{@YVe'=zAhF 7rBs&__\w2~'`0kd(KFb?S /{ hs2hnܱ DOIřk??Oʖ:)"rGnd"(_ތgE2vNFm9-= Es:!&+QK*Ԗlǯ}v<[^~Lu>cb(ڕZ-S抮rE/]R9\°!½_E3so cN5<텆/l_$V;HXTMO+08#63F,cxiCntQg|2T)k9 i~Ya+QhQa床#DQIZvl:_7`4RBTﰁ4/yR˱ƕBQ/;j\Ir՜D >17ً2!x63gSD~ۯxpW j[nkkihމ+¶Ɩey%oȞЃXO$~h |L+JqRV, r=п;bnQi\.Wr'b]l] lS2Lg-9斮Y<%apN|gIنۃ#sNnUPk?ۖ(aL!e(4*Dbly`IaU@FgW`hC# MQJ#>@{2)7;Dӎ\.8l:z:3L$QWȭPqړPxT )7c"LKZP>^;拨~Q=]߰-wCvnς `^OP&6Obu<6vR?l)r,˕4D+mCԃ 2n1:D:.;=TLvw86rP٥YY@Pp1xV ˃$左 M B0d*I6FS<3ZGoǥZ9n&39upɃw#!4pT/"/.Rg[S,`~SLJ [BQsu5ƺi roSoFuduY䇮&:'&@uQGt e\nZ CQ9ZTO%Ѝ JسDƠH1 ӻq2dT deP ?Iwo87*3>2B"t1;l}02Eo4g켫7ڱBpF>ziozel4קy`dB[|9 (hPw>0u6?:Kal"}zO%dq w|2-A4u"8ćЍ;i/hݢ;kEu!sSDH/8 s^eZN2}.c ;tk*;;i'AizƦI'#>ׄE{YbL (|VZ1yhxSYm֔P=4SH5cc[Wju׊A̷Qpb&=f20亠cjj XoռuLP&G:6"<*@3rVt|Mq,l艎R~Csx"*͹Y&?4|71/J0hLh,;aP$scB }vy 4ȸ C0z(eOh-?!Cx"5<*ێ%z4=&->rѡ@x>M=oXXj ;L<Mޔ_88-8>{?KSaRȌT:N*a Z5NUHK\tƂeuJvhT3. sW E\#IxYêt̠nKҿ*dHj5tCy38~_8[Ckl% :Lr~|R3-iQ}!K)GaNE9Kz-gw&m]" b'oPQodrb2k(2%L_)8kGLuOCX2*>1zBwAeew>yzQa|8X]d[eR6H< gΝ,b$ӭM=OxbۗF,qz qc‚$^ TiQ*|N;zTbAJv8"kv`֐8?BpI:4.V5GS]5r̍@fv0H3e%4̆X%6/%5m#4W{?0{:"!' M&6B؁jOG9Rp&< q@+zL?}˪0Ͽq s1yJROz4Ym&eoROj& B `v'߯8j{ <]&| `^Hy(X[ٗ=GE \R6ڮ˿[IJD sxp [ M?}.L.U*|{/3$,Pε<Kobcu-[Gb%8άd=-z wW Si|N@Fy (J7_V_beN?Y) 7T$H6K-8&5yIM,IڌcJ,LGԶ&$XG8YS" r!9m[L,!Fmt4NmVB)~{RG5^g(rbF[$Oks+9O C4 b/ ¦ӳn 3ĦEjw}}*z21~+Nk[,Z)\H{Xa>tYj$%g@ h_%nS`w{3QzӲ "XM!j@xe(8-țv)HSPx 9l[iO1rW0{kb _DcJPԙ?U#`^*•SGFx R&sճZ $9\1F僤b$B>v23r=W}G#>)cE_l\E%FJ %Y )r/GbW3W:olr}۾;?[/!Mc&LbG[IAIAY448>\M 0_LvJ)^]6َ(.ATL>F7[`]7"w_g1ry[*@&P>IF撪Y}K{#ǙYU)2Af9^x<>q~6w|٠MG*\l&"@-r̋m~RUq7ZKF&EFqqkfnw̔Mp+6Y,9cUG>n\IkYxI K k$f$SoƆO[Af 14j,h?5 F6_5sud!THU)H#YJ0FqRr.Uv;7nxjsG*cfv it$~ςseljuW凊ֵ'#x>jDFT7|(80K[Wd NK-=oKǸQeAvq2;UUNoZ*W'ϗK^,AJDI֓.zUR;򗛾!`alXt0eCL/IÀӋT~ǣ;v" F g5YǢ!NZB헥5F>g i`Et:%ZKpW> 1r 5PoD9yڹfŽ%e(I趆y=r}]Ko3 $C(%QaƖU`,~,3J}ZrQfwH[G'YluqcOmq[.rb.WÜK:ow;N8&1 " wQ.Wis %~t#p|8W_$b eٮ Tİ^.ώǚ &DMwo<]xu 5Sޝ,Wࢮcй_/(1F "\1+.ccH$0 ᘮ%JӜ 5bhm!˫'-=`h)r^f53+Ik׏v齙OKU/§o۪^7xv"3x9 ^-ׂ"z:J'U!Ҍ~=mPPh KY3c[=Q yꨨ.a_zeT癭p#f1fXSlA8(s 9د>AYZh󓏐J;cs⦳wS3RvdǑ\ͧo&AJ><=BTJK w߆%KRSi9M4p$-2H'"Yf@ W:<%y~xR6G*TjJJ>I:H]b)㨣zrx,ױ1Ԕ9țuC)2«IjPSR)AVk\`> ]-_۫.AZN5a!v( ^+oJ"-c4-߇ACx\uQ#3¹,VnwJu+ ŏ4Li#FP%sO( 1YE;h;l=l $ɦ; U><l1 Y﵀2"L6 ||[cM.H-`΄tLtb'=n2f5G o-'c,1WJ1z,#O`۲ȶ#蚄[ERx֋|쾬e*ᘓrN">p@ vqkGn5יGcڗҹQngɾ*DI+e|m|od)8sK$AƲ}15Yʍ&Sw>jBcpn5It_4D, :n%ϩ@H)d{K3Qv7K%){袼=bS3>_:\y/X*r2װY8̭)+UR;@e]@M70RDm[il\@_9uJe`}˨680_#Wšl#6GY,05J^~XN_yE3D ‡y* G+H8.=V*7IubŢrdwh"7랦$I%(bal :z˴>PrS!R)Z9ިU8HcEIEϘm&n]]euG;zzv/"io,[&7:_>&%QLGO3L(QOΐEM] Ry[s{֩J%э)yI,-IXk<ְUzEUV+aZcVr$,bxu!P%ejP=]iSj췈ddcu-AEwE 1=Cz~6A@3qx:Y h<|F!.!a0=UӘCE%sq|2v`H=HU2R FCgufj6VC MoedwhKg^a9X,PN#F(Qֱy@!1OnFAR$)!UܘpuFE^ɕevT.>A{}$@#9Q.duT"n%Xi/A`G2NX÷j#z{MBM+j8f=gS*yOY߰|~u1FIdp-&ʯAN>fa,l=Bh͝vQs*H s`ak4h:}ttI辥: vTKV_ #[8"e߭q:8kuC0Z' gzE~5vg%3ƑV#9q4F(m buVn?Dbz%1v˯#ґ$A&a+-u&YrHWMAhr_EsYa674RI"X4sN۰< LjsvHqUE%R/bS <z5<}]XՒ`*A{rlߨ0jcA KAUJg(7O:Р\* ou]^O{Ov`w|a0+W>;\$8Nר%{o")[ r f41bim^]w r?6 gܕ渺h"w/95X2GP"z ? v\%6dAaAXyCV#-!% NNY4tOd#a7ӟ _23[2˲!j%4 | l}TGqǾRc|m.Ym߂!oކ}qxc{:nJBZZnE!PO`}!*}ReKce M,bNJRpݳ:9U;pN҇%@6 gK88[ܓՂH1?"a-E-`T21M(%^&84L^ fWk{f@S'ñ ]MTS*w_ې\΢8rJ0 4 ެΧO0`0s~r+g PPehľf[AQ)7`n#NEapS,O6"iȱVF;.ULˤ.kT ^mzA~ fuw 8PXQIt}tJP O6^m8ǬZu( DvJRO$;aN3O51}2rguw˯o мLe{ BFT5,3!]$K|_-[0))Ɨ$>){a|ԍȴ닃xԊ{'rP{!%E`T*'07iPs@:q!tK_uoa >Y3/~[^Zǃ}**Q$hC jix nlIJ&X P^w1!1 u:Q&^XNm5C&Ԥ}VjkT._#AuB]+E1"^MYnw=AhgxЁm*exx|Wm.'eB*`*f(u{ $VsD‰<{2PʬLsnodŌc?H݃GT,LP2lH=ELw̏]#<}S*LZa &32n:^Z jq׸Y6yhs,~ YGݴU%gJQ29`!%DɟX[ã\_ /R9Vrm_BUy!>=(g He[ f/NK*@zη3Ƣ[~绘Sdv\=_JLpr0<."8}ZaVP(.J^U3 AJ7I$P!7N8$A]~q$h&:Ն͗92f!IكR&]ӍK VxI:nKy'/m&$!Gy2:#rƍ3YOhE"6kxb,]oco^;b"'gqL$i7b 挟SA*$ ²> kÿZEm*}cXi=;:lA:ʔt4 ĒvQ >UǸ1C"i.rl8^v2AH$(NgxI« QnF%Qa^Ǐ`GO)̏? :)I}ⰖCU:nѼ(kwD6Y /.RQcT]}cG uKZڒZjJɜ /\ pp+)1O?7l^TM?S 4FV]?= Exn{~"LEUkmY :Ӓd&T;8dCg3ԲHY|hH[|mQ)nw  :[NѽJ]?pw荳>/2 3e&$kGZs+}dVKI[a>?ƚ@Kxe8De'WSl7?XX N$܆`iT*0/N2pzvj!aɵAb{(,:+Kϫyo'Tʤi$ Xѿ-+_35qSGC4{~R~C7dM䫒(cʦ#~c )p/gUDObhE KM|hԟg.6_U 79hljT7!Av~2l^V zL)ij;οK#P8'Y}4@V|Sh~9+tn=d%noVGlߣB կvtǬk %-..ݻe%9vNfsQ`]Ǥ00&_ ΅)BtgFHpVe@EFrxKPtelQ/+7lOrbTqHS;+ԗ]:7ȓYM -_Fs6ja/$}YT(J *~Z#ʧw۳Ղ%IBI-u9̴I=,VFnԄ$u~D0p[%XhhqbjrOq{{(=h2wY)O$ [> xV4h穊_"RIWAdaPm*LBsMOTt-WS= Z^2>!aS9'^eoRɜ!q6 Y~LoG53SΥݽ޵xkK)VCuR 2a Sly` zdgKIY}q˒t'@իTA͎uOKcY|{}*yg=  jVwD伖o5o%eĮ :H.F'Pˆ&ɞ.N^aCk9EdT7u>!vro*votH@&<(%޷CD"[$h9^S5`we ΂P}>5"XuQቡB^tr#/nJ} E-T2!ƵXJ#p_fM8\pNcɤ`80Oc?to1ϛ"Z5'C aB)יhh9:i 稪W?"_M|u ?pېWD/RQbr/ +Z=ic frv}M@oiGNeI4^kg od}݉8 }RnOvq$Rt0aщ q`LWzO|9XM̀x[*d+E(=i*wL},b}պ3fvveooC4 woQq5_!Ⱦ_ȷf~Ч-V=4IA'qZvAyceͤF>b*{snXE--#XY@ԕ.꬜ ̕aӊ)Cq?SbnUY[L[}_/O ,U3ŔIѠd$|wF +_3iѺGg5p}bp>,a3{F<9~X9v~ט \ ۠0v{-  zw3\Mtؘ <cׯږ3NTN2|ishdpVĺamS.K^<gFr1=QD5a:ˣxc`yU\FAq5egvXoǁos&mV&2G5V Wa؉E<u/^o:<|LlC.qڼWL6k'd*u M92Qs9S/_pVA!Bor͜v)3 tFv;bDQcӣX9 ,p⍴R(0L#. XgPh@T{bX@G?t2`#L)՟z-nC n^'y!Ҁ"{~OX߶MPi05V3 7\wآ %,mtntyx\zQ M:F XuXUGUd f͊)gTcPP|Pf@,*uCk?a.$&>_؏ZFkMcA+*E=oy++)ގv]~۝z},ŬX !(\)`D;vv3jܢ @,ZW0| sJ@ce ! dZ_}5ߙ\-HTEP6_Z+d1&ұy-ۭ]{򭣓DtkU4zAFYABwJz?U qGKgI vZ3q 4fX?uqYC6p[w!`bĞ870Wl4Q OwGqZr!4 C;Cx뢦LT(T+:<ᓎ.Ez4cּCQJQWKj}ݬdFdXfG¸|ULNIFVj*+[@PP ǫ f5*A*~&_BŒY<~;$_$UjT{o bc1!|Z?H.L*tZGOl/'EY(HCsO16Ax3ŀ Ud!Fh!ي]Ac)-i5Jgz݊C3cOOɑ0`<aluK,4P# jp{L!M^l沼U:> 9 Ziuxz `+omkJc|"wxryFWԄG;WAŘ]Y>O32!d:3n/b⨩}]c fJWI8 i(1rWXE);rSW@@]wQ-2C> Ş|Yh'Dpalxf˴JXmq ;xXmW=JI#ΗU lRn `a@XXbh?d Ia*u41wJ/aX㓱~GSf[H̄}4P0+Ă(|[%X^ztϴӿ!">.z᎞_jI#fZB&ߑN(Y c.RS6uqgO}]=來sEl/dfJUh|Zz&}n 3vhȠ4֤v#ځ~$ĨcX $Z2se3xUR9m4c-H:1 qx) ݁{sü1 j@xWvq2! @/e# ?lf%-[yҫA}ټ9"߃1PdܳP雉kzؖQ}f<780aGZV}JDL0]|]}5sjg1Zns=|XlI)2, Jj<]Uea4^*ƸN#dʙI[&!:4^ .&ţ*AAo q6M #*XMmg]_%n샏. ؔ1P OFTlؐ©,gKgpA=?j$w.HMh3s( F +Wg?twz`4(fe#Zv2Sɿ q ٮ=v&{eٕ}^re,|L|. @H ݐ$PGV*77o^kݵ<'`(baRKTC{g%j7XX},uE].9jM RC9*'e?z% ?P6scE Yjr!VB-c䉝兼~a]Ts>%7iyӕa;K#_$lb]UdzkP(`| `_V]-@DBjXP|: #X6NpԚh]zrv9SNI>7٢X" !ʞpv£ iim*Yߓ\'$ϓ̜ PSTL?2r2nҧ)"*QX57%~(rTI v+)΁x}7qi~5;a.5#ؿKAj GAnJG(2w$I]/bGg WߡNNDDfX1=MWT-ӈ-\G^ WNR_݀3 7=R0 ]6XJ}OcصQ~q`߿3@JŏD`1/SG 쉅(-u!nxo4|t4I\[ߟ*q .ڄW/?a%9i@X{k)_MҋC9ģ|Z~պa s'Z[!fUSAQM}2eb ߶$+햓CDx2ˑݦ> 6T<Sh)D<ɛ2tCTxqt7>PϴV戵Qw݀:CN[NzQ|5=8(^":;ٌ[A}N}IB$^z ;|93N~@T-]uSC~~f2T]H(ZǯU]AZtj}{& ZݣF\ߡb{w%^ۈ`#ZyI:|2ڲ#~ :fӓkAKa0(VϑK Ff/z&5]ŘV#7\)]V KB\BدST0ݴڕT6g.o`27ZAp{ RZ}ѵ mZQL% Gz\R*`%e^m16~F Xࢰ(!EE:ưDU0Z[tc!ߴ*bl Jǹj^%%B#N>̟t`K* ե瑙kN_,X-T:6P¿j^Ck T)9Ê )CE~7k$k*~`9:Lil"]^̊ÒXvQ[GY˞ Ѧ8K[Rt|dd]JMfkpÉffHWAR8cz,Mg"x_x:Z߮9`6TadCǶdeBJFEeӉ_t8ˁM *V{{ɯںPjz q풓q߄"iAޛ*@c6tPq ; Vl0pDZ ع\` Xrqu%BYIj)LX}_\Ũ >;-_,z9&pW ~x8Sk򂒁@ilIٲ'kuwhP ;fFxh)x`í/Jw,%o!0J`f@IeLHqOMZ%( <1=c0hAx+CO,N} ~@6iI8yMTnyq`#%Kl ?I1C|{#;lR=WuN ݹ=X8s'܀T@i* #fy89W"'Dbc R7iwi79F91TP{ ˔@2ckh)$rU X6{#HN(2F&Ĩg}PCeze67fhgHYe5@N핵nBgt촡_>3OuE{$)ò r:%>NtHw hNs밌*~;8+[YN#Gp)\8U#p4-aj:noZKֿ}1:*ĝTdo[ Pk|R͂E@7uhWc, ¬ t6׳)3QSnv 6$+iH_c/ ݛBӮg$SpFp o]rth%dSwۜ"X IcZLg?(>DO`*d.y׼ud%d\Cv8Em@ teQ1< .%^m3摂of ƹ HȲWLEd7ft.yB}9).hOz !;#J8h^ wr8h܂]rcH!<S+8(MS51E5p RHK 8 fBUXagU[fPh.0+癆K03mY&*\con k*c0od c_;|<2{8!,`" d\& g['=vUzwx6IeV0{!d@?Nң{RgT5:xe!J"';>uJ/ih]G Zgb}~z;OW6I/Mh WLxz{$])xjiL$X<^zިUIj}WNc0P* ٢sGf,Ix+.ĉct"PSt~0Ա8BxmY}l^f [͋Kx0} }#`2ɝM %qC[abZr$NՀ2\#/M aT8|R4Tq{uL}uVG5zf̪n#>?FgpwwGel#U2+Cj[@|"ٓ )" ٩x,-L炎JegnQy=TߩtɁusXzjZޢ0}GQpg8{z~rYkHUs1,aλ~{R 8ۆ6oYѝ.`V鈉ҕ9d&L Ή+Ig "qOVr<= `?`vB+2Kկs&>P$~X1vۿzWg1V`K68ƪE)H%V$DtX{aйS#(C! Eq6z7cȥ;NT=އjM LJO׷2aEZ R'3idN :A CY GJo]j^,Bdak1Swu7gD $~CpzfP ^UՒ{OGRل Ll#|QNIӯ@eȃmõ028,H. 0xݚlS gIhֱ w'=:Qb};JvuaCx # K]yXCaCA$0JrY>t~.UE׀V_"ǁn4_M4rq}~W>L ї%>(@w,85yS韋6%9avxN%Jh;X63 +q7 +{RgoVq/}s?$*vPMݼ0Qȣҳ[3 xKƬw;8׫O_Kwa $wE>^jb Lgus[&)$Z79\s }[L18Y@%T_c1SO~]]@.67^}xZ=XD; XK)0=%8:7Ey,ϓt9%`V9tp/[\,ND5!RϭY~ka~J!Jv ^ mwYBףdl^z;gDzj?J`k\WɎԁ5!Ѱf0a5Mp,zD[}fcv@ndNsd-+Al۠U"Ku_5C,eG 3#ı8UH@N6o( ?b!C&TO98!$ӕ}Tm*G`3Q puA:" ctHr[yKR, 3V@e%I¿J[C}_V')խOu?y3_*l7(ʝurl QC" %Yu8^l+5I\r'\Xm\$54a밪JHJϦ4qYp۠Ck&W޾e4|oL5*:O*o-Vr{Ct& ]2CZ+ǶX?7R,u*|зd۱w5̲Z9I,V"?^wqn-UZ'@pZ@0+ i,g\4n&_'y:{43o~]E v=!ZhatĔ:@{"JpӡG*Ll&?EڈH!ï 2+4"SKQBi̪M4_O:Zb8ȁ ц H)|#/R*>^gKqjFH6ŤhQ7 =:婠굦?MY_mð*#<{Q/Hg\8$\ͽ֖m I)τY`k'NeZ-lh:RaXBZT,pY̱Y܏eN ǿ PVB;X5[ɠ^к`dd|ۇ~ nu;*Jkjfe+RirH}I:s \dkn%8^` Č҇cHx/Mbi`{T H4]63c$Wz@b߰L?(Ne0 LDm+WmnmEQ(yt+,fA!frBK<V4~>*Zv<0-S5_7;CsrÁj/x#\,=%9h,m:KG -aAv{6Y<O AN3i ?g3^[EPtO9Zô|È>ֆq eV# yd_$߇J„?,\,\mw8ѻ4"~ɒC8Z92cj4HɦBEl\:9551#+c@7q Yxiu2)=t8K_ \@=;)"Һ!,P`R%MDMUK9}7;&@D@݄By Yn%/h'ܮ#"|wxzt'̤xD; 젞vR>\ v/R6q{4CxG^X' >U oyyg|ҫaUչ5m8ZŚconQwV >zNe^c>Ǒ(\xba:U!-=@m@(0 F9-bhEq 4OrU+q!@y:-!s?l җ{pS붍13ŃMXznkN3;.M&pDJ>1\а\Έy K[$Y9}ŗ12 [1kH TT QG@̓>Dzj,|Ŧ e*k.)|hQz+ǂ1&@ܒ()TƖNa)w ! t#^^*s/ˍΪp f[s"v#z:`?]kn%:=%kq6K31Y` Xk_p[L+)Jr׷o3Ou5;)kМ{,"MDǣZFP¿w g`a|. }WBNB[}p4/A 9>,=?2l 0Z,AM #SF_?epdC@S0Fķ=`AƕJSDDFHMK~qMÂtne[YxSMu(ܗG?}2mӗLoQ[қM a).@G>P{c .>; O4 I6h`BkP™WlcP4үR۫?N1a*hzNĢ|I{ / )ٍ^7j7h޲|[G֨&l=:0L{i{A9s&ybƖ;ݼ2_` #\%^B̜9G '蟧rj.įe1$L76_;M4p8 qeOcV.YRqfζZhbf 1v^V,m7RWQ`arJMh:?G7vcA8HP,g^9LYJ!IYnv0C0J/R@笄 vqȯo \hi8"}&!F^h0k0[:G%%5\lyڴ1$Kv=dѐf7 ېAԀ bRo!u"$eE!׮KFX㫢FDzٹxF}{YOY0(/PNPםartinKd)xkKcF& ~\ù| OQ\`q }h1/2{ \@ЖIƐu㮴f%>G 4x)a !-nW4ovzJQ?m,a;wil[1o2XCiSkTlծ񮗪mc㟁+nGNAL0L݌j,̚T4u R=ۚGyd5!FјtR&ZYDGgP.\nfw{ob2-&瀎R~wB8GUoa  r; -Gp*"|efRF/=/"C4Jd5UC\od2QBao+G ~jeFR3V%d '?>*[##IGŤv*_O?Lۛ \fl_,=' ٤~+w2Y`>/F 绕Ki-} jY-4|( =~^;)ub9 R-oPY}oEX0ho?ʇEڐQȋk X|bž 88L)Qem a^Z;l'xZ@j?Dep_B6bv ͊B>4(r$ È7,\Ѡ s/ j&+M¾!(oaKε̋X4X.w,X冲?T=EZbS۔V x"B$b5.žcBa=̈́ ӓ^?DZ Mb-VRܞC\B2<5Q3jFa.ŇU\/,6ֶu2$| ݻR4r1 <*Wo:pV22NgA BR-o:{!XQR0=7T|z |sK*_\Ðށw'SJWڑvCFcCxZJNpr+ޘWKb~@i Y7%W%}F 5T1PE##S/=9v7LY7/GF-{W7}^_dz.^^ j#IAWnv(lU6ҬM>cɋX'iur (aW~֔S ́O@j~gu ZI/{Pq Us;x_|w܌K,I@kU$Qo俲GK@g# Sݢ S0:7/o2Ub. ϟ?s435Y7m/RQK;\/~®qϝDR kjSكЈ@- }ئBcʽ8=N;%YyOlNj ,;uct1K` ˢ9`3J@OUzrK9v?=¾I TS.%g<Ob%uđ^zv w\ur=59BE= 9[-uk{K'O`\p{kFYyQ(ȵĢ q{|cg 8*h֚/oxz hr PoQtyrt.ʎIEs I ^ 0s|E H͈+Y@tRM('З+z9|:u۬(/I~㞒vo$ʛz.`·7nqUbjաieHȱikJ[)9BuepXK<3E Nة~jo&6yxuӛƣ+tY*sv6P4ׅ95D>uzmoRҡz/o n 9E@Q*BjSdGHOzX=b%gAϓjijmlB$E"vg.qIefɾr?[0}Rhӥ?Ҝ7L,E"IUn2OGes|JCcrVrkcGC݂D !P{" :f-;4&Gm`JӺAI>RIZ#+r=Ȼe.+Y N&aC8dwG<<+Tb.E3W^&0UlvG}G\4AXf?!rм$'""W<``m?k˧ߞmbOhȶ(h|bU(3mDٲ$u#_a0 AbƼO.np ]g 19pte<4%x gk&= j]naV|-LR~!9Y׀J.5Ě!?BoLP}oQ]9eIkϩ&ĜsVTH_ Ւ2qwnXX$g|5$fs$cMO.8(8nPGLɛn8d7{k{|lBhFD-[Y9!WߠVwڿ3ᵰ,q=;2"huzjn׾ETzorlo2T Oru0YӖĊ W*}]*;iEϹa|bs2-{S*D ~p֨foM,Ūn~]\=nS PMy0\ xƄڨe4%4OmFߠ"(ft2ArG՞NAՍlj Ǝ9;>Օd5o yEݻpξL W8‡sb$@W}QM&xB4ZMd1sb 7LyV& t(FtO?\3((+ȐZd5Ț_1~32}."+*Yyhӏ H kJZR$,X "cM| Ve>&h*[ ;U"' ^苰9Wzz-+~TgLΜQS {hYOeJN* pW`ǰ6b6)P+(-͎krN\ADC8ˡ v7Z/U $U@UȋBOxL݀@eDęIqnW,ͼ7Ŷe|6o5˓/-- :P$٤ϺT š8 qӟ ڛ[teCo`?=tiMynO;~#6M^t֐ 3QŠڒU:~t-"">c{b IYEgqwe-fL[G/.S)'{6x6 Gλ|5]5: ̥v#= -Z䒬eߩamXcR?H\!`I jZk/K=Yiۘ@Td ߌқh?_h tݸwQ/T@ 798##)]xJEJЖ%Б&:Wr D()'6,8CPuXMD7>n&:dH\| PE9zJʧuiw,ײw%t ܏tV6o t` H\03g1d(l5<"1]@-bՆ7 ~! Y?;=`NZ_+x6ͷ;dƼ?@8y74޺sF\k-0V,.O[F3HY9A&)~whoq\5-iJmۘ{tHvZpvDs' -=^h,ږ_!пH;F g b_ uG|83ʭW(w?~mCz8Dan+sqWmHW -(_@šh.=?reldZ(-_k^R7EK3)x0<ÆW):*u..PzR@XC 3/4tq.ϪNH Ւ2C;{`5D9FrGoLza;{[\4BXa5af=JdM` vەۉg-&ֽa\ʶ|BXB HNΆDy .])gF+`kmxBD%g>KxYL ,7< {,z*MMdm {3fG17GzhwZlXq*ے`Lly+(bPPͼɵlv Pc>`$ LCDȍJ2 (+S ]RTNk,FKªz|8yg Tp+^-eA ͳQ8Yv#ԴV-+|㴾AK)`xca~Q߳: HRρgɛʾR-{U?ecrm}fr 8篡`d[huጵ4Q\ E_S w+{6beB-Z3Ul>X lfνYCk>wqn m, QsxM O[0TR躞xIc*Ǫh6)Q > 1nJ^d @oGڝPUk jV1p: %-IrTaf SkFoSM2D 6 AmL-MIFL8EέV %/~ceL=3\L .d.d$+CJrɣkuY5{%Xi ٟ)deٞ%m$k龗AF _t]{%eϗcn \~YXĆ'ۃ}q"nLB:;dGRGZ*y@Cn=SdCtUO`g, y)'n&8By51Bx*f!yn P0Sb@3TDm 褩 tf,u}#Iq{3;Eey$f9RO1vm|{ycAZ-;IV9!KQ{a[s{/hJC)))$1kOT -v]jĐz/}MXt]ҞTe6;8&{M?ۚL1zUPLPƜa;lyaEM6m%ŽqffQ;Gј0E1jIcqu{W+d @[hB|X7&U ^Ƌ$gjr̨P4Wwר.?盧*AeA5fɢ\6='wV9\;-2wh\hUb5l- ՠPJQ3gE wnl%(!ţ"Y}kh$s-͕fJ ڡ]3vh /F4;kn]5B,$T*Ӹy|Hݶzk,S'2Uf{F#@fYǯѳRC*Uk"$F0o2LI~ QW  -bhXhlk*p' xwaФ1ld`@^KIQ{4&JuNrƩ6R2IjYYyjE*,sn-%lh<< z^$| C{UBkuQ:lxX.v1s@"JS^ }j BwR\vșeAXh48[oAed*i^o)(t+ 5g礆ms>+bB!E#0yҕ `d:;߰`s] /0!"gэ!'#b*7+4_d‡&sB0 4`+#uOslN1OBW=TvQN;ᓔ/+3r˖Ғ^ agͨܢ~-+X NYU@MwFbH]om]!Ia؟hteJ1M|N(s+CA )!ϥOX:1q ;w.0PBbWI8t&SJ0xK{vNf"vt`"Oa>Q#^_'L R䤶o TDSZ=q8c* WH> z>Ļ[lިvMV+%T欱v4%F9%?+0`,B䣩-O}V{n04i xVDո4m+@/`AucgBeis,l;izw qAxM0pGmCU-sIS@>CϊDXLyi6 wEL Mzu[\, X rxT~oy>4Qo#%pZ:QHb?"y_jhQ+YV#xJy(hTD0,}5"3rwη 1y#ZvqA'$JT0z9H?Oq= .j@߁f tQ{T21BDŽ~2&8$s:#}ҫnRX|,Yud$_-2fDn=LUÚ j/ЂܷF<-h{E\8?_I2Dk,) g?FDe[nO$|ё; g.ΎTDoR UkS$0u M>bYBRh?l̲; xnʄ_M`)u:(nf2ksץ4G\'?_t{qW採š(t1(TA?& PN+8ևP:*vgIl;_/л`PQ=g+3+dWhCDYLhȅyxG\Ķ;eԜK_]<_|1~/BH ˼.k)@ ;1=.=#Yc>UX`q lOzLio+E⠋zμQ8dlDJGҧ_ Qܦ2OJPoOj^d10U +fY0m%s$38&ʺQo jraFDnDNVGrRR:L8-C=S#HʲG)(4ǃѩݘ! ^H5k+%/fM}^֪x9xWn_ q eH{s JZ#EmaZWQ&{C X[^ӷQpVq(.^ȢZzm^ʲ g*Xw`-Gwt-( e^>ޣXm9V7> 2VXsKm<ȠYc0t$` ;a/hKg"W O,yM/Q "Hm? Rr74.E%{-&͎Vг?x h 5I ! bŅ_aL."Ʈ;*}6DŽD0lY- ޛflTi}繶HnnZ/PB=Bz\5O\'E{,A, Tx>tK1q VL%afw?-Ffyc? GtH,NzWcC-'U[. 5+8)gZK-nԑgOe"0mL, 騧&WE]_Aӷ cUP?NA jlDPcxҕACQ8 .#%TZ  bqd,8I#<űU+Qn*á<Ve-{ @Z#BG+J لq;N~1A<9х7Mezs%WE 0 MTjqةجk/i ~pܐBw@+q_,"QOԿ}hgRtKACyvtŧW%W,NI~W_H4yEzoSRTZCӨcǻ*еCyT9=3HnA3QԆfi`ixe%ĕ́_B3h,d @xnqQ㕗 dJg]yQUEM3Z =I1F씴ֺ2` Hh8HbXd$'KDƿs}k^":]kΦ܀Q=ȍAk֞zv"ֿA~%OA8ve?9/- W^l#ˑ(Ox dWcw,gyOhiFZweE6ȴo1 e+=ډ(ᢺ x(޾k-A qoQ}礒|'V!=]SdD?8LK2NዿBۑ7T2m o-J|j=~KhrQ,o=Oe^:q?F( q;ȥJ % 0֍, |$M4DV3'cP}=I[h*_Dhˎ+ /%'R bK5ez+ }ӠҸ$3t7 ~lvI^掺*OF[uxwF*njCLB>UI CIP504%ei'>lf첄9,j%(&?A:qA XѱGgj2#1uX"&CClJ*+`Yʒi$ }]`ĮҨY $v łzL5TN>z&O-cmQ~1U;dq%M33w(5a{ FPN."W$UNxʧ~7$\)aƖp03:KXKIՔYˊQ\Vx](tmYEC?W>)Jk'a.Ԩ¿恹y!r#VBi]IpYY@PnXVկ{ { &wt l2ܕJLd`(NY(GCG.@iJlOb֏i|Kcw#x+P ]Ht,CÜ0AO+qb2|~sv>V/ƺ reuWn[~˔Mz(/*7 Xj5d٭P9Fuc׋'N֏N0 3mKTNy$WY:&+gA(s@RuluPy7ؕV]Qd jLY4)^4(q *9Uk%@_j9 xx.bja-+@7}>pDX7qѡFa`3T>pnS^$OۤS4a'i+xqkho`/o+Jgi9Rt,RV>Nu$Y59$ yooŅJ`%գX9Q LҵO|&MQ0^ Ȋq4jIm/Kg.4tU_HP򲙣#BFWڛYalD(՚"bKeD[ ~>T$BpuRܭrQuZ[}Zd!+}I덎8QI!kr[4`#6 c fH|0Pl+QY-.|j^7zOIfy+g3f4:l\KhȴCOZ8$4LYf@AB>Qd~b "uyAawPuN3x$vpSpbnS?ǯ_Ʃn}dYkwc#%MvY8ґu NX6: Pz͒Ҿ-"7/u8A&(iz@܋a8,QGvX}>ۚ>z3?J bo8-@q kJoД%^ጄfj4BNMZLfLTZp~Y2E^79 96(&_*.ziwW^!bpL.rDI j< %=-( SUOBMMwO~L޻w ߲<<9,$q'j,"GȮDsPMw $h(2+N:ֵ zZ$ΡpZߚD"Hoӫ?rr;B}54l#%3^,f+>}"OKЭsx(5)"H9ߢnȷV+ g%ȡ,W\1`Hg ߬;_)t4ٸW-?lh yW-†[|>*'h>ÒhPk%Jhx+q'b>.\o㯙Ү;prBz1]HÉٞ-t+zV`jTieT{Qy Fc%ER41w~EC1D d^ɝ>n6>)rVdP{P+)F8WXfw\dE#M3l/ ̔8O2/Maye9980X,TZ]bJ!eGϔ+ Y|Z< qˋ0=L'lLNb1a2F@7-H<:nƎ2Y}dkK /fEh&/ZpFCJ$-0-8L-o F^4=I5PP_tܰ =ػ"~NgNU\CWqF+rr:G8 ̥={0:ՍE|Iy=zx\8zBΙk+ {4)4G8CWA 0;o-]QXꡗ>K.V=: &dI [H g]{RlĚkXnAd{"; X:UpAN!!OH|q1 u)XZ%gI:))v%aPIq\ã@N҄lao'.p."S-MK;HbgE\R~춇ᢀ<*5bF 2;%/Rd'_BڃfP0H: s|`$j(DEZf^|/D@=wwM̘1PAƪpKbI zB1/oGv`)s$l^Xm#FC1bDو)I}j[#95Sh(Ƅ~TG_SqMTpE? C`U1ӌԠWQ `0&*,/!Oo]&jg|T9i-g}l|4;d Sw']~?KޱOÜa2ڷ`Iv.<3B@9Y준ؚzUcZJi $ضN4tB~z0!߽ @!aws|L/EE5*$&P8AAI(nO( ݰ >> jЈ 7J0?ۧ@m˨!w+DZuc"dG[ ]$00EU\0/kxu^!F@0Z!~+rkEh:30œ%I2΂AnpF|1V|JddPshq8fOHuG$a\ĺIr˯Kx[Ta)HW~_ȅJ+8 Ւ.QӘG-[jnW&#)G7лZF;lSƟp+̌qD=8Ȁ񡁭!ibB),F Nv Q҅tq 3€|z@)NHO%wbVbMB* rqx~NsIʂ@gUg?߬f|Rӏx W܃*!- >PÍ0A/iUu ⧹fO%jĻzӑ'$ą_ȫ(@h)l߫`Bbz]n^.+8tA$W 68;:@CK3=32a'`A;Ln ^a^uே&` hT׵@R4eeɔmd)W$ L$mھ@{7Hk&QM]>L {=~Ǧ nR[:f (Us|=i||?{\KvA=C h!^}CGFxڪXkEeGz_ 4? \B )C\!\fN2 SM&e,PbVb%S"Nʩ;Qd'i{dR _8GM}P⫲ e{h9-T2Y!FJT(,;%W;zsR 63Hګvڀn)ǀV^ `%yQϿ:]DR[&@ @N@'%_J4HiU)Rf g 鷕߃8yZeL}+3oQ!k謙,Pb NT rQݤvkj& h9[zdy(7QU4BPr@mw R9_ .s?AIXɯK-6iCO 33ʣ֓Q#$.rD`æڜwZO$!ݛ/Dkӓ}ܚg܉;͏Һ0j+=rCۛ/<$hV%rFL Tje!J8~ձ:ݢ2[̙syI :誘xm9@PUg,* dڊjXrkBӳt%hP~{wjiDďmZ` \ '/3RF(OpFݦdj,].7"1FB{>?``fŖ n9~k^K޶%AT&DZb4j (7fDTma?ϭg$DEѡH =>0^ k|_712/{huhVHQUsv})XM1 }/,؂cl I#/kܳ-!ܐI-@dow[?Za.JnޯЈ4s$\,e>q4pTP }%k +$lv\$[|*2|7"~2J_Z~f+5;gVhuhM;a+&}c9s/ig(m :acyԨKR56ӀgW0譞wؘ9|m",ְT̈V=@X#2l9!Qؙ qhYS"r)e<][VA&?ܾ=ybXzyY@utaDc D;wzQ:XjjE =aj/jW H{#u%g/Yqe"4iZጃͭ@ S4eg?Ҥ-K/=+OX^#pz/^#,|([w[̰`ʛ E$.yU2G Xjz1AC}< =kR"84D|?*Jv+8 l>Yp.`t!"nwƺG`'%;SϴS VϦYp<1[']0Q^O)T;jd 73s) 9;U[' GԚ$5J]o6\0S\wRsL(G™]B`4$G=6hBK޺8CmG Cm4~ {pʟx j]ͺ2'jFз1McB1jtVL9@nJqx]71Rʞei%9x,#_?7-R,_n;Խ͉a®\ F56m"5`\o$vII)rYRk &n ؠ|d.e|%¹Aߨmfm}h7nIrp9x ݣUn_)YϳH|^F*P8 LR]w"g:9B2I.Cwv} LtK}.lc@zzsǦIoNO5hmeuT0XuB|H|&_$l#@RC56纱fw SmSIUrG6DE)N~|}RtzS™jsSL]d|-"J&%GR Gicn V@1'9^%RRaaJ:Ȉ҇([Y@\Թym$٢*܎ц:[XfqƼ2Z'GT7A_%ai_ZCD)Du-;g1.WА,%KyF6JO; M@@l7c3|H{d` KDtBH7Wb U/8`n[Y8c-w&O:H&QpÆqSmz` {5wRm<Ȝ鸗u:R zo{P 1w[k^̖ķK3M0 _USz jW["St2 9  P?݁=m7G!WargyiY:xU܉oa- θ:wȍ Wh5ijѫ5- ?H3A~\T뎨6F2A#lGPIJSLk^֕J"CN]Y]qtZ;ǬnQ{h}`&,_a'<iy{vnH:ݦ"ɉ/DJօ柖-spFrXn{ c:Ȁ 2 2Ěc56-NHJZ:3هx_;ABcHdMu{7c.8?(He9@.۱}"0-W9L3r{!TAmfaIi逸`/߶Ph,WzJ6GbUmVWUsQ C)"<W}w6.I#rܺϧ)۶&σKp>{n.&yό=>|KY{`J lhV0уZIA]a q3unZ"\~V`m⁗OLz)bQԦ,!xiՔ*.aDnb K!m %'<]_h]j!]56*Ue )z}G 0B\&&@WCz?q~E=L7T\łOd> 1t9jMR}%1匽T̠^q#'EjBl}Od~ʐWkFv5~T;Xfyl8F=lk ]̱ٶdmg2R9lD=%wj[MTHDpVSa!:Ӿ.C0 twtr:Bf-,jQde>g~8ШR,0U`8.L{d͏K[Ɉ_<qI9PSbqio8mfSZh=Wta#F=.6nZl$d/аb!?FQ,zV7,u3. Y.)ANV^Q<Lr8ɚ2Ƶ6CF͍%g>`K>{O!O;ˀɨOuܟ*v͟sn}AV'RDū\ /i>ϬuU#<l@]F0ۆv{hĀcuٺ"fgA9pӑҰ+/̙ $e) e Gt{.4PhCv?w}_C}pL^vi^lOPlAsV^$5)01`C,1M/_-|{kb@샿dkDǩhNzG,mCu)ek9U,ڳ`q\S-ⷷL_7ѻDŽu-= 8jliyI:+j,6Jtq;b9YXm㲾 xEB_mJL m< NxT|$2e⢒* 2ΓF^z}cٍy$~ }26'bIJ$L= *pBuvř@e_b9es;  8kVE-ԕr6-nX>h(qaxл.TEkÜ&5Ѷqd)jز^)T֯yCꚡe1םڭz!R$rCʳuy#fŌOet(!HDJ.&I7V'gX"Õq)% T:TF"o[-o|DmB;pncz6ɍv1Xv#>auADTGT;q|{Goy}O@@:)_CP+F>ؤNJgQuHgvw}HʡˁBuJ# obhxaJVW!|a4ǰY'4QW3ȑM\Кm$!?B|Ӓv5· %>|@ E8fY>*Tۿ|kK9ܻN^w`^˒t'qE >{Kଜ%v9Obw,-%!1/շihP37@ \)wJ*;|wDbIw8 !Ī;e"𖨴eK껜]d:r..+(0֫J-݃oβڥ[Hkw*^5z$9AqQ^Ft9s)kA)`Xw?hƂ~uQ |&AMbR1bCBwhܝlwJ4nPa;|ޘ| Z41Am ~~|u2eS9 m*L֠d޺t|li_BgZ _|f}} ^d0RL@PYȆLKF"- D>vů 2%{YmS }\:㪍ԧŲE3썮&oDd$8GkN\'ds>#_*6 6IC2޼x͸)5_u/?s.Y}5]J'Yϗ0[!=N85>yI(߯},䯝ZSWI|=1?ŃBnw#|CWaTè@-ѹYڂme.E`o*dQ~ sp)Pz={K#zԄ!~f-Z݌q5Z(^Ho+ V>r}J:^a/ 8^.ញ/#V=`nR@hG| NiJTyb؝oSOPAH#a5@2U QunLuW$ #@Ǟ(S ?$ 6 `8K Lx3SPXe-[V0͉7* ^ϔr1j[hIcDFM;N(p\,MO4\.auԂcM~R ܓD{g#fs33c kL!38Vx: Ol33(]{iBh7s`Vly6e=a 2Ł k0FWM,* qa4=yXRF>)K+EG΀C H/UUR"n\9%a>L\ Y/ӊHЇavb%Kĵ Oٵ% :#gH5~_' q2n<ϕ2Uw[yB`۳]-~1n;9H(3Vv ͅbױ!8K^]!YZCuưi1+!tw(pٱ_)BXrJ3ʴ|/M3~PZ{!(7+n` :S[WL "ʚJ_#L,!s `li;{:/{I.q|D8TQYOOl9+us@(F9CD )OWX!)HeYdYgACC:M0BqQPޕNxGM ܢŰ?Oʋh7c#4c9UT:8XHnB> /8N?. o?jѸ8rPZty`=RXkD4i T Heqw^ֳqZGˈ{.vdRc> ,%=?`U~Q8NYJJa)aptUo,!"Y;WBzwE2AA9]C r!Ȋ$_+{XjIIjH#?S#֕Oi>jHI5J]hhjq/IJY"% -RG6?i5`x#% oU#ӎ}ͻ)][yžTLOۂ]I0fo+dO%o72M[2iyUyPI8H%/E|beqIl˂QhoTǛ)l8&$|Bڦ]#Ӝ8l= NfDM8~' 6:7"|HfHiYUѲl# 57%>25Z؍#3pByhBM;W1+n{AwkPVs&=`!ɫ迋1BƝ>$DSR^Vs.kM9,'CwU{OhcEy:w+t U&}Axɝߜ)R̋df!GwU<\x긿x%t.t$e \N1.t5!5<ޏU?ò}/whʛr6T}d6<>P5xry!ߒ.DaW$X<ZGQ,̸WiX_VA%ӷ~MQ@*64MèǍ7 S"mpW`dґ][4WN樦ZX{„֬,g-?ӜY ˉ>pzT\>y޼/zۅ* N@$Ĝq"9tl^(f ei5SQjޣV͠'g.tr#Ba1Ǣs3CI aÉ^fQ0 l(VR4\y=JupxW2t"F_ O슍ٜVu}3c+pc橐qRJ&eM O#~W•We/ERp,m@CɪMvAy&-n }ܐŢexkҮnGȬ<,q@~{ 3ŝd43&7u᜝ݍHE4ДwwӤXb3 RĒl?Kjn렱peygd1QC4BDu奔5wsࢶbN?sAp]ĺZB9TVA[f 7 ]ōDTn\f{-rx-ʽzJ͔P 8:)q#'txVH$"Pm@h|8&؂45jO朳X]"°}ma0p WI|7XD&s!'" ! 􀮊xGGarsVt" ")).K{V3@WgmD >4MV(ZJ;sGBCWYo0 T@Оg1. lS@2dA-IOd˲rBz:t ak͟J=%ꄎR'Ufex9^bZͨtw{kwMÊ[tM= и񎬚̄>q;cX6""-E\6'DY ?$nζC:tFy^@VKD&w!f.u`Y*Nw9ZzJTHR"ol2ӷ{ty I%ޗc,V/ķÑWIfô_lĚЁU*XouC/ +LN(yC}AB _;Ƿ=HndmNnXԿCeF^Z/Ƣ52KbYc@*0`#HlHגO?Z >}ާH4˱ Aq_T6-V|UH:7,~sbaD? Βƒklkc܄oͥ|z%g/ VO8W: %sPA|X}T %57י8/[8.*KDDk kuԁD O:0kn]>)vU}:MRwٚio_'^.+ Ϥ|V+K嶒a j%̌s44~;.! ܝ~D:, #SQ=@3pC6C;R(9[_7 cs2d&v,0_k;xGl5BLM0F-z!&@- xU\'`@4R]B*kOۧv ȥݽ!GCuuX~qT9 ՗y`9jS~ 9!cNmD`B9P$$'é2 G,u &, ajulCPzu{Y?ՑiL( i{ i0\9\uo~Yԭ[ ++%D2XJ:G8[np ,ƒ$)iiwF3ZtA%g O HxVۨ^(tF-\PunU'N'h/WQR d!@XX9'V7kKr IZjhŚU~7p?4yWLg)dq-T!J0- ӓ (ɧPY=H:ɼ!vY$Ss( ?QjJpAQ9mE'(~+s4cDөEwÝ M$DBGr|ŪL u3hdrgG*Q2Mΰ87Jx@t 0]&FX87Xn.$wa'O`ėzp T+ ߢEL_ߴ?6O*3\Z4{IUDT{D@11ÉP@p1H(v( âY&`gϫS,;TjB74JaYAҧC(f#N.oBō|- kbac3ݸ^[> d<[zBh|aBKiA xc ^kTNkM1 t2B f'}"_) D÷y`lEoNe2 h47 w;BA"-+,O'8Wc*8\?3 ^2MB0Uy)PN8CZTIALvħX2IHU<  L¡iH{? /ק![N&`lG`DC*D"L0ڒ mo@'5O7Gu(VCc _/A(\u? ޯKk13OSƒڎ(މ9J,g^|˦m~w׶*FKy?&/7 )L5GA=SUv&908=Q\XZ>,PqJkVoAD> U)ZXt[:*d0?1ZyT / h')Y~'Mmݰ٧?)U?ٮUUxThP$"+wm΂qa)q|vǺbU'PR:y?G槀~8s)a&P9?(! *}A{՝tE*^Je%@vɏ9-ĬZꞟ7<נ{11FNpw~]\bU} ? ۹W|]𵃰LMѹbY^@y7G{tmfU4`2ubG "jq Y6e;WH1N ;h.6t PԼ>lD_ֽx #+'58Mqgy!ZƲУ*S[6^>SΜpZBiA%h% ,?)xW؍k$[gsg)W Ȭ ({W.|Rx0߱tnV]7o˒nĵ 7[T&azkBtD lS^zчqQ(*y*Lds(IBkN%)B,pWkc6*I=-~ұz$sO+ sЦs#܃J]0Jmѝp3? (~X=.OS!q P-̿0MVQV`7/VZU$&Jh66+şHYI)ܚKzM&7 o;2 dpH*B) osN- 'J|?Q;xffE,_+m>0Ftii@ $!7H+q L$gArBPSq"얈FA#"\wtQG %: ϝ5T2:~* k?D👇V%;Ww_Zn[~/R{oiØRW$@Y}Z( Mjɋ,|>/&=Ʉ8ٞb0IjjSz椸4^dlHÂDn/mZ꾦Ť`kSyPa5X82?5 O+f?Mx{Ieq y> ,H:tvYh4I*,ggK @ $_Zd1z̓89c܀9;͜#,j/hQ Gmx۴In.x zkqp:\QNK7'lƄH ieqڛ2uzfKnI4al 4>& %ÌR wVu>@;A$a-eXX ąqv[ 6%Y")h K+Bv9UHQ1!_m*GiHU!P40xVd`Iۮ'7g#}8A$V4jy`YKM/FII˼_&g3!5C#3S"GhvP7*\aڀ||Ny!XOߪ6 ߥ׌4AfQ^f>#ju#ꈬӲ@GX= ,M`Q4ݨj7HH`ɷc`pd T3*RKD3V N^ţ rSLΈ~"$t&MrF}&:՝47OLUXN'ZLP|_qY3sJorVp325a ltD1Fz]MAJ=Ԙhȃki1j;BC(JyPJFt"N%k1@f7CIZλ~a$ʄh=߁U~A0g<ԣg+[:ȿRםcIUu-㱾sK )|ZnF|-,r;2@H6Q;|?{o5++"|1U8_N*1$}T!-lIREKʜS{ۦ$Հ &׼vuc{N*詻Y!U*w!_lWFN1 Rx嬦]pzz@q=]L [v,/ʊ0^_豙+|{oDV˔gdp@C}Rk}X( zLv4:_6|VijiePE8[(7 SaB:X$!K _NU7#XB1z58v!Qq"ulϝmz$鸶a0+ @**#NOW /,UgM%ġFdAAXP^k }P0-;J$W1['AŨ,oܦ7l#:%="*\o}<Np?l2RVXxsh ̘Kۨnf!6C};U%QGJ%JgB<E=g\tWM$s%ɌCW[r~_h.&\ݖc:V6W ApBS6; Fۖe\qan[1 Mchܼ _ҨLjq:_mӎnU~e%A{:یone ͛N|GDnVx ('r s5o/JtWB^L-5ql>ci]q& F 7K&Lf[#]o12gMp*O>=+V+LUl]x}z*c JvM@xl\fxsI.;$9DlMs(羺o.RVOc o R;VJOtй9 }4P?n[1na_NVj^?QC@zin!|fRl;dWv)k(־>=WyEYOcȴ|OԬ8]hEcqeCD2$_?$ٍ$V>zw ˷5 zM Ҡ]AzDu-1>8AaJw^kg/Ш;P!" 1eS}TJgoj'mg9> s݊$ЭO?Jю DQ$!'  c_MR,w|قzH7W{NN9oJ;AKu !V^LzT1SOq9VEKۥ0= YDOFx̨=A;2D m<ݖ=.}J @l!̵`U\~aimiw1 J@ r`ޛ%kAF`v'U+Bˬ:^$p TfV7 \R0Ǯ[2b-틛Uy Ojς-<U"Y[ }ʼn&0$*> O CDKeMc\ݍ8WJ'0]"~h`atƾ92пHC-mnn R@GĻ XD/ïD/ 0NĠ)l f_c!~ͺ|Q-EՁU,B$ ]K&k(`(:(dOq|ZӦ<.B. @Q:r:zU#?n2W\ד?q0[+x8rlsx߯*ϹN fMXfLjT2EmwU㨃?ڸ-o7_4=/<CC씾'UVgҏ !\I_ ݂_ofK ϛO= H"[|F?z'F5vw$\7䟰¯q]f@$([Sjm/Hj6C{:D.e~6Zh]P # YƛHֈoYϲrp F= >)!n-Ͽ0^yBU+{X 8b{u!lp@ $ޡ#S4Ba;A65s;t:hlwHy禜n1c,jF?.v[ukw)s |+ zElh:9D&#DXS6JA&>f$;a27ql.sG!F_Ŀϱ6P 8oD:O+O@B |1KnDr17&e4:W0%$+#] gJ C.y 1O=\:ּ')Z."s786W`SsE7)s;!Tz/(%D758 s(M fTHWu;|:/7*h^{."p$0/XwdS;~!ywwT4tL?ῠU/[.@x^ NAZ_p톔?V"Dr'Tѭ*lgMm޸8#w5Ðksg.o3c,<͖|9ހ]BD<UQ*liZ뢪| >9*/'R+cӐY^-7EJM +ʶ7@pDpd%?i#p['@ֻIN]A=n f>Wi[WD\ Gy&N=0eqyhx#?Ѩj0k&rHJOtٖ0Mo rGWM#50b~b P!H!D@,2H3C!Kֶۻ0U6óTDa%xΡJH4|5U$l8nSF $2bGC~!f]ec1G xBuͯ<'óųEzS6_\gMBhBA L%չ&ewz4MqVEC3F]ϧ dTBo]E;<"j:D7ۻ n 7<*{t5͋"OmDkg9 u_`}* wIG6B 4e}#%%:t adG-j'@riv,# (ae%Ğ>Id[ԑ a)Иv&FBB2B|=_d˒7X ;$-#EQyuoۛc O$!ohh.vu[7<968a`| ?i4z2jϝ)sD$gR~/PB K1”-qgWgoڊU/P[ƃ@*iS,Ԥ!qL٤ŃD Na[4,789HXo3u sO.mZPqEڵef^ԔAVݭv;V:1Ӎ ^ ADC zy̜i($Kv`“ρ, 0u* 4LF1_=mnCpօͅ3ɰsY;R:?3fiJFrT\w/ ˆdD!|W N~OoU'fȇ 6dq_q [%D꘥&zL6a5HRk}j^}(bӄ ؔa]߹biET US b4z B#]*ĎrOK+wj RUxn~&cy}OEˌxebL #1dp͒f#4F s[)|i˨-3}Y-Gien1rL gQ|1 UO sw+%)+*7N%].}Nh +z.to,@$E]XP  zͧ}}e"} #:K+Ɋ-j6}rKk NR9հWԝiMXWĂ'Q#RcZ>rR]W̐zN ˤ0<(ߖ w-PrQ̕]XMᬵ8sK }OA(;#ڽ!`蛅ZYꖤV"?vmTR6SRd% 7wZ ͽsۧXNrCq_bM*I#[ƱZE}r)+LBh~+ uh=唽W ;xf.mL()i)): D(DIȇH6Y_k-d+)e0x?/f"'"@H9X9Ĕ^9Lfy+2yuxcXQ_vi:D|E S J84 fXڨ=i2*h~ k'KO;V1u;f<%UN3-*(0/Gq-7ƧCw9B;Mk[\`oowT^\'>%lȇXL0ԋE* kCm164,Ca}1UQom .@Mn&fcޮbmw?+~G%´r1G?в04Ð80|cO{ER˜WM)7u_9N 4;ZkmMc,' 18YۧߙnWxF%h,$$Gw[ @6?i8jfZ3Ltj[b Ú5< o:bl+X<8 6pU:l|Y/>PPf@&YMR_MUWx^:n\^o=Im=jJ'Ym\LL[CnVcZ弻DywܷL6h S#Jk#7PYPVR=HZmB $59ֳ,(kxF޲ˀ`+*:Y*;1|I@r7ը2ۇF>;`f X"b#WĜG #-87`" L$6[HyeBSz ^1«.څ zdv?|Z@e]c`6I8+=u׮V=µ1*0!yJҨ%&f.i3exƯD3S`tW]w^2RzN U)Qs͗V[j.uLfTڮ+L;ԗwiUz=Hz y?ew7 C<8ڀ50Դ'Zi)jPcXIZFc "Va7YG&'-{\Yߖyc7?zR.ĭjpyQm, rR^)amM2kOB=V7BbBe3wv܈G xu]SS~.64ڪR5ڶ9rk߲i)+j!S;m=UG=1S<"rR,}986}~- tg0I"+aiGc$xO۬($=`lE@W"Q׼Ge2H4'q_#,wQpb8L$9-?a8 7_#]S9ci2O1ģ2uxՆ:4?E~}suxt&83\2 Q:Q ?,FT*%j*MDy)>޳JQ{?SVk X57noAOXԲQa\܏we: *v;8k?y΂-qtpHnn$ڥx@7T6Rh| OCE!hweK "օ eqV Ӵ%D`[tFk\$ʼ3ttuh _D$88*+)^yEmk3>WFPYLЦ 7d4t*؉>.{NkQ"2h}R<'UݵLrSjSG a=Wyfg櫑81BI=\r!"8\n:EgⓜmkmӤ?lvj4&aryjNRFGuTDV ku|UϊA.#yf(. tPVr-TBTKDm3(q6o!dh+黟_kf̎$H\X4 5ƴh#{@Ⱦ{+*rY2cq 1(Hv8SPj?u5QuYb`KԢI} yKU W5$gHoKGX*dMZ)J V &:fBģ8 D7(}p Z|H8yf>Oc7` > Y]Ԧ { EOc>ܧY4.,顓xV8"?YЍpakf5uNRd LyMV]Z3bnOW] |AW=:a;w.g]8t̿J^ĉ\;d<^2>łv7wvCPܝ6Hµ,3"KP}WӣZ$d*u%:.Y>&>!IMa^L_Wi/Yl/G6GuI<A?芠w)=\0m'weR=y* #A8:Q%u8rMfHmLa: 6iے/I2ڮ$i`\ܖx_%FPʡy& qʡp 4,{m7C|#ڰcCtס-}HV]?5{j9 D$/,Ul_nvߠ$)K#4+?Ttaξ@I,+( l/"< \aבy]yɰ1O*d5Bj_oйd EQL4ZR*% 3HOr'EܱAOKc3 +ѷOAHʏ܉t+i7" M#<YOu[(~E=i@%Ȁqy0q,)adH&e(}Ts$k/R+9o9Fz!˕μ|To&gEo(ݷ'2 ( Q4kTe]9* -P9"qcZkPU uTAM˅!锂,f=x;׊4mJtz%}'  &Ԩ@V' J+$PuՆ:S9 j]@R7ۙ?BlK ٜ‡H;ҡ649ǡ$jK|uzO*-2&1k~Sċ!Xv‰bD.4-.&ɰi߫>,4mw97LB|vjh E`SDdUHH*cDeCз&$Z)f+B$n߫ޜӕe>-d/`^Ҭ6I-2I9|3Ÿ.*m$:FjgF)<-s2a6V2-ZY. Y1;Ead&9dXM̈́!U>֠ Uk}ؐ'!`"i V=yƙ1* < [j"C`6 dccW%վR#@H/"qf`C #\~SXqCkǿeaQ__P!-`կDqJ<@W!Zy EipZh=͏CHu#ˇ.;`(ɮ5h@z -:W>soUD{d=KI#S+%['a[TMRŸ ]xbcO.oh+&z,M$mi?2)Йy-p3NFڷ>'_HV|',Y,_} @kŸS $^ e2hɩn8 ;*PW%ٍQC|s579%Y40z}ـl<(E 1[a[0i_*q>yD>v@A( zP.ZCPh`J6vm5i9G m! qs#x@NTbp#caP}@ҧn7Ƌ|q~K ̜Kxr.4'f_A㺎nkd*SDžsM*WK1H)X#/W5 L6yjkD5\vuO&$('8ʣѩI;t.GGLy,:  tQ b7!zX#*b:b+q00NڀXkR6ifmb_2GdK.FĴʠPI㜪M$ѝw4YjOF oui]Μ.^^`x?)TI2NU> yXroVx.u%/;wXHmABxȜj37f !O4jmY s#@Ku2Oo BIt(rs?0RwZ)vqS3S{SfVt݌oSE#jo_-<ўTU+oIcE=5ySJ*\`R-K-FcI=/=%C-18y`0\857%x"4״H5|i̎o KUZC4}1~x$PQbN84KЅBdux.D'%K)Za鴁v|lPbwfA+֊fED0Kbk0ٯrU3H\kdGek Ҏ XꔮG|*/ N/%=25>W /^ (Yu`ɶaZi =^5(kZ 5[ƭ>gvWj# ܥ~Aah=jhD9$6bH9 GGӺr6JC=|\A'桺H mѰh&e7*ct,y?*ɡE7S .GEDŽ?`%(ŊL9~CˬJ&wity9Y%rTD=TzU/zu2nQ荲/aJ>H,<TM wzzN/+7s_)zyo&ޢgrvYLRf3dtMIc΢\ 1{G :wIP F"bAw3JYY`(@l30O ~ PT'Pd!hM<~T[J.)yxK~28tGʶ?L)@̮gZY;ls# (-ϙw&uݛW?E)t+4>5;Q dPE D\XUu%4\Uխ0V}XSh>2._{9+,[6`wQIEZQR'17_:oZL_CeoɠTy~Q-XeT oM}oh3]"?X02eޚB40?EO|(MuhwH]b/Q$kMY|u/pT\Vߑ"SixG֥K_\ ې筢Mgvopg/ /di \d6ؙ[ hW2SGս..@&ȕu~ z=yOFKJC Zc9{r=@KvGHu fށ}ԹdLD`JxU]xI egW,~#ՌuiOWk2jh\Qb2%u>ͅï6:NOG_ޡƟT/^͏u^}Hki{'CZ"a &C)3gƠ1wp" NзXTF-dY+@WGAuQc[ob>,P@9*ߤrɭ* Wݕi '^Se[YSdO\ݞ[H^R=!q&/+TeS `^S~!5)Db7.pFB2)fޅ Ȁn©p2?FCy`n~<-+rZ$jnkRU }Α\k 'I4%!,_&% ]M.UCDAo|Z'F%NU~ xjjcMb ^bB(6܋*Y*Dݟ{ix| &6% 鐉%w~{`Zy['B=48zlM69#ק&"CآGqwJbBj[w(~݉?I 7Rd [ROF)B"h3ssX!D} mg!45zP1~HGmjKEkh|QVL>kN)qVVuds4njna?PǒT[Ͳ*pT?"@2KK'&^/͘ٴFs sbߨv#s8'l̩5“\~"tn,ۦA-@m EN7{DΕ}npC Z撍Kk%^_ C~6={}[@CyLe}HQ.G=%y':vŠeQY6k!RC5IhZ*d0[5P,~+L:MT?&Ƣ, ΞʂSR2y+):`w-yha)#!jKf$nh=xpc!iCaP_}m_B+r#V*iU_Dm96=cWYD[֕Yݏib.Ht(9~9b\D g~ḴibmΦlml2yc\'β I^y+ckL-4vr*)S+GR֧3Upe ER_[a;nx'$-{:PV{ZUuǦ'./ >}1rs̉0,cmLE$d|sPG}]U"_¦Agdko+|Z-.W0 gF+D))3)ߘXI(H4YINj#{f%eN;OtZI `4[7wwaaܑ ZFCFLW-Ȅҹ/t\iW>ӽmJXؚ? MXsJ-n.sÀY`hHC%-$Drϡ>:-97`emw"(k7^GjYD2#^`0`LrS5y=nDL)6-LAEN?+4.?# ^}C4E ?-5?'N k!;sG4pu/{ %ИZB!Hcqplmɰf)Q8Q{KԬCx"Bʩ<"e`2+=N>$>T@,׌&j.Lx#ȐHg 53[=qy@:s%fJc*L2>0ވ$ع} YZ)Bce$\Cqvphj{}e7^״_]"yu ߪbzZa'"tI@|-2X,.A+%5HX !nAMkɪUy"v]ƌd?G^ޮG%[ǀXY ׅClDA´#l36(zgĎlDݦ$pʼXg)oϴ'Z[kV]2GҸ¨F3D3XQ 6Ǧ@' <&m.O Llz|mr޾)\ݾ0y7+?qKǒ)"9agjMg̷bBnV~W/Ǽo0\$~;)q1pPиdk.'*F)a綩 $viS8GzDAT+B͵1|p~l(t{~ܑ6{yߌQ|tTa+vAn0>K%Ì#6ChcoŃ{W? 6 IX-4qiBs8I?9Pκ&m$A.K6]q$BXnM{ lx$:2V8*#B X<[hJ?'PaPkh3[v$,5)P@]ud^{@56wG`pO [:2TW*v\Y3a-u|QmBVJP E g(d 04c-+:9D3#RsfrD́b䍾PpҒ^Қ׼2+۾᠙DuFw^8!)w7 my[Udk(c0zʭX@/x%aM7z1Us Ȥn+w 7XV`A΢Iqos3绷1m%\չgpz8fO#琇 uvn!f u;+_JYKpr׺[; f0 ?M24XwjRLD[&o~FRADax(ìod1/LfƘ>ȵ2ym'dht5,\<^-V̆8c"]9I&rIvS4k !3tD촠+RD{?GN8OZ2fc9^m) x6*"17A sA./e+die5P4>423ˆ@q<1W}ER bksTuX4JO:53Nd{ ɤPKӊdj1M R\Hz荪 pߏkhzt[>ZiB̨x9ҫxҺxk(^ֈq(KTv)0SSDp#nnDq](=ZnQq|LPoJc~a7z\vV,7BHi>3ucmZ"-mWsB0U-seߍ ) 3t}\|[Iɋ6im20LoE_m?,WWr^_&M4'7Ubլ gNA 8Uȇ~Oœ/cL(jjl`FD_[幯!~mqBUh*F!ҽy<7^bvL6ZwRzgr{~>ɱdo$tTIr%n=JE?BER8-1 }QJ,}?Ʃbܲ1KDEUF{6ěX' &W؝f2pyk13D^;g)z%ATYstXM,3;"Sg; ."CS~cKLAVh0/VwUIŸ ;;cSZx |LDYd(Ҁ˛!M!7zëCL9ʓ4$zxd#6o2O'b>PʅJE# #:cUB)2H ?e nD-9G2ws$@jםدήbX}X_P0ŷnF},Z-!δI6/>McW 'Ub͏KHȺviD>_dCAKw]j/MduJy=2ӳMrHF-e5圁w6yDW4p\r٤ kKJ߸^_ L SŖNq)-(v**⚑_ p^?o&'S:uK GP?EV^UawBJ2>ЃhVY.#8g'e|rVRl*"\уvDOΘ 0pH&7<02Xr}lqV1Jc?)XdcJ&!E߬$iɎבI圍҈Z~/ٷ6mDNYJ ƽ&|>3u~ki6@W. zDz3VJO6TZ[l"QA]#Y,t5'&WKH=pn'rT|]ry޶!6o95snRٗ-o*3%du5lWiU0M*ZoWŠ'=B ,נ=!c6 Y(7]*.J{>zv6TჁU{U(3 $*wAZGtҙ'2Cc'&YvcܺԿlXύ ۰_i۳hpY%gSœglǡU:}F&]=&n NRgߗRfش)2j愼p7m[cT;ںK<9_f ʿI;n!dJonHqֆ8)ʉh*èگpdGEZbwt:׌F3x+il qD, s۸B @B.>"s>'+Qѱx56G6=X+UHHԡ +T>ʥ(Xr"m 5}C#=3zX0 GnE /YsvЈ"{Z|;4qSDpߑrѶ6jTCHV:)T6)/ WNV˭ceՖ*P>`f)=1x >)HdʄT~J2d/knjxw -#th{]6IzŏK]¬Fo좊C-EqR~`QpVM"K;bʿ󿐓YN4]YG7`nfOwKخ2Y#:c/Aguي!$CMH^zϼ!XuJM/vlNV8튪 8D/`r`g ~|Q]1q_OE͠bЃ?s^߮W$Ҧɣ LJ#>g8dvҧ0)'R 6/Ȓ c i]GG~lDlG7dRUyi{:=%n}z#Y;pA(b Nwzy*V?L(K! #} )? _m_P"0>"~#*" H驏m~;rK{*EjjW[jGaR= ۢ~8`S,!yZОW^R#c< >B4J6ߗ0$} g_Be.:h޷4NB_}yKIA5PFb rC8+M1[/7ů)P] Du mr B 3wgIGe4Zí\T3T3y2D"J  EYcZfn9}M,*XY H1|?MC~KYW.mG~\nlX6}-CS'zFQ1GtZv[衍@hdNA)CeK-f57J5˗x:9b"B- CEZGe`Rf42R đL]_٘&B'S2}nɫ ]ac Q9t{17+GhȕC "2?w)`(kv^Ί˰FxMcˌ`]'>4ee N4D鐴杻O ~ =#M復l`aٲ6#ɮ$>.-v37s7^=%H5v';DK`lվŒ@xU]Xq(hg$bwx:%);E;XNg[^=]o_07BH%ŁdʦҘ#3Tq(~J6PLɜZirڅ' h# Y,jȍAK1]]kIc+5vN:p>0Z*WJ_"vh 6u)č(*.|HP}#rRQɓZ G2Bۻk yOPEzl&nK`<%'D5)? 5Qa)9aTQl@>&T٦ b0e)Ϙ NV]փ`Ea~ER[1 80AK.ꪹZtc; RuiaԸۚ}p5JNR ȖU#2xu`G_ЗF 9 `G ͒HaoH؈1P)Ma\"iO}S*s @@.>Ev R NԑƱWkt4D@)jؿHb߆cPv#rE _t~  ?eA06\ǃ{rŸ8;>m:baƏBEi0c#RX,~-ڨdP^1:}aF_7 䦡YV:d(Ox a"IV͍n:l.\jyCk:x\ypRؠVa]zX&ҟz-!Tޑ?ATMo Sq;d]cUNe־KTgoV7VDѼۈ6L;`r/ 5|[顩lȳʘR1WS{( |nx'ol7»`|G~DC߽}_XqEse"kb! V O V2Bͩ b|KMvJ WӋI}e۲5㡚ߚCD kC.pTu| Į-YO!n|Q7 S$tkW,>wV&KU(Tp[Mhd _d+x SC"U'P`{pd.wzYW+3 k-${I+[7M-Y|H%=5gIwc#!tޗ1(; ,U,$AMgNgKwaDj졯e,.UgtOK;mq2*{ #gFB2d>q&uMÈfr<*?E IP `PѶ[cy'VQ !.PB}ţ4L&C~p$/LӓҝfA _ J - =k阰@MmcoQ ZqjiK߄Ea-c4ҹ Iu+VvMPyR_OfٙkkԪ BWCΜ )Ab {JJJOodkDt2J Sk@ץdӱ[ǝUFNaUgVzĔ?d'_R*yE?Pr9߳V10i$n.- Rg4x:yMO/VYp:׹K"3ϕ7r4+Q.e$?A@+5d Eq5-4U0 檙wE' 9zV$ِSxgy5K5p,YG%s(ehe*f#ܱvpt\&W`A~:4Y`wo蝫fmireqH>E& +?+,Kp#&˟]2roV5倝;6%!%Ћj$]=0UNZNyg!qB//H v8!H;@g^|+~]OfOq:@^ٖxZtSKЇ$=+225x}!_Z qt}*_3v8O 0z\_U2Z64jHJZzG ){ '> S9n9zy1l{*,݂bJ<| q:rp<B.ѢSÒhSPvab 4Dm=RoQ%-`'2; wer#?=Pm`\ܬ~D-N5b )vіlzb3XUHS% ȿ,=b`Sc}r[d%='he=dO[\E>{}9]A8_z_+GC}~DŽ~zs[Od5i;esN'lٯ?h/OAeO?.59?B?^$>p5>s:2PC& nz;2m -:Rm A,7EFelwCpmv]f}"s̀Ɵ ٹ pеO"I*t$VȇŨqZqsH"csCvrqVD >z~B(!'8IC8Gl%V5Zv'uFY7qON,mjē' i2=RIQj)4 RʉaDA8ui,W49wKMQ_T32`:y.1{W|I27 dY]MĽ)amK^!Q5mYY (" J(dIo8~B]ky0@X%v(̉yaN*836k<`CY@VE= ge;ug-T&˓_-=%-T|o8Y;}W!Q-l#YR97R#S -Ln>_M ;E|Y> (#6UO ^"[O {ffa Dߎd"S LL<"># yGógoDa=k -K`]WKh8(M2L"g5ګ V11C)gtWMc5~FD #H*R2.l3  kW$(ӝ%RzQ~ DBP>4okcǺq%/4ճ v6?OiɹDμn+_ӳBb=V\}%TZ(܌ȦDo#r7UO>S Jė?&:B7ݚq1!=nđ6(fj )2T۠a:x7݄?ݰYf@жV1ơh<6: 1|E'd/$~/WM0@^%1^p2(EC+LLi;On[\(Zy~h*kk'Q#x0$e4jM,y}Ĺ=^eqR ݋Ic @&J!E=ʨwH3l:h v4e0Ǻԏ,5s.wYHXq֞`vNgG`]7]_C>@2F4 Nh 8LLo$yn<,iHBP7IyoHqOn*7LojF㝆%R̓EwRrsBtnw}G8P+@eVl|b?%x)9&:Ti<ȳ_,?Զmu( HT#WvsnTjVm~27@BJ{ Ї*k=uu`L5;[LDVL>EcCC~ծA%u>JT5|2\=""L4R_L ț-?0'gjZ$AN+Uؾ[LGbFb픉߹ Rfg׋BC) :~Uok*]?V+g:8oaZrF K@Jeb VXSivW"=h]wkʚ6>!9xw2P3*ckƐ/O; : -ls'N;ЋQ#7fX|1(HO] Z,ywD ߍ4囟20 FbcXsǤ^9bTҶ$8:[Sr| !e yԬ Y)# g:UZAC/LwŞZhIPhĔ` m-uF r70 PsYBt,GEKy`uFd^Я9T}ԥ#)*ozOZ*O: ~#-RbCͶlof ZޟgnTk-K ˎ5^:X-w&Eb3xs"Nqōa!ec㸷##lɡ`3 S L|/1ŷ+}mZEYvyF |1sHm)giyAT Nd6bL;߃sLGq'MqxNSπR(!Ev+? ړS )ӵigVg_XZHo޸`da)R Pu+_"wg$FF:N%ЦZLe{^R@< _bBA%' tD 0AaGPiZZ(]DTO6 ue. җ4/߻\7/$; Te||9'Ee[ǻ߰y닝5 b/#SD(n_zA-ift*jZZXoyؽ0<t燍NyˏhJ,e/dYv\2TK^9M蟶va,{^J4lƪ!Y(gf7yv#0;wg5i*q:B*\ ߣAj~d\ <;v>v;~ 0ӧp`dV9ɰ \[6<Ӗo$TSWLl0Nr<2'aD,x~RsYfˬk7󏽾Nq}#g~+[ym'fM G$Hua@y,[#ux"Ns![Ժ!?.9bŀ/XPgTasOe1zY{S:7*Sq$W5M6ѧy;7Ыa!D_z<Š|D]S.%/u:vtnZVu5p{MݱnsY. rbPDc&[O.e-O5'є=aMn3qv`k%߻Q/\JLCr8F)}Uƀڈq+\ޕŠb(n?r{zu.i $ j "D5T*Zwi&nR.%ߏg*zm,X l]kjן"c |,%i*yPfn5\OJ%"v A#ní,aV|SڞkeT`X3)"2)mLyCOZT5O7ngI X-{ f2[=e+{[GedM_e iC'3;O`[DH=faǀ=a:и%{Y=:^ac!D{6aն@x+pR+6X5`Uo(1>2QrM/UEuvrlDhAZ{{27v%4|qaʭW0T5=GSI#s6hh#=m)גołTsanwBYnx NG@̉cܩ ɎWa`OKW\C*Q=iN15Zizӊ7ٌ[=G@IOZw'\<²!N?Oӵ#U`V`MDBGk |-诿Nwl%VR7zf1*w-ӃJWuB}H8<6WGR*r݈B_M609DI]^7+Dw$Ee[hmebAP6$P:oc7 c28~t!Jn0;)v1pH\@{˗IY) 8*!)&uCޑnCW0#'+"Cnឍ$ވPM6Xge]_ xNmPO;Ua-@ƐRMLjXE Ndl9&䤊-\o]@BB=?#-î$!>/&R-9tifLDR)vз7>-ھLU]%R.^1sZSh ̣ v)!_L~t_|:-U= ZAyZ( Jh[GeQli*ւ_ą=[wQu`x/Nky[Ż^9psN!a_UWkF6aCխ 1*1f\c[R8^=Eaߌuskz!L1yB͕CQcaA4'sD?{&T.yds;oי\kM&fkN/J g vM,` ܭfݿ͟[̱ k 9$Ʌgn `hX)~S$笨Q u{ן/hsp.HY! XX<Ҥ7leߏ$0e^Kû|/n zQ%uctAЅYt ;2v FV6lk޼~gFWg r$(Ҏled]w0g$sv?z3MCuˉIQ"aaZbdɄ F8g]Id>msf ;Jt74_s-+ꖜatoPӕT'΢?ܮH?%iy౽52T[(bO@+3I.lٱJ Q/Vq 3>e.0D5wg.szwo4k%H_(zLoG\,񛕶(+h\qQ8QDRt AWLǹ$~yJMxwyp%\syv4K}Y]JyMqp*O=mcد}lչ8`woH+( P0CcnPU$"SL7swPslӽ}3MǴjV.'ޟs&V$ yyq _->.b߾s!IGLoiOV3}:< [-v+` 2|ps0[?hNUWtYhxkSgЋ&) 3KDہ4fKsypw9xpuryTl Zlb 6=)\@V߫HeKt Q{h) Bܧ[ǣ3 Ď8!A(!3:مe^ U*`x"; QeJ*J |՚l_~aN:P~MyA@7SwsV5 }L!/q%`+!T_x0-VY>/1,|'ENXܗ%;v+qt/ D0* :צhp Ɗ>/JpfC%~D(c*sN$8,eOxc&ԗ"xR( * @ʑ+ jqc1F]%~@w &)ȥʐ8 ÿqBV:ehrCN*0k28y%$Qh:0ʘ⽶ sb6/1R|)GljqtqTXB$ ǻPAꬖ*Qe9El& QF}7l[;I&WbiA3t}GE5#h-u3;\qmt YsŘָ1/"9@/9=+/nH*{ќmT$"hYTAOfVDͥvɌumY }ٯ̹ɑH^vIs;>^ĽIrcxB]HG߷U+};06/7qfJV%hW+tr6;Z̳O2" k06f :ȿj(C~Nnh"KڲܑL|DT&'I6TsƜTkࢮP4 D5N4PD_ ΋43ͩT37:FnuY [ &0wyr=d(2|P&$)3ӻ΍7wK8d_n= )zmCaFIUص =rN lߊ:^J3GĐ. AA T`E /m9@ #b?͙Ŗ X@&s6'oLC#!C[Y sn-z;4/Э2*Pco&0H%*k~jB}viPS7iD9@?\ʥAnv!.%TӰ2w$E6MLB`I2ʽE2*%cI遫s{1)p)j F2 Vac^SU>_|1u-Gz┾/,4Տ27viOIy[+n. ٭רhZ?q)+LE$2q įyssϪUķZb S8PaV l`d"P9RlowDFU6lO<:8maNaSlq*xم~ W{A T|WkVJyqgE\y0.5h(@``^} Nok㎑hM~iS!=/x@Q*c-7sQiID(,вSFN sakz8Rr4M}GW;3CZ#!lJwxv-Az\ 䭵c]z*GyIfέIxBLuX (1 3Wiۿxt $S 1c`2jg%Y`2h-&_0R!)}TD +֧T:+U%bH+{4PV(z?MՈ%F:kI5D8!ˤK|*uػ{Ž] KhWPrwiN2 4 3g{Ŷ˘ĘW_Y: =>|̤fJ.Dk=η7ABH98L.zg(7:bUޕzI))ycͣit]_ h:mp9x^IE4sH5eax16r" RhMuQ ?o1`ﰗ˥+T NQQH : xip1R pr*~i '/2=;@\LA8Lbꖧ=%{@9kh?B^]KފgP_E-jE+=R{l|KO!* F7n"9ǚ35*ۇW-HBX`³6W8F!f'gFU‡YZf0.lⁱd:ؚ\F\Uu|-р3Qw:*J%4L]JnO.͋-rYt)~KmR# ,ҍ1PdU` ncP+J 0ik,p&9xJr4M`ڵ3+4q(eƇPf~Jd>8 j,H`Y%n |ǞX5/TD1+d=qYO"&sf"{PddJÛZCHUf 7ӫnMثA99ҿ:]zeI+K^m@}N :7{ ˲l7_6-cqd.d5cWx_$#oak|aiE7c?qn'E+lblK+5u'ȪS=ݣ\o*G":q0|"2&$ L|2s}GXaVf1`G03q\O+:wy}8y>SI Hi_"ԺO׏4~"U MW SlǛ2y~56Ua4Rf^A&|R􋥏Z'p O;]IULIM(I?"q b 6oeZ Uƌ(lyG^R0:\PѢoM=DAҮ .Vg~~XN.K}Qy!8&V!vW` k+stHbtWq&_Ͽ49 w6l(r4*gdqPkRIJ-EËYڿj#Cy0JkZRrT/\MIc1#$US<(̴o5d#א7kʛ4 ;2~R$<Ҩ\1D6UHêkZqyW/R 4&~oH漭j5lG<4SUm^F&W6SGqAq,, ^ycGt@55!9Y,\1& T#Et [2ꢫW V mI?\5n1}$C#B+̪Ǣ8Y'lRfC4'Ģ{Sc!sr'ݬD ;>ܗm;WQZbeJWJLE8k2{2AhmlQX3Ʒ PQ47e&P)V)}8?.nZNu"@z:d8?c3`VU2d$)(< cѭȥp_ ά-ØeZEXvT.akI\hҠzHeSmz`u#, _#2fV#,3YWݛr-|J+Q[9ZixA~DllT_MSb@VrvC9Ue +qjr=3H4;{8nՄy-y+ɛ%Z.?9@* ?z};?W<ETg¾-䌪BƟf,_>HGRvZ *tH$v Ru%?ˮfB8`U&/A=%jU]L4D(j%783]>eu^C1s`!ׄʹ=x6e k:HO%"yk!u99"v髠!#qquQ_YQa(m.E 0έ'늳k]%U4yK{xCGx.$qu#lkN)#8~,Ő6d/>_軍/@OdySk #H}+`ZxO;)~|!3UUVɼ3~t1ԉf=TKUBrchmyH/@t»"OzzێJ͂8z; R_/"+Ssv;)0i(ht#UYieLH=QH޹8ʧ0;=A4='mҩ9މەRHѲ2kYr)rb5 6)0Sk^Y˒Y9PCBy/=3z|Sa|8S0E$lЊ4#zxEwi+oWTdyFE!w,QI7s\F>e,!8ْEA)U $(:)Zml]Xg2dhnY#_u5Fwv/?:G[Mr_]i;-D^f X9 L?R6+=T; Op:oųƖuuG6ΤjiE]|Bӱ7~}M]#COz:BnGutDUpد(% u3KKcVV>]$p| a[#ۖ,/eW$h;|9*FmԧZOLM;3{"tx1 .Ą=ml>¯ig=Y409SAnm[lw;H~'a^[E 4 9Q̗8T@!8kH0l삙.(oB=X|6Ym4 >ta9-p"ĵDCCRQQ~ń!^Z$KfsL߅*(zf!s΄ucbPA͞E"b18C!" )Ve&e !zIlz#N]qΗa Oܰ8OIA>9^,a{1PYˀiiG^mAgBӔ͹/Oڽ3=zԨibj؂IV+!!mK_)T>V>N1=,azcx2}Gzp*Lχ+1bW{>?[Eb R[.2 K7UR[#ۺLYL.˦PϏwOFfBH7wk^1-"Kwqpv0_Tz!][g%qx.A2YSy)&9Luu7%b%jk}9I%0v/^~ҹ2GӼvQOF}ӌ3^t$ΔuUeH:T0Qu<_1 a!a2VSVh{ώ*C/iT|hx_s|^Jt]"h?s;{j.vjg\3ŴQEaxE6:/t,~м,TOG=C(QQ -#\Dx'zeLhQ5}{_rQiqE#ql5r0}_p$rT^&0]FYvT,qXmxζi`.ir(cmt t_7{PU;9T׆|fM̶Ug 粮d}KvjeEe 1U}3!'W2d@8Ԭ4oSc:wlvk9&C%a@iۛ=TE@WĨMaH2̅^hHc91\,fyu3Ki75V37Tf`.0z3˘\|%Ռ:qj}T[aoLN@L26϶1=3r eN !\UYo`V'žF2 f Ch)'_]Q?e2#MҨo9G0 }v+~QC.)nv 9cMں>R|kwʵ@@>%&T:jrMG'+N#G$}0*-1eB-R-%f@@1#)ӫ-2(Ou־tyqY$" އecϿ^c[d;"PSFٵa1}$N,wk# HX6(toWiԀyIj߈ڪ0rz\6]ۯE_XiZڮ vb*ρS!;VN?1eDݶZ)7Q9;wߌD#9Te_[b08r&o&,>r/?MGokv2]tY&&F?kJG5֍RINN%ŜzzJOqC'/jAhi4VU]I)rb\ atgz;VRi4r()OsG_`[g3]};PMcq 1!fRA$D&A^ȮaWfM 1UP% !m-h/8OX2Ps ;SCnaeA"k摯ւ%3/=΅"/e*lQfw@RkE>zO20o^-[DƞNb߷UcxM^wqÜ]z!hLfeX<3: 5FKtadiD{7{r "1n_EƳ"Z'֊tlj7Rch 8!YwWW&"dYmAC/+)©3nEB013r&fnoGQw]P3/ 5׹5B2r#_E7vh'j =kM=F#=i+1:p#Zcnz޾.mSqS'vQ6ZUz"0Jj3 ]<.e"'Mnv~DE8m|Q7{u$z29w; `| --#ԡ+Ǚvxߺi`hӢ~{XxcW3DCdINc^<L_k}I2^kb0r9?QiIģ 7Y,&Fr6. G{l*$S6=>bc#%\H_0z1gEgPW_Ƞii,>7^d/rՙepQVQUk!>/7p.ww~a\WK.ۖ_*7V5YڰwQgqÉ5c^o׃'{j戂w;ɮt j=9_Ƕݵk<tUj,IG)R>ܶC *qY(NZ4`ǯzoaiE r00+ukkݸ "W l 4 V(I4h H! =zbD$\u 1mW码L{K(?G(qyIil0Y~J>9NDnRTL18gf*1RStj@VJuR8 >s\fl6O^t|pc-Er`s[)i ~襮бFrcSy<Vf2݃KC!l4a\"&BY"3&^NxX+lq* o.|~sIz$xX?YrƼ B"B M;F+=>Z]S|7U#֟>oKI ip6W)9G%H ,Kn4HLc9k4i@ѺE<0Ijfb> R _KOb1bd4hg C)_Lc,wFisrqcTr)eb4{,sіi(rgpKl:eM WGᾹeػ`Yy6K2m5>F`ҳtߤA1gc# A0l} pHouMTܫ42=e R!F}z N|,klq0N+ҊjK\6yQr&Nj]xȺSIk>Ogä&jXxtZjkDu`2/RS8ϵ!OD|ՉF8\EĞʏ: "_YY;~H߻:B8G00N)P ' z~뛵|IX4 吂  :|CkKVF\:مOl4ݸ;& s)(W.>&zC,$`R6AD#9WfE+K\Fp ccՌswh%MBȁhQ6 8燁G~4`'}u-"WB!]%RqIoz)Cò 8U +wnl,0{t +M+3(UϙZ'O͌153+PRބOSA{=Hv3qkF}Ԉr2ҽ58Q3뢡nEdty{li'r./śX,rzxðtG$Lx:09be%|˩H|9 K~ 1_̴iJZ9Zo_d|\Z(&yͯk7D uO3b~I.n֔-35^^2*%^L/u߃:d.aYO6BЮ*$-0 V%& d}%ϤQ_RhEfRg)/TRe'' y3qRǔDB{tmm8F"q ,Zn--DS Z/n^ 0sB⸙F{ i?3RL6XE6AkIp w(q)z%LOݙI thň#krS2I7(~4  6!tµ ) Wha8XmuL%7`[P ջx]tKOSi(uag+pu kãXR٫ɑĬ#FtECVYA? yzy-\مы;!\grB^~>_hTʏf3#MDG`p?Bq6MXkX}@E0'ES 7#SeEG"[JP]A,>[br,˙bra揄KJͣAxXV!^+Wh+k9?]N@oc\Ae)ø`j"sUA!2apcVJy18tq l9nX)]<ӥ3r6(K~xp-c9-*% 8 J:qTTo$#ܬ )C`~x4u5qzVpqU4[,N6q )ך3 1 }ѴѐӨZI\9Fhi"֋uSG.4O  C"xld|d3=hx\l8SmCUtw<ȝB*&Ri8̔ :vg"&PGM3mL])$gUD]T{Ib@+Lz,b0RV:+8ycDФ^*4A @H:׃VmfU i T9$X ֫,  `BϨa!6cz4JN^gqC)3 } ;1\a{6S)v]AƼOWZ/O哕y7 ֵ :Z1pGUHsG /p&W,^f~<=B TO oGVꔹߺEtc5Ԧ@3]t'RQl(Xe IE ;4p P]LTA}bU,XM5ꍓ ^9c鉔;G** A_Xr*|+v8x m0mRQc,W1I'^Bpz;v&֒0>1SPy3U&9M@2aОmn&}҈TB.S-1RekQa|5s}X<^ 4\DB Oa&P "k*$tgZ GL'Z@۵@m ]G^KE\]٩[8mbZwV5ԡZdѪm+.T&i`}fvk9"%zz]1r*40HHNTHN`h ?1AsedtĔ'u zJUy2>MɧyMkY0MI@٥](ǽ DhY Mq63^_@Ox)nh(i2c'ָsZӁ_Gqg/'74I^ l6iZ7=FMO JXpK-*.v9-\F1߂[+¨7f]R(jk P_ޟ{$y>-yclI+?A\* gSZe Ⱦ(s}A^6RD^X=ƮL 7\g䌑E҉쒤Y RӤИܒ7 /e> u,%\,(TƕCg1ezK•XF{L/?ݩ{pj.B@:17gy.lx9Y@;DNT_+zmQ+j:Ukq@Q ;ggA6ٗ_xQn˫2+֍@){B+j/pRteeM7I$/J-ON,n^Ɵ7ܕ\!`E Sp~-vs -6 ueݸXZ5Ԟd5A)L uXWkpdr3x(:DBػY풝]~dL&d4?@ @_ҫzκ(3P2}{f0ǯk}VLI;)鿃[8ޑ g|{*Ě0ݶ(dq4n< [??CK M1,mM\Q "S8HZdSȁ'N+( 4]JB<h\yVxJMfFQ}%o,lަQ+,k?)z.F0Kߙ2f\ ۯn!ZMxӌ27z9֕r+T1Qby?4XOH;ݺ2d]>ܙ,D9 Qܢ}a Z6V7K*yﹰ rZUz)-E#@g|AђAw/Xn {ᥲxky[{$S$NO08[sF# i{=ڬX>j:+\b#w{d :JV`׎;҇!t\ZqpMP&OT4e.ĥGcsx*AmQ.jY-2ȳeO8,cɦS'p7wO:4=+ ?D]\9wBAEi:0pmѦ‚ѮSv)!G_xTӼy,/ŰɠZm$h nE/SiZ'^'o0aJD9MKeazL>);>-1ЄjUur11wp^WN|(7Ԭ{.H4R y qGCmRiB3(0R$cw :X936>]CQ+i]#yӚBz Iy|ڲ1ߎRSO:  YsU /WΧ)1i½\ &Sѐ -)v~jh1\4Z^,CVwћlMIi ! ܳ0 gDB x׳%J|-v縊B"и\5[Gp6Ӧ I#Qۥo;WElGH|5Q_0&*e);oȉOzo7C3 Ć=\LFf1i,5ɟ1,H."^6/۽d`X%u~ܲjŪAAa.KY# l>`Gg`i $EeMGe C5,YɊ0#?Um%GL,{hz,-˝ {އn!g{m ?9{N9(É4ئ/IzgP>6A.Ӧx砘laK?[!5,H.C>X2zٰU+@{' @ Z9̏=p?uĶY+Ѡ0tዮDZw/E[hzu)7 3FL#}w6 EЛ񊽁wuݹW?6rN|#H0b GuCwQĒ! X\@r&{@C q Iy4)7*_"P0QuL;XB_5Ϙ ^Y-~T-%U\T*>'T,=Oq үh Ł+^)Ӝڜ`>uP_5Fc~yR qߎL y'3+P'D/m';t3Vo`_$DUGχ4!BP+rG1\=V]tA_teUE.RX3хt|Ѻ Ƹ=_hqt޹[ЈJz{ m3B @J_Ԯ +12K./[Yˠ Yڶ{P\ y;.ƘPXhj0/$WLu(g< 6R{ҧt#\fG+2:4[sDUŹjX^{2 f8g[e"-̊eHM3n;L $ٳ3!g|pݱ#J;o8Tv ttaF : &~"}?^MEE dhNj(9QƐQ$ޞoSCoKcI' S4x`%OlXTqϬyteFhM,m-$ %JBt҃frUaLar{p+A9!0؟NQB;DAaϫt=Dc*!1;.h*aj(9B,!å: &Ό3^RQpTaOf =ʫWwnpD#.`,U{<}|d)~ҕQjzL߼|AQmq4_;f[PQUPlC%`c.EGChqhL*ڏk?Ay6k췣wk&&!xD;YCa%3 5v6Yմr*Y4$ Q Jœ~=!nNWؖ{:d]iD޺v 5R8n@#xv/<ٲNg⺏w(PUX+LGldd)/j`œomy+UePc0Dj;R)Q W!D~3V:A>p60 V)dhzڤL-tڮH8,Y}δ;(֋L{sKǘb8^hݟOkGFhO9k(*u>4\~oo/8NYg!F=[X1J3h:Xأ]kEP$ÆB_5~"W Log#ҔjB n,s)O:j^B?$ u|ki2b\d+``m$'U!fD\. PЙ4ık6u((WDċ}m]JġP\:f eEȚ?ԛ_q%_]9C֙ w& ?Ugk0~R{~h2pD @yBKtPPbp5*qcCa *ְ yg^HUF ĘщAACF&1Q-ɗU705<憷t hbGD0r, BC}JeJn]sX`F$u4;`陇e.) ,e2-wGN-|>R:4WS=Uپw`!ZyǢ+ H+՞ 0g>)mCu8PB/VYųPv&(0!NϺt2Sg>m @'ǺL1s Po2F0aՓrTS>nGy-hU$gƪT;$g@[ rXtCkaj_A;B*a35UB4KBOp:K#ΘL̾mǿ=#i`1i:r@= JԴY-u44Qϕe >uuql8̐KhIs70`݇Fѱ,A$oXWy>+^퐪]E0䐶s֦=w*M6L0襓J]T V. ;},8$I A)No2`8/kj+7`j=l~=K*LD1Gy2iyP5V{v,+v֍׊&\@ F[a^ .2ϪoASf-QsW$AN`b?W i>>kNaau>&^ 勞@:Sк UQ ;0Ƙ9 g7m@Njxee)ٔ{BZޮƺtZ,AJ < -3t ̕a/.#8Bgen1)׀ĩ?Цx{ʪ(9:/Y4xu!iiDC WP/O<6>ڝzt h4쇂}.k-4G c}Yi(>Glo15!49}N}=A1ddZ BaE> Iϻ|Z ڮ2vnNlq KCIl<ð I5%+` z]!1w` ;.1CW嘩&=;*$!;zXG\Wٸ0sV+;Q!&W)!zjMj)^Ί*a4;`1 w k Ţ%m(ʮ[sAw|'\|kϵ"/ (m}bl:wt<1kAq- a[f\cϘl,23B^Je𦮪:k{{KUpra3MݠRbhW{6 o N*6lb *<Kg}tK[|8(;q.fv `tv1Fpr^ ;dBU%]RY 5̋Kf4z,XT}'<4WHa~LFH/.h>/P؄_;LHtv9\?](-vs0z}zр:, 4t[8 .QCwq" {:8(!4~ K^6PӎNIW#/K_6q*!EF=! wg/rV.Ry.sM+eM+h l+gjc5~u)1[<8N6 &Н%0am02n +5M%PdŅic(r]翁hFrB|CXI㭄y~ҁ y}m*xۍ\tw`!|}>?LV$s6"H ;x gh5&__4w Ć 1-|)S]H 9vA Z'_v,:(U krr>g{;L4t=GRʷ ed:atm oqa`XNutBb ڭ$֯k`l=TR jW2PYmԳ'咐eb"cђr~fQt\ A*%z3CW:wQ|Wè7[(ʖА~>Gsr݄BαP0O!p=ZQÏI@5'-%^뢗r"YbHJj#]W&HVOS,({i!Ø[+hc9IZߚpC`mu뻁Ы8o)*N>5nGn/SLL-$B}uxq9Ԣu-EvwB 3pI.(6Ua ;w B; &VG\x? _ҥ]Q38QҦSG?$+|75P(^UDGfG,XTߗ}\(hJEFB(vNqyg˂Sz Xď`CJݳ 1%c:bsD@ާN` pO^PϓLe &l l}wȣпRk 4]ޑ 3P!l(~ʿ-BTh+|pe]^i؟ bˤ B AxN>7Q[Z+34)R KPOX9m8,Y.ne_P"$Txl1srO=>rRGwÒ9F<[W9bz(X*\~P@ًy33+$h|$0%$@7J˥`uid^z7`o,#Tx)vؽEou*k|2i jbbC"l# cp Յ|bJ C>qa8Pђ3KtjUmPe N7=/2> ̄^H捝F94[CYedk jQ ϥg]1fi\oUS/bSO:w :9L{9Jy2[< t9oj^`! ERad+_|>GOTn/2^{ n$+ U,49qXLYc\r0JD\;bҥ>;Ӈ#P%{)6@MT^ao'1R^4gk!nX *rgE\7HO6) ,-<ܔrPU6ie%4~(?xsϖL mq rJv _4r&] ad Bx6oR5rEE?xR)Kb5۶>bT_8AQq*U?t&A-tRj? +B{k7?' >_,65;~(NNて:(S5@d7W*EglZ:gxh/p@5spNZ!+!Fߺ yE53ӁF MN,G0WV/Esb{ːtQ[{xྜྷ*"*љR,-| 7Z.v{W4]C,o(A>Q| fRlܫ=ZVŵ;#ɖ T_,8?widgc9W"~3wB戀jبore7aC{g OaHƵS K_BڟLcvl!sf:kfRҨղOʢTFsw< |z?$pr `:7ep kw>#~aZ|Jܘc IC1" 9tw䀄z^Y(!p$9mjiT&C0x^3ۭ6׏(ᨧt(REĀFZE;rE5%O$t)-u*% I,",e!EpC`S(M>vZMCd*PIq C4%w9J?m&S+Y,v3ڼ8sV$h=!!Ԟ|P| {bYuo@4fkҞ,G}B,LwTiPI?O^Vn*}3Pn0eH+)S 8 艢~?AՆ\ pcsgiO t얨=͈=EF/瘕b9$TjZ3#d& g0N56̇ 0B'i53E-BbR¶di)wjLS9]~puz:]G\݉hsBɨk,6cj:?K;4xagUx~۷ DФ|kԥ?&]UtDDk{=ݥ+_@X\dOr+S~Qb8|Z/ڒ H:% J;Pko,9Z|;]凒WI,qF `bՀB n|CE($!;,z]#K^σh m&_saQ#υՇ29B-1퇪Y)(Z}gx2͜(lbXD=[B/2 ( G-ax^1|#]p O Q~0$`Ete[TSdPPkf)a7,@]jAJn~^Jk EگF=QI;*K⿿ y@=֋/$4)BG:\qlQ%i qQ4`nㅣ3703N+GeF@Lc9@QYL-CrU^jk7e%Έ E -|+mQ/Q({։i'-s`cBF#O.k|&4heBw2USi >hkH xvhܲ8'bϬ|rZz4gWKT6݃_nhK ~-bNRoyl\`=KTƣM}v|>6L߰RM*`ݾcu bKF:R>>^٤tZzr2!Wr??'s,6W!+-m Z{H^9}k`#SڳK#FR8k/#v':dƆ0zQ]o_s(*I=jGo+OW6?0bZA4$/39#ڮ7efL㑌%zLežޛNP'zib5/'sXtQYAFO:/O(: ?Ļ>@cJ,S:wHI:(^IX>L 5#Xr\ˢ{,#q>Gs_a-4վVLޤ$|3jAĈn5\vK %޿NY+0MA(!iP!<,Hi##nźc2 :YReDc!bmbs0=q`DpcU9KgN0-{ö P N"9@L,c.t'C]gf6l}++^&{IsZC3n TzdrFPZVG함SDB/ q(ϣt4:0 g"ky#2rPIYAdOPvt ]oE6țס(dy:ߌs^ ڌ%'~!|Nk=0V!IL܇.n :2DW]L4𿯊ld"5En'|on뻵KsWe9Lqd.ь$wh  +.䲲gjAXЄ‰y0^MٴNFVR jT`!Fҿ'yC4AJW 8qғ)C侤Bv$UX49RK39d1$X>}&3b jp\"VJgI\T??zoٜ%\x̐+#r}wY!tq%XSSHk,JvR,}Nydye3%{65/[mH:P^dBMyڐVJ+৳iNkZbƿnM~p?ʑ9ԉaVx׽zRh֊.vڷ# q\?UY(^ۗ%pw#~ 9:grשJ_ `6ݕii5I[7ϩ.A RbdQq(@9=iJQfw0gaQ6q[Б~}!nL*(g@=ԟlei7 ߛ~.$t=N& eɜR"I_d |2 K& fdy4La"ϦlS5B;l t Wosl`Q|K1NhI@HX}.DihŒ''] NѠW/'cдV(ba:կLCf1)8AthK,Mɟ;XH`Y]Q~[EJX g݁j=eCַ=yqT1~ ]@Ohb}u_ fw{ɔ#ukO  GtO`)_ZdgYZ0 4NI|bv60TD8y'BykXb~ V Px#;ɽ&Ƈ{̞>\dv9E<)L6}885ejQ8R^(Ƃ O\ 1g1ƆVmAbe%"_>Kip'gD>oDzMq^ޮ[z77)/ u`B%~WtKwI>IE S`J qqc83?{fX#Szt5"j7=y"n1^y'k(<V,1>{+8ڮ%OyOWd`NL>"`dkw9>(r=`>93ߑ͆7ד=;P<Wf:y?MY {*ysf`̌ IV<I VuivUksl۴cm7Ѱ߷ۋaW{Fj}O"\9\ָ4 DťDU/{!v^~nPX#]) L-Fv@wGg?H!qd=qoYs Tp8ApW%6S [Iݗ(Q۰-^f@aKE^=Ӓ"wUmU;FO0`퓇 xE`$뱳: 6E7``ɩ"+Co{4ClUJ2Iʥ8XӮ;;DCe2J>Y[:QFw)1E~:1Fcb~b?Cɓ N&boy!tјEe8}\_ EV[}c5A(@sL!\%?*u\b>FWٮy%'f0 j*rbJ6KWcR\-U߶ʾ0#A˃xWp V X!(*TG x^s`~1]O)*Q<Ƃ 4+;Nz'KC#}է[NC3nُq%sjtՌM~nEmDݙCv=z~^&t9b* w9qXκ_/5-N$1i28T_vH AQ/dFj_`p̣:kǀ_!Sv^*I(EY@fNU y]mV엧_NaM"({#@ ۡz#~@'$޵- C;n$X׹Lz" k'#$Lm+GHAƆqf< E7#z{V`aܹ䁸]np@G\yy&+T=+jOxaܫ0;2rC^iу8Nմ e"{g>l\GN6ˠު@hHu̴h[d5fL)¸DF,T!MdBoL&[eMe=H]`juV ,$/hGNlޞuf\gs Vzv9KK6=djCܭP_PEb{}=lޓy'^ᨠgD}5.8ౠXR>9Ho`ޝG}Vv2`b%5\$HD8j Vw%dY?  +&Q:=n49~"ȱ~ft1|R"W B?j M"xܢ7H5\ %p1:25WCncuhbQ`02 }S sL]W6s4efg81ढ4|Ԓ )58(Q3Wj]W̋\9]}XiaPFw)a^ c ]iSXnUng1F  Npo2pW^QeF李@QԞ4WW;.EA`C#?VS>o֚Z' r AJC+ TZܸ$$ Mw^݇ D#q@%=ئ8x43όϗR{ZB 6ʕl,◃XN)gz }]?Qm &eƐ+=tE+vμwa„!Bd%,0ߛb7cy̕n蔡!pef]"Hߚ 5dn*FiKnA_?e˓qu̚_^U4Irit?ش;is`5z3jqX\QzW23%_ s뺚6:MC&yPbjLzKO X\ihdAu%\_qga^)9l| NR}BHBfj_ݞ ,`Vl}W{O6j[Šό, ZD/ApwO7zЩYhAQN54Vu?Nq:A"ȒFV<@TWL/b3X2”I=Pw-^s /8(~Lź!I~IK7H}7Y'T=-cVI.1񕮔GϬ*?1@gT}67nEFRh3T/=ZzvO`ۏ2EA_e~ xVjwm;Bom;SeRS1Ȩvnj>IiݔE|pĤʣܺ>4%Ō?mvwJ:}_mu`b@ɜ0DbJ_*d$4uKnLuBҿZR;0z?~SlqȨզ0[=5|9e֝DMrw~%>mIM"Yp җ!W6RC@;ù 0 у`H#q'zwj(+\HG& |Bۀh=tt3|̛ug@Uz'W)5B/l?<`3lN(]W4j7.\~R=h 59besvO$sme <;ێ{yfO׶BIK,َ}P01[ڗ΀^k֟ kr |.k+x߰Px穯\IoFH_ۅDLIi.}P"%+ ) Ӎ,I@&>kLE@Onc:`O0[HJonԩvd1 VGVoRz;ҋ6fԼu#@,B^  HG+};=g m1cv9<5Q@/Ȱ>MȠn(A)J4OLgmNB&'XLjz3E:S 4B|q\'%0K"NS2FֽQűM{B4k!*'(FGF+/javuK@.J9IOrMۣN4Wh_Ht"t,eO<BwA*E+ᚠU/VhZ>Ug~U-瀽 !t`_\Szwri*aU wc:KD ְN^~%o3LmP^穛"hWGnhe9]7;n0C=oiu1^%4r-[Z8!Ӊe|[¸1=aVF'^oswځ=дURZYz us[R@Axi)["Vc8ȯ/x/ N-V\6m=L[tҌX+ Vl(nt/xQߕ*!Z)+El.7q mg<.oղ8Mn;idxhR!VI0VTb5dqR|d}˜EsiseX+3`Y.lTsؖuJ5=q aeȋgk^ŮaAwrhPԉRKf ҦΊ4mIR y["?cn GZo/CjHYn6 0tJ5I$t4'X_*\Wٛ~_j+啽j-м5Yt8b>ł t5CL!ey@߃); (}ve婬1W36^<)nB1PW1bR~5fcd'F|&%f&z p(\OЩհ2af>/2s KuGaK$?qR_ [iV;jSMa Tjlsln^+ėwMib2B42T,e*mE؋TwY6Ů"9a*c}˲2+kj!l)f˶)8LARjdcӴ mL7"F!9(:j5:#`155O6RJ{h i1;[dCൟ{wb `[Kw kb: ehw.9Kx3LKhT^KĎ5[[;w>j)rDT>j,dQ6 1'yNU:C)NY+fuF5`7[t؍^ DnoՌ |XƯp* X1 V7tg>Y$Gsj 0P+)f*iYk.Ilؼ ܺ頔G0BՎa0Qhr?Ӿ>{ة6*젘zF­JNif| j;萈 dᛮ]QqVi ѷ_d$?< ͯe&$1"čoYPXT䆅Wړ|R =\M xL C=DAs9D_WzfF$Q)M5INɷc2> =5}KYUd''95MFs'mTyB}ƽndJb &Ahk7VWl#&8c;h*zB$JmІASs[S=%m^aё  8@F !? c,q\'v8`ĵa'XqKG/\MF/A͚Ԁ,j5Ijn;rެkP@8Gk7Cx{.8Z#&ϐ|S_'3ޅu65a5x8By+uz xٲe 5[_Y;+ϟ`v XӘZGТ$YC{BdB5L菦JzSNmJ46s.qC)Qi :$ZPCF"(1NeA#7:2κVo ЙNe}]xcy4 I#8\&ޚ:KA-$0!FSu7GȿV2(PgT" , '3U,\-iGc~%<0fn!=ɛ*7VZ6|XE5T[Pё vO=> ѥ)6&FgQ]J,sC>yp¥!_á8*;њ^# K=?<r;~۷] ta#|mUrZ!1gw4؏):^Z* 9= [!i @H4buˇl.S7rrsomO_ V,KSn"qv(SRZEsaR 5m5'"'2RkA"ON.ȕُjp*VGI@wVc2ݓc[)LT-x3A ]N|R *t\p4 6_]cΧ2A"Y ]ՎX'hryQKf)GLCy㺙plD9޾2f]j;(!XմxNH 4KjN{8dElիוHc\l%a,;69˧yDf\V(ȃ!sڿnĂ0T&M5sIn+eS_A~՟A([/ ^oFA;L}>S\)^V c؉&?ɇ)RѾxǵ l9;|K.8Φnd؈`F)`nfc}UYBEOCFŞREqHM:˓']FvMxCf/C'Q:ZaW`3{TW1b$;)1?`27R1X$WK ~P1wKŧ+Sfbέo4}@da0 /f 5e#qN֫)H! dz1\ݹLs^ƆT&,}vRaoR (*s\"${V5>qfSFL6UoŮ3dl!*G)g$ǹ˗Qu5KO>4^Xӫt6{Cb\3#H޻n=K$OTD1Azɛu-aQT+Hbkm(Bf Fy4}+F&YϽs%77tJj ]Evj*.1fFaE;H6Xx;UՐ6=۴)le7! I *27F> r>DZ$(?Ɨ9DRݑ6տWħ mbH;˚zK4*YQI!fG`k1WD[Ke[5IK+~۸e?<_2s} 97j3n$*1*ʤ=P{h5C8zkWEyo( P7wfx A'ҩw+}-4UhT C;7-mp\E0 S0dD]C@Il-Ff%OXQұU 07v|=FO8(98m& !y%&Oh! zDQ%`m%˜`2B-m V04,¥%`gl=8j[앻<,d CDgNUe{3i2]WOdW f>$r#ٛfo 5&ʋe2hs){ML)6t?gϬ?e1śǎ6u m/ZUz^?du8W索wAu[Hf3VJ7%{mpSOq;V'w0!zxj ˤc+R_CRQDH&zEuj?խɇt nT [eC ceEC )6 vIeG@-OlK/VV6mªGDU|]́fI? ʪLNԜu7"F-rz&D!ԕzp$qШĴ^d_ӽR D˴]+WٳKt[;@Apk:8 6%)o >PtK#XQ/>Jפv+7H2,.k]&+p]};xH>qq+!DE, (?68mRʤm͜2ϑL1l׬zUYMa eE^G%ؑ|g({mIJL2`Pm)0X3jc*`֤L3L>nY1>jS`F5(qX'| C0}QU/PW;Fz!0tϫ]0ix )t^&pȡ[2@ҕRRx'q]j|J>TQ 0l{WilNmīnaX[Pw~CnV43 $A2\^k`OꂐkwdÜߦO"N bF=aa\ ˥LV-ƻ%~uP$uz&yruQUe`KP:8oN\8IIbѼqď#fɍBBG-a*VM35%/S䔍c`)=~fiUrrO/nk_7 \L&ىҶt~00qQC\y7y(eg)*8 m87=#@b|Z 1vrn ^傘El_&JDd!` kŗ~aڂ5؂w8O[bu}_+$]M%Oz|'6fD #*{ ]qª3FL v^_TwQe5Djy!釾Pty!+gR)7J?ѩWZD wU0.>$=d6ǢK}V$eiA1u٨;}Ngj|}7tnx%<H{pF|y2WkX }ճꛊG&2=<%.ʃ34XdN,r^:ϏPCW|OUثMgeÿ&.Y*ݸv l: (6fc! Jx{F'oDx/Bnk6|{,0rl+L[7aٗƳ

x:7C,'*` D|+AĴ7h&\IÙQii:2?m{L~'3itjQG.NjP9٪f>_n0\S V \9,d,#-Fz|蜘s),:o{5RM’8c2KYǻyRˮG&:<9BL9Y`%֠LJ 9SSP%_$fe `p: 55ա2J X;Ҫ"??13l? O#L 3uä!94{`>ОkJƁiL)rMH΂ub64y_fl8!"t1o8d-`sXnpjj=DRېq Ɨrݟ3# ΗFd>5R؇e!Ⱥ]j{P&,/6+7 urM`doA}-,)% " @ԇղa_قQr[HlP2Հʼ~=QZ͊c`34ig*(2 Un|>狵Y_(||޼fbJ!Zэ¼)ꅧ I,ˤ{D${u:I38 _O_CIAuH]S"!2Y^(تsfxum2|QQ-PI+gEܐ\G^Lҵlu/PJ4:qD2˝:ڒ-z0RrG{#YtS& zHV\y|ӝA">πC$Eds0 E8X |S\( >YmfY3Z+A=U"c=j5~cLeW{2qؔQQXnLVͥ_JĚ(;;ƁK2٫f ~m_̢Ԋv0NzM` (-9d[W6y–mM7eQ#DKꕯ>NBjÏo$,K=cI[xF]@7'UYܶF[84k픝a\un2n %cHja?>ثE Wn_ WZS@nBIMW00ĢQ;aEC$@{K}"uc$=>+ =k"; R$sȥQp_4eD2ۖ+ 128Fqrn#fCgiҦXe~2CE!}7۞\X2\*D vāȕh-ѡ*K>&(k$4BO) je49Y4Tͮ]&Kv DW' 2Ӗ0jQMHY[D\J ,q h$iKM'Xm1'fCHPl[_$I>jN595r73s4N,m̼8g-_Kg'mm^ ̃Uۯ'!ب ]=f=*t{pF$~ w.v%2,@"M61/+jIW> QJJuD#~:\̍S VN HTOt]fxU@IL,]j'̙IY 7PP5d H\o57:[%l=ȵsXFs I)>˿҃jnuSYEgs=ޤ*0 أT-ɟjsUEUYZ5RM#ϣqi==&~ Ozvg~(8,}Q"+G #gmAn590,^Ba4pvj#bkx]SC@;k6RK}ۏ+'ROKjci1 ǔK6qy(=u)fSÚuHSQM绹G.$ҦFv}M)閐Y8_^/.pos.n(Aej$kq(esX^gjB;1_% hr+c~b$%vZFŔoʞe8QjH VK$ߚd Țu O-3օܿ# 9Ά#&prUfѢ2R!Hj lvJ /eަōiHqwZTYa SPx2%O^[JXާL%77YEĝd[1o^{ 'Fk_[=*=2s>JS=X̊YJh#xmJ<8p DBo"I,(ԢJî)bbsLJk҄8p/%swBtk> r̀o=^^8PLzz͙LণR>>-Qu<ͼrLz{e^S&>-s !d3ch}K}8saht\6ir~8Ȃ[N B%I7+.~sSĢ/`)s!cqΚwRk& ͫFIKS+CRGF1iQѱǹEYZ-$N". 4qSHj#x@Roth@lM]}U0PDi監'ab|>xdg%|N |"OƆؿɩ &nj208Z qxxcA0R¾M3 " $K݆ogԓiq=D.WrJP1llCEqOxgCWqݪȳ2|Y`ve4I_DXn`Rf1p]@ѭeJv)!{8Izh}1}97 os\$^8/TꊮS hм5^L|?*@䡺+,󚑱K'D2XP8hHZw0/9pGr۵ѡ^^PDnS$QVBbqq"c,+S['?H n(T--me(!N&àgDP=/X"\x1 2?cG. p" )P?] heȔ1%>3D|3TG@߳]"qc$2d\& LEԒx|(:Cʵ`|UZ!8Ďn,>ߘFI:@38g@e0H*ttk2:5xZ$nֳWEk/ fJ秪h_oXNb2PsH9.&(XϭPJfL}⁰ ن{qjLlUkY g#qsao~RA;1jtf)\>i? F: 4u%BTGд9[5;(DmXvOslֳ@f92?,WX.Gs!M ]xS;J}TsZԗ5.^'m:7DT\| v2&xh^Gz+;*ETJi{bBOw[}M7.ρdBw.JQOpIZ Zon@%_OtOІnC(R} Pi,={N]F&p "aXTlDw#q+Nz8>=k˖s4KT35o(m,o6TD8=.-J>C2JZAzH>FzL-!)oq:>ݯw|CбW$!Uq,@B|0icٺ)*ǰ`i}6gCbr;j 'zma)l3ka`xiKGf@MY.^H_ү8 )%Ty:>6T%R}befv0L).}M +ivu1#Y(蕰D6*k^7-;:( '"Yf)BIZPz2&K/ OaWǖ5~k0-SJj3@7= NLHZtFF\5-Ljܑ UNQb+*Td\}eΥ\`Fr,I3qR߫|qkE?pKDa5Mн}_g[jWr h[T:ӈ)sQ.vn'ʷUDͼ['>lUUlPey)ԕbyFyזJ*k[ѯ$' d:lF+f }Pw.;õQ VLRuȈ{fZNO`G9v2)+C:aMR|ע!Goxe4# k&l0Ae}$ԍv{B6>a.jaaK{h_ZͪNї[8l1}OVL['8B2~x;FAp5R-.&ɩԢ;c^H/9'B\J>+[&ݡiyV &z*ee&#隆Wp3'鿼Y${M i!^KZWs,8rZ>JgVX3L?w1f9 UmG&dѢW+F7k8w Gp$J+>oLcLU3x`OoTp|DOӻasۗiQrkA7R )!~BGǼ3O4Zucn)zFzYM1l侮sk5x>ӏdwf!GT1: (+^3[c5z6>3$+IV]dT2&}uY>V#kEM(Mc  "a ٨©͡ox=uP }>n5=;z-2~2w:y4ع^  9$j= aE }q(MSуe=ވ")xp+ o0.[Rz/ P '^'enpIMs'n~|޹e ?(b_73|G 5ԥ(kBQE[vCmY$jn>&Z%D0bԉ2 ,dt$'O;57vMC-.1js}HHD'?>m^Q1Ҍ_0ivSZ^|@݁pAԸ ~䑹,hb뻆Y}[F_`ً4[T!2XLaK##K_ňs2%e{q>+cP3\C^C+[Ybce%6I#ڧBۇp7d\r=mLޜfsE04wQéؗ*&` hftб1lE8V{ $/Q~n[d9uTm58ܿN/ݦkѱfgjYz6+`,C@0 ꢝI=!LDd|Աd1Zq>SfHS,TM!<}(i" R-z3x6?nڗ%3<$>eFr{ խ٠잂P+ЍkM\2 Tk\6cߘɔYzgiB/.jQ,аy&ٮ¦d1cQbUQW.+S2P6|;OQJ{`J&$^$5!u8A2@=ρI[0S@kfu+Xs3 z/b*|aSHnxQ e['hP#N*rz N~nO<MuGtO0KS`jCt# 9੓%klE_%Ef-"*iq8 U[c actR#;J#۽/vBftHbvI؟džg>6!o4@!5:6: @1GmāhaCE_,9&|5w^6bn}B&} #*b: <v[X䓰x2r:Ϊ"NA:Zk9˧B-k5ÃldEc͛Zc4nbk5VV 6^5>}Xr(ЀVlJ:k\2 -/ $(kL' 0"(ǚ"ֿ`3<ަjLT#ۣdr _NtW;i.k ySz>KwOKExcW `^+XYL* +T%w?&HvR6`~;F|/0+v([U:֠پ@1ʜ ~wP$i&W+7sTҝǨ۸-U@ZZ1N $!wbOd;L'C䶝.SƈHd>FHG]0 7;btF<&m+56fz nq:@Fߧ /4Rncrql`?E& ElOB8}F2`D~(+=x2{my*ɥGeD3_GXj( ։5돎T`r!%L*{8K:>,t? Y"2}[f{p0cDV>/K(yrƁykr5K%ʓJH;l4"bАi O޳dD%<:VaMBPx?\䑾b_twqjKN.,XzT΢,h̔I^*&Tp~3ςc,x ΣQYGPaȎ$JtN7^i5t+H~Z1sݕbeqBk{ܮ]`,GПԚ;;A|,LXXX{jWOH- >+F:U5tV4ިyt}5B#漅Uo%_8M =&bq2 C?/b:$L~.!)gNDnpL5!8 W]KAۀ6~iF 5,p׽1?sG!Ӣ cZ[V(,Oz!\?#58Z*g΢2|lteAވi狷k,8piٽ\2E41<7l`RmĄB%emiFpS7{6ܦ2w t!iLJJyev|u|NRe& {^OJwpbF:&[i1DfUӃ±"tC%ю/$I8b2ɶ#n%?.(Q7StJ>o֯`9cw(Q8!:i$0iͲ$;C3GIF~^ln nV!m O֎FE[ ˃"j * '-ezԌn mvKQ}~K@5MRe4n:"χ) /}4rXCWlywĞ PU_狋8S-rmY;D4)4&mC >_ 2HerL P.\_/FaҙީHNpm n*j9 :2@h/~?ta zkC* #mAWd"jw0O`Dn<]=qVӲU[gzMoWV\K;ξ1D(hs溶 Ѕ犄n,ϼo}{M^G&cxP^=?5-6G` "m<ͧai?<7U 8Xݯ {Ĺ|ur/99&̱lba0H<-ͳ7Cӷ9Cu;fW)&UEYvu@ҧ*C\9fsv{(ްn?<篢׻Τm1 2 |'I׿.|OMq<T{1VhS.2l'OWb&@ :+n"9sen,>YD]8B衑}'t="v*oU=w-w#-ߘ+-3D.L ^FDjпĺBO8+&ΆQJ%ݦ-'kIGx28Yg*6)]_0w%iLOH˞U3(vVH\MF{ cCtEp̦9n󇺔)7PoS!\7pe5{Jc]ǔT[oDɃv);jٔERpm_7P Y͞ %iK9#`ws\};]zTZP T(O  Ͳ?Vj>H<{Io\eԒ _L8L;?7'c軦UN "XSS̖~45=_V1㻘xْȃ5f ܰ*}+<[aMٔ?infV?_9ת2&ؓRi@Kfұ@c8Sj!$ѯf_MuzM"<xW}l̛?ES1 -L 05{-f0}0> fHhT:s@H9 :VN=z$-UI{#R*yQzME=s,a8 xGӗ p\ ?}d ~_0iiXpeݾ!}j3a݃>ha%I@H!<~D eȆzB<9V_^ eFs4 ܯ[G^a"BtN26}T eO!t(CɾYb W,h5K!X9ͺޞ!q|DzSĸ8(:uY\8-LQjKL{`Lu:x yo.~#.}'v$:dH@o֢b!{8 Z_3A=N#tp3pMM&zQD?:-rQÑT^Qػp)Kg|fqkrc] u{qKe;W9d6 ,,yeo<}>l6bL j`(Fy:u@2VΡk<F+=Gf|:4+imVM6N xsO~wT^a##t<"bcMdȑ6ϯ=1}Sm3sR=(<|ϡc1VPvZknj!Yc-W0B$Gb=egph,Ϫy 7Mގ2W, ٩϶7 !ö,J^ Yv=JϜsY5u>6 Izn ]=rTWQbjO*d;?1um,=-ljs_peo\&B,z)24XmsBBʀHSFфZK5JtvNm62Chҹ;V̕mt,cxQ+q6,?` ~ֹtHN5牼}U+r; @j'T;*:La&Ma]׹2?A[{TD-ۈcp+Kܲ'.]oc~`ӺH{! }gEzrSG}/V D7g+L `s,$rs6jzVcHy4Tcn!B{s7p3[n 8#_~ʑL@F,`hQv&y.  ? |=b$Wj҉LQ82Mfoa{]H=SOUIȫFPG?0r¯2k\e Q9\Ac*nl»1q_?G.Nΐ70g\Q ׼-MDM]砩1VZ-80ޔHBao6O}C8)q;Tu flJ7 aWi#Ξ^Fɟ~@}iH`]WΥikޮz T/j"AipCSA dk97rNC\j/H[.+xDNoܳ?˦<a,oJzV9"x!%R H82Жyj^O`  wE0KR Ŏ?6n2NJ>E*^*XJ% -ird\a *n5cO>Jvp5r3Jֱu Ǩcxo `6} BQ ksK='-3K`er:|M]Eb/ZZN՞=݁#װ TƋd =`|'ȬRMYrhͼrc@Uu܀A!l1c#.F-=o[ː>RKLykD2e}@wC9'rmI4(LG.NM`"GbEvɖV'W0&ޯf!q᭭,5`to'#9cQ]AJ-h=ٌ6lNg4bpZT \p2O' e^s[LIda,H[-~d D@?K.,B2RhVX54DsO!|Q<եW >*`  #9I7V=G4s 65]Ј~tvg'qCh6G*?ģLoFa !TMy㄂ҢVZRyDq8d-Mb@7W0/9B s(yX,OhSM"0nz_ гSUw)U3۬)G5*q`K!M>f7 5%S;tzeh!TƋۑ2~Tqt1dB[n5fa>K,I][u\ oEdΔ{lNK.y{ߠ` =TZ%\ױ<\@p?“?_Im2?>mV ޵a-א0QYk&ET Hzk.a"/36u/[Wmn飙°/^5K*W"}MaV zZ`ə軤7LϕvhKu/0pP"6SIu&KM=Lk"܇5%" B0kou+޴t©cgԨ{oXrR`TRamo(`p!735bqr「u‹4u㜓U}X~O}cCcS$1;.hGeSkfo7DSQ8Jv6aS&t_?|jb ð+v@8F96r\u4@̤XBܹ϶o~tZCI97<^Y RHݤu0V@V}T5"׾ slMtXj]_qr&)e`/FrQIT€C5K≿'Xhq0*zhY\nIh?u[<]]+H~헎'M" )32+[nm+ _b edI(HEX@ @Vݛ$ysbOCV ?vѳQTc\<35ဌ'sSw,j{^bҲ "ºT~z:/* Mh]n^F5J^I4a;T>-bw=ao!;iBݠu?4fo0=/љmXXGA5\6kCHWu8y<ɺݭʕ[{{O,~"x@ۯQvBuӕOiVx/RD/t=oD5 d1A uX{9T ѧ|:9gsҺdK瓪-Q¸C3d9K gt#1Md7 7̮_FK1Q/^/W7p@̘P#a@mb Ts69}Ï*^XGt&Kn+k{IY^:x0 N!y{ܧ?3SW?YnYqmLq+0\cHR8Ua"dclR8֑:MbWDO<ߞA% yeV V/Z}|fJ{w fЛżAN 2;Z ۮP2et ڐLՏUԟ7lr;&Q}ͪ V%&aq!dhٓ7؈wvfᰆuαT/˗e0O]-_{6 $3Ux ߬mnz[6%AiUjU[7OVL t8)AxXYw)&q`+NJ߰qCepj=Ǫ>UQSa,EjΆ /~>ph$3聡*tݛ$Xۑ5s!w:d:`YזT-ę5_0:0CыBAaa'ro=RyUloj:N]':U~^b=e63bIMdgG%u3w+ v3$o'sEs'.NvgXqtp`<r 5sDNUem)rȽ#ZaWжإ:TZŅlIB4}| O%i}yՒz14л#qL8E.aj_nArBDg Rf#o /'R @e8TSP_fz%wQЩ36Qzʲ6P2_f#Al fl1&>XirL)DZ@CqE(lx-?jdk#/LǞS^<WIn,. >wlaw56tܳ͝fX8>]1"(ڢN010R! Mq4{Td"=9'pƩJtt:Ve<>Ā|jLJ$V|NFq9`b\8(fs=y(+ UwY3*_⭃&SƺUkجYS&i`;qPPG%_o\}\ה b!=(ez4[u\*Ԝ:krA+c˨Y##ѥp iL^9hdtS h*a!қ>EuZ.t;o}Ì[e7 ~/)U#]u CWr\Z2ʣg=j ~O@];w(٣?`1.S8"I~f]m ).2! TRjOgEj<Ne"!$<177+ mU-$;}gS@ݑ3^ׅ})d F{=<[6C d"eSle) ' 3EaHݪ1==ܴCM+fPpJiM=FG;(T'-l7?fM1eĔW>>1Cz奿: %<dc-$y-d0#3@sj$}͈ިLOz6 ꄡVae [vJ2M| 51] \}즎'(p07EJ<u54kBE3끈6(GK3 ?ŗȳd@!":Z{Il6j*@nz5 L#ƑrCFW nwp0'ЎN S_,I䥳cR?_VV-焮$7b-Mk$MOT>s "|iz_qډ _;uUt%#ǘO@J;,N .*#[Mk#Á5tK GB#ݕ_^G#z&m" s*]ţR2i|#0} v*̚yFjY$kAY %:*F%`EZ3jNԦ9iޮ)!'\-эr{Ti q~\Q(̎WU efQr9h}h\1WyԿʓ59nl-ސ>E UekgqX?`mJ"=GI/3{;4h|2^ ̗_ fFzfRHE^6N;NG-#J_t0  ? r~]ps~&-2cL3k)ԑC“gf1ʰˢ + (]2. BzoX *hD9KX͙rw;ju Lh雅9hë.üvW(@0]/K#RU1.F(|=_vY(9B5X7SE|@_Jesa|j=7%K-2;tmr0ZU^dRV )әF7K%?NY&s$PӏO8.:p+?ю9y03],ħsjtl zp Jtqae[ً%=THVHMwېN Cfg0`y.7XIv<3W? Ismw>/i˾g,|bbجT =ߛg6BT~qQfkueH.Bhoi)(44kc V?ZZ-d,[7t]9d3sOԔM~`X͇U1yx EUS}ەAGA8%, ;%.X@bc d{]hއUy&@E=hanXsO.KS8(s=ͣW(I[1bQN7 )T6~YʐTM>\G~otZ7nU"v5g̱{i ٥]Ds^(;P? Z\qy *Z82fǢV~|& "bM_ 8`#aaP&^?K|uehhf]4"Y("Cϫ]߆Dl$pK,Qh5(NU7HE^ MOx2*ن`sn#|Gn[.2SS} ]F] ,prd;{g9}uW?a_d([d~2=k(^\ ([>լ}šXt@t/g8uO~kwK.1UU3T%ީIlζS{ŀRO=mg]9(o͟[seܿ,PƣE3+~ ͞ 9m|8'4XMsy>M/n3e;Moyq3l$qg6x2Vrc'u_ -C[kcD&Y8~UGޘ8腕CC_9Zl#y WƤ+VxjD"dJq&0*K4JE*h8; 8{hXIQ8ڮJ¢=rRAL*9,#fDڭƟܞT1vDgiX2`:aۜ,-?U:6Qcڷ BDxliKߔq ep׏d?inx|C2R^jc2ӒjXpD IEXX#س&dtR%LH0-4CՁڜ3|=b8O-u^=PXi5tBߜ>2ÉV`~(,Uhը0%X޷\_U63̗hTYjݶ 4_WiQK _wB0E1U"^z$uUNZR6jP'zmkPLhʟE2dia G5ݩqq$Q"wm.[.P轢Իq׵pPχ7޾⽘l]c&ɥ8D{ˬ#[M(;;j[XHPO<+LHRK",m3ν9Τ쥕R _؜piTe-wA$\ݬ`^Bx?O IW\zzvp|O-oW,!jZ.7ٳyKa=utEAxUηDŜw`[,{bE ]~*<\OˈEWX\V6.1yt:ǥ,(䴢5t!S/޽Y{U7r4"%c iָkJ /?hhzڡrhYyH l|xD& i-HMf]v>7ڸV*= OdC(S_Zai0jڜIaoH׹>YħIݖ #ąm_բTBȁ,:{,&G͛~hPwGHXB.T)C"`_stZO&z$EPQ iPLAT;rW |Y'62&bW5#^xqM h̛vu .zs_>]p/ H eGe))7y-i`5b}c0 ztw8 6b)k3X^|BO;HQ.1팂lYYC..o<{c#˾JGq(J__~Scgj4<diaΤe+Dq\6=kDx{%.&v]cҔ5+v/-8Хny``zcͥF*(wW B|߈:q%ܟ`l!.J-cuɢs`c oHfBn/\desomCĠv;f/Ԇaզ1ars!Wsy6vII1UJl}uYi ?NGT[ҒW8Q~wW5GMϜb.{ *vs=2CZ~8T-a6oO{=0L%ɬ$`~Hf`k$"j97 Ej]lDaUs_#=)\*Jpp~,`ATm|K3@U"k3H~k495#dy׀gR95~8k?d>q pbmGF//`SRRHBf紝=: >n[XGAoe8cbv2ym;ԁ 72ۉUV`52t*qNG7Ƨy{J r O%/sLc*2| Aw-,`Ttj{`Oqfh*Q]0Tf,aoW=P 8^"wUzځGkf''+^z{! v| v", x`Tzv"fGvsI܈dzll6s᯸o/AQtg.lQ j‰)\Ah/Y>qXn7h 24OmMh!g 'ZUmFmxaCc&PHח7ON)o^݌j/sUr缕Ax:șP2$6ib>u< t@@ 펿sN NhSa>(cDZ7Ii+EhH /tpXjqoX&I(7;ׇ 9cܛB G3^&kXGtYG# =׌\fBѰNmԶز X Kvơ#KvQpSs*co&*` a/MVrFѫ?ys2VM'!<{~)njL$0 j\dM==GkT^6 [fci>Feg)=[I[$t +ΩSc2"N?ÛyjX9]̻gCHZSu]Sát_e;WgH-P~OYuR/α^2::Iu.A;0GkY`ʐL#Nr(J7Xq\tQ]8گ!+ZǾ]|:MB> <=Ms8z #u~lHL՜W* ;& {\43yܜHu=W6Ԗ˴(`Sj@7AhDXӽîKG=#b~;ztIA?sNk\NoAAQǵ4=E!5 S/t(Hmh=w|r!?ac Pz)/01Ru1jhQ mF]3@g5=V]-G_3[>Pφ"J u!xٞ%HK)H,H0ښ]Q-Nq!2or^QJ椵XgAF=BP"͠=3Sw*5*uhSd`BH1Q9AI^D#uG DzWĥxWY¦䆅&BeS3*5aN c Nٓ9< h:eSS"~ZY?z !E%#5D魱HwWt?"QlHb{~ ʮt>Y83^tdIY{PeIHQT6V-O#gvf R,-hwuąe%x`%OOu+t<־XtbۊܴvT7F  qeߡ4:̦Hi31wU ^䉋L웉mG," J\EwK'BSY{JKNͅžTO- G# e' Mq_K8dg]iXᔻ&8͜0+#vxz ‘YGf0ߜ[TGXl"b?{D5FayQ8Is̛K}⳶Osr3`IꡡH0g%yspg=" (p~JBtԷdh tru߯ 4 Zr/aeC ̟ ʊl i|Q6Euzo?Z5Kpko~>oO,8Tzࡣ' LDOJ^)Erl$X~d~V`{lx(5F:eg2 MZom=ҊdŌp)swPa>kSm{.I@f-,lVtʢv,1Ⱦ_3v6\ȣztU0%KoپzZLP~:"  ^sĔTk̪4o1kJK!M{f5ɺ2kծ#w=w: 6LHM`# \R>PZZ"eqAJGO(3E1]~mB,Cٔv/Dp3-[M6~VZ2KX]جxjwjn4˾*3¨iV9/mL?Ե 'ϒu>ɫFԠ^_vvǺ5|=^dR)&>VJ&(N=ƀ P[%GXtJa=HL+5T;7]hO~PtKP \CTy P`$5!j]rqFg}b&Z),0dN?4IR@8 =֫ _HR/j.(,wa&Є:z.zW"jD]O06(,W;o S;g_@uIO bE8OPO  ܶ+dnfNDf^9)` ԅ$'u1vIۦk:F%-%=턏DHIQ0L/y[UZ|~F3' d"sg5h&ޞFiM:K|1$^t+tPY%'U/Y^RN`8_$O6El^8y-h9WBmqo- fs.nz9CN fصjrSL*6pI>g5>9r0|__%㸸E9T_Q)214<ɀb%o׼O^,$oJdBn64}2($i{[M9H('S>H^I(a$a ^9e*X8 䂈9ֹ$2`va!m<!aqu(WO(0P-ၛޟn` z6nAlsÆp5޲N::RV&~6d .]tOIɅyj/RD3[@k_tɌ# %_dϒ\XH\;~6:2867P`g b=A I3;0'c&U't6(yrwx#tw τMht-.~JS,{DL$K(ƣ6rͩ&ɚ%[ ` |V5.Q^B:7AM!Z)wnXv<|Bm+aulV#a{ 2Qs=3mu"S-wtKx7ؙ8aa9NzY !=7H֟[[dĂbA7ʺb`%^7^e?ױXʘ7%ZfG U)9!+TCV8&e<6trB:}[OpDžogvО{i?ڽO'pP)21f2v* ]̼o؏їUtgBX4%w$73$&Gfm, ϱM!WAM>=G!l0ljE(`{vi!{xcS8AQz6\z_Lw Mk彿{5"#J ʼ "BHw<&|%_ r|.=;?s6cv/x'&$lӚ&/?iXqVt $bVu!/E"pRSo,)oɭ7a4\ѯp@fG!=0 ^ۡJ0q㊤+t.S+g}#@̙[u95ZFTIB<LyS-ܜ%i`6Ne_I(SaMs.h,?BBLaX WnS&hUzMRR aFV,uѶeU,_抺!ϤܼyӓA,(_}?RNAl1"GIqES,C^#@鏬]+Hь!lC_]YBoi;jNE9L:!9+G LZblrnnJ07ċ [ǣ_˺U^8Jj׬+&! QDK\p3"“LU G«!ړbVrnlXO9]1;ͩTltwaPU'wCZA @1f npSM?hv4fPܔ6%JrS3§{-i%,E~v%Fru׭|}p+'-WZ@(g:cyZ.6`{넵܉#hKc=/y3~.kTK׫ڹ z96mZσѽYjװitkW!ܡMiH&Ɇ#tSqz~_n4 @3F~ )ךelcilj|db/D]FlJMXp)48@`n.ERӜk ;ۃXtΉmY8XW7}i+mF(Rų}VQ˺ Lv*ͼ %Npr'zK&zAƝeJ<. )Nޅ(蔁?`՛w1{ӲM^cM1Q_Ó̠ Eo^hk:2JMdv ) =[jk.]b^s=e(3O$0# BM+*yKg|h*aT%3(6+^J,}@H[[_abzQǐJ//`9ˏOUcIϛ:G& ,|b\[Oε4Nv~OuB>ݔ&=t?1(9)X3|fgte['2\X R<4nTB˾+]nr|x,pLVԕw5 L5rxRIFWJ Y6Ͻt30 il-6RvX)Pp3uh\uPts&51)ܭĀ*#sIgp/}ݵʡUg%Gp;-|48*%HEcW¤lg2arfNVY3 W>G*P̿5ۖ tJsT8kùЫ<,f &ŶѕL|IQemtmY3F)ȑ8ay\U4z~TW584X-FV`^ya(zk?}휦h.k @qU#2c8Yꦖ:l&(\h-s.*r9>9SaPGvb\/> 'Z:[%3RI U{sCz{_/DdNSRVKv5u=t4w] P"UTEK/t#`tiúE !P7g*G:Ɗ`{BC'TwP J7" Y`:l0XEހ)Z (yCnc8>X}Oz浅XW{2,Ա!oSXL%Vid 2rdËo)yk8&t%Cz^72ŌL+:RU!T: 3Dfb)O! drsP]MMԜX@ >•k2XT{_t@_[ kbtE*)2Z;n=|Л_?̷RM?֏T@U55@yV}3-K )4\8VB8 ,7A =O}SS_~d.ӫ*,)^on\A/([RRFeD )Lf[k# SDd"EyB7k #pLP6GRghh5숙 a&c$"#a[G樍8߰0§Wjfќ卓ӉH/hHfб\s>iXd_"s2N?Ux4lA_ߴbo׀\N -QkaӟܦjJ0̉Ƃ<8Ljv LuHc~N? RŁwVLQl̺E}%M{-se ks!`[fw&d ~xإMgm'Twx1ũ>;Uh{ƺ~a?fkcDpN4U,XZ6Hfȫ9B }\ukSOd#yyqrRAC- ۇQ~PD>_[jy (zJ5rq=^ }](Qgr72˄U*s]97aT|kgh fĹӘC Pg1=Gbf Ph՞J1AfflR忙ib4szbI!FѵJr2%;p6S[ F~9])AA?3JĽ9&޽O"q K#xY(dM2ĵ 7vtT^uEjp|0'pHȓ*`b <_ˋ~7})T_ȣ >-.I},]|&t)dV2O *;RT0ݩpe\/^tc ;2kҮGG 7*dIA|8d悫CuY*? MTr0%٩OT~ -&80Q RMv,diXlc6]]#؟~(knek6saLŕ콭1p}L@ u=Vpz58;N?i?Kmxi>Q-hE jȷڔQD Qcͨ5LW?,ϸ\">)dl6 *ue#~¬H .(H3f:\Zz0az (桅Cl RUoxeU"IDKg75 DYqq}vsfO 3({u\{4XM~"_k#!K4E Lj-ho8^lqCDǭ,(7_TQ$m?+]"KBD}T z u攌c\EG@*B o:X-K #iJxBjt'jQ~wƒC=aL*œ~w>` CWpOe, r@&:TYG RKORNp+M:`+x%9oe:/7x%28ꀭ <#Qz6-t4m8`k1%hPfqaBVb7S'9HA`%urq=X 1(ʣHp= ! tdSQpht[bt'K";m DDri)u)-n V-]m(v]Č{L!˥SBȈjN3c xٻ=iPvCuD5&\ ԐV~Cx${`.4b6QYs]N -Q#J0 ;Ks865+6FȊQG+aes ->ɦkq7"L" qÆ@Z[9)u(Y{v svZ m>`/SV:2&HLЇP|nkH:5\nʨQ4tnǤT*kLwRv갽nAH;[(G_(K  Nx^s>Q aO1ֿ$SA&nW, "kFO&z.a8>_$_CBJw55iEbt*bݛATPcklz &+])nF>]ӎS'Ĵ͏)e>Fjt?zU #= ,Dc<zJ^0aA>3M=/!~*\Op LMs9!d䮛rxeGc&pVm1#ί VYx6Y2B[yueʧNԿ+[/HIS*?gN69SSvEr$}{RIPRt]pY A"J.kr5qTGNz:`m쬱]<.70Zğ6Kӹ7h( C+C;`Pj#2ۅ Y$BӼ=2q]|"ܭ@>#Qx%0Eخ 1`хZxU +&Y`YSe|dQ+>սE 5/ jFN DG "3ZNibZEB˗ViEm֚O5]>b:O6?k'uyc$5 v{+`>Jt} D{RIdb6z`ug߷PW ΀yzR?ktV<)JNo~9OJ_SiqM}8Já?UYw!)OI0I0©3\~YwtA_UW";Hz9d'92S qP@ 3^=9$tnLs+rO>6/eV8sTM=k\9+pڗ/() OET}uoMsuD8Uބ(䔤}MJ1bm]Kעme0yeͨ}@U4d.o#/[8/@wwQt=(qCRmĘzE"o,Ȫl![v|]D..ИﰠaEG%xӈfPiM6U(ih5ggQUko#?)51"6Hf pMS> >1bIc;ӱ%j?_lT9lT#?JR}!?2PDb')@^,\\@DOQj(:%fHA^T(Y_xZv[s#L_X*I៥'j{$ȬzYBE01]!#zf)Y;Yd/j**R}PT걽Gv04Bl /LM~XW.ƦC*k%~%}uFK9O0BdRWBy=TI)#N3uFjG~ |~]/<8Րu,rW [e!\:7=8V->E>,q> {fcc}g?zxlNC0SP5ױZ/#&Se/&Ĵp- ~ Iw"x]H훶^ x^ ۫ya~`93`NH {cekH2]Qϐ݁ozi脔 f!?.vc-&‰OjߦL^0z92TA@[6פOng ,btmiJFǶIgGsfRo~#$܂AڴRt- WCNdq8SՌ^{({%֢h]hme͹@nyɛ$I'0dxʨmO H~U?t[ /ĭh 85X'GzہCRlgl46K4rp+bS\]VrI 4>J2@is5bC)GGH3C1 Aa pA]ߦTWS9b>{i=0?|!3.޾9B'\ķ4UvAH57t)>HOsGc>tj &UG#P9X7KPAsxLRmvcmըa,VU!t<[ud#'h cls{IdF~ eZ znZnB)X[=pv5UQ% P"lNzԢ,N vIAd( GPo9%`I5CuOU_=JF뤝QciE`)]Wy2kѿ(ty(?_o*»JɇoeἑM"T$цW j*YrB`T;khC'w1u&0J}eE[ ,|?ƾP2xu#d^P)L]&?i1H?1١N.M^;[u\z%sTj8U&ҠV(q $?ivV ])oMF]?2 Lnؐ|v}$fe U]/y:+Ofʠ4C+27]q<?uh*ɍSܟ3o¹V wJiMV.@gM`o/IUUZZÏ gq鮝&:O$_94~,񡷆a@fFPC)saʹff>-9Md WVaD!xa3˖xxs)}6P0_PoeoD;;mBF=u9g# khpaGM0i0 M;>jBjtɓѪa=Ђ z8Iau"kfe1c+(76В D? >ܡT*环yg:C$>dZGץi%9 @ð ~ !,ACڸ>%©Jp GHVNJ"Sj6Г-B7+h@-Qv~=;68Ŀq%(Xe^ɨ.ύ;Emܕ<=(-^{++eEhM!6 P\h{7$Ը H=boFDRs զ W{z-BPؿɘ{L|-{1oenU=[qaQ>zKwb'pEO6+`n0z"䣡Z. 1>/ PX8RlmE[M3J;MaS:XE$~cxI%[&s+aNL |?vbgD9h䤂tߦ]!(͠:C-wRB^ #DDY/pF̳9/ S<xQ($Oe,vu4\z9@ϿoBVKDVf wNaġэm[-…8 b~ :vr9a͠~cl[1fܞn pyvGS#NTʒbI"qށO^gy8as{G(h>_,ڒ9AeٙZQnnxU3g e/ Hj28IQ,{xrT-ژ¿WdNg|3RWǻv$S(]?) VZ1XTZ| 룵w>ft^Y JyMe>=fc\R6-i3~;N}x S.O^@Q6Rhv*s\.@g~㤘)^𠫨׻|ۺɡNt;wRpv>w_7{Ļ1s `*NP|<ТT"Z;Rb]l8-Zr 1"|[c^r:B$կɐ`Ge.] 8r`ɺttO?B`\G{oA`#6޾ZMzab1@ !F"fk>ˑ=#mcߴhj׵5'V-*tǂur g{/P*n5Hldg0忴$]]Ԭ}W'#*_cfI1[Wp+FsDѤ,_~V~%HhOL"OR?ŵucifiAcf5–<] LC +3<ª ߫YiWUB8;%ϡNdϪVL$e6m0ŐA8)<fӁ}^U|O)ZTD$]Zy6ŷIJ5 `|v8Ly$6{" 7U^X/6$q;{Ax3 ׬o؉yo1i,\2vUs5ـ8DlZ3Hu'a_?-&43xu$Һo֫K2)E_6rCjٚ?NcjS'$011yw; 2nDKf2269ȃkS!JhneH.}CQjB`f쓇i} mFՎ9:9p%SGqB5#{.Т ]jF9|ޕ˳z.pCԸb>8dN?}^vp@#wY n|,F1v{Hã'Pt!V wC'(@ 3*(yPz*,¤h(g{=#=4m>ܧ t`H M*<`лUFS+q%8x:S֥N- Z9ɩ >̉{%yj;@3zmUtjw ^} (sP5ytsah"Y?ꂙOk=%Wsqձu-'wF B׭r5D5J'N\$]{j[S6}7 svrͩV/_!1苟,t4 u&`Y[bg(IC$eifF--6,m{y0QJ`zNpZH:013Wla?1wd/$Ь %7/5Vbd6iY˿B#5S;R%k8sR.O#]P$nˠ@O `,N?SC6ƚ:~+7r׵{eB//4;ya6Nʵ\E![|=qp;K3FDNWðрkeR  K[rK PNC Ov %Ԇb'Ub vgm)˨EVުQ+2FF2v'( ;B ,e>F(;w"wP?{[E?e3CNK{^.޾> mR8ma*>}.:8B9ъ=-g\xkxI^Kx+s!POα|Gho& 49JUo\mi23ƪ_ͫ~- --̙'ܟi_~eW[.s7-U7 ekm\鍎aR{u/9_$eƒ^y>¤cΡX6 !tH3ʎG1O_1g!_Ȝܦ7)(W_Q*Bq L-|?f⏃ -|})s. gceXGh[ /7Q4JN%:*+ <$is=pD6C!VnŠmaqx#y H(l3+IL>b=MW0PʩMM. l'wK̒GXcb'}"Q-2yU`[R YSIup6}[b 9؏*ұ=]>҃) ,ODNs|1&^NCZ*JV{a()@j&˸KƎT~_ m+ Yқ׆a~F^) 2ȵH m>7_ovd0z7IeߛEi#^8;Œ~ϘyVRa~s1wYE_4{4e#5rj{Q1lnp%f %>ٌnziOmV$FoNaYrrouy>KX Sc& O FؘA*0g_s9og{4Gą$Nkv[(BϲUyxkF&+΂m ǼCM9y@ ewF).X9DE.Eoȅ,0(yJ<Fd;4˙=R?2#Ŋju( $xeɗ`Okd<[ry?V?w>h+ rx܎8qF0>@ҳVX$ ou۹EnvOQJx]w5mX)J}&#~ÛMk/"&-Yd%ᗎή_;!40)3[u a#D63{]ff2G[żJcH 剖 bB$Yd[aqh_^h+7^jKzBUӘs.hVԫ{,fF 4b=芾X4c[-KD, PqY(R ~= J;PPD(~*xAliXz5-fw&<Ot֋$R:c? ܇րE`m ae}樕B+UF!Ī5ZoEF[gc%5,Q%з5\!*ņ\7Wuglă0CAkHfJՖK?4w_oGT ڔ?=3Ja7v V,Co3$h(ܻ.B93o)I=؛x4 Cr_xgK$4eCRO; Xz0M CdT'`{r_Hgj5>Z呍$R!|Ww NW g,7_ι3ߵk/̭E/aތ/x^ZPМi!!0֯E 75Az'vݾCDMۈYJ&uËiĻ?eb~B3 Dotޛ3w lbUS #"]u|ّ?yv";{p6c95M }fb& r "T(n-a)l VjLJ>VstKyQBs l v #$`7@ &E>DxHmQR’ ϛ#Q*}?&#ydBK) !l&|a/nl ~ԿZ^R:4zZ4LSwK:G1rRULhJ{1gAof6Xugc?&[(jF Y"Dk&ZQ[HIAE*e}WgzOzf@{S2E9S>F@f[&:) ^O}:4>c\L)="_Vy 6ͷ..n: Ѧ!,a;\sH*O)M{ii&i +FDlar sw#yMlYS_V\ T1dWoI^tGK1۾)ma _0(`\(0Ț}f Tg q|cA:^Ј8;OΠB'0|EC>%gSjI1I$%({ie:|6&fJuciqiF di*s7ՂNtIA>36Yg;G$vS 5kux0&dE."yf8">F6 MAa^FuXkq4=ۉ;&*fx;F2hJ ZZhN)| (? "9ږӌvVHR o:ri4=$c]ü\LoѮ3.MӷB2l=R"/|Ǚz6 hs;3Lm^\ݧsp̒G9Xhn1>8?Aw\H^JLQ4:tł}u7*CeȊp2sy.=oȐސg_Ng-_J<<gA$CtcQpeMlZ 3(HgX+(z"5Vj P"+|; bC1CNa.*w(` z7+5W{W*|[FoZ36oFc*jQ C0e9?2q٩oO@: Bǚ3 C,R7?uM,3(P:ixȳ޳\GAnãu,A їϵ+"ER#Lwסnlaӯqd$]U ZzQp m| Ԯ?dQ$A>|}Jh$ʘ4׫NU %ϱNO Z4tQ5*|Aksqy tM7PӜ<p_yUU˧ku+NV*YHĠ/Nm6uLf)\lϣ}_Nsdž6!3S90Y AI)^"Xxއ2z,x(#7x=cHr֍WOFBy cH%!&F[hXYg2I ώo!(3' GѢWQx0Wy.kV%uGZP81&=9˪qW2|*NlL.vt{&U/qvLq2#H4MJTDg=xGRJ7!P.@U*s^yau`ݛQhC뽬7A[4OS3Sv ̢3SsGɐ"xOK m^.hpRhi [;X[{񤡗c ?hF‘֭f~6!mG9Lɯ,ɨg锁ʅ&L4"rlL!1%ᄱ-9 ebx;CH+qڙ`NwO9xjPUD瘉s/I?In_pej{XZhU]6"%B[6rw::u[Xt̫QGSa8Dqr@Z!5ax)2V=7\"C|lxKiJT<Ҭcj_0C BʬW0l'Y(.E/Qd.'~G'cNmӢGAjKP,܊GMĘ=:,4h9ZE cQad<&W ˜F+h}FIO>BjH_RFf[,!_7 8_ ˴*x+WiZ6ev2bej",~Ie?50J|ClŦ\*M-MQ Z M 1&+yx< 1 թ }dYmfzỰ֬r>D7Ba78"bJ\1go9d8:Ԡ@{-vfkK5|M ?8 ߃~qWV8UD٭ucZمVD_7ihE )ϾYH(pi04[5"=8\~3"&9BGɢ3[NSI /yߦܱzW2ptLL{K"K[[# i$b3R HߣZG%Q m'Gi-inr~2DcwLS}!rt]|_]%Q\7p#62;B 8 쒾2oc4xixubW!.xjJym7B_c*!1.`#fb4P=[8ZBКFTB%ew 0[c:6rT, v4A~88^% L>W)n'ܤ"o< s߲Ci+3 ]c EHxFp^;Û2,MQDX}gLʗ_q(=f,ß@ s%/>H܍FK?]@][s۪8B%rK` f|:w6_3ω[Py#_pAJۇbfaQ~̆IQXjZZѺu+ #N[`qۥ͝?lOp tN6?]jyf%>iUCuc8CxSU:5}rSjH$_فBɼ!HzSL7|}t 65EfCW8q(Ab560| 7%ZeKq{ؾ.էU7[2srnn|"H݄ՊMY'ˣzju.Hx|6ۥI LC#%z@8td2 E?#G+fy KMp>Ė刷 {&))NN%T0MEJC݉;> n4ݹM#ޞ=o_ǘ@v*v "͈Ӆc^;yڧsx-g򩯼@WU 3<0j9)9 ;v41%ӬښE`6KU]p(YT0fփ-85bX*s); tkb{bN_JW$$S] Y1dgiqhgՆ@ϴLa9W\qG54A)x6Z`0 "jl_Ԥ{ݱ/G_֏ZJ"krb [ L,#*AixƳ(kΕqZTP `ќn=ߤ+`]9l+C!a>4Y"Wl#;H]*I; NK}3a7DN\VBɻP)fcoi)ExVBQ,Mu͉9xIGČ #x$#?#l-(S ܇d` nu~ coW!E$NpUrL;zr;;%9Dm.cQAr0Y]v=Nay5Wy^UL R]1.y=>u79!$XV]R03PK 7&0wjŠ|U4TѥZ1{dGVs'c**ڧt69#G_"IJOasC?KKٷkJt=(CǙGҩ.&,;~T`pيM|X=~j>1pGP"o! rPyjS9᝗o7؊Pq])rWtm< x[r-b2W ܚ.S" Fu4#S^ {ɷO~X:ŷ.5Ny6~xB< qqNt6X=s]5Op!-Z4SDrĨu~lʀǃaC(o @pOtҌ&Ba!1&za4"v8Js6KhIdU@:FgKtbK .kFcådbmxunŒv/񺉽.m.:<iݑ0goHa6/ BO^2Qppu'zzh>ڟ셠p=:ӖyCMWk @ABh3RcR '܍8:^m~Z{{ OmA9+fr}~<9ZtK+aՐw]<Ζ҈S&O7o-Ys:8*60tcN7(A&iϬ2)X>}Cپ% Y.+_>v\j9ћ3 /wdNŘL-}" itE̗\cۤCow+:RT D|i''Қu%r~+571![AlAxmF0t4f ,ȆDfR^ra>?'NJ9/:SI\ W^3CYR`k.XCOgށ`EL"Ƿخ}*ʕd') UfNd'kB5Ҷ0>˞|r{ziFJ1SoCJgr_&k7T(ȋiZ@YV6|*FT`Or!~.WXVgt2FW}s "+`ֿI3ûyN\8jÅRf.Qy  d`HLp#1x5Z5,3&I" ^{E*O lxxiAar#a8c`EMLh`M|N,v~b8]EsI)[:f '/cR0@ނִ1>@Bfb:K0L8fg s^^.gu6&@zl'?U n}86+aV:A1K[ňłum'eD[5.HUf::_Q+Z۔%J,Sɇhj(sO5c;{ G*cO CeݿSUw~h+27T{PZ,խn$q+(e>vђDϳibk懕&h3lX46FSGhˮyAS;K,hR-͈8YXK;-c0MwܦU$ fQ4UЃUZ|8<ǁ+_ 2Vo2JUˎ tt ) qy;S3[qDu _&mTV$ƯULB;ՃX/H>33{Ab&OZ /5Y~Ef b:_E>+(*ruVoynU9g܍y%WtSI Y5v3CϏSxV'B5ĸkqrU0C4p>tԓ/v*W׷E].F&"+."J8ra| z%>[B!gS{J&Z<[O^CC% AEC-?V}ib=-L {^ ( =YnQ/=Jx֦gwNEbJ ݟT!u(3s2ԓvEI_4qAsޝw-RS-g)ɖt_⟂SJ]gNӹ=rMMLƜFg=@ڊsy' { Lk3n lu ^1be9M 4dJDy\e/E]-.ܒ*2Cw:xG\ie87vA~!-I! :yjZX!5oM g>v n_I \W?[PSʎmX;iBΨ,boHȜ,Đw8'-~;Al7OE d)Kl.{M^.jжEny NEsgh>΅mvL YH_[4|N{dyqOR&;*4FjMgA !y,ܛ#nSn$[?]JzVS" ?T rQq.K):nےǫҲe8Yǫn!X=I"ɐO(˳~|3Ҭ}}&뺢旇ϽT5- R:-n ϫ¬4}*5qxewh ~(gRu6vwxBA<>3z%8`?&t&|eQ7ܩ.2=9aBPҳʭB 'y [@ ְF?@ّ^c(os춼߷SF87OlŇZ/F2ޓYq<nQVܭTXr?H ?ȡZBG/g(_'j~h=5w2(6c&-$|!ElR,zLԠ٢G |߬کvd@,,;&(g͂ЖtHb k؇;AWk]81ۯCDQ2]Ksڭb_ ̞O1j8sHyᮣ-eYuX?/?RRߐ26i̙g{dm3zσ󝋟x_}u⑖J+At\/%"8|~a[IL?b5+ڃw-ۡ: ItaOߎ̖_KD6VoN<~|Ջ8CI>@XP#&)Cq 9Qw+Hud"* qbz̄N%O=rk$~ ?{GF"*=RsDzf%G_btguQJ:МV2ĚyZq[$$MrVDHJc2p' N"ӽ >r|$eeaA>@dXWlˏ;>cR"9q1~KuBNVX8tV`i=̤~>!y+%^Aݙ ٍA$oeW |yî^w#?]C~ Jnmgb oCT` D*X"@g&('A*1A,aJx P9 Ɲ [H"(>*s/(:=;?%뒂MN,QSe[vXkI ]hY ֻi!Fײ^ɋy ;'@#]0Eҳ~I 3ƪOP\_͗8 qzJ齱: c[Hckasx*cuE7@ç%Nz #r5x," J 4_1M&1xGsbk  /;#*` Q mkFi6kG F(= 9@!(ISL]Qv M7(@f}F+Z5:.9ߍ8l^ P/mʂQh;jmZۉobu(1G?C )c9{kf!~K%́rr+g4-rӇz`+ r3GE_k*FH|xZo ڒtqr%ԚV}#HgP聙%k`oMLe kQI\^u)jYo`hSz׻clS |xq>jH~|Tj ށPp|Ơk2`}<-ls!֐i\]#*av)P0_Bx&򝹘<Ѐu_q/.Ɉ)!YYz4n=>#qo]|u8^!s(ގ};YF1S6ݾҼ!Gj##ԒW8Gk_ aUM!FB3.^^k:H^}/<7yczx*O@bv+Ռa8lFڷc92!ݫ \١LmKddM6@rL4Ӳ71F!;T"`JS'pR3j O As>'!>NJ12 ~gn}"{5*GJw<Zaš3EFVFfs`IS}a6x/n>( :9P =+SV*r;];o bFmHsTVTq'v;(6qϞ/_ HŒx"Ъ 6-dkR_ꌡ*||(})ox(2XxxU7ZW鋘Inm/Ys%Vt싦@0^I܀{QFV"~ ~Dn6VkOCÉ3Ch410Jվ)&_k"Kxz }y%Uq% FVO_FW;*nV[kJ+vE{f D.UIfЎv1'#Cqx,3 id!?]>HL=ݼy$`:(J$^HfG8AJz٬0T%*5Ċ !%:˔!U='ً2}fN H5 O:[~+}N7s+/i>bZբm@OBB{R}K,/L g͸⡗KJ5p4ٍ%9L\@A`Ȑ<$x*lKL#?5:iꔝ_ Wӵ5̂]is/G]dx(2u`=֮/R} \uP*barv?:@S"˪^WBcPC>peEK&mY[s`*Stzx2f#@섲A`g,{{``:_ʋ<0ݚ7p̗MkfvIQlg Gr5<Ϩ~77Vc̟:d˧+ŴU?iKIjv ~ ˮ-&c.p0|:a5fq>/y#G9ߎE=T}c坟q&TJ4gݟI~*l }}CV]h?BdZ.7dj<Lӑ>'i=„1ƃ+;U +PwMRkPdYFD ?}*#ޑF<}\ @"J؞G )JVCDRˮ 4_^yrbL^dW+ylDeAՃ5y fT<:wzV2jL ߓB445+(qUC$ 7J{ tŤQ ?@^;#HBT{lP\ B*Y<βod *U[xK(!,߸lK{9CNq{/@8 7Jle@Z~n'`!(T?0=~S {=ҰD"(gH{c*8VrKUu`7mhF_;JnxF2\˭irw9dEh5ۈ9<gIG :m5!~X,WU]>2vELZT&f/ GgifG==p 2-,0^и/62:K3(+cUzRK]9.PT,VPגأ{fpH%)>\NTUsێ jfVЄm7[/Nzw #O; zU(`BV:WZ]Y+m+Tƛ>3ǴkC.toxTP~-RxW5Ov3<꜉EͻG '>{vM)q}4YCvQ;<s}ZM=xO%L|ݏF!t+rq8Kèo:0-EuAW?r em:h*vD #8DnO;p-YI+~kj4~ZkxA ?&V޵09 BB J1uj `Oghee5GZ:LtIt"cJ`@$v[/nn-!=$:*yiFF7nfpTB4``;G4|rE"^WH7@ IM=m6n@狠ұa!),MEzڼ .AkC P\ "cv6mP]މ;[S3kuU_j2zǜc,9kh^evbK@#WE;%x7lmmkM{P#؃$ yҼ0O,=.g-v~i[[ 5 kpaM$b!}͂]k|d1lu)T|wC:nbB㠼䄚K9hﱤ-:0Tm1=Kj}?uimg9*~|7[ʾo&_ql{}boe%fr{0%Ja|Ӌ _q;`3)ߍU^}rٻ.8cyR[U!)/j6v7+ Y$ZF#N52#) DK Qգ IɚલC'Mmsn7oN0̸[c:v$[ M]ڎ(N؎9alqePe9Yɟz`}=ёpS3TXohɝ$mݞFs/oGJLW9Él2Si(Q+]Skdxfd8ollo;g#@YI4h8bᩴTޙ$ #ҚunPj㌬!Axe EDա$*-ֳXӫ |(IZ@ ad& Kʆ?}˕pP16~:g}]`:mnw3t촥uS]sY z>;ʇ+4嬒Q[LhC-4a(Vʭ0`c5F\onwQ7<\S,+LJݪ~RdʃUQm(X1z׍{/|Dž2# zm_稞LpkN}e /gƟY{.hlR(3&[|_NȏKiCh*K>@ y8mϟt,ТecFK6 vk_S-!I)q˹+;:?yVXeZ4MUEs?cI:Bզn=#_9th4|fc koej+ϭ@VhghjЭ&d*(_ce^r1Pgx"Z4=zg8=:I04Cʆ6O_وr$ 49Nm8LIGi+lJ1ϔ]5W^alcbֹ:'bHD`X/lJ/C-q| ܔf{8b9uJRv/BO?oZvՐÒZ%3A)1X\]`Y鄿z\(Lß xR^F^ƒlPt"bPT")^ 2^TYe -@3S:g-1s͆o0xF(BV0gx&"]”]?ˡ,}<Ԏ匈G{Cs'O?p (` ZVQw@*@y~S mP~%MO?٠Q+lb i %+mbVe.ܚ)7LAm;gSr a  Vg) o,n8Y[ ư2UzVx5TS(lctLi>:?;TΤ=(eZЍWZ6[.lIWŸSÖHDc'ao@"EoZ7.W7ކH} m{ ?] 0ML2T5Đ*BR/\kKvBN}}_;:@'cS:ʳV}"u"GH)`l_vǔn8IDuuAsc؅%*r&`P$L~ۡc`F0H"tt@uHf;t$Yݭ2V."(j'ѿGUf\LfM} P+^=r&ED}p1̹b">"$UF  /v#pI?daݝ'C rBtɤV {2riB›8f"˒69P 5/\㰽7PT睟Ȫ9}o)2ttiU2(n4љD垮 }C],IT*뉈>I38mQSCʈ|RF܏Bn#K H.Sxw8aD< f 8K`Bװ`L݄?`{dJi~TI\$cT=e (َi_ qt~TfdX7zHڒ]U8F-mtnC08OR^H ʙM=i[Zrn ]z&|Kufw%Qxj֔9֥ݚVt#Y,$["gIW]e ) zޟ=^1l]Qu7Q >*ߚ={;W4R[" :~KPU]oiٟ;p6@5I_랭eaSx7dzqu=^!p}4cܢw2@;1g<1bcI=U:I )NLΎ4ݝ!zMy,wd g}٠|Z`M /.D?5Xh#Ha]7"3;adZޓ,&'U)H-40خz;Vyq7myGmUN7M`VlڃMb^Xu;&k I{Hʣ0fwonxp0z/dK|c@usYB*,;Iz]fgq~k,Pq]QGngzTbMpѭGܳԒ"|ھ"hR |ƽ?GG5Ur+o nrDEhӘ{; 5Xb8 ;[X'A~EfA bgV Y=aW( Q8fZL 8+K;[`~5q_ĒroQ5(Pr }1octBֶjGvn]Y,i{=w*%QhQ.IX⑔iDyG7vis&ȓu?~O C徾HZ֞} ,'} ^&x6*FL9MŇ4%CY2lbol,1V0-"kc$NwCTsF *~Mm5]x{nkE 0W!O.6&=gFSr' IaVҢd"zC)oxvhq~5H➭_d[Jԝ=%+ $92aLN1&OZ8(ьaw+41]Wk[nI=gN" z=ֳ͊"Q?9 -]7hW}$GG[i0SEn6xDFLRE;X ?].oxu+4V`A0̴@CJ 8w|!Chpl MR=#Cuk3daF09C(DW,(IdܼHx= r3I]_'5y&oU:}F=a{tZOi#wQ"R pٕ-GT}xDZԤ(qڲώp i`م'\[cn,;SYbo*$c٫"GaM1+B]5mO?t^[r.)VU[ ~2-<=^noSa@^^s{,Og͸@4FXY>.6!Gb3tE0ZuNq L:)VGjboC*,zQr0LgKݩu68'QBw,z3xx+t;膒= Gq i3Y*ě,!H`o(I2e] ?y>M~-r,le*O=(酃bx83ʲ; m&mZN:땉r@~+D[ՖCҏ1,bq0eSp@wH&qlji%pG)uhڒa~Pb@#;4K`z!Q2  MB yob=&ls]nEx"<4m54 kBpeVt/5adDj$b|-HO:>l?eβ]*l .Ԛ@@ ![6>| iFrm5A;sjW"諦.yvM 0!LU*$M!"iá<:ɓMPmCs^'t :؄q޸7?2X$NzTzI^#ੱL3N8`ʅŔ3`p4IݪI^ڙF.H=DCA/a D>n[%Ŷ H6e4FCM`F@c T37!Ow] oDw5ſ">lMx6)(YW[_S%m7jcly&e0V AuO40B@m&.Xߌr.!MԋO=~ y%ok4\Li!ܭfY)AmxTM\ri#,rA P:N ݰm"XB !s03o 1]f u ΃u㟕h[ YX nPp+4av3>l4nl:{1k2=w|_ }Њρ0_7R 2mW _KBesJ4~$WP k U߷9STN-la܂drf+{q*h5H@I)>4]CE_[-ʜ_d$Eb:'qHKȐvY.j}= zO:䬊VsV C)qu67 CF_) #u\O~-k1K~L| 6}$(:I`ޑ? zjiTsC h:훆yM.iYLkf=$R|DI !+"..zz7YVjb= 4u<|K3uE5!T3ZKrdߔX(L}_b$4Ũ˜ hpEזH0GHy"բD@оOøPґ!.;sK8qm+Ύޢ$0P=^ i7&|\qtnlGishsCyG1ֆD_tE0 ~MM ,%,쳌E Z&Z#!!#>ʲuEO b'cߞ`6L\"Ll/xnwB&y$dd)>+CXZG_XRPrν+z)Bh5ztݪnjҿ7qө^?8DAmN t8؄|cM3)ǿ'0hj4sQ>V< -bQa<Ě޲<hԨY$>D-ftjgx;{M"s+~D?/3 V\gD7?p=1,+Ħ#2v :y sX4n{<;6bYJdr@۲){+ 4}\TS^R>wEg-Mpj.UblmAJUޘhߵzA0+SMk+ɱsGc '6 w4l*xMZ-ngV=wm4XqvVUr(8:9u5P?*ޓֺ0as}3Sܜtk|axs(yG<Ũ^e{4fOai8/$i=q68oB)rf0R&w~9j%,VUL8 iwhw CyKt" X!?j>EYF]7@ouZ:e/PJ]±H'zylHV!ixrԃݫ#5 ApYJ쫆>Q$Mx,<evug7w3ڸm(P?mg9xB3X^XIYuƒ4ttz흿J$C5` | vbO4\su: Ӯ.K,FGFb*>4 "1p{[eִXR~Wp Uf(ysjxʊ̑?lm|rD^<`d t,K1*YDrSJukqax_tw]ϚuXLH-:ۍ[(Ua}猲'0Yq#w,_j] ୳i[rյ4Tu;@ȒAA.Iޕ| @,;h*%#ITc*{z-&m%Aw]僣3/o@t#I_m;@O_9EB"x7GR5WG.F. $8F9+O)Fp;2qGQv/6{CU"F*y+N?XFHBO!b0hDu{]ئ!-!G]?bFRY(M+Z ̙=(8E*$pV7׎u^u5mH.>%ab]=wZoV6ң4IU(XHczdWV!zspfD@a턪³WdAK\.o!"̋JMս@@1Dd8Q JpV>f-Ӻ0_3ALՙD$;u]>g 1c C }kfAi.HcX)n&G8p "fZTkGJn~ β hJHc@$BA263G1jl'ϾIkm}epBBam PS#XEXτ<#hAdunKjۓ ΙL߶M?ҼnvK* 1K9G~'TGyܧ8#I[ܢMsS3*Wm[PFsF #xϝ՟`vo^rCz/6t *:SKcrg|mJI0QOQ*l{`1Z9',=vi,p@s/S3_UC]7ғb LDb;?nYxjZZ.-v匛Mv4IF|cz )646\h۲CU_G/ԍMb2r}eFy .G,,q*dϸ.j X /m3`pwؒfU7CzssdiKPfL5L?\^`b~D;I5*Jʧ8~ fL[@^ope]E`ʼngӽqb.ń#-#:f*|1Yu;*+Agh^8viA#f'CWsלrQ"=]u4W Dd:fa]62 ;aDXm$`vij=FG*ɘ,/B1O9'o3dQ*o>?r,OVxS?b5uP\r?Ӆh1!5ex䩨Xr0m. ؽ:ClBp䬥t lVbJgj='he^AI`ճGg_B3y#d֥,@5g\z Ǔf RGb(4Dƞ'SBFPfYW ~QəA[uVH|t#~Sy b+Vʷr6@)(E@)〓} V~>x3(?.m$9mLyiKq%cwx?C;e9sQOF=kbN}W0jO`xݺ uZ>Js>UeJ-1gRt4;KK\iո-/=1TnA'Rsx+!WB3U.*_*^>-q7Bap5>"h򴓇M虦2*]/=Tݧg-I[ (uGSINe F$3{ jiKNbDY 'Q~-Y?#!K){{QcfC9#fձ/2:2}f߹/G';hL,1Q "+ gPJNzG:(FcC=eڕc 3^ ,5cCX;gl\}3ͮ8@ygd+Ą@-e9KO4ful8k"F$ 1Ĵe82c3q^[(E\5bB5&ݜÉ#fEm.pZ oo]&X{7|_g%ldV%17(NǚEh5/ķY,d"2o S͑8zWdKv†NQJe>YKX{dS.7!$~3?`;"alԬaIj XjPI NWm|6X@ Q'z& 3Fҁ ܞ@CV +[}ؕ5QOCöF2]G0oFh$~ЪL&# {Y4%6昸 ࠌϣvF`xAYkPV*t+? PE>p(F(I4Db-FFAU`h !W|:K_G)_tn,kc!3^Dw zW,!uD q/R <dzG(_*)Ar(%CH-nC.We]Z01(יv8(z =@:ҀN; ],B VioYj,י`”)hW#;rPЁ p r@;).w$do*eTKqPub'gؿޜRu'$KEg0b]4t-IIK'k.N>{svD ,^ quR]T/qdͽyMeuxY,;j^vjKx֌G'PbǮ0.{jzZҶ[#md;ظ{<2zlJU_,hy_a˚PN fvuMw1zjǥ@6>,gi,mBYi "){9̓Kwa<36sR%2c$+$?AS F56q{t3;+챦 h/_S'{F3 Rb @!s,1i.ҳHDd/Sn!̒X`S8PI`QŶIDXG_Ș@.mKN pvfF5Md9@ >WQ}\?KL;/CG_onQJ]/LVD8\.YQq:[F ?33r*vg%i{rp'VԒL O ȅ2YwX"\um4^Ͼy9ypr:F}nU)X7ToCi8yPLˊ'[TE$E?]#v< O㭽jB|l)u.E™a/ة1D`UN͡ T*r,GŠ;qپ*7/UZX|da* M%[z6+ GD Y"(tRx ą*ҟê:k $#6jmט b#bX4ͱL2?-)ㇳ@{k1cszI!ڮˋ*Z8;c;[ B,Vq/|ԄuX}̠sWa ]rz/ HgVî+y0.Dx#K"g#|z$Mj6K88a>aq>|kd}A6EFH,Nk'g{}HAIzu_zۊsa$$< {ggR!]|l] i-;絉#H뷨bj 0CY5£VլpYvn*CRLtCV,۝cKTV"h4&aܾY.' Qf>i#aL URs 42.xT8%V(*.0l.L<%j%/kvc_x\T&Tt(w>#;m(\Gmcܫl10$T{`8NJYZ7+;RfMv@mbVA[`i+$3,q3LO\=ιToDtz8eh(Dy~I]bT>X(!lz0fN)֟R\A)::>ZLWe8]21+HYLR 5ljWl͟-0[z 430A?Df)+9@l9hHKrC!2uzPt*od?S8?"'kEUY wb3bݶRDjngTiLsU+y":*ÇD,D@4GB3@'Bw3|aKb?ms]ZU2r@opH h1XNgGo`5Szd0* <6w&35a Л ( Q:V2 Lx%QԴ r!H97[{d.#gu&+,L+),?Ng Yv'YctsU)zy_~Fg~֨x?Ei[}9䖧<[įWSF>G{.ЎZΖɅŝcO8E^.m1?`RUNDY&6%3rYGcξuf|f>"&Ur-$Cxr([(ڄv7WlPT՘mBk,G;կr# |IW9(V>(LKL!@R.wo.ΈgJ)a `Cb4xqEј9):ۗIu.yu=n8M7b}Ld/dJDՑhN G‹( 8 )A=/iG]2놂Ce*iPH5)rP؂EĨ/H=X҉"\ s.Fs;g+Tl!r2lRZUd'X;D9 trPmuOp=ɜV ,z"Iԃ`pBOoun=4C;2ikr 5qdY+d%N+|Iݷ5/jq B37wg˄l t #ڷ",cL+}S5*5$`bSeD:.կ SgGjpPxw=kcV$"ˆߥa8<>FBQ yvr 6 gVb3&+? q(Y r3%-*pXCd!ں8%uX,ig8`ך'ן;xOK?$fL6Uu=/C.g9)if toQelIr$+ag\f?RԼGh p:dr7j)}6P ?TfƯN!cj4 p߸iJ{2v3,i:Ӏ5 N ÓH[T3a!-|'=v-O >nX[\-rdPB uBc֝? G8X&Ldn$PBG@ꎬ,j q_CDb@I_Dճr1N,z|MeZ5 и-z}?ӖQ/%+@ HPy[|;48#B.\?*H(5(vƓ"cEx0C4BUu U ڽ}p"~݃gua߷ͰA'k [by;`G1#O |u>dߴQ5| Hz(;{Vus*ULkBg*w#6ʣ  惂42@.2ظ=0b,VXosG]ܖMWU '0&ޛ(Do~#[6X\_hzdWfsj9Eotukط~ hRgd&qڲEkW<G{Ls_BRzǓ>fAr(R_jfZIT(+2> ؙ8a{t$e,>oF`6/3axUy8.8EQLnnDLkV)M^=x̀Ϊzu:Gd+CM29jaI$Ci";kM;NWSpUKC׌zK #\Όj kT:n7>` XB=*{ќ,D'k{!$?`\{n1nTLjAez MqRJsJR9F. C _(vפJy|# 6BŀEԢcqwrOo 2Vg`H1i?=1eo@ @;d03Is\"/IoM5MjII Z 40X>plTE 2a7,ă}O9%xË@N 32nwow3@dTu j{WN3\rk. YX7geSnowV-y}6?XY•}6[q71d2{&K qY%M}TPll|#+ĸ_"# ֙.-/žO(Zcl$)[b|Z5 ' v+M9^ .t8Tp;,'ʆݩ39Ҷgl!j{kfQk\ŮaJXud,W)F]-nB{IսXL/BWDF~q4CaO1Q{{eXՕ&E}6"_ r⯸:ʱ\@҆@4`WUVf sʽ`g/(nG<_ y!|q Ϟ.J74JZEKk:%,F99Pt qNp1SvWTȓi:EC$4߅.3՗`5beH|Y&N*QChoL6BdAU X lzP\{f˭/0sqɨ$7 ;N^lshPL$M邋EL;@u ۼhU„Um~6@`UQ/q A_6^~0{y7rbb9Z$ca+tx8cp^Eye1Sd腮ꝯe$R\O^5Bfِ;#O]^t_Pnj0u(4?p⺛e[# ;B-&9z~G;CW3Gh`xL3V>U9ĄFRس;.:QS#"GZXf-%RzHȣ }w&@k9}߈27),ck~0n"eւ6l/6tRR\Kz,_BF@Nn5 Q75G!_5PHA LrarP`X[y6Wa7Y)݅3؜o@λ-ϒY?&q]#2r/m(>bv4lq9h۹@v-ɚx*L~(>8j.PY e !fz64 chX^k56J`"-{책K$6H 8~4e~ՁޥN+!lI"̣a+"@֐!a1Bs0ij]~{N8)']˯{۩fZڳW1pBڂ&-D:~ʵŋIHC˘= !D# IN'[*D8A.x ȠN 26=zv>J9z6$ a49%҃˗02uK O X~96$2x}h6y N5R],L5LFΨRcMPQo P,h)l-pjۄ*J8N(v>HTK6݆-rrA y֩d1 p p3"rp ȭ;CR6" v;ˌ NAtn,Y"VuGnȮ5-PFItϊ&~SOTRښ6>eM CZZ7:TC6܉g=9PX_;M[hG"IM@I8A p?qj'xP{OP[oHTHlμ]Oo}Ð:DY{).o.,N@YWwR މZ˚1ANd36Wn] i/kfz pWcz zGI'!y~4)Z`j7fFfWE-pC鴇Z!am&0eDtS_u#Jy0KTblT<77ږ^H F(XՄ'8tb-r~^KMi}s.v噦*O3W/?m4 %{&=Zy}(j[0iDj ~s}gCެ*|܀O|]8O~B<\h:NkŶiёm}[ڌ* 2ۧY=_P'퀍epZ5ܓ[M<~=4ϤK3iby#:BzSuJ[r>|s$,p}T:Y)vOИ<#MwJ I/5QߺB'\@aHݻ{lHO\-7=~I7NP`=6ry(b;[ |^>ݡĩCFjV~ #iJLݒQ[#ۚbըnj`0= 4sL_‡x/<+P&G @Lԧ# Cr2 FYOrezLy`jS&({t%,i{}LJӶD:B]|&v:qdLgGwAåѼy*@:jX_jGI#D,C78n ֡Ϫ 5mb&6՝dP}ۍ,mG#[ē)5;K猪'A!q֯PvS`n ^%ʱfuf6Lf(1d&o0ݼЧt N' kjfō9s )\S?_N%#]~] Y$FVg;Lv6yd8F0LH^s;=l~v8f՗C)7rE56?K[gإ{,q:vѠ]sjgh?1&E̬w,d⳧u{>>[܆T *CN]-ޮӼp_c}$# %awn/qpqT4C{8lLm/_vbfQM=>xe2Gl%W$#-x/]Q*ݱk/fM 0> }AGT.dp+y۳xh0 $@QTr6W$V8z-g职i@+VY3R:Q,!,K& gELq4d= aPrsI&KNSFॳtL ^3SMCok#K}\bC^ג>ͳY<"iX챶!`o$oo42YAW]%hX=%St"&"rK>ٖ[IJY(Ajȇ^up[+ą>o.ChNіԵ⦈@=11VD1P)s.J%u6!c8p~&NMNg?CI{Q@ ޤW'?cH>R,ơ^{UxcUt=H)ԈEQ?7;=(6X?ja )px2 YcQ)0T΂!L_z׻9}L )#Mlf'^j?7Eb^Kʏ#: /=oR?CTK6Z@P}J:92vT>IV!@;9g"vQ G48WA QB%:Zj_Te2'vnOPBuU~Q mAgʡ1ZU5JNo4F>bN.e\ aۿZRI |A#Exչ O$"e@jq  TA4BP)}ˬۆujiBv//+VϗkF\16z^%&ٱQeO('/7 },F&3d}އ;3~âOg$NeM@cތo W ;~k"H4,f]^Ҿ+(x/V1ң:ώסVF(U yc.]ƒol !;qv2 3ǬEK*Jt r#/'[݊ xTzʊ늘0nDw.w=1!DM`[ҙ¬vTEB7G].bӝF%w`㜡9(6PH? se07 <ĹN:çP4AdI_=5~]z uPY OʠbWH!`1ayRۦ 10a5>m eotRsy<ԾpA^e 9xxٷ?W'Qz? s 6X ?'+ʰ?KflSoZj9?_z>>4"Q$'᳛J6F ica,RnoU$vj=@D'l9TUQw|k(K/+VlĨ[_qUr2vŜ< l+ވx9D^!!>Mx{^K}}bRg@6_,!0dh>p:s0*Zm `xpIF0f螠. [Ś _|YNv','uI_]r霢۷lKݴ[N2 6(,uiɏѣ 'f3 (xvMFvyr%uOu{ąz Qd6X`\ " n$;}X5")đz"/z*!+ȦfS>[mO^ރ͔JetqhYL[_VڀnIf[ fTmRUX ,p a!TBtSu׸&F&H @$hгrh_2}>p3pܞc<0`Q13ϥ쀬+xP ;&v3]w8Qu͆+ݦ|cA A,˩܃B{w쿞hN5R-9)W0ϻFzMڡ B4wʦpP;/( ;Ʃ&Sz 2;;Ҧ(o!7#;m{V ̛{<:}Ա#30뽛mo gsl[fsX%BsgLIeuȮ*}JTۦU ?wq+I\v/}̿[3yDita)ܭ#c=-bʞ !̹@3]-*?bQ[f*LUY'{$c}ɀÐ4 ^*A i?}m/CO5 r3C[蜬H8N&, _!a`[c'c_U yCiAbo}\+BAHT=l@Q-0 i[ ?<]szDc(9Uȁmc_LPjbנkaQ'yutuӛWAo@ ،J0ܲU3fxjQlN=)ꗗzpz7L<_J)A߽vqξbqU8$*69Wفx謡ey>]u$"nٵ4GR) \Nԉ@ae?2󧀩+Y,La})IKl`32AWkV-w0l܇7§#_?g.Z"Rs}Hete!,&em @?Bl2@s2 cΈz".l!@ (΂A7{xW`* x;}JNwt)r>ԡ  OF>Oj穢a=I"YOB"[;LFOz#sM dͼ Lx`J=Ŀɦh`lE[ ̘Leww 6(TCn[j[Q<*Xc,/! 4쯽c,晍;M9mdTi)zہ) _e<7mo?MX.&zs w&$g$|]-GgNdQ;{ޔ/3*oeC&0x!Y߽pb1rifV|$?$'0 r↬`[%l[8IzD~j 6g=]iJ,s{hb!Q aA(ሰvK睚+!"Lt"/`p1qKy1\zL/NӁgœܨ$q=HhOw+H sHGZi OM1;܇VPH=8/tU` ; C8M xrC#RteŇjlshI2_H:*2l_ՌIDMTW7xĈYw5Uk*vcd7Sa+5v2)Afۀ->6455i8{mr8"~,4fAJr>1c;?@o c'^Y-Y(#Ҷ]ڠm^>LHqjP٨XMd>{1`gK;ĿӍi~Kj_BgS߭`_Xfmh):MPu{VsF/}\ )HvrOtXL5\Tr] r^ք3Y #q)#9t0/T&Z**ӗ͔Xٌ ϐHhu,9_ŅDO_0ab-+0v@wW36$7P&of觖r_zƗkQlo1nT+[T~!V-&:|42cWٮZSC>(M?:4l/IwEϚ%7A0u3n*XgGp "Y:8|,*ǻsGMqK2xMLmC+YT.!KɗT % ܵⓊQ]bm&;/z:Xw&j1AR3p^ORHγ>f+ ^">p Rn XX޳=d};g%!XO9m:/D6:~V. v;dB[ZC~9a%^қzV3|KzOpBnj Ql58 H #ɲ7 L؀Ҏm@^r0RYVcZp"[ >'RKQ7J]zfDѿ`}:* w>~p` t+k(@hj>$ N$9`" HP7|fvF̘5 3D€քE]I\<1ǺOKvk\VA;~xxh';3oϸ̓{왬v(=hk?}`aBI~q8a` wIEY;x05FzD/\0"k,)]Ie^{q O5n@BO9x֎f:'ddh UuvņO\A8mS^ba)nm! qXωeX Ʌ($>n~ЂRㆪ;%o-DJ޶ԪϝϨ1mҼ>r&ws(xWMyQ=9=nez~EEs85dZ#բIi@HקEw?<ZAFDƾb ey˂C9 wЄú-v97Cj΃XL~y` \ hwD 22.U<|9>qv /ZB@+d- `9zJLLHF`>37(5|vH]SǺaS2j9} c'[7O SV7ה;좯n& HU‡e6d 4#Iu3C? Фd@#6YQNTS. 3]L` #25)6e.fߛX,T>Wt W\.5imlR(zG^n|Ե<&píJ\9Co f@v@Q=0H0 (71 "RskIx# ża%z|Sxߊ>Zh@AB}-0:`1#tbcp X[u'@ڹav[Tc{fĴdDz<H{2"ˋQz<T댋U$6ٸuӼ9ypew*@q<՗PG6CMH+,$irڵ^}8]aРSeʬ7vi-u1b E T/kR$^#^7{xGsUMUH4pXSmR0qlR $ /͏0u\L[g$=PE b>;ir)~2=bw?rIMYi)>uU!:¶vP"vpԝRBIjLMTU1kISX΍m#qtflp`}~(ثY8 QoG*j 736 Qܳ!ITnV7Fxi2Eøp_R±,_m#B{ؼ?Ӄѳ1·7Bp]0!A~Rd(O3) g,ʭI"=DB22vx^J}v¬$phd=ù|OC|qa :?0HX<%j.?Oq6⢀8w2u|+'HiɢRo@[o$O0c=x+gwH0 w R;ӪjXX¿Ek2b*% wm=*~#:m/%o9I|A Uou?tg65YTT >ɑJ(vKqooٴɌt| C #l6RBmk3G>C; *ޭ?$JuhpX!f3ac9l~u&ѥ0Y3.m.-RF 9̊7f#(7-q+4+hP\"%͑ XB+R\^KjI5i9}y]J'ȟ&tE,I&'(2+42>6'_hj%Qݟ~>7giYwvePqry h;gD鉌1&egxm'w!O]#)e<݋B ҆H*h t_m :YC~TLm-]">YA !Lh^Dnۛ r͵RSVQT~ev Z]LuK (d%̠h{vJKɇoqF^u$Lg4g~9:866p׫y(oUU2Fհ?W`(Q&Xy"Sp f!5@p[x^ZozwlFیP!d_:8}HT;y ;!2^Ƭ[Y Z s@ ^dNWƒVfPGNj8Iso8]ҽ:N4ֽ){h(eY ]JI[Qxε[T1ro\Eli5=ADej|6/B"=mq`_0/I{2o5:#g.z)|lrqtH$ڭZH4M wtvV^>1?;3N/Ǩ@k?92H)@kX*l:Txc5u5iRh!U:<$HLŖu&CBhz?QODz8.YzTHv( eDWхB0}ǃPBŃnK c4Wh M3q3ƎV؆C x> |iomYN-w˵ݲO7m(l]7K54f*-'qG,E@~\\N4;f2%6}r)4ć2Ê.hoWf-]͝2eO #I݊i[_9ͮWfserB ՁgwsH_wq/{t^F) }W`X!1v?շțX1u0ϯKauoO,"Mn=dc]`Dg"F /s%&- ~f3tڭ]Z.mol릭)rd *M|5A)ʲ, BJwRb,*߼0evN lG)i~<*< 0"MJ/;{=_Ddzc ?'lsTxf." 蹼wG I-1170v`hdX+קg!* ;"@r.bYeAgH92bJ|]W3xٞ15V:7Cwj $? iPo8\]08T8T__Mj!ط)۰1]^'Q)U}ә#&}kߝn4ծ4 Ѧ('[!TԬXs}ŝ|xm 1 0f]@snvB)*?,q`GO*.UԌKj/IbYZzv\EgQѹ{st0Y^Iס h)yֆ}48'~ 5凕87%^jͱNyy?|F $`+H^ejN~ @ܼY*ƛ"m_ "ʘꁯ1 cwBa{L]ݪt-4;9^@f0$#r3XWgF>7 Pb?~t;7zat.4h4xml0ϒ̚, ]A᤺7아 hQ\^[lDy%#!)G[%zdS輗T3Qq$H=)Rt+/"[8BbK_Α[Qu"`3Q-(ط֊oum<9d\`"U$\]ש2Yc9}\P$ƬqI&ŻDP%s@?p7kkX2UG[ҫ%/-f uݖ/\H#KYnnDo{dei_"|ސv|dO{~>!2G\$J"␒- :]6\ ?0 @[,pY] |!r l5\`n]Z4VȴomwifA:Rh?KqMOdϔ3KKa(!ƖQVOif!Z[9E7# 6hUGR][Jm%uWv$TFpVM՜/So%ڎLzϟW7WAe ' bdGʔ$MX@3ӌtJ<)ZJk=3W,,77Mr{WOݐK[2ˆЗ 0QV\Hð ? ms7Br9+<`ԃb^`hC9O{:~kih:2kWLBJ[\(%9@z|ߝOIaκc-O,4SJ* EAKasߊl/(r "L/QTaLmv hRpEN~Ե753j]7#`^zTv[؂pk*P{A3CMW|_e oz)R\PgxtI[=L˭۵x~ ~7[uRBQbfr }`taBŽ+Mw!A32&̟v TRO!7YU7.Z'Oꙩ:Jw ?s0 o4$|G[Oh"5cܔ0`VB`W%ġM- VT 8gxmd'-IU݊V/r赽zh7:,PQ`grzȌ~׭}Arͪ`6r] O0Fk+ǥSO%<|u4).֢|i[81Vł#Εjߐ61ltqk.Nf(ri)[D $ ^**h  [Ҿ1Vj&D-ɩc.f\]f$Kf.uG|8;Hx{tYC\l‰պk Ed/BdnѮ<˟WODBT2 {?MhRmqz5[79Z 92vީƧJU]١ΞпwSOALXU߯1;i9a4el73ocx%pE {EA$CC _o I*Kһ[%З 9`x[]%h"Sh.u3[*g?ut'w#k]i--M}" ƑE#v7آκw3AM%d@k+ Pa+{!K*(>ҩu)u@1{kĿ<$i9QDw!6Z @sge-ڽD#MfX jsTe3MZ"nP:o+%/B>mGf2LA&I \Sl=kE"ܶ%Ǥ"'d'SnM%Ac'7yJ $&#$Ž03"%.o/Ž2hCFG<2 P٘>(5+@vҡ#p[) 1>nA?.|HKTB#g<kGz FK$wE \W6KIHzhH8Hp9Z!V횅_|a~" fBa D{jHh/&g#w3i%?igqn%r5 \:?Nf+hx&e{q9p:2דgfd+l߰Xlxma]9HA}st3U]oZص@{ҁ..`-V&rmӉAw=fҪG'^bO~I_)y&W ͏h;fV^vױF͖@OQ άU|LRSp]]}ډ5UyU- =Y x|uHFqalQZ:,Pbk(; UYV 7t&ZZ{BuZ{`o٢y5$98gT̀5L:a0V҃Z fjAJ`FjNѷd4Rvvb_$vahUR2_&$8cUk|_$7VpfWKE!v#Ho K~&PHY+$Rm5 0 :]ƨ◓Z7EFz 2DC>uYdU 12/| FOd#,砩=NJ1VN/;xu_qrNٸJPQ'/{j9!$fڑ?٘eŨhй3"Bx30MwIbI%)"cUd X >/],ݛra}9bTͪ>?5_su?seY-zB| {6,ƥ XM0vJH8K ̔-dcj!sV3%H! ~r5 ssP(e%;1LevXԝbng=` nUܶѬ.dOdǷvLJg`䚽(9v+AD$~Ko6$V;Ǜ(@D0|#WN̳]@fTH;2Y]́_ xZ A̱{4$QޠW.1'kIk#V#t/7ۖS`wcQ_^|]~*lKrd47u&=w1=@@X)~BѓGyǛM*<6reɨ: )E) :?b3ĿbB]Ud-s9h8)Aju}m[G Ȣ+Ã![s'ŝM8O%.A:G /?̻FĖd߻zTtQ>JhKT&?DS6.3o=-gB˳筁[-t*v@2 2#Mеrwl:d_µ}陈 !"<<;}-@qip2ܹ0dr;@u&cʏEGs; y4liwt?oC7,pc¦]`K@{Y ojx5ߙោglTLK2$PuJ{&l9ur3# Xv"IviVVZ0R s}5uD˲&L#nAăg;vxO.Oh+/Ŋ֊@X"7IA)J~kQg<kء>Q)41áH-7!s/m*|x]|ڲ%|OߔX1@V|WhTH+FG-^'<_X4UJ?B÷r wbx4rZxĩ ΙAgiu]l~_jD>%>O1{[BEMoDa+$ov X)vN](G}4|mx w>~).=chAݷdI9Q];R@)Yߖ!YmԷA= j- $`?0Mv*S[Y{H"Z|1cQIK7FAbe+ *5K2~#3~E}4eTqeHNpsEnM?/d)U i/磪UPBtڲV9'!nvc"kTM)JcFRwX>6ncXO8Zt`M ; A(}Y{?).4Ƞ_3 4+̘CUظBA! "E)`B%N3R061Xz!&[4fYM֮V39 4Fʺ۶Nͨq Y+{m瑲͂T/Y0$]P>4k E 6<14?<|<`RRRjTR)<7l悿DF ~~0|Xm, kJQk:K\2La%u@nߍ*c69cqBX˝V;v%qA`?1JBlxlWˈD ,W{~'9B ꍰ_8٤ ЙvֹV;?K j ɝIe#!gOOk3-[w^guX@Q)Fu %{_)W`n H[ w>tȊY1֛kfTVJY>p kI}^c?2^UZ3WZ,t]pi9nw$TZV@Q:vV*$捿gMQ}&:hfJゼObݿ?N1VzЫp9zŰ6x!%̈=WGK$Rpf^מ*j V#wخT^H Ci̘(^_0_\_J(I:#h%~Q42[Ŷ{Ň3+WYT<+*M[:uD.$03i24䂛4*P3kBtBQC(e,fYJl[7lsw Τb LkK˨O\id:cȻ/'q TE4kіRQ8݄A}9tť'eFwjRs7n 䠶;v5"øqpf` $yoרt x|/$FQe<( t%c7ޔkK^\鬆xi`,"PWTg|>+ :-UD4Ž m +ystaYYWt߫zD)0r5{984bOB&ag8@d.GSɒ$5+(LBx+Y=Lsl=>/yivf}5P42?PI "X]Ze>+oXFIg_f䂷P*|~onВ'Y."*BGC" ;L*~ڬŗFpUtԅy! mhОhxb;BP"S. MTuJSJQ|]JfcH#&1#6'VM~ tW Xrxf)6? /`NIFW= sVFR$Ҵr}25PS@~nm*"jDMj^sx(`!.4mUuAs@4r}kٟuK:f|2#Q[PeQYĨXVi>-K{TPC $¬,C竚h; nxjmotdj'\d%*˥=A Ayg7'K6eI'A JevsI R@ ,}X~1ܤ 6g{Wl$j6Ek+p K13ك՗ M,/7z2ۺg̣ts;1=W^s&!ؘ#K&9͕7" [cC\!s<~Z0 yNC[녎hu'B N !/Rz\Rn,k^!I _Sهn<欕gIɹ>[c-d+t]TrpQ1Y-$%- }BR69TN衂yp]m.o_jV^t(Czt3ADn&ɭpN֙o W~>Mq2*͡fr[' ^2с֩ oBbf̻tif%Ѹ|DvH񲮠"p52{A. qҲU|E:1<2C.~m@(@+p{}ߜ"ҍԦ#qI ִuPT_|! ;U/u%:H"XbCDdh".z78va:D+pt50[w-0qxGl} ~ܸ\W*ͼcibB^ CkfO;$;&ﺌ˓zƼzE8ufI>0RgA# iKW&qV& 4S`✆N,?gV㷞@4ߟy2uDXraeT>&eghLR`Ukf?W.=5;Ӥlr55͞Bf_'-cXt#הw8eSTtzǣbz4m{ZT,~s?%XˏF]cSQ#Fi!c=\<ڵ&TWDl]v)a438P>|ʚϷQZɂKx 7$>wpW|_uV_7]=tE!/,eVrTr _+qԣGPgwz+ٷuMb+lz.tνe,[14ԗ=[^O6cct2&0/gۊzŌş#_\GБ@RgXv7ˮFΎA*^f}1jbUcے:y oH{reΖ7-&Fd!6Hy@d.jd4 s@(GjI`C0lRcߌ_Ѿ3ODgj`q;,"'vN 31EB*ⰲ?1d婚3'egjU+/R@Jt"gYt0+*bJFfგ29S''*!Xn;Ey ?`g%zIDX;a(7 uBl}Ui4n+ɼlX[a2K5_-{YV\ f@}Awn͆!,8ۜeo(gt%e/]Q qj~3V@ }K >9dS8z VDUdNxJB 4AG޵ހ@2!1-ȸGa,5 ! b9ApNe*}%(r)%7ܨ{l צ\uw8 0OUWp[ &c(:N - :O@j⠢ll{UW1A-F:@V #:SgW"P5^BP4ޒ̿\#=6/Yy gHYT0nءK2v#GG_I|uTtd`7&I˾m:35- rqꂪU#)R[m@"$*rY;3œF2Z 2d=&`$z>.JrZ/whJ[']K`3`J} 2\X&lR"5s$t&]dMsA& M.AJxX:&@oE CINMRn+bgam"cX_ȿDTVHJGL)kgQ.dAqOuoDsc3.*MTʊg,ђg(+ПS9XEgSֺAjϥm3r|1òIIgVg_}k[(Ei^7Yibp_sV't/<6ʧֹVsU&%5ʔY c9R+Ppy?G 41Jl*uXWlh( X$ 59^#vSv!)!IԗDa-ȖM0PO)7d0[k69!HfkIFvܮ`x[7-e0f:vm29 ^ٝ) E{^!B-Dmc"@|Ƶ/gY^u#8 NhK8RTGA5S2"Iy#= SyXoЬZ9UT:Lo}34`x̣wt #XZ&엹YGi73{2˖)筩]?`8=:b$;:*YkMg'ewP䓷B!xRQK2+*6]Ǧ# 0.yiCs7=tf,cjҼre gos^KV#[=Aa~Ikx9{!ͺMXer| B?I'f36~5j:"i#֓rquU 籷fwȥbg8=.s< %S%$%gjo 9 '~v֬J,JI>\~q涃S6`w S7FWڧݝZb5kfe4U] ?8{Ev_)P:4sYӁUBp)wݡ7xqzaowD5^DS) 1<.!9"y yƳU-| Nu+ns' .X"ъ6{ K 0> +D/&gsJո$BC׻7\-heL>FojL ld=Sj}ҘXSp1.uUBu0w]~:?9ahGTU𳒾6ҟϵlbW`I6cF˼w{q)ͫWzAT W7%kFBL.֬#nA͝M|F\ #YS L)P#)eR/V4B[ /@ouvڃ {v@&-uTd>n7)86wgxXo&)ríeVbK9>Wc4Ę0K 3Fli:N聿Ҙ#t@k a9'*֐uDc ~r4Q1 i͊sٳc}e"/,cZ6P 'Ftmkx"d Po#[%ښ>R6ԋ_YLl=@Atk.T](Fvk0&m> fR ]+5ŠcԙL֍b\ =j#~T=A';8uq4r)Z z./ͯKOd 59ǎúp0׷_,=`ۿ{@t1bH_& ҫv?欆 = ?4vz==Ú62:_~Yi7 5%[ \0@VGw9xKo1tiM49Մ˷xp^:?~H)<5>*n@+ []2;ZQ(3Gkv7|2񁇚YaD_ t6ljqQ@.~kiIT)HH=CE *Y1XO[tsi֎74t]Rs;KT5Z!Y@ޯᓼrNŷ /e=l\&a  '?F5}8 hsTޣ{/IC,ydO\`ZW^6UQ'ީ*׬Yas+*H&JR,d±@N=X6'7+jڡ1QڲBJF4.cբD3LZ7cW} `,,!OnR{$( ~90zTX+J?FFnWeB#wua*i־- ]A-vb"N[9Fwea3gë$1 ئ&uE7?\jY,73cLFÊV>Jfq\"M %{/N X5*vp̶W}>,\/ 4_#=/bsB\T/H.@X\*|TvHl~&kf~ZP\e^oI6?X'*`-emi*dNLav/E Z3 IP|o7ǻbz7(flfbhfdVh1}hGµpxNȝ.q֐7 ' T̖d n:⮚˓#o{"!3N9Sa""c2NNMdYM@pLfׂ*YqV}#)0cqb/i//p\"oN4Pjɷxt>8`_&w9Wy^,t?׈8rDG$ UeVt2qԒtucI@pSXuW+OoLlBk 2K" j ;HMU(5dD  s iHSsbOh l'v8C[0DavhjL}gf\X1|E2C9BvqV"ݺ[c\Ew]}dznl";/'D+73)NXE%gqoLDnRΖᙠi)P?:z4RkA7zQE?Ri}إ'-Xp&6̸\Iۘ9*QIu!Q!TXww送{%[~r8+@US:,KU*hf̴{ !H܃+۳~1@ϴ(Yʑ8*R{6ӭO]G^!d?:'L/bhx4( .q-4zpGw}N_ZsWZr]0i'ۇ?L9ڑn~M PMd YoqccwӈAY kRDz Y R #t )7j!ĦMg 4}.A~03 uyWmy!Xsp6PcK&u؆VihpiF{AaIEg 2LJvٰ$|4Hkp:9[q.ܧ'U+Wb/s齏Z`x2CyB7Zzb wIDOq]$-;FO }47 ^vo/bKVrE#@qw?Vpپe-jG<&+ ƁE;<[–+s6TԻH-"r?tN᷹Q3ng w`"ctC`Xuci SJN*dY?xdy mi%yO`K,Or-Ci&L0>tB02о<a< zs_k6fR]C]BO8GAt4E#Fa}SG6.bsl8vidj2 o5rF8֤` X&%R0*BCo,3mk+YMj̄8QmĞLOJv__{'bhZ&$B;";Ȝ1MȜD 3jgEAmwl)Du[W%A "6{<.lsnho#ڤI&\ԷBo,,Y9hP#`ؒLS("a;CPq@낽oUSFDhE_|4ƇאU6WRKe $!|WD"F1&cV_Dԍ&-G3IxUZ t̆< #X+XZ& p8LO|V(LŒt:(1W/:U1.9@L/賣ˌ\k'"qF0c ;^vh$E= dLM>C\Gg~v7XV\"auTA78k-eN7;WEZYBٔAS(se4"DkQMޕaU#y"Q{N7q`64 R3V 2{j u=ۅ5Hq )3qdaaPDlpUC!7/PrޯyVAl0c>i2>y ݄j2ˁ›h*4 u{'s6c RӍQ2bIUPOƚh&{1K E(G 2a0G̴0fB 0OJR ӐNMfp60faW$-Vf)nl!RrLX^G7x=p#Qj>iOFO4oHai>ݘpŸk+b~STWWi%*@mr`hIRX4ؼ3W@RsAÔ+Yֹ.h~VIPK H)[ )zjEE@Il+q⦜tU -E,xomxmY&o5ڻoab>MTDoaV+(KdcVG'q2e "hؘyI\bm?H-(5"<@9NF[A4_X|3ɖubmE$MOK%'ZA psKw=/˘1QD -V i j]MiEmF.dnW1=tIk!H{km R5-"&ϝ:a](} -yf'-HW3 W~rJS+kuJ?@$ro3V =+/,)2;=0vDOL4(G >1sCài{/Z8ɪ<|54sc.hldWv'W<]iGF+8E2盏9:6n.!4NY\ș:WjI]_%83Udxά.JRJ-Wezy?}o(F/vЇsHo(50ld |^fBO#mtŞQAG'w[ 'UxG`n4@0?%J#>98[*S!?"#FĘ.^lf?iY %1mLP"ͅra>3mt2.{s+qrcHS&{"gݬb58A1qfvۮA5e#UUj:r.PM$VBQ1h :KņHQ)N4y\-zd%^{"dun(739h]P׮%(rBȃy.O-܎ \DYz/Ww3 F2T>x'XqOnva{tgU}I,LI%SL(wEâr}XycN&_8:& I O45}q'kf@hG*7$p"k1xKtYkX.h}7#87y?bx+'3gq5x{IQIEA*7O& ! 3Je#^c_;Jn[]\HvU.rm8kXoκQrm ; Gbz [&˟4)/O=\*WwÄgry:,υ4"bpi8C^Ddh̄T."<}D8%Srt"8"L/y#!25;>Lԩ Dr!y>:$x`KևgâmZf[[z}_ߠk2A^NNS+کYٞ/_܌SL\Qܨ%C\ &=vFW4Hݮ #Zq,C)(rIꅢ'Qh&.oCVMMץgꮡ`Q++H_ukEQ"C(7!j"ˢ(c}Vq}eU}]FMy(wMzkZ-akRK7ʎiDLZ5n~FJ ﰢӴ,\@|@c( mA8qhlaEx%0T1wW"9t?N܄|ntWWqyU!^(' 6|gsܦM&37{88#q'nG> {pT5$b/#u]_؇WBA*I3DN^S@M+N}ruX~Ls֞w"?bGjj:V\ʹnT$;mJpBLMa +5{V5 fka-^AHom5$`K9n;[U';m/-yȑ7˪V*dA&>j L(wy:o)?<ZaK};_Ay+BZ1Gơ>mKh4SAocr+zq53Eu0GMȝ0i&h(=?VT SH:ƝÏl4sx`qjH'HƛB8Ӕ 5Vh4.Orz mvG%0PZ)XP?IGg=5.Q&n׻;' N{ynwdc] ú@xz &)wהH_~R9+0bWcz 4N!g$汊% %vlI%T'+eJp֬)żPA%mHRjB@]3HQ_ 9v12{ !Mzʐvg%&p~.Cϳs.c8lX:` 2@"]j+Qh]ciˬFr!R~ѢPϰ9u@NsrrRMLf=rpwdnAɱR{#wxKΩ9CfbAq؇e[e9/C @H/֛TOh٬O_~)Ca%="EE}i!\/hFǒ9w'yz{Cɫ^isA7#BS燮2on-Z5>|ǟ:ÆEvX}@\vupqxf%y8M2U2G Se[T{",$0;~RAL0aH@cUZ4)N~:(̬sS?ɼ2Q)eT*[ \H*deژ͛H M aF1W`XQ%oJz E|_Sq 1d % mG |ߕ-6^;PUw33u3Gh@FS(l,$ P b浙Oa$/Fp}t^1NV}d^Ks זV$4+S1)징*MRScvwt]4b-ekR0o T*]pX[rPUJ|aMzcDR35/FX&6m#?VP!(1?p~CyJ"x9>:`+YěҀ382Ȭ[( &<j~HXG=۳!cx[N_똮9&iΨWJ-B:DvxJ\Qo̦rc:ҙ,܋+WJ&# 0N# c H݁+o*Tgxl-h[a&5]V:{XZ0Nlo҆]x(;$&ԩ - [)N;,5o1ͫfCB^rO=yi]M59p.WNjb'=yʼ0z'䢟1J~ vkfGT4FC38CE!L]-l"].&3Mx *I22d'zƄԀ(-ki9HJ$k^9\ ]d:IuAk5;3,;W3ͳO2%v *_&&ܸri }~7b^QB M}K=Rw|Y-5Qm |.R.g!iZl*5tÿvrUH/oQ*5)u5X8ܗ=w}s9&2b抔bu3#ZGYZÏ7IGh?c h+Jh0$>D01:MБ]d\Xu̜ܷ-$tZ3̈uԽdiQ'g7D1z90'6;'-{~Ƈ?^f)Ig7fDKjoy!I}q%x+ZKފ$;'ew]'6;@(_4BNiǯSa կU}4.2GN&r#VӅi~b$aL&يWaOWcѮgl21]JJ0P<$ȏUTCT"q"MMfDY5{%~Saռ8@&{#}`iWT)T2Ǜ',9x. ɜ( ûFu@s~a4{ϼ/\84skRR *m!\x};q[SnZ"ܖ$dV b 7-{aoPR48 'j-aFkܽk%ob6{R9ȿq׊-G^.\kR'A-Nؕ "EL-B x&T>ZgSdJVN[*bsE Օ$a`-n{Q>1my BM 2ZxgPjEFϳyc UgpĔUZsŠ%*KyΆAxx}S8G}9bT~GYae|rұ.Cxl|SIКؠƧgWF<]2"._Jb: \W5uꅼ`Y2ӽqs\o^ƂICX P?-ۭ+=21|uENQ98 0m;9H\t"ڜlo=t'P4u.g, _cgulU"WԚ8锯IqTlD!S.)_^eӷY>N .{c}yÃq^;[wiDaVn|OBӰ!J nb^9+|cqĀ|›{J)6iwK^GN"R.}+ː3CFf V[_+fmjV]TTB?M2ESn%!*(/ #e`aDCԘFTn_([r1gO*RdgE@Q' s]*pgw'6P8}e/`C`DGo x!}UalY4 Q=rq 9A>ձX Y]jJdMHQz|VD["z6 Mi;@Ӑr.H\vٷ)g?afkiB;6/?Y,E!4*1~K裻¼"|^{ZOI|KP \Q~hHws GWƋH't񦬕{DK{`3_5H졚#gPI}GWT  ȯ:7ϲ65h >:]&궫Nmޏ $<ߑ'{cs>M)# O6? Ķ%7Dp.fi`j"^^T= |W2$KjQ֖M~\6J>Ϊ/V/F>Lk#Ŝu &e&m_W\:X' hNӏn1PIh,UTt*?|GVS[If$܀ Rq+%3=_]umΥzf;]:Qx ƂT_5E3moLԁml /|,TfBw'+J-$Q )e*i],yb\P>JD U 3].D7(޺{(cZItyQ㺹txRϥ b睬#G+C…ݛp8c6=M̿]vN+Uw5P. xN"z}o[Hg˘1H3:q.o$c6axySEZXWJ0Ok3owMfBX倥$"v,D0X}mCnO `O؃`c;%7vzwF 'f@{ibڍP4>\>i>EP,J_[륗0 '# o#?Xƶ$q&5^ KՎ8DvkBAŦS?+T3 D%޿eK?F8&,#T06/lZcRX!{ \kv0IElR%z=֖>G;<~1NF4)93V]ɌP̅bO:iD@qCݓNO>13q/xH5f/kjլ~LEMtP R&MNxÿOgPeh&Y읯d1YBue8FyJUKO y ˇ3%:=qtc ҈+:` U!րhdcr I0 e `@4B"[3:&?COKt:=)|G70:%f$ B=mtpӪ,9 M6#s1qnyTuڵ˜ ǍH\.pP7bx StmB[XNuwvqDSGGO&2ϝc&7" KHzZv^[Wo_ ĉgX`Ps(ޠ2wH(Z^-D@/6Qd}_^|rjyur%?8ND =3ppeY{h46&`ZƮ7Q2vu} ~ ";(dJgjtqBm W"JLf̩Pۏ˃=Dit`hmC0"J_F'DDW^x?KQ\Zhҩ:@X|99L; %/!!9=4p=G3(*LrπAM#p]N-C z&>uϳOT0ǏW`FC,40>-,\2ZBb>XLVs;ʧE޴cF?nDwWļs#u4u>k.,2 MXvJShwʏhbb::ڿ}*vkGU>.;UqhoI畿ω.:IB_%$pg-\!K7)k1%p&ܪ4宋$l)̲|'MglM$T]"7 L'"HB4RR]!f⟄16˫$PP>Q]ʼ75[? p6)HԍFm,FƋe=[:o`e$e'^zOditYUJ^T'o^?NzHt"UsHٵKWM[P,2'{Pxjsxܲ՗RlE3B!'F C`AK:mCHGۯ0jE !h_*UlCAFX?"a˱ԏL-TG3ehZOGvaW6lgf,E=l{ p!7NP͂Uʈ&5z|PCk|Xh|7Lv\D >{%k=K7n`V4=0w2t-65꿻NjX+L4Odϗ˫v"q{8QjlQj4e[![&=]2Yt84ʼn I'[,VU,bh.c64>W\PlpQ6E ,3{ʪu@#|ގ)pj6Qkvo m[=9C]Bޙ1dAVe:SOi'6 *A77M;聞ʛwپ7 :` 3L=Rͬוnu7L5=([x̸Ƽj. +g(!Lo2Wn 4WE_2*yR۹7 l{@C kO{\>7<1LQt<"7i(KSŧUq.#uXK5y}lDX * C)Qxͧ]GRcz">J\+n9O&<R4ٿRj Xx!d#k}!"d^l5ͦ]3# U7^BO-&7]nO1躸N☦5$ [ͳVdr9 :`SZɟLq52n|4zkZDtgvc:tm;z,ţcWMLZl GЀܬpRl)/'H-^wd8qyCῢRV[,Cͭ j? p=W<׳lk{V_!m(KHogj|m{|IOFt<]1B-S&AFǍp*[?.ZךMe'[ Me_$Vs7~zƘ pbyhu LUhwpW„ĂsjħU`O JGG60 9[*;7Bqx}w} sa.{'z@H~"~)-);S}u3Z]r8PͧeǪ%?Մ~QpUWbí}/l=@ t0|R>MoQ~6ZB^qyi|/`~Aڼ-n qߘ 'ko% 6rn"Ѐ!ޕ3ʂ-!n,HSRꢞ`@YM4Hs{wgN ț_n%,g |mO9J77? 5B2f=brl ֘L!ɷy2D(*1p_h^%*Í"e2b*pe۱ei[K^tXGԛ9.'A~ۣ*Ԡn!r^5v~x&Ddܔ2yWLT4zuTZl ;d3yL xuԐ2 0-iy՜(M,rY1Cb[~T͇@\!4LbV>>2pɳmP2ޑ,L,ϵŒOu|SsC&>A!#+1|yD9*7FK4}HNV \&r]luGAJ $gPg3LgegUk0rJa/F-V(+eyNݜգN]rAxcEpReɊsyn~ "F;H&twջv{헋 fC(h XAnxt;6Q][(-wx xfeܪ ֺt۩I1(9-L!j3"&5b)upO$ĬCաt#)JWN6qeS͔T[LpeA'!ͶQ4s#Uv"9{U& yщ5csi%g[ǻh<*sF.%0>%D48# Umf@]Ko~޷IuDK9su#ߞj @"2dh*r-T1<;-$=JHe4݀4~0Xo^+.jHk;#1WVi'?ƌHr"j1-y iE ag8 )eKhC%?-~qf)|R٧M%87nϋ/I fS,`N1 O,d~W9]HZd җyih> !xz~[JAju??| u\0x(L{mZÈR~3J`oN{uDQ SxHBp`V5Sp*Ps;TR2ELۉJ[cL6]?4% |ezmfӉ/R 3QZq@{wvf"]' ^ݚ[a _õ"SX\/M 8jxk/kzi^\<{153`7/8qër8v,}Bk5wIɲ;ZxZNU_.ԲfGue;R9H ?"`E6LC+ĹEji"5M" *@]iՒIu\!iij&) \&k;=&}A1s#:@& ,(&<PC3wlǂ&kx8zz'WZ` ʢFCHX2^Y0)r';V΄b@O6Ԍ6OVrjhm@WD9n"ME%^.pcSTXn&Po}߿RG3ut:4|̈e2SmE!Dn)ᱢفt~MSäq2$ڠHO{;n[.L'SJ=9b(J~,L;nMr5O_ ɸ`̥Jd3̎y4Swx)+δ_WsD4ǿ/wxifvUZwHmLNFxHLuNoce@pmLoC'J\‡1 Y QOU$IiJ>Rω/Q;he l3+K |r*\E3ň:Gya淀`DW4*M3 )L( ,n>uX]_+ӻm4gp(q <*XX/]w=}%Ͳ,P( /áwAfp?CERsS \o:;QL]>fO',9dM:V&]f̽)Upx#K$yHPX:tw1UaNdTza¯9&om[|z%bKN6!&)_\p/n˶Ǫ-!OSxTKFg\,El ͗( d{)!8-n9|Y9B+|hc+xc,mA`9t+ǵU!.FTg]U ~?=>%O r^`.*T@d澘@$VUSԢ_;u,ܮTOŜ7GQƟd<`i 7}$S4&5g;R,b$W6fBo/E3+H+p8z 2}G$( 蒷BMg4llXs}xI)[6vuD QWC(2ƀĆ@rKLkTf7?Lug/r~4nA3JC@$R kQ/.Sf|JI|а#*ujT{KbY[y!&utF^$/AN7dLpB4|@Z %WJdr70Hm=p ϧ`_ݐa6zj?gV*SVp)C:ZzMsh]#ws;\`˘.VDA:]zЁ*X*F{Y?Q\s !Iv͖s8Z,z2\qD QԔ+Wkl >P^f9#k=n]VC/q3U;;$xR <ib.TznaNν2/KB [_xqvn}AK`w"kũsTll",9#t0K(vLh*Г~"&o 8f3ζnE`5rҌ}jl(/yģ5/罃q"~l?e[/&bh=JyȹT.0CbVXR\EL} eeš7%8iyL*#,X;˧l5ؙ)yJ8eGaŲN+_ PE,5aE%Q*9$u!;RbAjWwVda^ZC&`"ڦ8qq^D=|T @|t8ly^m2m{~ WD; ~Hv%_Xݴ޻| ]|YZbbz\Wz3RN.ڷJ>?O ?c:l98 ]X](Ss+6`ވmbLm.^z\^9`Muai009tN &"g?`C#,jYp雄EyxEIR2h>oe\n |7+M[{tOWz]W\Ƽy! AG51جL@)7]j!uL ఄWcɁ716oӗƝD:0TG;YqF L+'vY#>Q D*_kȚ (ڋζ:"n@P4tv4"N- ,&ę;s<ƻ`#ٔXSA0dbv1!S.5/஄21 |,N▏{^=$cC8XwKdpxlY>/ԘAqAM3XFQ-y8 r1*IY&5r=+ESA@mT]k[*/#S+2x l"E r(C2{Ah }^h}D6(${c{OӧֳÛ⾄v/<12? O|F#?;D)AbxlK4~BSs@@ܾx};jAN>mbb]CiUs3CȻG oօpoCz5LCa6ʬf4UmQq/'1ͻ!}o;.l/d4''V6 (l{)CV< t꧰g\/ : ʆ\AqhSif*jl&pyo4q#O;kLd?M#"#i^`%KokܚJ$!kɜT²ZK/ڎ2h L,-r)/sC%6qYg瑃dFu#k`;Ƽ0e J&o7;vqdOUvL##[~TmvL)Q3@a z@5W%?Q543cPBeToꕊ3z[(nBAh!U=C#}yr< IMC Y(8Ao.0!Wېn(]txopUc!姚 }8~wߟ&&rU&& OqQbGKI6F33PN}nBSV dhIKe . oweNgϗWMS͢:5Me9āb,v4[\-|⟾^zZ Խf[L@0o_/~0xlZGH:Q 5BgYDl\vBli;`i\F6_cFH(Hhj ]=1$8fl%JGަ쬛|'?q5sU%{by1N޴\<sGBw)>O&jv6ˡ 27dOc.ɴ2z6^X:wJT^Ի̰x3júL6.w7{ʲFvߥK|;0 dm,Bց-:cumBdCJF:,̷x:罿үƾ=< əϓR$Hj2 [C 9F%8^1Uܙj}XɌi.Z4,;Wn/3L3U'vIrb9nϾEKݒ7Px{' ߼"kCLZjos6'(VSFf%Ikocp_Tg'VOA!7Wia*=QV }aN/<]^yHNeo{mh_ESbƠZ&ߖ!riZt |nM$fמO #f>b٬ln?`)jLcrtfT~[]n3_]_σfmcS~rgT5-rR%=Y$\4Yg, L-@O=CINW@d@/vLSڍ޲^Vp ۂay=L<{]B]2t=Hvp}TsBuU`7a2_\`ј?)  ډ{.i Z!˷݉kUG3/8,zS%XFWeib+䤣Ơ-Hۭxfujdj\BVfX(ѮaL->tᷚ8Ze3<=g;2P_k=4,yYwѝmE服 ߓ0gLkcDp*+C=A[NŁF.'O0}}qy VNVy;<6+gӃR&q [pLd|ЭŽWLFA&=,HBz4hykM`^I޺ܢńR!1~Ռt0JOC5*ӓlA0> jJOcף|i^c}Fw9NEncy5*Q%CciGDjvBm66O/8N[?r-z\ _:WpyKD#Lqv7:芐r/aeR׿H W0#L291Y.ui\~1h`ʯY urA&_,&6_.dz#i6F?"1ooi0Su) }܃Nj>z8d⭥`Yr4+!)ȔسcЪ4M*}N{T/2aakVmb7LDrFĴ'XLzCkk/@Lb ĐFNW: SB],2&>3T}ނ=lͮs#P>#/%/|%ꛩOT/ᫌ&xKC&ń!AP['φaoX$|ﵫ26epyW큲Ltm, ihQ/3]vH^'^/,u7"ڈ mEN gJ.bT^cX;C=hz]]YRV!#k06r `FZMRPĐC޸XaRFɳ) gp:3 KyЬND!niv: f"JQ#enL- /Nu{q_ 5Z{q@PțЌ>˼]-wfO_;LF;ۿssTZ(h W3ŪgRh@)2O].cmR?]" Fݻ|*6ڡgٿ.\QY8aA(S S x@>ȑW ۔21z1C݌O<ݦM/ߣx!6FԹB,Z8sm!b Xs"-OQYvf_e] %^.>9v Cbw ڎ;viq,^OW/$sU WZe1Jm@pKL+ؑh؃~4#cQlb,) Z 'kDH"hPQݣ3G5}rIy'V $w1+_.Vrx7BƢ0eN 7w t\'R,31;z9oZI) mnRB܁;OzUQ _;s\gi;eA?zYPEUE*fXeo]BtFp~s\B}c-@HM{;JD)xg~;bFApB܀[̂z0gAVL5%0rcs7_ukOCIC15y" ~L8\"a9*{:l|Hl~qIҤ`m|<KģB+v:[I6ZZUJ%wt)OaJ3[kd`ϛC/6ln*o:4TkaBSgÐa6wr37~1hvg= b3 $Ko m8qu?? %VFpWQV0P]n^ ɠ"MIē<|(Uܥ3kXa.gօ8u+fIݳzo>\ЬpF?H"#P WKPmrk4ڡګGUYaGPP=Ǘg0S|<<@?3L1mƲ֫H[߁t|To3~u]1qMK c'QP 7h#YAm9ƜGfmiwJo3zIOSozC>!-enE>6) ŸhPax- ?L\)#zk"Yi$>o g*GfKL0crwl 7Ltչd)ql#$Vr=Jsik;$lRVۺ@@Îߝ gIn̝}`SdΘ$()IABMyqa\{왑M=)'%[F,燜s-cy ,NAqWhdp7v &ݔHHjQ`0y7CDTZ#_Kֿ3h;p?8& ͼ*Ы<8'E24>._rŷH2,.2<.:mN]t P & {7cRYw%FZ603,Gp9; dJg\Hҳ9J> 8or:?7[T`B-prZH53- ,d-t% Wf:"=sW tupF܀]G1/6ʈo2 uGE:b~U@AQYhH4̋vy2Fi~vp<p,1)b83DĞ >P Ἦ2Ք>Kzr5\ ]YZGZ|oTerǣ8$=S&/?G~hӗ~9D35)!TԌ4Zkm [O #[8YpZ{vl\=ݯ໬۠{ŏZ-+ MˍV(p,_qYC0sD_ ̖y2>3a7`wwrǣ(N?ߚz(\t,Y{=%] fG">>X5SIlqdʝO3f%J^TNELP $G'OdUa?"TɶhrvW~,ˠdxhʜta) IC{ǁʇ*'IT*t?(XikҚeKuRs)~&SfC[1;4sTD!{`)EМAybx,3(.);X(+T F3Dmklka<#2(z$#{gfs 6rG+A <>N x0ŷ[J'd .@jNGciל)BD?^ \B't*j?ܘdVY*Dնqfj̀?=cx5_ R5Ye0QFDRƫd@p( oj2J~?0&`F\d|N 糣uLЫ_1M׆h?:2bo[j+wSRm^y-OJڔǣPm ,9 [WġiQ d+mD_#2y`-."& ?Xr \/++T}a@|!r{`Gw&q! c99 'LiyyK:r@V_?[kvV#z!XWz+Jh@u\AV$᧬YVr)q$u ONo|łɞ};,-;}hu@RՏc";N[҉Z Fo<>*7!~>'ݗO9Ѣ߳sdJIxo=t2z{z|{1]sھpv%Zb?rafo5;0inң #:cYܛwd^&X@IGr= ěP.l-$D/\ kGLjDIUDd8$;;Vqz(%`)Rbw $&.+A9=#ڹ(.fjw6{Em,}T}*0%\-T =CS#vWV7\#S amY؀Xf/ޱ•[NJe$ ZY5ľTqv)'qD)(W ImUfc\E;Y~CM]hQУcyX0_䁆mhn\"L",jqv`M5k$ uQ 9R[G֧pUnOB݃Myx'&+k|Dȩˠ'^z>Pr[osT Iit{: u]3/hnY=Dߩ=B G˴ f 7^* 7a+=PMlo؅Z^Yjc 3l8*.Us\s= eשMAm v􀃱*ot~OPu9N( Ȓ`0,ŃA»!%»-mf)^:qWr"Ni^4es9a'r QuAloB_$ocU= E !v0D;hBb0hCf]ߵ_$Qw+ǭz5{;r.5C"HiƪILjG׋q76Q+i{&BD!D$ SFN^ u/n=q/#@LWOu#s.uڕiE,ʉ5ܔ$1οu^.~_|Ă x0l,̳̆oWN#vJb q2-񂼉XL^s[n^4k5|) @uu&Λ9T~c&Ƣ6Qց).n%—CVe ͚)Xj.2Xr9TWa1 2hq AFR!p0掿M*5ȶn 4<{!kk(6oiDo.l#ڶAZNRh(R_l\V_ OT1;8G4\JZ+)y,2 '*Jʹ[Yôʛ2ϽmxhҰ9 &00)~dU$7BXAI8JّU 1КfX(֖ {MUhy>*bQB9y,te\+,Z% kS5&]h@MdzmkRO>= tb 3 ۾y?; V;y~~ONx<"HE[֊@&c ab*TjB*E&N_(gRݯxYF{pVJ"q~5L/6LJ\Y憺dsg%n 5AX@YPuo;BX)F- .}Т7ĵ/cEgw0PM"|W[1#2skM}永J–Vs2p'*fc`G|!%d!=,Oa2UoYӿQ;QWPf͙6 l0BOR?9FI/uJ‘,^rhu8?6\wI m/UJɀ&0-BN/۾l Xf`|#GKז ]6:G"[fc˟nu4 叙 b"bi-]itcd|YA[mG/q1FvoDG10~ T$6'lΜgqQ0hikb,lN}kpm67;F[Ri^7Qֱ(7"28) m9fn C CcbuI;zlnWpU`lZ`iφg#\1sfqBoo/ä~gD= oTȶx=1ĿJ%>^eND8ZAϩQ d"= 7R1%m}hI)@YVb/ -rL2ˁa8 Aʻ݆O@0~=yvT2D.O bVncq~ձIYb9^uӀttL5< ==~ˣci qQ!~mztOH1?5_p!9QpLFMꙝo%z>JrG!x:c@0b#'_l ˈ9Ԭ~j I{so|ږ}ϬfO&>`Ui2#w[f ևwK 8bLx-jAL|@VI F /qZ&5$? ?=D+bF6;a$Ü4=  e ~}VSzX] >>Gc8|9&bt);\A[r㨕.}]~U0*.ɵbGB|6Tmp(8!HXqU 4Oo͓+߲]2<%a96>S=_\/_mXF#Va׊ev?k4 -Ʋ+݅rE_)J5r01ygҡ8J 3!*&NDoH%ӛJY=k9'sM)F1N#;p%$)%f ܈(TC 1gݓ0౛$@"oҢlHTG玥#i3PtL~=Aؕp Do0wUS5/%l_OSX.4|$.^'led R3;^$g[kUE*D40"׭H?/~y^6 '&fZ,Ѯɣ= +DM1s]ծj"݃_^D9h]mhTQMBk2; ^Vᎇ_ڸ- T+Ya!ޏOhQÙn<ʧ4}ukuIV TC(ؠCcmvw'@5UZ=PDImDxbiH=R*QbW21Hv%DMC7]_'uschSuhJc"B&¤6XTkN?+Q1ߣ{mi>U+쨌ch5d4 9Xٮ_+\"mze+C\!L3e:aP{ W _8{"< M妰 tYpXQu}EUK4np[U7xs"APdO96F Fʸ;*JLG4V z*,V/#7˹Zn oNTuY@ٱFV5kA ;.yBJuc\R7fgٿ&$J2?K)P l0Tl!PUeQMĆ1G`YvRC;[ eJ=|e'E[?e?k;P(:/]0oI_)U7/MQ3A/o]%QAYPt_KwT yA$rT coU)9XbIsp毋bNڪ,3A!!+9͢iKw&w)4‡gCo[c y'@=ɃJpV5&lHZx/}?%= Мiک7<ڴ9<bD3SFOkqqN芷oe]" tR5^@0Ra ( $b"GCncGޮ0j^ѿق @߈< Z+ ^7ftEEOO3Xn-&" zB|i:,q"dQKI=5v@塝V%@?pnG@DD=:G@fnpd-q7A#I3s-A5!t) 'M[muP;jL, &L ҢRe5K_ OxRY-\.' Y]5/L6Wc5ڦڥf#de' Fٹ@*Ym-Uظb,`B4[TQP#Uˇj\Kr7ܴF>ؠsQD8h\cMrZ s~OyGJ_ 532RQWc7S29&7w-xvU 3ظAP@ɦx}5ߟ#c(*Ǽ'G bPl^Iϩ݇xs|CtZUK'ҔE 6iT_ *-p`w`'?Biz*~uP%\6e;{ݼmɄFX ߧ݌ݪª| "HL!!{e՝Ý>@ mT &Q3 0eIt~ *A,} 3}ъ3s"H)84u0aH+=U\pQ[}W7@$#(;50P0-"bf?T,?+WCEܸHSIֳx \xQmFVt؂uX[’6$qRIhpxt*eHh*fݮ,U mcUӫXZĨ8)hRC7JbAubվ,J#^0ک"j86 5Ԥmi,T$u&|0^hG6@/ʡNDy6GcɛG-o,jy:4\[| N|W.;Cwԑ/2.zOa6g_ =`*Σc/däe~EMp2Y& r¼r`zmOh0?GD`Դq9A?/f\g̯%KZFNLiYM֔Sy{aӥt=fⱞqEx󸼍V5Jy蒮0c.llFHHQK.0tW~D*Vsn0(Pˮjpp)CdfQ.'#sMX%1YD7T-VSlɘ2f0cm:!뼥;vn3)&|Pzg#E?t,L8^'YR`Hg/n% FP̒+bݟE L0c{oaU[b:X/ѪZ{<3xHL9=sEe-P 9'dF7er@ + Vtf 4`'5.! (t-yj .|cձ`ilw{xUՒotez:߾C;F3 M\x9WGfQ= W[cjV. CM82֩5[% #\5}66p Cd|r"깴3%oMZM iX|sۡSg dJUٳCi3Elfϵ`F 떎VUEV+i/=d14hXA:bg%]n>,a4ZN * 3 ؀y3ARiZ>DŽK^GI.T1+QM-fX!Ae^_.dCF߂;T28#Gd0m̪E_k]=Y?%b ԀID%HwmS@.Qߦ,zUd/ټRU߯Gʇr\W9lC~dtl~po0!ñr}PEud8c6(lhGr 1t}}mU幨~Řժ[aquִS%{7B0z#.S$ym {a+90,sż^"@ 뉍kJ> NUo mgmb(^G'FޑtUtր-@(A%v%jxDbm/%Ƨ| O5@skyXB|x=Oq}SL`[9)i\=1Ϡ@/6>J$Y縨0a v5gÑzC,GQ哪#V@|34n3 @^K&l`?3E[iJl6u=#/.߀JFN&_Y# o|$ .s3"686ÈfZc&š \/\_ &K eh 18?͆0FJ qz.nU☗1確 Q4q RaGO%B{B+>  \1<] i sXyA^g߭վўvcy27\~Rl8Q[P m '7VXu}uam3ڿxK,2>TG!ƞX|b 93<̀Ԍ%[eWcD{}}!?#GfQT(f |gcVc9o4~Xʚ2Nk`kRh4 I"|[ o+/; "&$ڱ%n^T[EeH鮥%R~)I:Sx[bdD=,9)h;+}HŸ.Dzyy,=ϳŬijN',idOK Mk?JV66VҎ59лdM,WA+ #t2a>iMYTkIv5"Nh:p%Ze>#.\sC&ՁqhlL_7M&_n઀[3u x D+ʕ22H'daZd'3 f԰߼Fix`!_yD"LDz"N4/qd>0iԬ8;X2ʹ=a ymmh)ʳj,~(N:-ZQz){@G0?K͞'@ X,dJ?dg:E)/P~lQHdo`wgjt2jOGm-"9*&(H`ܷQګ@I~XJåYL!T@8](ȋKЧ/KmUqh&gx8KR+a))֩ۃRY|;5EQ\:gsD&vN]~s5U! BGiw?*XRC) b3%56@~j¶ʲX-Bg3OGYl|ֻ=A7MLecd5 ]f}lsQĔ*;đ^I z#—Fҁ sRQ7 jN~Q:}P^eUeJa#r/(׽-+fA.cExzO3Hvw+CiNM"|B7J2}sOTyxF~.;ƯH:?YSpq>8Lq 43,)OkfzGb1nkEpE&M6Q_hr:*7clzA)v;l0S:xh >na7@rpB\s-J8X C ;r`|Rx~KJ$eQ2%H)xK"@r[t+SP^7DhH0ߩuK s*0Uɭ{-|fz-Ƥ!Nj]/7]Her m삳QNau+xy^8}1]D B;v걂HCI!awn r}Hrm6%`#ў`Hw#xWS+FUϺ*abpkg\͚t ]4~Hxէ:8TSIfO~P+މtAAQ( 80/ [ZasQ| [dlw+v+wL->Y )[vhs+ɇAc/[uyܪB^xaM(YH垴[rzZʜɗXRhHDuZɉ<wowzȴv!lw.gd?UBoP $yvtZ-jZYui{!Mv(>|dA2F?ꌳM!9H=߄eIgrAuzNSd6<0Qdԉ"m :FAS{T S(mez'T4V5A/uNy DH#>2 bӹ7\.DģQ&McN>; *(]ŬGD=!j~&b\o""A%)o'ig^ m'[w^CϢ.1U=wG+~?ڛb;a\YD/Ød~G^H-;)4BNڙ27[KAp,8th ;A,uc 6[h\ N u څHnjT|g(a.}2Jj| XÊ5z۵ༀK%(=F C3-7UUqZ#uP)>$Owm^B?lId=R2 _md>REtO%=C;s3!>޺w,$P@Klyp֢5F*Jz ^FQRM(-K`MNq_slEMCe<혶nb%6$(0aP1E"+Tϛ\V}c6ӰW׿YpJ㠩'2ҳ!7΂1p튘Reź:0z1:2pE<ol6Ӿ$eNY =3)k5x/  3(WoAPⲬ!f^.OU7֨~5{!{SB|]&;&>SE;_%Ẍߥ8)~gHbN^l5O%3g,Yv6&Y?:2_JE 24&t xOѦv`&ҩcmHHlZq`;h <,7rKMe|쩗JI„7 6`n߂8V -FFp5Cu?bPn\;J? IUeI7u*<"鉾Eŗ¶ڍ`O i/+.  {[g։v$.szJ :ɜ^\U4{cSlP < 1FW[DM(oG6 ssLԼ+*?C5p'.1nkr@443UQD}Urrɀ~=SؚM[; kMb g[!u*%Wx1z"i_ |I kxz(kz*};pVvе<),55UvX  A5c+GdB]f8d~P(gbQ Q4 hs*`0'}R,qI~E^MͿ'_㦭VyP&C}VH(GzUE< 3L~fjO jl;aC9OWi ʥeDrm\7YJ?ߎҴ4it70fBtB8ǑU7KFB"N^vz`lڝM.q[ZKV⨎#PhR/ 4}Ͳn3ۍ`aR2=O+a_$Jw/Xgi(oNB!D0 ,*pc$&:(L%'-ۙ+VmU8WM^c RErLX|Cx+kc=gx :Z 3.tL'Pﳡ,Dg_~2]:2Ďqj V1񽮚Ly5Uh7јCBRݹ|I ?d3,.?F`PuIO t3 !]?* C+=ڄyi5멦&9vJiʛuyAb#+F8"F{X%[tJkg[#$"Wis1܍s záӮbg#Lٸ jMA߻ (Y89U1 )ŒP~ ":n≅Enz&GE{丰Yv9{|F։Ca%R1VubwRjA-/{zݖWs.z$Hs$жF|P"e25IuAHouEPSbs`;n> a,Ԭ8kK"<)@G1))kLsXGȋP$gq!&X4U3k*zvLPuebaFuY[l璴´s"Yv[_3U!ieBh"fTWCUPFCP{ -^숽ݻ$"Fq8cv(ƩC?Ϗ Vui?9dX8KGEI$}6.K^ 1(t^D0`12vkg hVQL6X0w&ƊBP:n>M y0l}ozYܳϢ*B5">5#ЖpBkڹF,¦C%iI!K N>}ɉ`픖[s }PDdCA<լ*i/6윜^%,c'ȹ3)eh D9cj1261'? AYa)։= ;Mԑp;ъ JFC|3tjv_WM$cf? $_\Jd\UːNU\E. g5@j$m}J9WrDm?VCȄreLқCeXR yVj)Јq0~_z&L^m}˾-"`n 5Nc۶>N#SM)zoKG32 W?B݄/=%$Q\'jt B ȟA%ZTi'^_`RO`_Hv0ɇ}G+2͟V?̟GGNIjNzJRx蟶m-/kL* 1BM`w?kS. ՔC?N?3xJ`cHWCp@~pG7U(:,I్?aV#kN@M귣k(1enK{k=,CKX,H]#`mf#P⼰O3aq@*([XmR 1%Ո ZޜbKqJ;;қq$K/LKj|\L\bXPQ\ >vᠷwcaUݽ!Ƥ-"̜Y-B,YX*' LmFX`}f͔Qz'l^c3 ײw|Z/KW POё% &~\3% ёl5őhS&5{XsC i$Ý\*brl'vJ/y#x/*7O Ÿ]hڱ(h5޽8,&hK0a iQڱOnk>MjXrɞ`i h 2CzVuJe6ikX*U`=HBHv>Pс-zde=.rA+{ m{r"^-O|)2&Ez+({1(!N$a!De]z)<`yt_zw/3;jyϙùdyS?0zJ%nE)}/X 7/YCu|tAU##JZNT;~()5hwEI?[ymeb,n08(m=7l{ W:y'%=ҹv2@  ̾V6GI`z'rO  ܼ'Gׅi3.yM;NL0S0!i6*~m$v]|%Ғ1lEDN0B-ڦf1e -Y"V5Js- qj.RxT";(JQk^ 񎶨fb# J`hv+sݲs-˫\ P1t>ΠTcZ_\ ؿUgVٳgXi*@rYԣLP=UgԹ&\J2-CSe]g]X`Z mLU_zjszZ_0P3"uIr'LhiAQ$UvKgV=v{5K qQы/.q_rE ]t}qũLk}Y]OML$JqUùCXJFPJ'k.W<^ԠNT̐bWmtn=aSQw{:KztaC&uƉXU͡c̫_s{h(8qjzho>?< lJ)#q`IeZr>PVȾ@=oX#^LCdeHާz u3>KkդJrVwBR. 8Ld(:," d@0*&bo+f}\|%8OJIa(RlD`nٝkV˙U'.Z+p$˯vS#  m G0 ! q1遳yE,:  s,{vsł'6n$Qy-5X ,yx,Ϊ[5I ) l|59œt WRe(-rmh P՟MI 0Gm-#i+ {M^MLjf{,*닾:*8|{C~;R+١$z$Yz-[+XNR 3ߴnZq0@j.`h{Xi c"pʈ:-=ߥ[ A/$'r#ǘ0_bPTWŰrrg,ƒb{A~&7)Ш|I11o/--M =]ySa(@b %u^aKeJ-!'mjD6X7p=~!k6:![#(iюŸJwgT L[SW{zDT8^jqT-ѧ_[u tf'uTcUw% r&J e74Ti3u$U3ʪN"a˯wNoj2##c3K{o^]1SS O4w2D)9F,78TiOr 3~8"0rjÌ_FdH ˢaDNAAMZ>RM볛R|j9u[1}{rT|kżrY(MxGXxpqNXI) y, 1^aߐ+EuxvHmq<\bD> V1|r8~z2d_9돖 W䔎}*OpPܭۀ֫{lCD%idí3>Eѓ,da^Xsh xHqYN4\?8&I/ȘVP=]ٻF;QD" Kk yD#%NIَ3$qu\0qkֱ(ҵV]f&$n@Nb>Bʢ,&cÝw?x=m0bp(V[GSՄ{ozLߨl&H' P-쬺 ^rœ3rp &-8V2%e2 ,(?RO, S$3DuQuNn U3za현2;V:?|;U)",Z `]z>tށ=.Vx$oJؗtk""n; 3g~B(@F#_WN1cz~ɗw~ H I@͐BmxqQ­:oNυi<r#TI`kޕ#QvTP8lKfMҰLc՝ F| \unj雦l*Qt=8HXt02r{ózGaI;нEte }0# B8l,LꐟPA r Xɡ̐(XYa|yםE?. 3.ߧla S)Q>ktP- Yphx{ PбD3+G,<.֦ӄ]4E_g¥]8B<>_IqG.0Fж?)XHzI[S"Y) }~o-At)AQM0Տg%+.SUcO4n@=|#tmeK_ lJ"⸌V2,?~PSӬ>kѵ}40XPsjuԶ J-Q`oVA-`DLo.p\aWGCbC=Fe9!{6'oȮRᚮ?q1QaƆSD>eOT*'&ǔ,1Z#(!_//CWa9~'bk f=ayjHQHBv=9)-1y07o᭚*ޣ$:tt(5/i4yK 28% mϖXPԾN_) G2/_]N_GOY#IjG+Y6>ziu_LLO1#=+?rzCӦ(d ?J6Gp .ߨN!#&Wۑ~`O/ d")ڣsM#4?TkD>vu=-&wA7g2zCUBksq#qd. ۔޸|%-t^39"đNoD6D>0\*+[! Uacsn]﹞K0Zqz OkZƃ 78i?7cb|RѬɇ,LPLQӟ|sˮg4%leu! *H]Uq c t-JWV]&-?@KGkP pz:UPt`m Dc!E]Mtk\Kj(֌cWuy mY^bs2䦱".rO uKhFUgDĿXPR:;Aԋ2Ѭ|-8?05˱D `bUC星oMiSVg Cxi;'_/P-U?w'ydT=t'@<(F9o%ۤ8'0Q!H5@V>=>%_ |d]i00*F1kuE])OU1sDI4ƛd{27MV{8LwYQr0M_[ElN[N3M瞗FM3f~nX{ɬħ 2 ~{ fj+gnK=A3o͢eCǼ^\4umfd~zNS|grZWNI6+~LgQ"/:SXoQH~9˒c^Aimy񜑍̀0D|[r)&2)%.\ 8΍YlzW=be ^xtX⠔T嶠kY!ZO4vap1jԗ'ٲ=FAe z&H*JsTӬ >|?g|ttYy8TaMJ11Nc&Z}moa8ta{v n ,%X.oPpƮm@~fHxIe$Rv͐1`5Ai>ӿ4B4 j^ee0anRcߣ4RP31jƜQk'kKb$N Ȫ6)'R7HQ9HJw9habÜʶG L kh[ҙw(}=}ﱇQ1R/$ECXz#L mvKz3cRJ`jchNtYh4GM!m .jy8 e =YRgE1) nXhf, H@+Bf3qOnRKi_COdh\]hu݌2FζM/z0>c"ĄĊtZ0j}_BuLTUa KkZфMjv-SC[%nѷcnQ y_qqa;Z S>Y5,QGe#_:At&\#seH(T芊Hy.PS#4)O(M D~ FIEDPr~ :ao '%U0F|D]G4¨#Q*OV3x,iU"38&3KGxuJ+jR5}:~Si+GI8TID"Bwe7RP;pFe W.9 tsy*ߵ.w" m>*0AƛxtXL*7z}i)e?IE!0=^ w?e$y, "x|*uc ^RG #RZzS]W9FBwX<﵇ߋ& V=WH)<4ra x) ׋UzQF ^ rېRSr -~fƒFl{'3$hMPT2g,RT?"P6`%ouY~m :B`` HY}̓iޱ !?`! Et2M #%x8w^_rӠNuU'b>~Dm_j&ˤ/yId|BT3L?^`d=DjdU_ >%V+k.4󝆻m K?63Ru0rԩ)Œ@.f !)AYX"ґ*L#}A@*8x㑤:*(H>/rB,|q1jr:VpY眛>f@%`}vḬ='M3 QAL]YlC\82bel]|$!-ǚV曪 f{ӱImfTȏyMiL;fMaf(̕s2iQ=ݎ(@|n޼oMȝtqPw:OLJ6i՜G,5G`łt&X**>הzvW&/xW(%rXvBM %TW *;BuJ`6:&~z{jW@ ~WWU%4ۘvaC rW^U*2xɣ7GEdl]lޅcnGg2)=_\\T8ǡdLn:, QrϬѤjQ5^җi|N;̂45vYi^naJMZS=ĚopoQva`+jV! i>\#xEl;O*Qh/F%{*#.[L.+-䍶eL+e[2j/yW$|d##E/F؆WX[)BkcϏyh.ޏ:6W9- 1f 4gO͘QR"JHCE]]1t7wJq:es0bUhxP]F.CoGh?=HOA+0OR5IV*j0`\m$8vK+x28[t4K3u$(( ".Uihhb :o91o<1- F&^+zׁ)Rzڊ``ZōOM3 liI =%<șR3Q͞y= A 4+ґ\ĮOb6?[6[̳4T=>"zI?ΘRk_@-V1*HsCTO<>soDL-n |~*!_?lUNDHߜo-854ΰ"Sɺ !RGB?kz,SV),d[8ŴDW:e&6S# ;^i, GRdF'/V\~TS*ÚXh53i:G@AϏLVugQm/P1tկ\mWz0AD1}u9&vSj&]`% M}¢tYįgD+| ЊP {FfV*4*)CMpT kyZb΀eI!:ͭRw"2kHS,FB!k]bt*SwDYy2^aCϓ'ؿJoi>E)/4AnxƱQwu}&UWK.W>.-c<Ù_Wq?Is I)`)?!wv+5cLi)ߙMԕ/x#g9]VXN`?US0VDWq/TZQ坳s6e[ ֵZ+hxTOqVJSÊVeZWvl{کatPi֛) ɷHGp 7pe/ȏ;z pMVZ;jKr@Fі^VϦ'ܠΨ_qSqྉ.,IɭqH1/=ӧ{ɇJ#?0,[2p Lsw|28& >GdY)J4P2-B;Av/le 2`&^ˎEJ!K1ZpUE_:G~8HK!eׁ+()JpOU%?v,oxSYh8N&xsqƢh ^)]tՍZSKF\u|8Hjh[Y3IdYυ&T?Z\ojHĭ,WZ .CtUFޅܿvW'yA}=kg&UG:v=oj[jn,iJ`$̜[R] VBrd"r3|= ĩI7--2,^)/1ۓG^MXv*`VH 8SS{3 XC f(15&rj%r v2KMuQ?z}K<"ih-O$( 9ϴj^FA-D$}ȒW08YAǿF'B 5 jHUրt0071(XYE~,L7UUg*U*%uj3m2V۷_}P)4J&Ib$A&pXmΤoq+,UUi;z9,z^Y#byOľ ΤnZ +sQKS31uλ&j`W7>vٮֱZ 0#kz,=0 (IƯϰķhμLF3OLZےTTKO=t]&Jԯ "Ni'y~\tP[e0ayIQĊ_\bzn1̣[IԞrK/PJ M,߉ (\,(U*UM%ƀ DžE?~Հ^M+IHnul/*pDKcM3Py/jvo`x2`8Buz^,R'9p70Y5b5*MZl$'>8;]W8g%qفp6z(m1&V<򻎃{pH4b $dD-쉵ѐKr1Qg .<i m^ ,FpI(,boJUW0@196F5XYS_E`Vnv<9 n دG{9EͫH,3 . |@L\ Mu̡t{ogJ0xN  NOX"4Cp5 w j o?gr$fk:f"A+=:[qb~*"Gl)+;ډz- r\LK&R?\\vϦ0}0H' rt"scŔ4u6KԧS4:W w6y[p# f)weyPfִ}EѤ(K$tA]6G1z ,z.Mx[Z"*_ Qy))֔-h4#7ffINeJ x9;&v+&W|bqҭ08\-tT'ii$,ۦ՛͓[QCwd l0$ V˲qϣѷ|$/{~fS7KcΠ[3K|y MyBD*J2jmeTxս0 !>͔V^xƁЪ*;h^qϕt@TKtƳCa>} )*"Y0'0T7+P*ţԄAgH `#83_ȖTI$k={M lSo[{z5@ C~W+ɫ?| Sմ--1%2 ¹+-JQҽQKgAj #f AH1pR8YFŦx_m0/Dc.mqU#'igl:/&XofEoyiě㼴a#B{3fY{ oޕ7pxUV,sjeˡLn=u^%&ZU,#)v5v jnu55=ʏ %r,Me^ւMռC$e)=AumFV6Ofk#Bĥ!a-#?O]s rD2ZQ:")g}[w9jP[M0Z 92F3el8QbwUTdnۅLϣLx$eZ)vޮ?)gtȄrPVH?{: _jٝ営#fH"\-|-Ш9!([[{ZH]'JuB-0Vup^R1Qp`(л鯩*6E^`?ٔ c|3gF-#i2og<=Fîr{PƮ$a.Q)gMؘ͖=;n(:2:>M=185.kF~25r^'wb|U(5Z/] SlhɎ2б2l4Ŗߠ`5vŒ o,V/2_uL$ pMxnxzﵢ/wOm>;Ji7˂y (>Ianaҷ7lnN\57ɯ4_|Wz:-YCi1۶[>F/MN(&Be+0+ a~0L1O_0teP'@![-zKa(/4ƻ05m 9*qiĪ=( AmٱkN-C$tT@3iKM z˲Q1]"J |a@FNN; ^tm)j"?WA'ӥե;poCG^7`w͈@:/p1LԌX$vo-ł : &D[Iqn]5ȫ<w'k\3olMF.`Ēk*09B{ht$49 6rP}# `:g2y B PłZϯ6>vy}?Ov7qgb+mzNRa*7/w\TnWȰrigƣ YA{;C:>&Ȩ~1<=;wخUut@5V!׶JȮJʯ颱D}CcH˱4/]1o0qfK+ڃo lPF"X,n,T>3^/^SF:xwJ#kEFNy>W,F)l yؤZ:f~FJݥd }IZ!܍xT3eE.&@H 3L>Za1j#^?Zy[4|oHZ}Pʌ x'isQO7C^69Q(fQ;K;r:HrRYypb*aB3ap -!:O4=\ڂ1WE8xͷ0``V4K 녋pzG_f ,WmDU@Տ$#GL *O]aj\X߿)c.D=/䚼-Q):Ȩݏ>!-, ؋QP)XɕKm}#W]BxQ YcNsOaRU:!z|M;auzxt7K yBu$LUeFD&`R { T .kmبIU\[&E/p~9ipl1PEONtltf*cU[(7{c՘-[(<4,j_*Ofne C'g4 ;Pwv 2uc@]c21zqȐс'R$HJp0UիtҘhD73)eO4sn=xU7sXLoYv6ײ?fV2{hdD4S"ǑYB1饪'pίFͭeyn"őEvM STǿiHRPS_#%nݹi4@'MAaf 0U$kAW"0lJ^㧅9$/?Gv} \` P0<1@/wݵ G0-ZPDpb+R 5r4->E\[x]*Z PFGY>,}rٚG FybS"hWKrI"&қZ4ua_Rwb(jph>]qS'0#.+:A.!I\Mz_O z_1x8M/c8>n2U?}pK.s>,L"38лЙ}bP&z\eh%ASۑzl|0H~*M|vvNV q5"hbF^?+j•'@F/Ǚ`Jr?{pMiʹ>i3фA:-AxWpH+X9ltAaD2(01Nj%X,5:>d؃l3N} Fǁ%FkbKCڼpR-5^'/ژQcȥ!Qdu$5apsvO|L8(i{7C? ZO;x0EtD <#092(FNY\fY=7,2V-mk#مkl3@c%ǩ^Ht\ER -[i 3z p)MfLwpjR/aT(^$ģ䫂WX?ޒ-#Ԯ3)Yѫ8Q[5㈐9ʈr"Ӯ7l.[}mΠLq{yIi,?[kW֨.&U csXF@h-cyX V,(u&hx%~kd&¹(}&F`(uʳ>^[D }+830 ~ibj':v/rf4Tq }k3|vK6k5ϩɪFkvPDm'4Ya?S8$3 sv)B\yʌhs!d):DAf j|tKn}vc@D9GxR0LVf?eD)\w')x MDR.q#Th@e^?f4MiO;AnUToS~'5dDӰOX^ƨQhxaawU"rANYcS7<4'w$MJp-3yb!fU6C-q[ݮ>+7i;w$At;/P@(Z&+L4!FLٝl|hؾm|#|x]NCbh2cPgK?Oǒf "Ѭ>?r:1PC(s5 AgNr.+Uk3/7թ[| r.jlRmm3sH>Wȍ)eE75|o4HޭE^_N5JҼ:[w%bRƒAf^]L1v/q4.3:G4L+#Zd-f&证s0x0XGې}"~*f:L6cĕep6V(4 征FsPU ҲWs%Vh_7a{O zMEP rzt{I^au?ܐmq˝VrQܳCuW{\-o)6nIpUONF𰵲9D1CXXaC# Ji Iqtbi=1q(8:jWn~}٭B.1璔2. IS"߀Թ+MH'>jru ojkO4I/p׼]~#D'2-!Ԁ55cg0hcSnS74]x~͘F )q*?n'p(^;& 1Pj$uD+qO]~jelL+] P{$ o~_ZxcY\ÆڀFVDžH4,Xjdc/__%CH</UPj6 {pvg -v*9]}eN5uSL.@nwDЈ~vIͤ'Kuyabp?x=)Z?:yѦB|;94*>,_;>Fd#;&Q2q1$#V*u=}d_~Nv7v.%EEgwM=QDlۊ_4IWvjHC3q+P\uS؆k:g߱4b2`<>uyE=cN?Xbg~vEӢѽ9}'-`f٘sP 9Z~S}z~9J0,fQ.jp3+Fj 6ٍ: e Iр; }ȵzp#t~؊YCg%xh s+DR 'f.ť,|s22xCO%dv PL^G)&{d0OF\?>0`ɲFR/DIgc|{mӱUvˠev2=T+P>rJ10U۷jIc}y?::eIsY[}wʅCDf]f8C3잃U/-6f4Me F #l>L,v>[JG̑m+Lh$ }xorvGA€?SyWxRًa 3|xtQ'o"=B):S(^r8d QlR+fLE0&š),|qWk_WVq@ϖ z(G|/d$?ԥCŊfyWy.ug`#e rչ) uˢ\bF6D݊<m8{?nɲ"1ck!g^B'֓& ,#~WnǁOO +]Ȥ/YaG\7Sd~فaT&ϤDwM{5J\)W>1{w ao{"`" 3.,`ˈZ'WuδQQn,'k,̚ X%JKcM(U]iW}%7X=|c& NKֹѮ]ZlBR';h~TZX?0f $ΕnBwqT+M.}dP;wd*o`kֿpo3^ׁm@_Zր2-*wsVB)3ʽ.B0ޕ(mm!YZ8i]7Q-'+2[,m\V?e>"J =8ڠ</b%e-nܘD~rUD@HB=OSɷ/ %?VӉCsh+E=Kj+5 grLqSC)Dt9ӄR$c jrd' R! cD†!pKx缌k;ZZ<+g.x]7< s:ßt}%sSF4tu@V-@\͗PL[Lsk؀k)$UPs'cM%˶+$JP[X)Eޠd4_Ŵ# ^{]jwdh?EUipÔ;9[C":?L ξL㕤We,qƏwpU8$U5)\s2yQw[b<~\˳kSޞLzhźfI"uxF}%)Tzm12aMC)14zLS>)?DK'cJGgJr9݃'ʒʀzѷPG ^TL_ft4 `Ѐ^~T4E 6xq7!LyQ5쑆wL%P5\z8?ЇCb^\+θ! >ʣai. %c88I7Tm =<(y(0f vB׽w4C#ģ Sf%M*D)9 ڳTcNc>#c.}W4V吱 $D$3\R}vJpbWAu/#&Wf'fGci_>lCjmۼ8 m)~2->mSjSObM83, r]Բ`7/8*'W۪>MZD"10Taݯ_UbHa21F@R%?^'ۼ ]T^.._]dzg&rQ#ϡ@/o\7)튯Ak\ȏiہGA} ?f3AauHlyP>r0!$9.Pzn+ڭ\pC6Ƙ{ey ;"MYڀ<}kȣrB :'b o桲;\-*^,$zGJ@*\y[6;HXlGxjgת[#mTT2R(c{*k°(̿Ԋ;n +tG].hngbqO^$\| /p74KK '_ȫkF$!~g%0=gqh |G> >B8I߫D섊s<C'&#8]j(l̒FH23sqYzH4]Cw,m䢊?EӍ!->Y%,\EoVN,09PL߸eii QHmȄkoNu j%MP$ |e`t0gϦ cunTU wjRiys06S`Dݱpw¯NVx'{?ndAq 38Ĺp=Ed56ON %[ܣ,Gݳ@NC ,VLSRH MqI`k',ifHWb ]P*Ea>W: )6AB ~)4K4F Wog2z]Pj"!2܈JkCi&`fpsiڮؑm EY_0owN!s {8d:Jbq3f]7wǤC V#HXYhLP'WB'K}gیPݟ&7H1FsW-Kʲu! yp{~< kc?=Lכd,sp}}EeTk}]F}*MGo̡hG%8 v7EoSUXHGDy3EoLR'\]mJe:z}n򷛒#mwJʴ?` gN;g1Ȓ˞ Sř.yo98ɡ_*Oʼkppè<}oDr,{@Zc#ʶ vRgX}_ޟxC_W.Hqa'Tז0 'i)ѓkօyXM>Ϋ,}8G@ؽ^-fbvd˹b<=v/(+'1́|KNtOxZkW`Z፨@h ץ 4!;x*s3m揅cT4Em }Ǜ%H A4mbKL@exvwZ [ck=XIZNL wi"ba` %}|j9Zmd+Ȇ:_B妈aMw,1nZeRy%/RTXmGm1`tҝx%U͌ڏ4{_y/_**iW1g`W%cN9sV6j)f5ϭoH\G ќ 8-&u^x0ىm cLD-!@=7lėy9fϖ-CW\{*MPn&F:nU|a-HB@V$]eHyJDj&7 ^8%2r!oUŨhH#b>?ua:DIM&_&PH:Lēj\_o8`qjKIN٬AcQ$pkQiRAjr3 Ebs\% 罸!68y(H(AN2+ΦU]ci6˙Zʷ DO mL,Lál(G) )JإiƶYnNQ"fFN9H^׷Rm##DVfY, ke!ʔ Rn<م\eلܾS;gٗkgYm4#'CA`)aQ:Y ;YaomP{[߫ɮ"'w ڏ)~`}l`/=ÎDaIJ"VarHPLتi5N9" M^? Ł$+.s$ p_q(~jTc}Kҟ&,hET_,<碙i,5S .oe8(٘q'Axo״A/Pyt3yM'qJ| \XGdBkZT]!wvORQ+`xJ5A vv_dfÎ jHQQX"1Y 5aKQU=fq[ָrTuW}g/8`P 7!b&Jl4;lP]t3_GjJ0G]eL64K{bۀ$)JA_I&; fvK"GY*̱h~*h̄ 'yتwۄ$qJ [۝og]h@;WH3ݛeaKȏfGl2dԊUʋ9v.(}uJ]&~Պ}ˤ wmbaqtWrZoz,y\}V"mlzǨ.,Ϩ2bӉ& ~#KEN/ 6N_tuݽli2-czd@vQUXio{.J%qR-͵J"YNaE^YFA4F!r?|M}Zn;ǔ1m,7_b5dZs3C,ϰ /9/O\'u 5d5ݐȭ'^+Ӌ&f@ZA3aIX>ym5 {m\a%+3lj"v#p}`bxˉ\*,4ԹGb"ݩ@RƢZN߿ ,rDr*ŗN~BAɭby5lq./ cz1G~y?q'ja.^Kذ 4٬oY&;SưRO㕣tZ '?Km8J, w9?9,11.٦5aǶo+S3 (2lՇ"ߊ܇x:+n'YtB3헬^O ZʽT0й%z%=e◒e0%.HWk.+fjL,7N;Ň%MF!a$CSOKHhviK]7Ft@mSA!hk9.mt2Z'麾#rx$W<J */~GӬFe #%[;\C=AtfB4r.# cDqrGκelih0TSօ'z}CywY r0 k.k 7I3]]$4 \F=C02oul$Z$830(Ζtljk}|dwlӓJ) N_ pRuwM#9$Y/+ՉsVh6ֳa xa(727a!5Zͻ_o\!f-,$.: #R&YA`j0 0޲9wqxtlÿj(P#*g:hʇ=TI αy /+ >i5(X0/opfTЉm. YϳhdBo洛 m0aBY$ B:Jcih"JQ+ 裻8$/Δڮ+&=Dx1II[QO<%kC$i,>+;4 '14WcG/灔jPxAP>T)p_S/6uar~\u: 95%D9b!3WI$Z/821MёM;a;2k+!:F޷Vלi=KĘUIEYedڑ(5 ~I׎Da\"ۇWdQ \nCӳÙVݧQ-߇n}ϝhPc0|dFS6HEI$Cff,$nhmU?|B⋍^]kOŷa}ι VaƟ.7/֤7b8j)xI3ݵ-+h%&= ld|/KWHoynp]C!x(Y4X?9ꢏJFS˧f;x,+KK.kJ wpC>G+u:9fiAGA(ȧN#`7q ? D1]:4H8<2@ͤëנ,ɗP31r{e; y-ߐFjNlFq=ܑzyxn" ~FB_U d5z8\'MXEN%HGg@]oFGǜ?5K)!V;FxN 06O>=xPs & d\ Rp 9 w% c]^} yLCۈ9dM<Wy>)J{ʴ{N5—7Rو5eTx2\]CNm䮱!qy?LJS!j)~yJϟ̟ZKyԊg+rTk;d]ot$ k,8xʶYihΚrԤzY{\>%goH4pLGU^>WFZ%VeLD]Qo՜Bhό&)se}QBKa.s F0Qz%z@4P 7뀧NvVORL[M,+@dǛ!/"Р`wͅWAQ_װZ$qdXx'%$ ފ"xڅPVZbX)%ko1? K+\&&)$#!=tWM> zl c{Jzo,VQ$tjAH2גi/x .ߣ&j:uQQBlȁw^%c7ҴKkZڻKW+%owuQrԃ$Z; ˢ}%܅dHRߧHu蕈',9&gG<ΛÅlrYi3Kz怇<"s:Ow X]K1!j6$p%E&NR a}db̲ez<`L' 7?ٶ&?V-dsAnHZ ҈!3MeK:|-M?莥T.8;-Bĩdg:'yNe,͓478NSW7mՂ@sf ,Ƈw>a裾WnAJѻP,M}Z:A;'4w j_*,!BEd6HP F*IT5nZ1ͮ ATve$B"r /2_3)cH"_*![ L!OāH g404uj8ntu(zc0IpɬLCE^9^DxVXۦ9z[|2o-* oΞJeC\z;F.T}7b,5H,\ԌNluJ=+`NƇb'(.Jl|P%K#ae /%N ?e6\FNBqZYӻ_@ۗUѠ3JN!%%2" 8T ̹Jv5J  *e}ݩlF\y YK:q!sJ.5`Ba_0M,X.苕@a'moOv38z󸁾5Q{u|T˭Us31_nEtjT y,t'~o#üT WqxlOK 1 (6 BS~3ĥ.Qh#s@g#+/ `ޔsa܉S*"=M Pu\z)<t+tVgb亓}Y&V>q/;)(ޘť3k[m:hZvTG]"D G.%6 {2r߿>1Rrm\fS!AJ mvmn ˺#=XL~f^HTu$.]QAƕ@BQ{E{;B}d1*̜-X07RѬu/=` 2 ̪opS urWY< . 'u050W-qvblL⸱=Yq4z34$sNo4N[eTюFǐVou?8Ι'M-5oN5?HF m.(*&BZس\Fex+`Ԭ(kwD6T:KXy% JM&z׾RۗK<|MVj|p(szԷ ޞ09y/u\ qpsFte`K"PL_Wm=Xnv8 v}hjfrʔML֩*ghaeS*WPsG' r.^-* 5Q6jTQAp 9W TuIMpۋdέ$C|BixC;;IxMm(ggㆀ}:.q o,[KMD 9!t7@D0̡.z.1wf!Lׯ)(cxQnbtëYMA@3!FOa%鬺g,a :˱Ȗrpy58DmG~Sj?$䥛C(}kJr{32G-b<4nlgzhH͑o Le2Jړ6A:˝`"NM2Rb1mTֹ.th`=߾"-I「^h*ዘɪ2Sx^n\>TL>ߏjArĀ\ú5Us G>7c!ptAUIcsdSm)Gs_BK qm8ɐQ|f Sr{u .IeC,S?*=7 ,=u*.؇êy}GMS5Y j ~~DgX~ JCf0r G]_v ~-c"(ړW`?i8T_7\cv4ahlqBvY~L"+0x}]{ tBF<щK7FFd>/%Ex7O7e{~i5yuF> 20q%}4d[Sug%bP h߰9٭;].-I/U7,p2Vc`[x쬲ei"dc}i0AX;!c["ǥ0//k!oN tb~jY4[&AVjsn |rDU(tzLzE}d1 c1(ZZ 3T޶|h}|T 8b2nω$1ux*h-!kR݁3"_LiG.E"`JlKA:GҲ[BV^qSH<ݒϣ!T ,lZie[ 4U|%em?v9WV̱:)o\9¢#ufH'޽9OĎ AjڊV.aG6%rJbjkc#,ԃv r:hȦJ`_at!ݓ5#ԍ ɵ%٫oYx/;.;*!>dRi{K5?ϗ8k捙A1Qh E+'Phw3ݺGa[$)dvc-Vz# RQ+l{e% 3 SA~qg AbF $jTu?mSg1s+O8 jG( ¦S.g=0.5}GO*q8dF2_TTYlAC 1EYN,2g?. JO635wٗKۢXtRx zuZغy#1\Dhߵa/m߰-+Q-ؑ7doe/u)ycz>IX#7=5&. 5q g ԄZƺ42Xh$ ? &N 6;*'hϔBIN@UҽHlU!Q)Ŋ_e 'g[ȑT|_&rqRͮ큮y=1%oK9}/}rRR~Vfhy9{WG\ sqи~3\8b;|'Y\2ēخU{km,I 숓Fw v$YqvE`[׵nl?˜b(dTm| o{H-LBVF%V̖owٓb!k>395oZ?ʃkī~3*f zp);qXJ +E[f! \O辩 ୿8i mbU"_i`H'WWR!nBQ*QcqBט5P;"H{ڗ(W#zR@۩$:Q du `.̩70"J\zʦDPv9L8D%mwE[(;PMrky4uںF?qıKGx]-KyfV ?6d/˭5_ @0cum5b=i% LE< {vWkU{4 j,5Í,:\z=OW:PٍGԢkSNQ6d +`OKk񠺥J qgNW澭@>mba[4BVO\cF݆!}3"sZ2}P Dp#?i/:]"36B &YhFV@J+/ D{*\}e޳Q%®Sj0d&ro@P8vY˜H PȖl Ll/YTE>_ _ш9;8L7 %9gw6&f/Wk5y/}x( Tm j9ЉJi樉gxq fґcZmKCΪo+zu{v:+V̈?Z(6jƪ%w> kc1 lHy:myGf,Tjb _B;=I"io vq8DXlt=r&i>'>~ CmUPm.YN?q`c29ny3'u3AlC>lKNٱ%+r^r-ᛷ+퇳WG]JmNJXŠ+|;H@huBeRkh-b ޛO cGVC^%5elxwy *Y|^ IBqqCȔ\',Vۧ @( $3/|1õ$7UTګ:B (jnMR(kN.ɯZ{MX.rq"}@r<;Qkq"狆Dl6h P<\FyO ) z6atdItWQw gS9s7!p(G~ RZ+WP?L~$DXQr %|̉̅FKEbPnh5Z oIW}0#A\,HJ\RY py4}+νt+bra&+"V=\u!tElO @ }k]p$'?XȊAc2#_ncQ/b&U3enM<{f8M;|)hoO}7ڨkĞS p!y~D`JfYhW7 <][nΟ}({tѡjhyr5ts+ZM^ridJ8yr BcA]rL \gôE !V> O=ԛN䏢m|{}lNo/8ČĆ )PBcqP|E iʈR8]ZwXkB^Ĵ%$[u%(%ᖓ3ݔa'JpPwväW  ^ ]swa']&^s2 _dc~dth/xJf0ءBku{vUZzRbH{+/p&8T^fD(c{7gH_s/.+<؋z^|b*Yzθ:]P TTPE?dKy"0F}ݥ,TQ~P{78ZP+8;HL8w~l"ѭlDrAx')7Mږ 2~UÏy'&YXD4 tsn|Qʶx-ԩal\Z=U4kU̲<8𲹞zW_>,`ҎpɌPZ v{aR[M) भ+vZXCuC9Ъƺ+ Z@ZW; 0cBZltYh*0Xȱg7P>I-mו| D!o 7E%-u7,Q@5܅R Inr22&e/^/vVbN%ޤriӍpQ3c .I-XEwm.Fytˤ-r_)!7hO퀴zwd'R}[Hnz"1oxqPֺ{偒V&6(Rt ܪ9̷_)\#pM%I0|F({%hԉ1*q[5r0ibN.4Y5Xjnf0~:7i&o3 UkEo0.%G%yo_'~1|M!t}0GdU !*A>lYCNb!.? 8se;kԭ TR#N%* rSݖis<!ρ)7Qq:ICGs4Yͯ_!a r7`"}h /:ѶD b'im$I Ya^tDLh |ݗ~Gw1g bvt'7x Lߙ[`1xykOA?;ҳ7'8Q*x!-JmG>c5؅~PNnOuF\5;M if4 J93Y_s/RM*?t=M,k(}myqpz2Dc$?9R{&j^kZGwp*;ב2س?ºnh >%hӦ:}a?TGڃ/7%M۝]rYKilҾ51L!L,H dyW Fb6H6.3>Yn;LڼɌ!n{٢ KmR>W E1K]v3 TYf~~P*CUDs6UߕoeM;B0$u%x0UNɟk-9%vr !9`Ap=&*>>F*15D* k*TIX0|ۯXQi0-Y$ֽf _o@PRĩCJ@׭,(N{q͖OTbp]ue/~9OK>AC<aH$%VD"'Ũ_jН͒JG 4z@"" JzH yf 菀;*|m/κY!NW<"78,t=8|K_ϽJ/%_7{#Ѐ/Ya(B=\BD\-`?%?TN&J=me#] o9ZEi&~1T1SgnT{,…1NM $rߊE{:~7ȂwF|_n5ǩr+aPPUB˚}IƍAg)d:e=H3ɌlVpTDP2PXG%C庤%XC"GǦ*ov9)#< }tI$q!=ޟ:(PZ4Zq凸j!1qZub8x(e ҐVO 2y`gADSп1HfD79W?,hۇkpȌ+XO"QPcoB%NV]H| ,H;()Ć_#m|N#  CE-^CW2<\T4xB[R{,!$ 9 Mk*Wb۸/\0ꞸΥsr;Ltzq?66T5FK1{U=`q3QZI |DP[;/dV򛬘vAkS~OcߚPBǬ0w}L!XEz E|$I7Q!Hjtʣ .g'ZwkK^v`;{cn@oLC`e`)w#HC=_2y.r?~5J ^0҆V|p.ZJ^~L4LCoyhr A%7^À"e;1Cm%<7x_LOU<є8A&=KNMW~,I/ ˝ʶYz`؉ 6[6^yaD +Z ē@fBp(H3KϺG yͫ ?foQuOHoZg@O3VE%)YT"H UKwBn6VSBd7{ SQA >,/ Ě)=""@Ev2l%2D,f7CTΉFR.^wmؼJ9,H5rCy4&*+/\)vM%mO%Ŭ~|r7#y o=o WK0S8H'' "8/\5'PqELׁG<ĨZUn"by[̺~6:|jtm͌H&#bD,wJ=*]ߊEMl}8xs?K[|` 4ˡ?OA읡etQN`޽ IyfHeB2/ ?SFbgju j?mNه&6琑E=Ek52zGD1J4#[_gݦc[vQaVhYZhWp|\SI&]Get+Tw{ޣl /џ9a9U֟0}DЛeЗTŅ%''ΗP}Jc"W+pxNIGRY[&sQI -U'|.~8F!_ܙ=S^Z}Bac'ȁ-jSuMx&W&/e= {9Jڸ|)Ĺ 4ЯV𑑊8S{;&FGl5b8%Eֆfh, p#Sn+ V+Ga}|S\6ar4~iS6_^YPϪrYVT͛vcXHC[p.x[ E$k_ 'EJ'a3^[7A %ʳ|z 9GN7m:vȞ1FZiԳoFd % `lK[؋y0e10WH4q&ݬe=L:w<-_իOoNYX~]C\7 ;tAXcjʜsx6 _+fQ錎dl>muUsya|X zz(pi8=8MpzIOk)& YV{DexՁB0}2Zfl 7ܨ TըGڳB=?\jRp_뼀L{UZGhZI\Ca, 7O`$v1i_6*LZ&N4OFټ(`ki#djC 6+)&66/\ - ݘ h {J ߚ=t=џ *,1NL'b('_e؟MH^Y>W9z9^垡Xޢ}78odz#:BC lm]ӆTX'ӟjrENToCCv@s5Elt:ڼoˉ 1}֣z9fgF!drXq}^7gRb,,bMoF~~=A liW21JvWdWk|h7Oݗ*]~x9ΰeGk1a9 Jj69=&o< 3fq 84>jCYYn;l}( (gnFdby5zz}OdEIjWu_HZy2m7[Ojlrg}vo)#K ޹rʋCJuûM2Zyy9TtY!.KRTwP {#[݁Twٌ6O- 5*^=%«&=ucqDlr%p"H4Ǔv},K=|-݄r[\mMVP2`2ezƝQ1:'4G sbs04ڡ*60y1gviѬmXhb5C>}S74R5;veZ4/>+$us4W6ɔ n %L8ȯL'mK!7_6_ۑ!CZ/|^ !7qЃE3IB=zx+&:D5@y?|EXRU0БH(4DJ}p}Վo " `9נ'QF^&^RzR8 07TҪ=-q?7HnE>Ԓ,luǡ4R 11%[ FŪ g'1ObHWax~z~__ÉFF໤cw&LK[^(7`q u3T>UyRk"^KjkjT\.wHsޢ8L%?3XS TrF&Y/9bS?Nc+b@9դD_ /E,5 Mɀkl{KCq)}If~N jOk5)X~bh۷ 8aM |ߴeԿݲW:U {rW"֕hob }^cx +c$5") W˙gU` VeL{=6*l:F[ 2 o'<#ؚ΀Vb gwkJDN0Pш?+# x_ ЖR & ;%2B33͆S ԱR2׿x[xQ|<~"@qJqUʨ[j=yov|E**xVr슑ql:nhɕF:.>% VP#?\ ˹GXoV$:uԅq h@uà=keϳcşP(''H7r<[ "5 O'{"@-^FNAIbAGy֮n{^9-U*ݨk_-U(R;$:[v˒nFП|FX,{^8sGSxYK0_EWl|"kYmk6ҥ/'"\=0A;^o`~7 7&/OҼK<  2@76'|,=vn]JԳ&{L?A zl18/DJ'J֑' a5wϷ4i6S <IEcʃ p)tu ~I >q=ah,V%?˻a73)+0ݩ0cLJČ%~;/.BTPu<^U} *|U078%fY\oi4Tj{qId g6"|(uƙ^5Ҵ11?!= ,ޞ%0M$!6C~,TB!%S:P&m(v9Cⶡ6Co4p4k!StvT5=|gBXMY[Ya%"u5#}~.-,)^Dc^0YENs5xh،bCzRbTXz)9t\Yܐ|n )$BkǒJi 7uhjXl267 eĵq Ngo(n+~Mȼ1Ka` iM8'qG7/]i>%:ңow8턇MǐvEսPF@Kq i߫|dc49ز,oۺx <8ujR_@]#aC~"=;ð5uKOt%{&SS^ 0&R~ν뺐[Κ=R)DM=vHs~SW)HNӳmPHf̴ {ms0_15!B ֞67_p54ᓖnՅUtxQ?Т_lh_ӈK@EorgWu6{]p"WB$}]r}9Q=?AMYƁ/'+?[,"j:v[8ݼ!b!Ĝ {MJpBGd[ky_)j/#,վO1״5pʘ(q<9=bw}D>\1'=?* _gm'87`fW!!*y:~ڊZŒY9OunN8Ww tAo4{-ݰoK]B91lɿ?9SqD"Rβ-}΅Y^QG1~'X]rI*&^<5DEzr"_ gi$x3}3N;hT.K|#ݝA+tIYb'٘0WP ya QL&`309q8YVxA\QK9R\1kna /ɘ%džkdN I5Nw{l| t(a> jOS,ߚ+I |{6ђ~TxX9?K4d<\qOzAfu~#`FKČh4`O.bL7b#R>9Gc`;ۨjaW ytOq$BIX㗖gžnʕ؆=9zLݖ}k>l0oGT7=0{nv~])&cN4P$Rn_n`s\ݎ,g9K7 GuA8qԷ+MK ?'fuw`c$)l PIV]X~P)ۚkq%SeЌgA{"-ݶ@-SND1؁僋4? vKp8Y10uf!v[owVJqI[k2|Mc JY*|پ ,4Us֌)/ Bv9Ay]45]Z@*\trI8XjݬiF]PTSMh9%an8E@V!|ltlޥˇ 9sOm9H}Ec`Kg51Qes?]lHQ(vA,M,0Rg/"mt*::t?I`ii݋ONˍ'YjY"2/vKv@%KLNC.nWYl^\ H={`ؔÅLv`xqF:G ?FNj5rpPO1nSzb^3 eAĪ`z"w=B5hx`NAD %i& H,ex2{LiXgQiAkp@ ڏ{{QnMQ2l ޷ Yz>ͺ vMpXA'XW“DI 44^) GNv-!_)`vuԻ"d j*hPHK"r̈́ͿRy1{!. x1 |A2_ŮcfZdN7I睜߯MP"\Av8X~aoGodmiAo0!=>" tHuW;ZmƁ-UC%m~|i0^uX|Dx-]BGb.rKq^[P}*rA['i&3%-iij3c+Px0%0@$,(N3@+d0;^k?G_PDdi>2:Kc}qc%?XHNrbŷG->Hń H?m2-,KP.wp=ŏZBFiE1O(F-q$W]mә) ]k1puƼSg`1CK myXZvZ| ꉽ<.s_R|@{@gf_!@ۯ9Y[VGNL$]YՋõ1MW/)3bOu'9~ 3Va̤=8Co֟0|Ld+Vsۛ3|rF{I̠ydlH:n+R al^tIN!1s-Au[S7ma ? `5%;/+R߃zBؘ(H 8xJHU_RQ0^ZbceF pYN`'|i0^_h|. rĤSoy]/~GuL1c P]b2>X/sA2)ق^b6˄{魠׏ m#T>١ F~Wb%R-:EV}Zd{"Hէq\,ڶcxpFo](}"!o} :Jr;|+Csg ƿ;R G((ljNݪjȇ8v՚TG\9; H~Ϯ8m{ sf_&彩>kU",PZDAQ?2J'O7HεƖU&ncM*^6b5(L_b&v=pD;4 [1uμ? A`$uNj1Zzhc{0iji;٭Q ˆ9V,%wXLWxDEwUunkzۤ`wp@6?@@hJ=Ӳ`F^ʷ Fئv0d蓧Țy+o%- a= e!aM o\@Sj#CGRlvzW€%Z2OJvO<\Q"ap!J`Y!Ҋ$;:y]h^ /o?K{lҏ0Cǐ7r"w{A a{%9a?*[EyVݤFGldF?pp<ʁp8<jork f؂R K wXP5uNY ̑%~]^cǽʺ2F{* ONǦ~ʺl/!ngF$2 @ΊQa(+z_)a6Msf͇^L=AFH-'ܫDI~꿀 'w ݓe ˆWR\ёVݯ=.Ui'@7q衾;)GU'ERD2hnԗy/:ֆ]W;ng/pc ML}ꇮEc.-MeϠ nX$g&+-OJ4 8=XJɰL #a,TVQ )A#+b>EBRY-)=KCxȅP!a G}[c )&Sy05I9ދfd(qRDl@AclKDiz_ed*Yf)BJ 3Q_bIY7MR i7+J+dg xTe-.T;-i{TeMɰr7@ E}Mtkܜ֭kQJCGŹ3XQI9hha\:T\6:={='|*G/}A wi ո #=%,+:iX7AN>*kuR1_b#Dw2DT3nr/4\YFJ$r̃d5AyΥ'B^^݌Y*UGղǩﲰ ?RóE"CAm] 7` rˮ1u{p7]Og _H d׿9\̌،pW7e? nA O˽뿓rK}ٹ1˲,E B'!m]NB9Zll㍮l6Uc.|-D'i;]Ǟl,_>GR.g!Yqw~a}|pkwosJ/sW9-&Gf$Y$A.C´/OUBUgk q{ k!FD5?f!Yzy &=Ա{-{w[:"]`ghOTО,7Z70Ojڑwyޛ֢]*_G|ɪYH ak}Q.=mIX~)7G{4BQ#҂YA g/83%v_R2i%_Vs!Ռx} ccL@+/L,K%ǭ6S0p4Ua) 6Ҡ 'q4|<áJWyHgУu г9G}m]Ȣ5W(oU}~9VOُ+KL'k~uƾ2!3]$:LrՄ@I-~e}/DŽv[nI] K/W5OJU8TA@}Ժb$*%lc1R⋒N4޼u)-9[مMt9},IWd:ZP ss#AeЭQ4BvFE ǣBYoV#bP bţ@79 $[t0^Z_"fb@Jr@Őڵmp\/R,3a;1Vy֝/:*6##dÞ`_Cr#=bQ?AȦsk^ brmU F4̱X sȏYDz7~O0TQT u`y9+ݶ$Lc y6!!D+wy ν]FNs]W ˤ9X]=Z̬ 9k@ibMF4GoT@ӂ_yKSuxw#Fğ'|ΠkqYmfr8)ohBM7|n`鮺CKO Xx$RY >1}N7c. ;cˤ2M7zS?+^3wQfHpelT(N #($:teqTiI٨X%{:]?.eZS1̟X+:ShEow+,*ҝ]I*d){ybOj|r顩'X]vls,B6eQl;EN,Uڈ/֩MONb7JZH;eI|g/`1MouElK zMjP^jci ̖sp =ަE-K!N <v9zib ùx )-)>6Б)ZLQ!oUW/hClQrdS`sCi}nѳWL 7 te{{TUm#Y֫&[hsWd}qYt+)\3<:|S#'U°E ˽O;bR|)t##!Yx3ۇL1j"z3aAL"m e,{X+WG*vH8O`FZŝ;8GIő ;^}Cp^T~pެF6IϽ™j:cSl9w8Zul&}ۗj@J&/'|yšB3+KV$] Ug}-ST/9P*sG.(7cOi!L-<@=_F (qc f JxDc]cb <ڦlAT EPcٮ-O(Ě T}":f5Ym.kɪj{^%=^t~qG_/wI 7ǁTcg#_2q`0 u*j=}=>K;LZ0;TTgavrUU]5P #V8+~*>:YDg(D <.F8<: nDE0)'֞Ef9$K wS!-62ϮN˲ؖD630zM1d+Kji)]i=>"j:)DGӡk,Xy[H{0Od)u+ ydd`+ _Ъ?KxhYtӼanb)E)`~$>oˣ=6n  E f28W?;(qPm,"zCް \ N&Boo|3SSpĀX Z mwܟ3á(F^G1e%Q5>( T eQP Cx /8xrs d@ӞTXeBI[?<_v-'$puY?I&MDdezI**[V{MyD818HY 6D49!~[zvWIw&ļY~jJzdv|R2{O%qO4UcM*@i}.fw]oXע爡. OU` lSǤtb>?* !]l_o1ڞL yɝ=@J| 6rhGOD_zudn_0,dj;>90+QEk .U*7:iL"CPf:$B;s5fmnl3T%y쯬~2 +d:ڠ^" o_3~|܂.gO`s0, *}yɮNụ,I[BYR P6X5ŷ>8/` JJr"fYq3Y^gZJWbt'OP3=͉ c %;٭3蚻žPʗgU?U8T*釔>g#pk|u@r׾M8@{t;(jǃ#. =pޠ7#үxr,ka-_9v 2&ϱ9}wT䞽q9n9걚_$dh+J|^ CloK/Q31 &J6Ku w3]Z>HpIOت5*4޷!c ]fX7ECsx+eS{n0G^VUĤ\)0&4Q'[b~ zW*S1j^wA. HX;ϪF5$H3Y7-la0S_99R*ރ:\ <() y?H@ِ9uET7!@ev hOXq l:Wgڌ3n !q'="/Bt8NOh$ dRV-QFltĂqN{hk 9u& 8N\_eaXswefO0аSj47Jy(WsA+/j3CDS0__;VjMff5$yZ%5\aFf9c2`˜>~ 5;Ul& ee׍{[--M ߓ-yEPZ"?.+}1>  9#G+> '3Glv u#-d%Zdö/J#1Vr;d}2T<,R#¯L[[WU+)SW]L^PwWlfB(l n ˟FNJ)xђ#KAnȾq~쌐&iCN nej=K؍/7zЎv"@Kbd1Hr/UŏEMJ8 ;_\=hn:sJulZ7PTuekJdI,p\ XE}-'K}X#K1idkA۷ &О&dV7K3-jָlpG4+t}Դzy7-'{ ;.MrNz󔵪Ǚ8Q aDX(J|w_tA^#7R~M +E(OeEs4<}2W!;\(33y|?g/ɐyan5ޯmWE}0u %z4(tpj~O`RO*pg_y[G pn&iFXYY'W^p^5r[LrqD[ѸOe7Hz o<0C<[g¶5DtܞOz kAk)rn9uA yxO %W*n%3Vl>jސ{pˡgΨcwkG^䬬en!$J:X } WuOG#z0V x̬TMSwzwe^D_aLuo$C%e=. =w,/A,I;,zn ="P޳nshhR662i#2FNz0c tqt[YHR,x+OGi%G+=Úy#=,U$W/ލ0јO)umֈʆ1IF/EXXx|c][(_ % D#%3|!X /3'`vZIQMbF;\Y]K NZ\ gmy~h :tj܊+63^OLEew- w`fN=0Փ'M6;ZbWChjUG}=.E: x_a*|"j- e ›pl0߸;:=Ԫ`X=bh5EϹL6!-/C`XA])ٌKCݦ@}7ӵ KVJaf]ĕȗV4צAK$1Փ_'xvmqLC $ei5x"6,W֖7֙Ӻ[6%l/GWa>= sy$|pd ,&=7 D2hRf&K7Z)FtW괜С A{ <|Jj['fx`YZT]!V] iQuf+:3n ru!5֗.進=Uj.Uɜ ̈́B7|^?<)Y gz5͓هi:^isbNYcuB =K->"ܒ!.1w?*֩A lǴyoyL|6%QIDW@4+ ~@(1*#5[a߷v1-rRs'CW؊:7ј;~u~ç! 8ad =[gkȭD:X[yBh0?y}*YQvL}77i@FҰhR]9R) QSBͮȰ5EVVVy~M-Dn#CMksj<߆bD<×Xne!/B e$Ͷg.(JΌ,_qV[SBʯ@[μ{ 8_X^!O\ Q"Aq(]Grn6`_p7tiEK8l ߳-&|*x=yŐW (q}w Sh: /vOzr,kDDVֶk7\9&X9|RWՔ&W%Z[Do, 2;+Kthq`w9NK ̿5S=vO]0~lNٚ4$n+W҇M.z n`xˎMIv,ԛGEt*f;02A<¹^i RĀޣf"3<,1o\ HĿHRaC3e;JCrD,]UrTw"JTĈp# n8 o~[:_HE4wWY}N勞2$+sTE*(Q iP(Rk V!H>$a3T@7 dCMBLpޫJEs".k6@nZhв 8dx#.N,7,NZl ~d|en.u LOdزcF\z~ƇEawI>^WEIh;&>T0m$Jm-9QA,PNﮢV++}m (>Hy -#;(J޾p2acF:@~\3)0\_gzI@B6`gw(AȐ9^T¦ bC7TXgSY5zntS"i+h(!%wnr2BbᲵp'nE o ϙ\ "F,3C:Ds Qxa jWA,-u^>Ύ9&:0/Bt> 20d*DX i%km^U)RAڔ(]b >5'ꙛ{(3ߘʳ*R{| dux-b+SK`L?S{`++-@qnx=_9@8i\Va#{åw;Ρ4'Ǵ66 kJ_m4*Gc6c1NG/ {kޭ)rϵ "TJkClvݷ?h#?ӓA) ]W6V @8f3JW*mǯlW#;ߕ:[+mtV9m_ ]fM,h#U?G.mFl,8׹)WsӷNn΃3tԑeB주=؁usN<ЗS[XfAnh (/huJrŒ)T#3 3?P_.$ bDL9iNd0aѯ1PPfʐJ.wI-tjq=8ǎn̲^5h v*2薹 r.*%v ڳѿBT4^azlۇHPY\[HMܜ^ 8k9VښSeTT L;MϫD1HxȎFV"2h"Mi .&ȩvu9*ӧ5O81.AY o~g+^1*Hڽ76V|[-uRtU8eDӋ6io| J:`@@e$/j,&rfcaO.KX =r}Q1 t)nlZK^-LT(a5Y0kFmëe:=P'7v}̽Xy$Ѳ## ,սJF5En/^;|57\ DMUŽOB@ݲ1 Cq\ h¢dh c[`-]q@zϤWk褝ZH)4ȕ\C Qpʱ(D+8WvdvKoI[RɆݼ%,'iy\MP,<IY ȣ.2>glhIuOM5p3 Bc?ZNp,9EV2U!54?몤*Y8M#|U1ƽq_Q o'Ӡ4 -vIduѬ8b^p (̟6< h-\0bOn:}@rEzYhn=}mv5AFiA2tǻj >Pmqb2S/&+ T;_{[+U<:0֢xknDɕx6UXn>?v~ʪѧMZѕMYVͳCEۜD iD3yr`_f+Jl:uȂy4n.)#UycD6Ҡ&}Cp3|ZgLhh wϝĄ o>hI83 `ʼnd+M6%!R m0roüiR7x1䱒-s%+`}k㻛De#8B;WDCQ&!+#մR*ڏRߤ4-4^ yMlkUs]nlXKúAxӔ2o"U&j0פ ő\c$-Fbi'kNڏZ_x<YH\\S šF3w8+\2j)EDl,i3!%nbƧfR!f7.֌[0=A5F!#D ٗV`]MC:dgߚ@zU6c [P~xP1?tD }u͵OM7c77ΑEd+ dVOŶEN'XwJ~$MssnfgGD"vBR2Fzzd[|BÞl{_ X>X@ TWῒiwf% 헵5hF%inP4YFsvG2(h+mDZ{;ʹQ7:U ݇3>Q!Zfoy GMT ëZ qؖԱ"Y̰ҵFC?~8Yh?6F G8pRIt[UR[]z+=!;e|ZH1\D;! O ӿ*%`>U>-QW?- _PS/htGKn,?X^_vٕPjC !Ǵ||_N1j{ѹzJȖ%7OAxVNքؕ޸L_Z [d|°j12x w bpgD|wO Z (bĠr|XHxv7|WF&.& -e>H-&gvq\k (RI" K=Aa,^`-"'P KG42ӑi 0;3[3}lG6Yb':'GĥMD'U[_!U,E_G BmYf.s֏Ip0*؂g-}BQp5 MAqYB\pnfJ-&DF{Wwĥ2ڜCol7 k{.ٶ>uMʏ69F" Ai%+!cB4BS}]yM[ pR= ì'v|q:gj7OQgui.a|qsJ=&"=,FƸd!+,cOT5k0ALW;UfOXF}zڌ:;L6G%KDX"l8F&^S2D>狔(yÈƎY}wF Bv4] B !3XɛZ'tz9>vI6[]R1Os 5o+:^}zvqMT5x%i/0FE6 l,pSܟ?$H*S^]ۙ:d8Fu؟ڕn?{%m6ѣVe0Iu-{~xݻIXk 90 D%oRA'ϻXdBoVoX4JX؟jj~ʥaErc{y*Z7(2rZjy^LѱP.X/bP-|`z/+v/&v5gﮉ;-/#ϕÚ-Mޕ"svE [uR貨cܕ <$6@bʥ1`Rv-uxo31㬨Vfzz0a T)y#M:7[M}u ЇQa)AMΫ'>NZtzd2NIfQGp(2] ,BZ 6m_;L 5mM:^`MrG(8 [?NdBsj3aVnhMaG w(CAHX~ϕ$@` Rjt*b;漒liƍާs(v'2g8cD33oM#XEZ r|48~1Ȟ}c [\ 4*‹ͧvS]rAP1Oj`d:6=dyVr3?**CYA!+! -gy&XF-L0!)!1[sb. 3+WD1ak'9FR-9GSU$lZOb9WziRDNjz##G4[CUBϭjI<1Oh'Mb០[30x iJ/kt {VeucbXĎO 0'԰" &K1b[)BI4,2L`YL佼U*W=MtxjCYwqrm OVB mьS㳁F~Ac.^ jAƪsG+~ "[z4;3,&.L2[#B@?C Xoų+OZc ڕ:N uɪN.|6څ.(7:ٖz|D}#:+-{$:1ppwZA6#z9˫mgqJRzɱW8vL<$˥+\0Fiu')~1%'70gz傹~u*̓^ǶXDTwP(cNj}yKo,پzɔ<]!ĥ,:t$3tz|Kl;5JmKAH 햂3fU8҈I>s5Krf'A r;ܟ-cd޸?1ejoVDEf,U3 @1 הLk>&+DHw)_soL&VuɊO idT=q H{MY${`ҝloWo#o' Y=R[v݋"X$9h)bQniڒ"DGf=,aD,bnʡef~ϋT]0W+T6Xf"G!swE5 ˕9|[Qgl', 'NW& pG φt}y҈y)jgƽJY:˱Ĉdg75%@8#Mt*9s7]M  Բֵ#f 5FCLg 2+83&aLh7:BTUшӔ jpyXtə/B)G85pR<AAGS4y?H&HI3)ni5B G0X~e@4VeI&L֮&5==A&BEa,0|N,$55 'V$_ < ;z9¡vU}: edO])혈+Q/iP4_H{># mz]ԟKэ2څ7šۜOqRa"ި"6-(}o5 `84xVEbG&A|(p<rD7gi#UK@KDCVՓg4̲1?M+lnA%ZN.[ G$I_hZwY]v!+:#JXS˄  ,Q5ɤ,ﬞ]&rᾕ9Aq=Q[_sK  E9Gh25/ (pi1C?mCG$TjPwth%P=)8洌r *zĤh06A jF.56.JLI !&+@ 0xmގy&XW4`GeZUs@qTl"FEb4. ZBb&d \*0+*P xޕe-)&Q)"ga\4yìLB]w$]߄SQY[Tw<bU':03j,PjeNJB9hkZm?ğS8e`#JT d 836յ6HU懶jDoLr¡@",ݺ!mRݤ{,r׈[Z)=][Z!SC[X= 6Z-!,B,)."vRc9]}78J[ۤތ}n-&ޗ{/`)&7P;B\Î*]vʹ,4&oU(Ԓ اCPUmҟ%Q~P Ң"Ȕz26)iD^|}iU1p1\y(sHj"5Rq*U)NW~fGE;N,ZgXj##M"U{W=&:m^*u*/C'g\:N7>;JU7[MUBTL&I7ƭ!VhJ$/1;}#d4Zr'-/_ZmX#1xI%D$HwsgzcJǵq`Em<ʐ:Eu 9=◬=ym@EGFyk,.KxJ{:|%U{ƈEb+ "*39BAҢ^ J?30 G p{TB؍AZ꠬,؝HU kvhEXT!1*N5BQAEy@ ܆P/EIoxFZZV[S|T~K8q^>ĸ?tƞC]@\c^IsX^WDB U*/;1RB%IUj' /ʊgJBV4L>>)@Wܾg O@҄I( 7jt7v hSXhzߕQHy6HEË?UW& #T#2ž,|x$xY!U1% p¯$xnp IQ VRv[TxiVM t0LQ*;"֢WْBjS,1pb a\өw`^z$V3FawKGB'٦u{kLp{e>)iLåyWKIW`YdT(H`e_bz/{$;6A u"OdqFN``gynXy7)U>@nrugIYn9 w/DŽ4Z$Iu 2b 8|\SB #~';۬ ±S ƝT ~wV.*bn1`?O cZBR񋡣ܝ#\>vQisMx؄#(yPP%'Quߌ$/ڞp||{S>%kUqOp fTة9#vwrXK(R߃b-ѝ,3xM8qh>z.Pq˰:0Ja/jUH@XnZ9X9WeO0ԫ (/^.oΟ@ObUOA].*5ĺT}L;vy0TDoң](;Dw#6˂#r,!S'_S[6{{UH5:mEd:K,(N(î9Bw>ÞHH1Q>.LQ$-Fӻeby˓o%!*LA(MO*v80KHh% p ƺq4~5?g5OFxC}=EX((AU); _bmZE .:k4TOjN+RBI@z.6v J3{ BL_ھMcg&BAvHv)JJfE0fnm{Ԍ0L]rw]dC eK!xj幒Zrt t$¡g"e^ʬRo[2SIDoDj U(b,ŚOMt 'PaU|2U$Q(5&u. ' ūjN: S}y!RJ8#Suݒep6EUU2zpJOhO01_ewd},|Z(bdb4 ~+t?HDu 3ai4)VB\<޻k~t8Xjyx?'-v6kԋ -h^{HJd Qv*5.<*^e!sjJ,@Jww*⼭Ek& oabMI_|yP)dd"(Zm~I/'#@|[(c.yS#\՟f$Zپ.^* |mh# ʧ ҃Y'q  sٰbcm6Ư? P^'혛U:gx4֞\n%c£C1cוQ 4?96'L3#|q58a45wLRI7(/-'Nl,%3-/؏^Ag@ޏƌ+̻vLpWDZPǚ3,.tg"ֲ@([`hOL1C==VrwՈZ2aͧ'DNƶ&D > ^˼laMܵ.P#.GyƸ: o*A. Xy7E]_{+es,a+{_LpgE7ZmԈf`bddKL1N6@ѰyRFЈ#0 `n}RFBж̉&gI4gH `jAדdЉZqŁ>˧^"/f_5TTy*/]`z'!QH4U7aT_^l1$Eǧ3:qcg/K24l7S޵6ޕٴ=dk6<܃*D>' aגU{Xk(ɌcW*FFa4iJrBLARdz GƁpYʎb mf N/sfr3&[:ݏom.=j,:}HhdۤVqtĜb_;~Q+G)2@q"UII!I<R&ۮ7]Ob' WlpGMj[WrMk1j59U:. l GHGhd";AD1vυgy$0J0 {@1.yǚ*8a*!w/8%0^d܌} i]iӧU FDa#Z tdwm#;_)]ȡ!{9g˩n3AD^ 禡E,@]ɖ3I7|'NS'lIIEʃHLթ4v܂M<J XV"tG%hyEv5>ו\EPI=xkkwQ-f!Ś``&_ڃ¨j#[N}$xDE] Os}[A`9K5 4G1m-*ė 0Ӣ჈paBQO3h4רr*Euse0 yD-ݹ>x6tN~0U{."WH-An &(L` %cc/4n_*e aIJ)͹kܾ{]ѸJru-Ck%j9*Upen6aӗ?q?wl-V)Mi(E$~2G?z17Lw ϐp0۠(&WW;~HS#^ X{3T ^H9yi%#.P}x1^xhQb~d:?x!"bxa 4SqdX }XK93eGYwQ9 /e1 G(&2b>`={̎ы $:f$okc/7ӖbpϺ#q]eo#|h?uR-DH sȵH5˙% CdSS::}̔Vw +*Ag%H|Qq"urűfZ #n6VӶSɴK$FkIV|(tye0WdW]7Jݬ G 0a kk.e㋨Sb3{6/EuZ'w`i$f> ]$ ]Y^mVxeR%0.F =W~Ux4AgS~)!+Kđc#*%!;jgofx~aȅe!^„*=~GʈK>L{ҥ u'itSM`CH܆v\?8e74Q6*+M^/< eа2yXl"5.- E^ /#ޢV6 PwΕ ޴礣QvƊy+0RE0ҷ =U< NX \ҝ\T!˷Ck3ǺvͅHuZdpzbqjW!-y!óDDNm8 M*wLjĤfXmJj%HQ'eꁴE8d5"mmR(z?TsQX9y]҈S]Zo㢰:8q_5Kf5`hjogo7?'ߌaq 83f^c/TeBQ>w.=DR-(U!B7Y,'D, 7GG? 2g]cP7wd-rCo;q9. >b=^/L{3 Tfl>c7m_jP:s Z Vaat9rb6yE%Q WQ/>gg abw+{Hc&~`%KǤ L CYwy6evj\ ῃr!eC:qUiˇ 35ʠn >ХX bЩ5IhXq6dOGT xUr-^V6Tc*ln EN N$3g'296?Q{BsVWM +uxI~B~а)>k,TѹF#kLTW~egޠQO: +¸T 黛5  _zljЧRPNcmqö^ R^y W[܏9[^!]d t)+EJUI640562qb䫡ݞSazCb>Y֜hjC㉄Y>n]0okeNCTd4V~$`Bǥ$͘;Sk`#V_?Q };i~^({G՚]`dj9)(bG2o&ಪ5X `ta6r׏ KaZF/l#GWCqW˝Xv<'w"Ċt2Wx/s9¶xQ^iLu C m@GP+P2~[7T{4yKB\RwsL໓r-/Ge@Ao纍0R}c8s\*w'r;Cg%Ly+ " qG7>-pyjЪ}dw+"p$&uHV"C-uh%a' q~0r?ŢzO7 h dnRn~= [?"uB,t!4N E>ꧡzٷ&$o#\ 8dhTY̊+ z/n-imm]iO@XK|>OF:g$6ejœXO2fFvPz q?#,-8QUbz )O f^@BN?.Ž:|AzK"Bgn>76| Q؂OꙪqV G t:AXsК}V1LQT)}QRU`$'vGU?sK$^u8ƠTѬbHOInjp!fLʋ5t&Aܿd1>O]NĉECEn?B Gbl P>эivvb@3W#\:4KBS6 N'8~\ @M=6WEi(m| I2槫2 [  6^{7 pt`d/jov TjlVr9' ciț_,ܯӔNYt42; Ǔz:J7Xt}uUʳ}3Mw GFЉ 뤸$*I ޑHM⟤MŦ[xWvD9.<(20E"+8zrfP7li ovBskLt ]*s|di:^1_N}* .F\ WR?.; *G+mq4t]$zI >? (Uy c}ΝYMA\jJøӫ<-^{eu80GQ=D~j4z?C mrNtW">lϛTNphY3 _S9!E.q'vB(K:N/vI\UTK7\0N}Xjr(@i`=|ScL/5 m%cU~e-$o/͎<孼-pNn YTVVGBrlSK:]X_J6 ϕjk3*[kei^ȝ$B`Y?4- q0zaU{6[W0ȲVjب^jJÈkB,/t2#wB,,9I >j~.P{ޘǯ=ZQi3p|FP9"`((zjh+~b^(F-Ⱥ' PgkK{6W\X/֝4ad9>c֩/9|~M~rJi;ZKx`kk(ǔ_̛ ~||D'}8%b.V ws̷Jlv"aXgjElE駮N"=UʣBnUL_t<{̺TG?m ,T^c bm?fE˘!ϊX__a6hiR\7R't_z裖l#MZdYL=^sP׼ٿ{ȑ>3}`Sl|̩;y8`)NF2ZJ>y3 *}1i~UgDJќzzTBƟb8bo%#E?5N|0T l kX{ӆwZ׏דY$7:Ap4^gZ S2)Зmj%^lј85/y*.)6qE[ *ab(ۯ 4ܲ7= PxRUǷ͒BJFZ`cy> x}_!#T.v+r;!r ym$sXcC $UZUtɀ5Fe ՠͱnw /eˬ <}R T*W 51t"/P5o\1!7r2Ʌjjo"frk=hwn,5υvjrʱYe( Fv,=u Y=[XB"y0n'xd5 n5lkem:^ O 'Q-n0L[BD\ƃu5yHI5IUbhϘ/0L 5r]U8mmgbAx~}.(LH2Kl.z<U^\}?8֘p;G+(4&{Qܪ[wޯu.VΐDRUq66x^K=ʑsnȮT1x"O=9/1,jܢIhBFCkM#kvQҤ;9ڸ/4(ݬԎu[ )]pC&Mr-(:_ӓZ82")N,YSJ`q`al`}U-'ĴiQfX 4wuNLq^Ђ|_j.OȜ(T?ڝ+!Y[~>eJIKfZ,G;X7rwɴfeu,۳`?uˏ( ~ʅ_|cp3|io8c"j~U{L}5BZ%b/%s9#N_ωp|UBu L\]z؄ xLH^cl&H>`Yk\=+A-oWή\%t9ѿ \J/y&?0%[urM8uvMf]fF.R6!CA6n@~i `d yHjdArxzjWg;հXߊlD]F/Fn4쥊E[s="C B U*SbX$?tb}[Ro<{-Iq$"UwMkp9Xg=Sy6<9X3EnaVA2jA+BxNGC :HR%Y{*)3hsB%fiTzjgk 29ER3Y4n$ݿJ/ZͦظqhP;XۣKqZZ_R?xwtֿ ܾK5(^BiVt*"tWBaGVFRu Z^NǞ/h,s6zB|}މ 5*B$q0*C. d>n[Y烧d-Ne;=0g=@/m D8a//Zp*/"yOPzA$X9x!ȰG'[wWt|s%vÏנBU(dш@uyNo#{hWŏ;q`b ee$W+̧HR +Wb@۶U[\~^;xEP'UgF>qT ؎R+$B>L\ўwy6殍 c=rf)(wBR♠ p+*5.VerNme sЁ1;u[v#ڔdz qjU F*)~7EXI;5 *[p<,׎ ሷ׆ovZ1LQ;9DYhƦ1oQeRᐂ9B8HLcx3~`䰢@JZ˹2Yx*xԍ^-#x?cEA1z! .qB(_"4}͑<0⚋pW8 $*)D=abp$ PT|FAteϵ~4ǻ=@ȹ*, /x-U˷NÊPLo#>i!nOl޴A eӏ3(CNŞ~pϬ_KL⯅4,xjVaA>&8l,4ר i[֥2+ԩk6*|-Pq-o{V6`5>P+p]5Bn!ӹz1"Jn.HeA3 13Z{ }B:Y*A7Wx2\4P uVᢑvfA)ܷnRעgrx'Ej/EmyVY<81s}Y|y%'V^RkJx&%PY\%`ȧd6ˆ!>SŘ'X$̪ҫ?1b2Hڰ qhfG}JͣȾiWA;j#rcͭцTvŵP}@!~H;舽vj=E XAr֘ ` 7iI=ihGF+SIL^[R!+R|ZdN7{AS`A ^!aS.m%LÉh|GƪU^A0jNڞ Z(a2˯V""0=]_vyS,RF˸79sVja#a} rGb#f#^#cX [.FLZ9 :xZE0|&W?tOFt*s-v 1U)B$9 L6+Ia-*Z 7fEC.kMؒbzZ'Uߋ[Em`϶}E2Bbc1Y ե&uGI&3+jhJCpl:H&8#LI-.AGhd*/g7͂Ch<2t:ئ pe_$]Sn\stN)(xX͉$0׆wX/ &9%;]lʸv)g|= =DmtcdqCQIç ʠ!Ŀ;8 gb8eWFrYYMprM#s K}1&DKY–wCXLЁböad@N'oh_ѮxNv"U{Tx{㳸+~ ؖ6#_̪Zi5 ya6; k.FF|;8L֣屦4p0 &81NӋQ1ӃPRBA*ڻ֭ljؤI[R:oZ TP, @rЫCPRQS&U)4~GU˲;RؓK?1Kbes:B:#(|}CW? &;r/uL/I%xk>h !D%vUMݛ!ᆸNRO8ưvƢ;咊ʤ.S[qѵ,<4b\Zo0f{8<Q ǚwnˌ[L -]b&™)Iь{Ԥ1 *>Pae Wγgn-E4`L% ?:q`c8QtXw]vr%=ĝwd녯drT?cA3^֑k‹al*l@hAEy$9e߇Nbpbar>u{=p7fYÿNwdNRL2Kb!_*Һ5Ye4%"c9/ڼ(kZ?"s`K nrb(]jDڵ@"G$+;#qV+~o y} =bZ@1^c~dž_Dg&&}qa{+b]@ޝ1VgU@o=$jO|y1=vMJnm: ;)I& B\~}R52}=v. k#jTyr|Mː0r-Ed-ϸoTزT/#mgbN>'5E?2OI U$Zdn5т6%G{ޮ[@ մ- 'I4[٤5F+WK'p:yY'pAk~3Uxנ[fwNujs`L h&r׵X'`JMîAwm9)UytA5/4`#` I-Y=~')lRo\.Cnt9L: 7("e\B s项` AZz,F6w%-|OL<{%?ZSq6L7ВEߡR'+'"H2eqȿ&|Qx ɈB -oG3`̔gZZ_P6t)g| 8ef9t$aҏ$T Bv5Ӵ/[M&'h_)i`Eyw;RV^ýqJ=>{xaЏR2ݒw͚tP*l-- (uvYTr?6vx r!.jVu=-wVjNGQ'y>+QϭϢUakgYns]&'&y'7'(4ڸ7$m9%ZQڔNt1ZW''G #-O!v5 Yϲo$U"XZ+-^ŋp+cCvbʳ)H:7W >4&d翵ߚPP/Yg3bPh U|'Ŵf;k%[yVpyG2Kת4) ࢲ:=FOCui+?Em{ѐr-bEf{F)ʇv\OY FO.3jG䊮ɯ@rT*)U u:YޡmNl7p d/e$^%d*mX=(~52X*]啎 n>'C6ZQ(w٘bR5kpQ>}6Ght4Uo*ɽI' jBb.p<Qy;&vhRnPs,z$`]ܕ&jÿvbVRVH%9y†.?}ɭl(+^.8t6:><}i`J [^=GԇU@ݟ?gYWwJ|kw+ň()fXE'ܝj,l'ye~5\k2wA;F.-_2@Qv}tlI))Gd.i2߰l&FBaWϕN% Krֵ҆IS$AMNGn/ GFu+Sx~{AK$7dјh!<$J;*y ^b`sv|zPsF %]3l_Q_Ss b F ;zxo&`DYt>bt^!oNC:`{=̅ ؄էU3t$]N5#r7s|BuasuO~NVԇ B!,:ZLS7'aN,V"JY8c'ɞ}}]}K61jn֜e='̻րjOjdMP݂QC TAnOcvce )Hz#cįM4 k*`C9BAm%7ZbKnԋ\s 2캉+ǝ6x%R?d_;&#أpT"uO\K+).H5魟#92m3@qQYg7ofeuSf]MEhE9.*k"^VjGBeĿf! ʇ=¥T3m?ךչX&k$Ô`L9˓Sc @2Tѧ^V]N|K[٥Aݯ@qy+~ݼi"0+ۖX/VnI~3KN&b$fd a={YmJl9X?}pgdG{_#@7`w\-«[13걗VE%`a ;W7k2?!vӣ {7I,>*vDʏѰ.hO'|,5 9,{aoCm(ԔP⊾# ?9}^VXƛNo?Tgr5YHd04 o|gu6sBYpc@6#+,͸ةl|]P4})I}A00䂭'J} [n`.`bfLJoL4s_26u[Қ{ _VLD\M|o(Ũb6 |4c_ȟy"oҘbzp/q%|i"i9ggKzf:l`]`_E_brr̺`wd#t Ŕ Z*i~I(v& GΊ, ԆshEZԶ쳔v"Rr^ 6")JlޮuSg<\+4$yEu(.]J6:!` 3KS6MBo>b s>ל$+ĴS[ +J6#~uV A7vT1-;<j~H$`! g-h K Ҵ"G^@T"9}L]<+o*g:=:;bFJSNJj]X-{HK*4u 1:qTH,4Dɱ xG-v,ϟ?RD7Uuh>TXۛ],==S|`/탆%K)49Rl0ܤ(jZ@ul &k_2!d. : ,Q&] {n GF>44{^Uqlb{ ٰSh\isJJ4%+GU;7 j 9+h*V R]~73`V<N:8|7 s_XG1O lDL  })XAm>$#] X9a#l `5B2x2Y%W 9 ]\)KI23pPg&GҾ1}qD1_@uG -*sh,*"{!Y8l(|߲|u%VF}jMepah(=tTǓMsq3Klbɦ|C,b*5lن2L8xйnL%%lrprE�LFi]9YƵt76޹SeC:O~u)֘p>/DOvLJ e2<,D2/J*}UKV>aЬ^:폘,Q]@VjTH;w>m"Zice,';*lw:Ibd:RR͕3^opؗiU?k"Nv~]&3c,;.:恇7ԭOz$}3fup (x4f_q10M9^{W-)lEaŨ.ذ?יmɂ9Ӽ }1eG:).1ax;œE@aT SO}*on_:|~c ,9X-B_֮ a^_DoMPJj9EPO+<~%a?xq\1%夅nd1rP Wƌ]:㑳`Jċ;{ ̈D~n(¡90- aszy"{񽴴es#is\CeA`GO΋*On%oݢU`}`')~w:J%)}՗Ƶ7}?A]Z$GVf$>%d8Uڴ`Z+a"e2lEhuc L ᱠC >W u XHF—&;-h肦m;6+fiAӼ=&4L",n%|9Ub O `Y˂j;K#IkK 5va6@AA ۪\x(RLƆH2}0x5.lm6%B>(G ʼn~MHH*=%"?mV>}jcVV.+P.$>d/$onEbkV1_r4b΃dA~ʂHwXf16O-;>Q}CӴKeu{[ik4?Jle VsJmcfhQw>΢z'MR) A'mT%>;'Y!PiECK bn:ߴ_} -ij;xx7S12BAVW,Oퟹ:)hq'LT+P^ޝ*PtO6W/ze(72fn) \,LrIhg#WSXnAu-^g˺iE$v(=}=n@F PoLG9'ZSy!ϟ c*V$}ÂPd.h8Cs$s;j&nc"fra0*\[b Jh޶cSN,6 td<?h8JIgr#nXfA\yY`&ʁ_q Ñ[T9Dit ֠YtZǝ;RȾ%\ M^ b%W;hSdW'f{/ ]{~{!#/&6P]˿ņ&TwQ> 5Z@}_أ[2DHGo7'1eLB{6 !WPIF"\J8֘6烺'(ۓ| d;P\f8cjs9PVd=,O|]F3{0A0>S<.]1qcGL8f64%˝\0U +S $-v֔ៈ6vA~V eӲ;B!T^+e8Ou):hmi#TzeɈܺܐaeۀYq?޴) YRXlk"<2U0}T.jLձyg'k wA}s!" qC8OjQi'2a/| ;rqiyӱ j8{a{\=߅?8HPE1rkȍr ߋ ʹHUCK4~PmE߿-hs I۳ n~/WP2{̍-ˊ۫녀/T~0U{#:.Mo%7J~~䝷ܣz- Grds --|^߰/TS-w!,7={\Av'2{Y_6,`0o.W⻭[,Cƃ zuWoe&*Ǒ)]_sDpbcڹ +y(䋞>@ [8-`q  7~"GNqg:ovsVH"ҬF<{: <۫O?IeV_T/yI9zT\?m\ɁU"R"G/־#nKwfxe2j!4!ËVjTZ+"B]t Lol_{?X!No`)_B8YFYL0u@b~zx]0cJn5اi_Jb2:m=~O=]`UKvN#cqFE'FsAHep=gk'@͂Pkb 7p Y3*g1G 걢[_񵚿7#Y"TC+O*areŖ;@L }I3疙h˸]Y-~yO늴J3Vy|513g3(Wiܶeq Xޏl$-K>!HaPGHTMܶ5"Ħb!AWCfB?XMMvƨU&QG-Pqi]p8 #oE$)+ak%4]Pw8-(8KkudnL]I%M &AeCh$5=3fhMA,=zgCnW"Q$ީD` ~؄Q%|g%o0 AV}/^^w3{3۰qT5 >>Q/^!'ն: i?pkRlE 6njYYgRSLjR{ YiC+-fh ة||LRwQ؎B3ޏRs؃1[wH"\Nm,`iqNiG9i/akv>HEg<&ʒLNU0tr40gz 9HLK(g/LEPMϢ[̽){5 \3ßf ԝ~MH`ef>&.:@DS 4T} ^_#!㰭I@1M viD*,)vp=ݎ@llQ.'LάA@B'⟁csCK1Zyz:GX@UBrU(]_(^Q n.i-7cYꪝ#{Id _k&n`T\ Bx\G*/K>9gt>dנ#J1|qӧ&XkʓkK9[(s5O 5-_ۥ^TiH U+Ӝdx]:/^wE "sRx% -mX{xHݲUq 6rQ0aM1²y*Jh{ĸ6xSq<aL`ɐ 8A{/mgz3}8R{Io‡}? ^~#BJSM1_+X?FmU+^aqX8iWT0DD % " h^Jf!DΦde0Oh<&`GO(-'BYkWimWmGhȠBoY@/Gum*5\vx5?Ru? k6G>Lwtmzqy x#+գn\" O95f:}߬?rIΆn~H36j4Nj1 ,z떪ğ 4U|:~wC"W  3uJZd(k'űΒ A$g !l-tsd@J=Gۓ^0,7&Jz%g|+gH/o)MB&aGDPĥRICI;&T{jh69Ԧ,vKR>Kƥ"ueҢ?9Vx)C2UW^`k-0RcR9ߺӭhH# X*aos'֠_G?Kנ 쥅ia٬aDqJxb~qô*PS̏ I u3鳟P#Ԉ$γ\e;6 #]P|6Cd+-SBܻ.Znض/%L6U/jǜb6l xGI-U6S#͜;օ7 Y"Eng=K.lTwfU!2vVJn[<;-0v{:t=D&Q0oX;l!,ˉ'qG^b(* +܆%=XGO,!t+ !X mOz]Wk\uzR}lGDZVm%N[0W5G~w4_nچECUԷdz2ocR&WAeL伔c\HO`iAr;4#ɛc3,^LJp)E_ZyтuQN4׼ 3ނ5g~6sN_3 iPӎgaw^ cxƱr"IJ {!hS݁8<%;+; ƌY,]2%ZįGV(]9IL36$%ɍ32|Z}wkDpeL m&$"P~[ު˄hY/#d{)w^Ӂ`tlm9}8BV@pZ&3 rg}R@Ս(j%߯ÍSnީϸIt1#htNꐺ%`=Z|V)jΈE,&Br˖}(zPMʻ  {i#3M`h4<::̷v &D=ɮc.1VkaPadu% vT.7Z|5E Z8^l8d}^C4b-h:pe#핿:ɼ.>" Cao n~ 1}\MOۢaK1N 35sEߺ69#ҹ K8AzH8ڔ4Q{2o߬YA#~ZR?46BktJT%~6k#+> v8"[MM 隻w|s"WyjqD׼Tq;4rU!fՕՕk!ȋ:3+@"7JӾD ehM13C),q,F1UYܽLtSaG:nNc. s1/WV^ сy->V3RWǺp ǙTSŰLt:]Oi@!י8󜆺/$pwьgl 0fSVfMy-ѢjƮ{虔5?pe CV5^;STC(%v4>o v5-:ipE% aq$&٢Y=CnhV/@B%u*ɉ,}暑וF[G8gYEr7jV8\9:^6_=@^.Ul~ل qF4 !wʮE0])SŏKm JEC b(d~L8ci &ջPzh?? rPyHTuL9 o60VK-poqЭgC~i YNGdI̐)ڤBBUpI.Ӌ#:׉WUu'9ⰐcZ_4Mh*zDO3XOH(iVrzub;X|+. V Tq\Ec")r}5-]cCܣ>ԼczEGH{MݱᣲUB۲3#8Fdehk9xEbڷ PRPd̢omKo-Աu (>DlDHAVt>^{ҍ],DͮN"P BMZ!rYpsAALPݣՕyb I=?AE1:OZJaxN5y ObpT5Kcl1>Y hP"13AμxͱND<͆>XbNf5GV6,W@ۙ.u!:%RAS x͎x\&)$3LwNa^ \(U1p OvNaMT"P=u'g" (.u'pOݸQ!6],4ˎq~忂˾Vny\+*{"4'$!=ʛ[Ç qUHV %iYlzsc'#m(`Q{ē럽b,qZ3f1)bnN?$Q㌦98R*ξ{ycyw=P7uq9uD<_!d a1f籝Gq_E7[> ӣmX w0oo dd6B!B?f{ȿB_g|c8ҥ̀}}8Ra< /SB9 Cjp_yIMIQPثlc';O4C,3C#Xro|yAp6:&0W6NhbAzhGxQoSWV6KA:)1#@lLV+ K_*^'a[i Nw*nbG e1>7^61:ɾ s#(nAky\JWCvF2_lvlמs[ʹM&#hd CaQֲkv8}aMya9́cjBynHS{' f/d+4f1W.R9w+az`/㬊5DCނgVr0J p 7gJ^f3B@BEV?!xQWg*!s)/^zZ ePdJYŅy,`Sm-XLBoB6 K;)G}9?O宄> z8-/z8tHXy5A2ፖ[FsV#P3l&GТnY_idI\YH_)ibLm4Q&iY Y8ϵiD@{>fv2CSǰV.s_Epqs'XK^)'55@(2$W8e噎n`:fMU"qPOWByN$M8SK\9iրLʰxYPtvdyz|-Jd/NA0yR'3=ÑD]̙26† <?ޚ 8 =+qkr(U0T^+(NUl-{/wԮBkub{TFO'ĻVJG~; +ǁCɂz"dG͜eW0Hrd7~6na[MyUW 畁@έT2F{ҋճ&h6?V<*38潙-wC$%4ft`~KNo}ka?.P6tCs)TFVAtN"&E$@GG{ӭrE(Z-Կ`Q-XƇk$A5$U69 ;!- _в./:9(|LbEpQ}+#E㳲RJ 8DgmĄnH>5A=O91&{g֨1H\MISLoԼu%r7Y:[M :b5|b2˰O˜20i[eN>a`:V -h 4]=Ua, }DAɔ) IKb\a@o',$Ha xШGθ%U/ ~V~ŘIiBXSO= |:3{aĝ=>JxH%O|D$y5yJ\0vJfLaYeJ?5Y|pgb B< )2̢I]"!W :>;*qRW1`4 Ic=jq"‹WKߪD)RK7A?en`*? 5'JN{!TcT{-t{$ʙ/|55plR8QjarM\g6xOGap[Pw9U5PfkgsXjPb͂Y Q3x}+ss^gFAK=ד,Ed%܈΄7#Oa6##4s=%3X,x ̧ ӣa+ʥ`6BZlxO^. E!qd'sdֹA3b',g*m_윁퍙YQ@hZ#L#Sy0ϤĴtwNTmmľ;lhvav[wC5c~YOο<o ::yJEmwrt;Tvs4SX<];G V Gwj^߮yӺ)LYL'؊*}To1Btn"]1 +♰v΢d:8`Cwg LfUL|,*}rEva]π 7]P?QJP'-Y-pftͭ" *}[nnVL`YSF][>Wgx<#U4# L[ q%we3ˍ R@E W,gUlZ!0bT; msI{+LiY9+&H/r6dy4Mov0eÒ2oog6@} 1h#l3f\؊6fqƲHω(<hk9SLCT?[1N0 :mǖK`D#Ko¯_3~ sN$$:g7CKrǸN\t4}=SHǎUFw+E>\YƆ7?a>._SdqRģ:ٌˑP,]c̔^Re+Dy+pJSXaևF1sşuDb} )BE- giDp vҺUWv(l4sWpR!=/:&3f5_*gC[+[` K<n.r,u.O`,HʆvF/K.V^B-I$T@"V=R ;ս)lLa34m<׼Z ǣVhl['QlΡ#$gp`٬L{b}0V|!|мN/6?̓}ڿu F$ oۆ^}H)Z*7.dW;m0_,S] Lcqywc,%ɞ*sHMO9aP2+A O%4me: 8ŚqRUY oM%\>֍?-KSMHAMsT뇛)4ʼnB[K{df ӾwA73Pj(E1&RemlSq<]k;/V1) K ;1NEp5f[>;7ujy]-M(paz&ØȺ=`$nlZa k!maU>!u䯼~"ۚԺr#TEx8M;Ȋ>/4,4` c;8*IKĐ҄l&2Ghҩ B{Ϸ>xIl!Iܣ~<'Au; 'Llt_\8C. _4*,U]I_ >ӴpdlB 2`v..ʼn?摝?PHYrɔ XDjpgF*!zXT#av{ykY@L6w`r'3ގ'ۨNJhBavRaiL ݗKP1sSǺX)B!]ݤJxQ>Xr:7f:i2ktOy㣡vjnjf.EyU †R`@@-h>wXYTSMlҚ%P".qwT E5v 4E-Pjs5eBa+LCѹʶ uߍxq_[ \wRú7J1Hru-m􉁗|&@3H-.qzL2Rn}olRu_o1||N1Dڎbt}g*v_KuIT))tmO Kro=3'3S8='^!ڡj#A[CSxV@Siԣ ?+4L3veH7PoW@/5v818Pи)B"R"C+ X޻Ec:^-ĝ(Er/;fWIqN>x7}-.e=eɛwq] >nf-%'z"@Dff;PgfyzPdLp T> ܊>դ%ӿ7[lup3nL+,O6 !}6jDW5WhyHzvv t+z#3%|R̼ bc%i7H]+1JH?ٛOz!-{lV`QN؜'ֈ$v"+)?PU#}9 @BijfrzAt0L|aGםDI E)!E@C;^)2Q6^ox:<%Ak~vv;Ǐ]tNІISB ?¡4?|!@](4Sj(#֓R!=RiGp,6),F#{ 8;_VXm]EY!=@}`g&aB!Ne1g~i/ '& [-nꔌo0|Q >scf:_Y>DtظP dΆ m%v7CJ XF/SGBĵ(90J^mLo?VuFCB{E?}T6K>TL42d3y702:Y^T6ηڑ W4~/^ՕzǟJ 4 25I:aHe9-|O43|CSG?L`6hkCb[@c +'>owMvtt;0y`;6_+ڵf՜E=7x`HgZFZ&.*24]ϰGh5NHvNLgz!`fvUhFrh($3MX=0 Fnճ1͉KCװfdAXHS:|۷# ;1D| 6~Iwkx[sDS 6TZ,u 8չ1(e61>ָdNB (R";~oʭC>zIkGB^M]cmX zgdyJ,KbEFϼh?sxd6QɡKqjh봢W"Q\ 9-0GB< ջh@[* %gQcn= t((gOrIՔ1Rԛ  }~sxDYIWET7rZ(ߌh]$O=*ܵն&ڸ.r̪ܝI**u~Nȃ5ذ9 _`Lj7.k|Uk֣w1PN1V>=[nG4$C9_̌͂7Ч#mSf7t׬gv~e H^r2QDT 1SͿ/|Լ#6!Ѫ`N)_+8PLQ{oؠ;Ťj*Cʯj~2MX Vc>> `|"eG5pr2l 0K&\N-N<ܕ6쑻":u8&% 86(:#͢^#IM=z 7 f:H^|ɷh;)1Ut AȮT.O6H1  w"^9ڍ)7+G]ɮDxy]@͜d}{dVKhܻg,\:"hfZ-ě&H <2$P6' JO$%ȿf9iiG3I{bR\Pa?w!ԣ *B3藞WhO5+˯c_cϧ?5#qiЧ[Os~$ux=UnX lR;! U,RS̘d9WLu9Afx~+\sup?L8=̙gݝjh>5Q#Oyhr3' !ND W5Z!eg}^ Sp{@17. zWk$-Sւ_tUQ@'$Q*t8>z7b{@"uwtWSTzn<"݈2fsgOa#!*t)g6MEZGdE +.RRM\$ ?_1.=n^|[Cu!sk;.C|騥#ڒn{-V >Duؐj&&=h<6{p>$斬Dyڸ _YDAr̸m^ u ^-c3~WʱtJUE3'iC070&8e>(4ZUDvF1^VMvU@)kT`W^P9S310\RW' E7`M3= ''ӒFyZ(j&"zAbYKŒ?&[?WOY[AC'ٜ=u hC_X vw>xd)#_ v<.J/RyH#6:Ҋ KC}sǽLIh7zݰf4tg?<@6(}mjVyG?~i3x2$KB,t0(ऺsH`n?tT|R88&|EN(&jM5^]InO7._oſ{=}kXD֡,$k>I7+ Ʈ/yv\Xu!e:ݹ۳%rmt^NJ:$TQ~It`*tcԿl|l%3S>$tUjkYKS(4;~_*L) Vtw=N .LK kb$2NA)l=iVcdns<-J$ VPE€C._f&0e7v30x/$.m?4IFh}V[c4{.7pb;H -e3,}]u`i3BNFDenr7aH2ŧSrHњLu&m55r g7iJvMeO$_O1|c!g-^~RRWGf,~B1S }c.+Y=~WM|s:: pR!-#ؗ LUsYa3'GP(U|=UUnCk ә썡i78 c+Y%@r^N tO#B-]2p~6&TQW6)33q 6<'m$XXWj\\fmLcLϬ#e6G ߮:g\l{7"%duA6SevO<U,W" ukT `V30)j’jX((Nn}Ș"M[G ٥XW7:8T /@P?KibzXﺙfIHq }s)bY-y(S+6w<5*Oo bEC=>o7/4wBi(4S_rdįx$#S0-am7$*lu{)HvQc +KEZcZKZ:b(,p@AOPQV]Eo(NCHӰNp^HÀ|? .F_`aJwC>#)|C95dmΓђ)cUl;k"mD7p~K8t8yȅ5 kd5[ջDEgKSL>}+ SL/a ZBƕMWda8TEø /AX"}!$TDÇ;EWGn|8+/CmKvi^TlI@4; KiWBf&X<7=r*C/'5.[>lj4ohf6*菷 B@DB}Z3-ҥd:i@]n#)v`V}1KF,m7!X>޿@C qVl:n7JuJuA)58&T&:=cow<ڻH1yJ 6~x/,JάQ<,N}#Z*P7\z٬iY<)\•Kj`Sdwp-Ҡv|J-XTU*0^JrKQd@Y}EEf݄YzcPgK!ChCU+І[8<;Z#'c!B!8ևRWS!|Vb%9T%; CJˍ|pP%-,0Om GvȘ/Ƙ1O{LrƯ$cdWmWHr;/sHu{Rq>w@eg؎q9g3_O45k '4C̓Axܻc..NgoIU^ܴ P62Fbi[օav}7SXuʺ*t0+]q\kBΎhfpY` E ].20m8d?z'(ɈsLө0C8%2:hLUүqME:_\Yըapo{o޽,q2 QP$cƺ`>d@IHKu2o!`6tc}d};cl)aVt֢$x J2_Tk%>!%&65IqI  ŏ<ŹHoΗe-!}%7)NxuCWלV cUJ@ ȍ^|{Ki䛿YѮ,:T3 8yekRŸK٘QhU@qkqOov@z 57e2)3BGLc24Mt_BVqJJȪ{E ZU!j ?gs,4'x*$͂^{:L<-vyPWz9-- pZV\ɾP/Ni s~{/ Tȹ4$,9(zv,)!Mvp:bw\5 0_J1 +:=5q4݉2LQdZ&_^Z?@S+piL9OO5%œ!Oo #{D(T H@29ҧEҒ OD'0z4PI6,ٶ=ZD"LAшb;6(t fP&Ǥt3H[ڰ^*9It5vld*67nS>Ab;h-K=nZL9ݧJlb ll/дrq@R),@ERs(˂bqؐz똿DGTs6Sz\g |q@y τfwyS,ŋK+{ZiI2$91XN:s2Q{ bo!VG#oDDHX)c'j}!C,Ʈ@#Fs4F$/o5w4#cOï}%FQPDh)Kl*oT8kD,eEnz:#ՒL?*2”i.q3XT$TK#,ҏ.qCHk!5m8aˇ>/$:,1/= 8?HeX۾I@ϩDQZ5‘thW>vY& ͳ< ayOjtdf ]nWǠ_7u0bUV8V\[~a>JL $6XGU&Iy/6oWlY':J ߅@9դy%C|f{o'|jKՅax^fP˘9a <3T\̨'e:tQOI~G] wq=;Ax ~Ȼq8I2$Po;#y`$<Ưc5t%eZ0?,>]\ ;%^ֽiNWyiMa|*zA%eGF(a^8;jGX1V,Db!bߏ{ p 糁|5Tns`D' SBـXPoXGv{KD[驒-G2EXڍblݠ"_H0匚oq{"# c\9xYϩ87$SK'n՟ IS&,0\_~Mo3\u_ɏ?+\SXT5;bz7ɿ` ϟߛ(ɥ0+.M#9Qܡ)ޑ2.Nie0ݵ:i$8dҧ=;;$Ie+Ӆ5DbV4 nߙ]Nm'^)*_vfz} [1M3ьj^ +L۬GԐ:PCfk*C 3}[XJN6l3f?K4.$`&)p,?YI+u֡h([$\y}I-7%W2ڦGT)_hzyf4 |ۭ; >ʚbT9)0P*Ҽ[m}|v4]|I3"bh<] :4G#m48!Uʉo\ZkDf^eFF 8zERA!.-Uv%qفKZ~l!SRH+qf$)R6N+.؅z 0';,7t?\C#' Κ*lk0=>+n f V? 4QJj:4;i종 'a }7Ų*cB"P# M~HHR`Y#(@! 7"~FJ\uP剼2еTC2 4dRU1oC5Weq<zc)ڃ$*@E`;T݃wfg!xI n|XZ(EYF`H>iTj[LuLek #6+%%cOU7UuA0f%i( ׂ, eyݿꅾ .4+hy2%\gqiI.ha?3X䱧ͻv(0.*skW[+L@.SFÅ6ds#_}cbYvWA{[L<,}^ָc_Ph>Doel,c]P]k1h2mly+~^ ؙ)^5Á jx\uސraD@hGQ}4JJ(yR"t Fnd9W4Ug"E$}k?͚~6h[˾xTR s g7;ϤG\-$?6Y\WYv+4RȤ7hG 0G 7#B&:/@O9F[TUt &~A'N6%&oKBP6WLdf I'úX SN\2XЅZ-\[ Dxlqc1` Ba E'M:ux9ڶO1DQ4r\ZQԫ԰\0u5YBWI̋$ pf^U̩a5te~{¡uϻDx,l87(c,]\.,akv <⠬xФs`e̵G@Hj,7E#ݭkXAg5-b0hn2%w'ݧx?K8ZMorCrZ}#h<|P Uvz7l%hܓwNČOML;qV}9D"gS}a@gTE7BP2X~lmmUin&к$Jb!6Q}d0Ȏq5ޕ@o9^c-̧;vxF] D}°l*Q %wHe7)ѿogzE`TS{ [֮vfJ5I#p]>G'@S<":EL:YX)10' ˜/;VYSKƜEpoi'0fV[=D3="g"&Nݻ.q7"nMWt> T^\ qB@{ ;܍7amB} ؝Dj1*W 3 BOmL'WK4B-~zSa茛Vf}i;yhwD̍剉L|O?9x ?kbL{}8Mi1 &Ң`AК^ww.אHY ."@j;xrxO+J)xY/O}a + }«}NS>M]6J?Kg}2M<Y8Q<;Q7bz%◙zB^eB䍨^00 3\rÚ|ٱ5@6lNBGrѕ_~bU`38"6f ==Z3$.oejiDf]ӾI׌1H֬MLSD19%3,›_9-3Rߴ;(5D*C% V|\9軏TvAv춨(JɖtX~A'wH_ yhTio Z=]kCZPsNho*tڃ>)3vq@D;4MQS ⨣PcVDtSx&"ZBѓ)viVc02-\a XN`z]~: e/&o7)"Jwc9nzi'Mty$L^ŰMT YCo/̮lc}IcJvy칮\esJy;&\>,әvV荁­ƬTKGJ-\g EbXx^d8DޑY G[ 2;Eԁ%Qyꥑ`REx~I??m&0/-5AyI @ D!&[5Ūt0Y,M$$1=-~IY˘ }D OޑwHBN %s!v,52rp~Ղ'YD]YåVyuA=>.=1&dReWY$ڊucr8Bs;by&cQѾ;BS?ŏ*S ~2p(.3uh Ps+I7V:N P& "Ok:_d-aXM[AΛ%Rytw3En3M];$>ɉ^lcPTa͙Da6wWt^a&W%聖hfc|̟eޘ$yaAUJas?]ςl71V]Kcod:/}o(űNl Q[lNf#r=ě׷۬7=I5 hp# $'>oIپ/G]CE-܌5w܂Gi~(qD+2SK<=:DssYwÌ%h쬠$6}l[n3g-*5sD;УԮK#9]'ې&Pċme36^;:b"q= CWy kG7Jh}?J5a]a U!SE!@=1ǰޱcۊc"g͢! AqbDQTPCזn$@(.aAg~b̰Dtgh2~ݶ,bCu;Om4H )ml~}ҥrY_21z#>>wy e AAOTD^XHx;N?{D>z)nu #s#= {5d' k}tŸ'+6pi2+W/ySX wg,F?0vLM=>^wNOQoHa8Dj1}A9[#$ʳ7W0w2L j(2ztx/\LRfa|O.O&Npi+H M\/1͆?/{Xp TWir&l9X['V*4o ]2<}EH#Y 9~'a*80[lL߁bFHEh[ ^.r^?zj-a+:oA:n.9JfX oSMCdtPo6ZguX/LqvN]wv庆E)F| T5gB7;-ᾆboZ$s_@#J@HŰΗ^zemE I$FxRyrt):*|̲E+ԝMlˁk:՛L[g@DSn{Uۄqu_Zm"VsZ >\DyPgr䘉n٤P]-\D+0}$4]e(Anr/]Jblw^j?iBt[Ӑ}FHVwj?rZD!B(/6a*&gdH2^LnTȡ eI̿# ȹ_%Ȕ1fWg)-u^XTRI ZO[=CHs\p$n w*SB}#g?|Ŝׯ o\)U_T{Kouigi_@D{6zdyh(E`/winuG.}`~ Bjb}vfsyuPv?8Y+\%\/dIWYpa#+7LM SasWウoՒl+:I':hkn^Dv!@/t4m l$$:µߨ7$vpi%Z@=t! s+{#L<,ש"M;S0ƙ6YJ[JRdNO@쁊;K"v̠ gCl}q &UgWzҀq|uq3&p-H[6nnrrZj.P'JwBqζ;l?T7= ˷DnR\Ad x)jR7KbѽŚH r#WqEDϵiWdHrCG<r_o*ly\,}*~耔38Tb{s9G v ..FTҖ^F2Bix{V<*7gϒU0->7>g9]YWAtz!ָO:oBc'̛sTj*ԚmqJǡVDt ף~aS,P[xѕ#HqbOXK#}!x෦n-wKx!z!f=(:{1S\+]\Ҿ! fk֍›%G H0M\ :ѥePĂˋCGhCszbӚ9*щq (ܱi/h>"8$`%p!P)U^=U:a4l^'pá 5qA9\X22Y<N/csZ Їj|Q" 42%w0wc,0X[⻎_1~\֫iu$k@{r ]#e&penAYY1pF%; 6OfSӚ^">k7ESt .R%Qpӳ1"E-<^Ѩ ?aOK#j/t349ܒQt0=Br !~!&}.ѕߎC uRW);V]>a*M+[GN'Z3u=St kdMXOIár{* VtT  q#$&FԞƨ9sBk}N9ԴC2Z0~,#aԉfxpqa=Έf8lV tf$X%q߷f<=L9Rۜ׹& y;Fd61['S =gBLR%zlIV 0`%X=?e"P']e^42O :ra.JFō +^Om0_ èrjެ5?ʴ҇SKpIOSW3䅖A34{Y kH2d+J@7)V~UpSZɂX#և'ټzV>;P/miִ~҄x!7~ rPL]ʎHR*ݡu=m$܅cDƋ }K#J/zw]V7dont y*r'&#j8s{K_+< _V0Oi[~4Ǥ~!~j14gn[ MAThl 2Ywnܕ%+^nayT\r)o-.Mf(~YYBņ.%PɫZPiKkԏL}&ѱPpӚXJ#f|O?^D=œ_MUobCR:7*ߗyp2Iatn,e՞u_cE vHZtfZxKbs}A oC9;@)~s+WZ:$*?N# UYw6YWq侑'ʥD76D@q?n?(Kk( XW[\5lϩTuN,(|t"`''eu-и kH6s`E.C/~:78- zKS5jjAFpȭhnf'Im& &UĽ8u޷a&ıԵ<0=I%FjVϣ\@MMn$'XsF9@ݘnLb /ׁČl< O`~Ȫ2p l083a R+V`w~na&C 3jɝ!󤍽*!MoO9LTn!P;^ϡ)MK+6>#pMAgVѲHPz-)BwvYeFUTM t.0D 3sW1GOoxhƝo1RaDtKRq4UngDgBU;Y a@ &AVln\^'Mp®AjQ3sE5/X'.(Bd7YU9혪.o 6"(zʁ^ꭓ8 }yD;ퟘM5XM*"c{Ѐsiuw#gD}C#>Lj{&t>,zF\ 7W&3IA ݺW`cly.%qաsb\/aduá!nȶCuuوd'?%q'%^!FWlQK(.Zj0qK_3֯{ƣpadX7qz'g|ҍ/|чx8ܭEmڴ<'@_҆2`}9 )tC4N; XZVш/z/K,U){)pm]DT|SKHjb40*{?vz{؏H/uFT'cP6X){6B [a-0g$OA] ʈjהQu-/Q֝2y #ze< |xU # a}ptPޣy9:{# @zqZ_V ñ8"Io Ф!5 UӦI٩{FоS&si^+Tb_u( u;.Pa0l͍XH^' W)Ơ՜Q{7oͰETUMi5a&l1b9̳g:ʆɣ~wK--DW.[/3r%4bC~ߢL]Y&ti\ױS+2 b&rZ,~TCB[{}nXD}Ìk+V'HK0bUxn s{|lH|ȱ.ܚfɐ1LQxb>h=Q`Wؠ QIi} R5P6>mGD[WuWgCZ~+P{zڷDU5 p}Zpds/hZzd2e7I˖v 8+ 2?Ri)BxI ^1\'0X=x(@nFEmv6G`|"_9mNVLS{nz<\@̡[\L8nN D ^h(7^liAΎ".uykF[./4溿!>E!jb+nRD w(8Z14.dTA\z6#.!qn яx= !@>%ȴzʽ?Ebo؞h uEDi$B'SvwH6{w]aad uzMB?dC={=FDAV4 aOLj5-u!ٹmBrGE1p:x.9e&D>jґ巡i?xXThרzSi0TU@g#R]} e/5u1&BkݪG칲hdYU虹 #M3XIuI&X]Wcp{b~T=)}y84B-:kWtO_O`nwYwa{Х[z҅bmf2rf:VBlLc\U!5()w0/G!fX%`Ai% $de5Ců5S+$$ulshCKT eZTLpcT5^SPl4Us6,6H LM=𲲘U1O˪&JF>FgC'l.vz䓖olDz)t^UT`(i.SSZ ~"RXcul:i_"j!A-KB?DK g~,O /5ۆ?z]NiMJvX(SE1#ӗ!,.C')p~;VS<6ge)=(} ?xz<}MaFFmy7a옺!$GNJcdހ1b^n0)w<Cs@A 0Kb51v/Ӟ቟1PX .p v>gm_oe cntھ$9?D9! @EU4`48F}v\7G5T#yw%ڦ&}GeG'7&>@ GX0u$Zma&'-gg-u [=; µۂI%UvvID#E$O!8Çe</.rrf3-_Ro;X6+Uu#f; ,GC^Uu {-U/6SG ?bJ9TdnL8ܪ@1Yo0Ǖ:*ˆ:Ձ vow{ ЭKLz|BW̸kefVizL-'y˫a˚D$m+wcV 6&Pnzy|LN6ul?$SZ +{z{%b#|Z-T]68^^ +{XbVL[uycBeqԋ *dO% ngNLNjd,R94.A2_Ϲ$]~i*Ϝk9WBWكv0#[؍׀?z[6E3u" X 7R.{)5`$/ ܳǼE"A*O+I5jO<-8d#bأ"XcX{ mCDq:zz5]qqS  9RV)?0ZR|vH+%>zJB^џAIu]BՁю?z$Q$b}!Xx:^{+ mQ"AhiϠNS{ԏdI#J܌7~y& "[-_[N@ =23_iyخzsI-X7p()F* m|7ݸ^u9j|~ͫFxf\z X4NWbRi'x' :um!`]NkL4',ݽܿ0qUDDP0zZoA_Ny~ K(|Fr7Ak& ZQE-x5뤜<EdRE?Ob%2DHpLԃvwFB6pU(`NVIB'S&쁼7_G '6="( 3+U7b̗w'B N~DKm gR0,| *t͛6Տ|fu/a%@J'bJ9}pܓX%!!iKx<$0ϣ:3׼Ќ[UO6SeNLj_DFfhO 'j3-$icƆy@>%6'$-uk fCЄbv{Zq0Iw#ɚބX\ 9 XVs~ǩ:#2i!N-(ZtpmyЅDUaY&Xmm1"B uݪ!k˥A$jבxW'ަ|7]JiZV+t߯fHLKNJGVrXB>AϰE?^Ԉl}v>]rl?: ^(N2q0%&l+Mp 7oi0L4ש:,ivO?Mu=sMxZ)6ڧRm< \W< jE]GIKԂBxJMԧ43`"3mvaި<ͭ:oD.5YJFW)0~a ZqC, cA!Eȓ\5m{ Shz]6D'.(\rЉ.ɌU@|'X7(买7cLrW0C={6kˁzaXPȤij7@Tfdbj=,n Gezf$GV31kMW2x{jb}ZI^* Sv࣍ͥܝXm㧤e3.΋nw@sL,,TͿbTY, N$B{@m 'Xcts|ר/aB-/;/`Gf WJQ%°fjp#DBU`cj׬TՊ 븱4uBƤ.9WFtV.I&ĠN_.u6}(3AGNoYubpnr4s磯x, /Ǿօ YAĬ'vT I03$5~wϹTomZ̶3*5S1"cD,ǜ8 4W|CiV1DXY؃UcBLӨQ2NV]@r7}Few5"rvs)FJfl`y7N| քL'YH^И:X^O6\F$45؈mHe$Ed>]ep%6I`ֽ^k+0?EH=M&R>M5ԭQ_R,ۆ~H7+ *MafakbF1ɖ\_^w3cDf> +Qsi: ]ƗEƁzl :i썸<<u2Ci5 ] ~ !K9L1 "p;M Y;kf#)Jl)PuJtk@Fl:$ubUAhVMK|#8̈́NEjþ⋞JfrjU&d)S^dy=H-‰x= ~"JB8tqW/ e%du )lTKG*\LGn滈5g|Τ'kG1y1!h OK@7#|M4\pcz%u  L:vcNi4Q:$o%,Q'ƹ {b>%ڤ`/MNiB"\? UrXQ + ƌfXQ=5ɿ POh:=~|I{<7X4e%FW5[a,H*>rgaDn7;:)@?΂+Vpz3VrM&dng'v}CVړ|,Lym!6KV>m~0$(&.Z}\#u3mlsbנUFs0$~ I?vHy\^* ʁO[BH`B7MKO>8b% gԡ=We>h0A XB->w=`/ 4mY=Lϙtq_a]HJ/n?DIPJϷVZطZĎ\ƞ?QZp#5{"g aJ䚆ډA//Г{6<} "|]\UN#'KhV?7T+}-/̒ F׬3t 8H!So1slC)$?JmQ}h3d;=|?F" @~^$&#,pZc%9E U>'a#xM}y/]81OTI_>ԝ +!J@}D=1R?ye6PE^[1G#igÊ#rb<}k鰍=EQ79ґ{j;fl2PB^b{ x =>ǽ޺S;E :nNY#z MFxDd-Cx!7535_ 2u\{=K cJوx])dd W^ $elXg҄NۛV DPAw/=T$j$d;Zbon~c2O@W-@)M_3-M/fHS):uYQtfRS8{MBk ʯbeCXQF[*xsș1mv6"%1bćn)hb$1Y)0} ?e`z S)h ﰓw0s%(~ki֧ +f;'L@(yOSMP~?A^q>R;k"ɈPި,0G$uzgT 6G&XPC-)_. ՓU~vĿcCTֵ̫t+$.rV7w` 1Qz5=z% џ)!_Az'87Tt,iǹG0UO繩+݌B rt: iO]r=]#4(ZMI ҥ/&u{g8&b@̓RHJ8aޙmn('saMUʎn7dJw1LԬiM ۡ"n_i0Th@LJ-܇K4#@@Ko2 J 1-CW2ԝY 0MdK_0[2e I$1XEt+;47bmʣj,:s~-&>f?L]% dLpZ"^N5N{yq'Jm^fj*L߿DF\̓.~3ŗ֫[t_7(13Ogzm KI/ւ8E6XpJsUbj ^ M1mV.c ؓUsOLSQW,D3nG:BD[gE!V7gXxK E0ygr;h?[ Tߘ ,\AY/dV]A,5l+5f/Dk qZEoZ2-qG.)Quݜ Uqqu8M6 <s"$0S.(|̲]LiaX{_38G2ZL jfF %V;\/@PFÐl7'FoQtϯqAsQ&kBf'jHQ*ȘOtKf߭LZ"vV|%H$o_)a1U4*8X_9Y# _ ޳-ˢ_ꑮ`UKMt1 6q~g~MDeqHl*DjjHj2SW^hCJbm;)}{15A9!*nJC!$ iQAJ %` Ӭ!ɔ(+TNQY:*=շ>RiM;W dg_cFZHrJ̜4nbP .Sݔȼ,b@& G0ޞ1v5BLO UtX'btuXBDG@9q)sL>#;E;L6E R8ed1I _hF6|yX UK$/3haOѷ yt؜`+ie*";ıcrHFz9%m^((|J b8@{ld0O "?s JYByJQ`.[n_Lg[>.#dܺN_ڢZjp`~ԙmq&| ]]Mi,uYs[p;(6TMO sV\LN{wU,B3P ڳNTjcN-1s1* O`^#-wy RV &Q> gaѣQΚâɞf`qcju+ |dbML-OLPGZh!VsV7ƾߦqS;bqy %iL.W _8?|NV\ Nt9vص_47 |g3u'{~9;ʏ# ,$D1vW(>p1ޗ[ !_͏&Kb%:Bˣivf^_U)mG&$9$|3lr xX$ ?'jF-ËsX Dsqd򒳼rgUG;ѵF慘X[Ɇ/2-R 0peLB芦V_@j _` _tQ:u$|j޾Z|N˜s>;s8#,_VҠ}U,ȥ Z.NsVtmWoL}~xfڽqS5ʘIC=Y/X_o7wSEIyv提n.i`UZK|&acY2RmOW@^j0 йN<V]a/53Ǡϻ7%*Gir",`/\Q]WHfGs拈(`w&pGP~X[%iBPQ *FA/ߒW f$i%TyZQkSu+@)g;_4GŅ̩jd ZU+Wys5QE|4aE|JZEe 5.i8&_W1^^+ևV/b*`tNA!3&XWS#V!L{8&-.OY\n㆗Ļ:\[%8Hnl R׷/Q@Ԯ<\a:#G !y˼J{ ̾€^+|WjKpJ:ۉ2{47;.À$\JS),8WL|݊Cef;j'|صk5ֵFwbtv |4>Uro/JJO?eUM|רϙÑ3aӘZ~,S~vЮE] IKUU"ueeNzܫ?^OyF"2cPO8C̚Xʗ.3 wykZAFp3v֠4ڞ5`&MxQ>NI\F`ZiкRVuՂv ޟg]Y PgGUЅZOPW;sѽ> ]a Rh;S`]6,==P<[6Ε z`G3u5i%<|mQISӠ\2B|Tn?*:3k eޕ ['%)AͿj;?$f+̛/\AXj0eGx`֊lhv~PK{81'}7֣G }~o&4w**e|+(dƤ uPG;'F:nŊlا&sGSi1K[Q2;T I]<|HY"mhI#BΟR *2cU 672v ky3>9!R 9 UG"1𿣭Ni3I{!XeX F:#K1ɂ51V{ 'J<f{Tjo<2D.~o&&mr^sfjRij'8t$YA8ce$emik!JWZ:톒 jԴmj $A7i?H!ƳS{hcC~V$?{ Fk#fG ZxB$BNo\1 -8̔}]7T3< l-QEp"'cDU/?^ʷdD"OgnVcZf5P %h hjhEiv xj6CfHCz4՟و85XaZy;D#:뱤Su\]~ j;遌OcNe84ii}UL4n6~z}%VnQm9u=,Mm5+[ ! w WǤڱqP*loi{&{1=bi7?$5W& Pz(u-wE* c}Y \GfRSA2^MퟂT0۰C-ң&,E7t=M$>{/q;8 ]u;alӍї2?KOOm Gj (-yX6kE(S12ɍv!4Vgl k:pPz&TuQg! ݇<Ԅtxx;u@S'{SwϼV渇>j~ۨ蔈-AV;K0J R= V]Ծr(J B_1@]EЭBA8YW\m10.M1Ea+h1Ί^L |Ux.$)H>n;~aZ^59F-#(M5/IZ}vy3حy/[Ua#:uי_^wA:ls5{ʆ8\##Q`}8rU=ִ3 I.HW>C\]!“X : #(C *6]$x}ļ3#FTO{4Ffv8:vh9@a>! 'Ԍ[yB}C rI@ }+~VKu%qgи .FbvlƱu0&sXx ltF׵By:AHVO$sx R XP30 =']r9G:q6KPNg[GL/"9AvHjt." ĠgSwcwk)ҋ;湋 Tk6Ҥu7le!ERVl׉)?)1r؇)qD92eef #;M3L|{yd媹ZПw;e=^Y[pMoRͿ%'d鞭kjgA+c qo߶^0s Pj}J&RPbn_Gdxm iVȽ68@V1h O(}z ][ ge4zAYsŐ{|2ѾT[ʎ^(y:DUlf\;Ԏ"-*&{B^So̓gz]O!K ,ZK,рĜu#{:T[d\-|ߊ@̤}S!lnѭ~꺃p#:*UsOBYc4v&FT[GܾE"Dm]9+O6#ʊi =B,OF{sf6TvGkM|lƹ'zI-a3(9 R1pH;@ κсmQm":~mM0^붪]??E:Y1p?v0аD@G#E"r 1Ĩ7ōz?7u/Ld&$')>miA(u[qygɘr/2Vi ~LN5{ea[P-cΒ =Di6qmF=R yS_3"" 4GeEGc aLNiĭ#f^E'n^}P=_ mڡΎS ^`/d)iҦwխU.C[VF`o%R0qZT-_(6gHU! q}X:@>嘱Ϋ;; [U`cz'GF 0yE_HN|*nb <򲥕 ;ݪ" 昐9txQ\b*z~?Bϔ5OܲTe8r((UU:*A>L.6f0DVnɻ=|5"( L#uGG(IdZ~PxYLl|堭)@]lF3m`>&ndl5/j6E6i 0 Ixjգjp*Ma?-Ѷ<̘vxaAGPRq>:G f3uwQ6,wb $im|0rQvQZJH&†b_Z5Lt #4[|=wNX`MmV[w.N?p!#t؜Ne,B ~xZ'bmN M(6`DɒG"ȣ(<0/"`e$fg*kƼ3_nl|1WVǐm QQӶ ˆDlu=MݪJϨx]w߂\Um|Tܢ{)*sr$P.OTha GxOh`a_м}lX}~E}gYaȔstY+-7ʮ*on|o8B@jlWV]E RK [[G3eV+&$궄:ey$*<DCijD@<"ah_"ER_ fԒkK8C Sci 3ad57Mt=M^AڀV ǔ[p^}CћxqJhHn0SuInSx#ɁgA\S2O& 7xۤ.<_ƕѾJG'e@Fu~_L@&6}42JDǩQg90&[7z% xMY@Y7b?$\ Nvj/;T(>g?{3 U'׎tEuw3=!`:(؞!# 4;$Ney%<,BѦi@9uөgn3J. rbBB^{DhV^ t{A֔ՋFT}5wFLU##e]vD/[щ⹏,+qn(!-8SYc/$}#o7:ibwDMFM]sg!" z~ UM>كث̓$&-֒:qIDMa3ͽ_:+\deilewF ۏ&]|uW\=Zڮ{>+e<x2׽* S^U"*?wWvN(S2](Ҳѱ%V5 /e@/<}K?7xC3~j!zT[V dÉPj׺9|gٲxb?\{Ro,e-l "4n?Z&/]@?KҙuOת0bUp?׾e"xio ' EKGB.mq2--3p ;t,T_l6a,W#QX a4kppHۀWX6ڄ_i5>4TT^bU³k_<|h Qo1\UYl9EH ȼV~rЭ!Qb^╢oj/n'3AEas`xxxS`[ OBzg_Q$ߜl$)%Bxc  --UUP1tTVCBK-D7 91=fΐmbÛ0(}4s]!D164%:.PZztExk^+04޻I;:$cڇ&Sxd:=19d/_KMȣRQf(?vDhڌv?V$ۓ"[>1ߓ;#i2eiSUWCKF֫@: !O@Cn|]O/Z ڐ$LrڲMTrt=63܎|fCDf]w^}tOmh~F)\γWI,d(9L˨L~V. ׌WM{ iYl} P͠vHձ \{+i,3$#×D}amR<ŀ ho/ZfYRh~^*BN>*kD l=WPF[_t[W OsGkV75aٵ cNW6N>cGe,@&-lA(\DAg)HPj.f#6='Sl [Gj*ZGVh9䨌sN2ߚ5d򅰡FR .GR6rzuYNsP.oi;1j[7e ]aՒ q.>;úZ?q]GD#kd\%pޮ',j HYtSt`vD? dZk$@N2I]u|*LN]lv:TA/&y=*31B8ql"9D Wf;{ )݇A Q %ba(m[ IF}{LaܾJ,Do5nnVv&4 rS#-ueBY ykZ4Gd{ϯ?>牖{Dh@qFC]e- (T 9"8&S$aJ`}Ymv v?#Hq7;0$&Y)iOwpS~|RQ+[ lmºz(LﲎVQ ق{A|C-=; @* ~by͎^:Ώ-Jh KL`lUO oe-ZoWFJ]VdhzCyJ^lv_y|@~G;Xh r67r="9y!T%Л&sF<5Ա`LHwxoBRJ Ng*4]`b ]/VWxfq5SB*6$h<Yɇ-P,b[ i/sޔW5@nW\ bD_G?%p4!t-e.t||nwTrf<ϬU%em]{q:,ۃ)+>/9 QfutN?Q`EPzGf9J/l[* ?`j%nP5VI7Pj7>V6ᵳyj'\C޻|fu;Sb@c#xɘv%F/I1Ny@Ӣgo&A</n,#ZRkeLm 3` ^qy/Ziƿ|*p;9d>Ʋ=御u0u7mTmQ#|4ErcdKG=^|,*q^ 0AN)ܳI;֋ FmtF "Τu|ݟNBm힎Uc6X,7W%3d{20.n;5:xa_eޱ?qA[m3[1e pnA&#%zc4}'*sKHFaۗpI\hncLT҃ %fcr°1ش$q";#/?_gǽUA[ȦӵU~rZߦ"c&5\S$f#-rBG*Ej`֔#DQ (4H*cm ]*jp&gCB7#.K9(M ]\`84'_=Z# ШZ`E/{mF|j&C NvE]<:_x=6);(O^fBkusI)V,ʬXkˠ&M̒?jhNTdGRuh1qK˹ZYNt\A>39gXx(繟 1n⦉o+t42dŹإ ->+:.}q$D! ;_Qi=*SPxEQ8R%rM̅Q5KW| j}lqt0HK}L‚h-ĉ9bsaOId2iFNts>YiTJC+ ԝ~X$2Rf}ͧv_rr% |42Q ?fx9xH 3Q5z%د,UE4̽UNҞBͧ C@_~Nv~KtJ]0=>:S3 SJs ΕM26/nɼbfm@=Ԏ-_RՋ( (IJ֖a{~U%7k-DŽF0A ^PyhׂrbX[rcbVq ' a SQ٤*9 \Y:jX(X"fOhz;Y_b {\Ƃ=Lg\C= A5\ ̷n Ip1MNH[ЀRGFy!h5m7=p P˃4ZO,6:(u``uqj1<4I7t=IP$RȐzC9̴4&LE: g]׉MBX+WZ*y>jxijt' Oj&iVi[P̣TҒgb6Kw|cwر/l)t#TjR r *'4׎G̓l1J=d/90y538Fk"N]wΙ;#b 1B_Q~wO]2I~bIS `NgƱr!Bu0wO@ Q̻\Ke"xBH]iDf&y88'ș\*N"Q-Tg ּ)S*balLmyDO`e{ƶ.mj&uرo˚mpHWOZk:&fɰ%6 lըLh|C2} O'1.,Ͽ2ֳrV qsk``9250x8$xD_'g34 q`QPe.P;?l"aM!N 5eбu4LyP&ij%Z/W_tb-xX7sL4F0~bKn7wӘtq'YQ (Z74E* ye5b,) M) ΥƑMQ['/ K wؑ_k\zpbJE} )?#HzY\*T)eYwQ2=h]0v87T,Vd&d6`|K tXGnQ"NVYLX7&?yVtꋫ}?@)Ў!0SaFnQ邗L$ (_LG`z4Pi?l .Fzzk1r2Z(FhXKQ %~Ygx~ëg2*plsA*V?/) !' ]2ԙAܧ伎k%ޠ+Z;Yak[Q p>iU!\O:hi$PGM>  ~&X}~aM}&*` mڦy!1hkG: _^sWhh}!9uɳ6joJ/ |#VԾk_iopB‘ɴ`—(~vIϮ•bcHjOkҘ]P?E5ӟw#>5;WTc%M&QF'OjZ'kfkM7&8u:X/['=BEנÚUh[1^"u3ɯOb=T{SzF jkN ǮI!s-8 GQ rTKT`+K@|)!+8Tm]=?w ^)F3VF T%%%i1.\5<ұ":w#LHAJTa$yi#[ƞK z;ry G.jE tosJ55خs~F!v[爚u%:ESBk e 1BNvr>yj.E#Z+Ț|a"]Hm5V$Pb jd,B<4ڽJG#DHH"q"7A3oN &A_Aî'PQ|`3I0H\U糑ŝ#zEͳi^BEՕ%ȩ}b;NR$ C]=P&IhHp]QkFT ٶ!'|}{Y(ɀ94.9FaOm-Iჯ$iR:Q~;mPȅz߀ 0Z} M"_5z~$ jcy$n)|@f_5⦒?X϶p%D$K.|Os?,+f ;oJk7 I8| rf-)T{Q;]06=;$Wm]rGӉgna?lGR,e,Hܸnb|jNsCk}!+$OAFh+@H~H%H+Dp$4Ռ.V}W%b{Y.M0B ĒI:A٠[ a*-]ER#MnjN h)nCѽĶ\T2r էK$b֫G1.zz:8X\xՀܫ9ᒃd^_k}y[GFcI 5Gv7N :Y8ht˒wO2Dx[PnȹO=l*% * qw2Kǭɀ'j9Fեa|[I,r:'eL)q)@[ mvmBE{<,\GNYffJaCEY#iP`!>+P"7Fz1(0`!FJC=Ì3VQ]%MX)ְRl ؂pyU]v3n@}6܀QX Zz:QENB7_1[\EP.bh~8vU:.(lyB=aY*P21|`@ (9⼬X&,+ƒSR/(< a8An%Vz2aX/ɞ#g֧t]8x $džJY>N cf~xKԂ椚YP%|b;6}ze -i*S|f*t⾀``>gpxaAK)Lo|JXOӟ^k~WLҚ:GlUOе&^S!+5ĩszW3TDU.! jrR-z~wzNf}b4_HQPS)Aˋd@GڛNPfwپ&9%c8翄P>B-n| 'XF~\,>NQUp<@ ;_C˃ؙ:=̙ pF 2Jr/-2IH7Xo(G@}[N*2{N,Ҫ_|Bsl԰LaMXCا[@Mc uLnM 8H(߅xF.p'PM/uIMuVFRhD܊^cem1уad|v漘t> #ʲy5p+x2zFgMb$vC&TT9FLֵ,&mLm%wT!AD0H&"\íX^mUW'[7MnF +2Z "7} ‰bwE'fvZ|r)Zm1$  fTO 2; ۥfVp RZ)A-)/f?ZA 8!7| /.\JyVaY$ ˴c %m@ >HVIB̻$qYAii?ªo AzJ$ CS26g5J]1YCbV|պiI,P2+5qkCr[@  d0 wu5S[BUppls8J~trֵFXKo$U3FwaPV¢7%S[>] 9'4I(g!;Z+qquH%msgEBPN.8T#K%uytDDog%xrDaƅ#Z>@VDzQ/Pʖ,s+7#Vb74|s@tM TVoV2%.v2Em,0N8vVAdnC/ĢI!k v熘DжF;O iN)QmýZtJP  .n}oQmLz'Qt [KJiGT(]ygYF8go ď/ݦʣ3*!0/ M]U0$ĩW SG!+t9{E rR~*j Ow7 Y/jFaNv0nh&H"FjO&5+׍9?U5ܤ3rKYdP'HaEnDLkCw+~{8NrôYb=='Xrh>3"=GB< lJeĆ=?DRzAjcŷxtĚpPa >h.E#VOR !i4SHvO\.*4Os+]:SXYf0> 9%@)E_H#ad\]B4.0"^yXD,HrnhA҂35/WX 1RJf1MBIK.\o6S2 6plh2z{-\p  =XY^qnjʜoT6.$1 e7S,ڸs ED%uiOu NgmK|AK<#%+@BC7N@˼ة}1P[TN^̨Y-gs3uͱ l8g"[]O~6fz֗^1`]$ъ;)I8gjʏFOw{ ѴD%_(V8eEo8+'fW-ņw2**8 J7 ≟E·Z7[l5zZk8h_q/v ;nW҃eIb5bNRn5s;ff>_z3~+D+ΗWY.H+")/̳UkqQ^͡ /!xюf2 Oe_OyLGqϕx:k,`r;߹6|g?*hC.RnhQXlDEe$BJ`*Qݢ7iq2Nh'SPUs>˟#V cZa@ձ5JY6&D bhIdO1uŤ lzK-cb }ssh,Zq9oxY2M;}ge&e#,߶ȃidNȘb눋Xx@s|(c'mHnTj65Z`CY7Ln"}Lbm&ͦ2#3چ4Aj' D+y?uiv6zw+% ǸΏ1v0!fMm2u(wIխ1k\c E.5ӫs9(%sG$pDPB`},>LՊU :oFk&e#|\|Uq]kֿ)g YTjQ ۬]JNӲdz卮x!Xa̮'av_#o#k@Mv%z0A8,mj R$B׷SK{ T3nm!I:\BLHUn'VP1'j|EZTaV\HRK* ɧ@'Eb0BolG7&pƚ:Ձg +u 47-kpEhl5Zq0]iE>܏y4An[ h@ H(o 46̏{F\b/,Qɐو±UM7cP/m8TrZZ-k"nJ_+rPL4#P%J~7L7.1aOM|rM+sw(^ȽЧ ]bvŦHc6Ƿ'+> [)m_iQUDj@F ARCwY16w!= 4DpfVQ>,چkWvLuv S C'cQ$T(=68'IMAk!D ڶD6ðfv" u)",;^ Ҝ$.@CD@ߢ8aõl9bU}3)yX6~˷>tlӎQԣbKٲf4\<9V:&o.<攥 aN_75$b}Mzǘ疊h@z/4c ^1؟^Xw.^i@w yV9M?962sѠP_L# Wy;ׯn9s;ĸJyȃuADٸN h.;MCW,::/%#!ۄxDm(3\D&kMւCP)K$cv`of%?yWqDՙ2B5\snPE^%FU>:t/$q3E ֥p\TFxa*pY1ð9)p\yu} #ʖ^|7bK0iL-2և6=OgkR47K s2'J;&1DK FOjitl{ELJ;hYe~8>v>3p1K[sԙ/u:˄gJ?R<9Y17p 5VDd H`ⴔ#'w%]u | SO >XfA1/<<,N&Vз:8trU/dGoŕX7~J{.ByQ#j _yz4]nG*yLa޾q3Tu)⎁z޿duJ4SOξ<`ٟNo/ $XH .b6mtoCXh=WշZxr k3vFSaǚ==VvxL)F듉R'8pZ<Թyx2A 8n}Q:h.wx%)H=mUŀm澪 Taցs6Ų{Qsѥ]spnSݑQߡ3 ;9&;3P/PF#n9jvXaόE O-qE FFU$\X8/d# Pq%, e1u@n'€\tBw8~|'w̑4' m(2B14x:^SȅY(`7?7uȮ $ȵY]<ZߵEG`$?4U/j8Tx70.j}QU &TChl':A ?>ѐL@!$FM`~M+BE']] o}I ٯM{6ιVbJ--_9SCKB$bbG ըmF#tΛ,?q5ԉ9\^*.00^츃 w5.cxk+ FW.ދ0]H4_^V3ܧH j ,Kh*5`V4q4 jZ:̈́3;DiCxFhyQZlŁw Q)2 *SÝH;kvDxXBڵ,>0:Sa91x7M槎%e<v>5[ QF T-ZY3 {~XV8iv)8k#ɦkسTԣR\M<&?Qmp Rx(RJʚN*3_X^M`&Dgv #gs7FsIч 1սrq~]XJ~f+3/]g3L$0] W6;~ix]į/N_; , ܃p*oOI T4u2;3gS ݐB|3:(i'i @ˇiz.1Yh|V"0 벮A6?$S2_p'd_7z/ҚG2j>:F1t}=}rL`~pX1Vh ,hd 9XN~9cE 9bM?6C`ë~x4Ȍ#!*zV8;aq6% Ü71,fo3 (蕕UD[ !IhlR| VIq~ii~PBbsP=+ڙrݴ‡\6ZlX"! ^Aē؉@+w>aoYܐ_q{>Oϕ)iغ>"PQoԳD WPXGj1vZ$ VkmƆ@>WAkмGq% q=I'gߔ;]ɜxx}= ,e,e~ MJ&t@WY:$p]|B~z*V(74}pa ӷ\0̸ L=F[vFK3 >[VA Aӥ]t$Z|7jܑv$<-k@ԶaPjQgUem#Sw 7MvZwG R%N"h_~;pMmu&A`潿'Cvy~''4h8T4=7IAnSo'DikO< BϷiǽNK1m\jmۇWJOoProMܧ"q!3hvkOh|>>n3 ww„cB 1:Ύ|Gl 9g`?o$hvv"dCq'GBZ }8  rCJbn *jW/edѪ<%zD ha]Gz5ntACSnxc'HHiø_A-pxK~ ؗ7(rzJF!X' } zFi l"PjGttH0=gv4OVhu K/@z+t7*yY޴O=t3G,]jF0L!8,XzˎQ]=az7$:o $<,-ꏜ+G, 5= AFuXBw&-I?m㛐̐޿Pq ;qA[ ",Y#FBi@~zq/ر!&W^L{xhZ {3p"-.10(/#hED#D 8`|eАϨx IWRЀBSLQ j .E0P7MqyQ*~"f*gwͧAL 8W)h&ָuT8iS ClnB)MEIδ ZAJJ(w_ _Y|l^`(>E)l+Eb>Dto&hC"z9.>њ-tT x?.\%8.~:+&20d^\M4dиX]yɸػ>{Zak *,Z:YH3 d#rW-g8 \뮃#2?ݼ:gW݆5U)}{>M$FUUpNWi}߬qAm+'rAgLJBSO{ Fԛ;[^(,OyfMVd N3b'.&Pw,)K NjMn&,_@8uo/Z \xmb2Is B ?)@},SSqCylX@;F|HWeQq_׼>M!ѹ׏{ ;Rʝ+EsQzJW-GNtSw$PQx SNXK3ze/on>t"9!Ƙ> ~V,@v{ &F|"*[es ̅C ҬUdB'R!lYx|8/9 nQ=@D$ ,hMXBzjX$GAύ9僿@XmhO?ўĩEZ@ ٭jTuB9M."JDl3{ ^Hd, "Ü>pC=®z굪> 1v% 0bHW+FSe ?HC@ ƏʷFd˝kt@hh,! E:k #A1ۂ"ui*e^jMKGUV`_S9vdIw r/HW`Vs ;D6*˗!f?Og5%IaX[3*W\B^_^DYu*Mϳm>M^;r4;ʓ31\ٵF-.G+C˰3ZU%uBz\ Z-Xpb32J\;uSnShฑԩ7 Mvb\5Vt>Z`~-E(SjfMl6(9 AݵpnHY5RFMON(M[x%?C&+A8trM ڏBA*z@i-D*s(#kd+*qtest-files/test-files.sh000077500000000000000000000020721322621430500155520ustar00rootroot00000000000000#!/bin/sh -ex # SPDX-License-Identifier: LGPL-2.1+ cd "$(dirname "$0")" if [ "$1" != "clean" ]; then test -e thisisafifo || mkfifo thisisafifo test -e ablockdevice || mknod ablockdevice b 0 0 test -e achardevice || mknod achardevice c 0 0 test -e immutable || ( touch immutable && chattr +i immutable ) test -e nocow || ( touch nocow && chattr +C nocow ) test -e acl || ( touch acl && setfacl -nm u:nobody:rw,u:root:rw acl ) test -e sparse || dd if=/dev/urandom of=sparse count=2 bs=1 seek=9999 test -e reflink || ( cp --reflink=auto large reflink && dd if=/dev/urandom of=reflink seek=102400 bs=1 count=1 conv=notrunc ) test -s xattr || ( touch xattr && setfattr -n user.foo -v bar xattr && setfattr -n user.quux -v piep xattr ) else chattr -i test-files/immutable || : rm -f test-files/thisisafifo rm -f test-files/ablockdevice rm -f test-files/achardevice rm -f test-files/immutable rm -f test-files/nocow rm -f test-files/acl rm -f test-files/sparse rm -f test-files/xattr fi test-files/äöüß000066400000000000000000000000051322621430500160310ustar00rootroot00000000000000mief test/000077500000000000000000000000001322621430500120335ustar00rootroot00000000000000test/http-server.py000077500000000000000000000015111322621430500146710ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ PORT = 4321 import http.server, socketserver, os, sys, socket, time os.chdir(sys.argv[1]) if len(sys.argv) >= 3: PORT = int(sys.argv[2]) def send_notify(text): if text is None or text == "": return e = os.getenv("NOTIFY_SOCKET") if e is None: return assert len(e) >= 2 assert e[0] == '/' or e[0] == '@' fd = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) fd.connect("\0" + e[1:] if e[0] == '@' else e) fd.send(bytes(text, 'utf-8')) class AllowReuseAddressServer(socketserver.TCPServer): allow_reuse_address = True def server_activate(self): super().server_activate() send_notify("READY=1") httpd = AllowReuseAddressServer(("", PORT), http.server.SimpleHTTPRequestHandler) httpd.serve_forever() test/meson-check-help.sh000077500000000000000000000011571322621430500155200ustar00rootroot00000000000000#!/bin/sh -eu # SPDX-License-Identifier: LGPL-2.1+ # output width if "$1" --help | grep -v 'default:' | grep -E -q '.{80}.'; then echo "$(basename "$1") --help output is too wide:" "$1" --help | awk 'length > 80' | grep -E --color=yes '.{80}' exit 1 fi # no --help output to stdout if "$1" --help 2>&1 1>/dev/null | grep .; then echo "$(basename "$1") --help prints to stderr" exit 2 fi # error output to stderr if ! "$1" --no-such-parameter 2>&1 1>/dev/null | grep -q .; then echo "$(basename "$1") with an unknown parameter does not print to stderr" exit 3 fi test/meson.build000066400000000000000000000001651322621430500141770ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-2.1+ notify_wait_sources = [ files('notify-wait.c'), log_sources, ] test/notify-wait.c000066400000000000000000000237371322621430500144650ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include "util.h" #define TIMEOUT_NSEC UINT64_C(30000000000) static sig_atomic_t quit = false; static void exit_signal_handler(int signo) { quit = true; }; static void sigchld_signal_handler(int signo) { /* Nothing */ }; int main(int argc, char *argv[]) { int fd = -1; pid_t pid = 0; union { struct sockaddr sa; struct sockaddr_un un; } sa = { .un.sun_family = AF_UNIX, }; socklen_t salen = sizeof(sa); char *buffer = NULL, *e; size_t buffer_size = 0, allocate_at_least = strlen("READY=1\n"); sigset_t ss, ss_poll; struct sigaction exit_sigaction = { .sa_handler = exit_signal_handler, .sa_flags = SA_RESTART, }; struct sigaction sigchld_sigaction = { .sa_handler = sigchld_signal_handler, .sa_flags = SA_RESTART, }; uint64_t timeout_at; int r; if (argc < 2) { r = -EINVAL; log_error("Command line to execute required as argument."); goto finish; } fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (fd < 0) { r = -errno; log_error("Failed to create notification socket: %m"); goto finish; } /* Bind the socket using the kernel's autobind functionality. */ if (bind(fd, &sa.sa, sizeof(sa_family_t)) < 0) { r = -errno; log_error("Failed to bind notification socket: %m"); goto finish; } if (getsockname(fd, &sa.sa, &salen) < 0) { r = -errno; log_error("Failed to determine bound socket address: %m"); goto finish; } assert_se(salen > offsetof(struct sockaddr_un, sun_path) + 1); assert_se(sa.un.sun_family == AF_UNIX); assert_se(sa.un.sun_path[0] == 0); e = newa(char, salen - offsetof(struct sockaddr_un, sun_path) + 1); e[0] = '@'; memcpy(e + 1, sa.un.sun_path + 1, salen - offsetof(struct sockaddr_un, sun_path) - 1); e[salen - offsetof(struct sockaddr_un, sun_path)] = 0; assert_se(sigemptyset(&ss) >= 0); assert_se(sigaddset(&ss, SIGINT) >= 0); assert_se(sigaddset(&ss, SIGTERM) >= 0); assert_se(sigaddset(&ss, SIGCHLD) >= 0); assert_se(sigprocmask(SIG_BLOCK, &ss, &ss_poll) >= 0); assert_se(sigdelset(&ss_poll, SIGINT) >= 0); assert_se(sigdelset(&ss_poll, SIGTERM) >= 0); assert_se(sigdelset(&ss_poll, SIGCHLD) >= 0); assert_se(sigaction(SIGTERM, &exit_sigaction, NULL) >= 0); assert_se(sigaction(SIGINT, &exit_sigaction, NULL) >= 0); assert_se(sigaction(SIGCHLD, &sigchld_sigaction, NULL) >= 0); timeout_at = now(CLOCK_MONOTONIC) + TIMEOUT_NSEC; pid = fork(); if (pid < 0) { r = -errno; log_error("Failed to to fork(): %m"); goto finish; } if (pid == 0) { /* Child? */ int new_stdout; assert_se(sigprocmask(SIG_SETMASK, &ss_poll, NULL) >= 0); fd = safe_close(fd); /* Let's run the child with a new stdout, independent from the parent, so that the PID of we print below is followed by an EOF */ new_stdout = open("/dev/tty", O_WRONLY); if (new_stdout < 0) { r = -errno; /* If that didn't work (which it won't if we are being run from "ninja test"), then let's use /dev/null */ new_stdout = open("/dev/null", O_WRONLY); if (new_stdout < 0) { log_error("Failed to open new STDOUT: %m"); goto inner_fail; } } if (new_stdout != STDOUT_FILENO) { if (dup2(new_stdout, STDOUT_FILENO) < 0) { r = -errno; log_error("Failed to duplicate STDOUT: %m"); goto inner_fail; } new_stdout = safe_close(new_stdout); } if (setenv("NOTIFY_SOCKET", e, 1) < 0) { r = -errno; log_error("Failed to set $NOTIFY_SOCKET: %m"); goto inner_fail; } execv(argv[1], argv + 1); r = -errno; log_error("Failed to execute child process: %m"); inner_fail: _exit(EXIT_FAILURE); } for (;;) { siginfo_t si = {}; const char *p; ssize_t n; struct pollfd pollfd = { .fd = fd, .events = POLLIN, }; struct timespec ts; uint64_t nw; if (waitid(P_PID, pid, &si, WEXITED|WNOHANG) < 0) { r = -errno; log_error("Failed to wait for children: %m"); goto finish; } if (si.si_pid > 0) { switch (si.si_code) { case CLD_EXITED: log_error("Process exited prematurely with status %i.", si.si_status); r = -EPIPE; pid = 0; goto finish; case CLD_KILLED: case CLD_DUMPED: log_error("Process killed with signal %i (%s).", si.si_status, strsignal(si.si_status)); r = -EPIPE; pid = 0; goto finish; default: assert_se(false); } } for (;;) { ssize_t k; if (buffer_size < allocate_at_least) { free(buffer); buffer = new(char, allocate_at_least); if (!buffer) { r = log_oom(); goto finish; } buffer_size = allocate_at_least; } n = recv(fd, buffer, buffer_size, MSG_DONTWAIT|MSG_PEEK|MSG_TRUNC); if (n < 0) { if (errno == EAGAIN) goto wait_for_event; r = log_error_errno(errno, "Failed to read notification datagram: %m"); goto finish; } if ((size_t) n > buffer_size) { allocate_at_least = n; continue; } /* Now we know the message fit in the buffer, now read it properly. */ k = recv(fd, buffer, buffer_size, 0); if (k < 0) { r = log_error_errno(errno, "Failed to consume notification datagram: %m"); goto finish; } if (k != n) { r = log_error_errno(EIO, "Consumed notification datagram has different size than original: %m"); goto finish; } /* Successfully acquired a message! */ break; } /* Too short for what we are looking for... */ if ((size_t) n < strlen("READY=1")) goto wait_for_event; p = startswith(buffer, "READY=1"); if (!p) { p = memmem(buffer, n, "\nREADY=1", strlen("\nREADY=1")); if (!p) goto wait_for_event; /* Doesn't contain a READY= stanza */ p += strlen("\nREADY=1"); } if (p == buffer + n || *p == '\n') { /* Got it! */ printf(PID_FMT "\n", pid); pid = 0; /* don't kill on exit */ break; } wait_for_event: if (quit) { /* Got SIGINT or SIGTERM */ log_error("Exiting due to signal."); break; } nw = now(CLOCK_MONOTONIC); ts = nsec_to_timespec(timeout_at > nw ? timeout_at - nw : 0); r = ppoll(&pollfd, 1, &ts, &ss_poll); if (r < 0) { if (errno == EINTR) /* got a SIGCHLD or a SIGTERM/SIGINT? */ continue; r = log_error_errno(errno, "Failed to ppoll(): %m"); goto finish; } if (r == 0) { /* timeout */ log_error("Timeout."); r = -ETIMEDOUT; goto finish; } } r = 0; finish: if (pid > 1) (void) kill(pid, SIGTERM); free(buffer); safe_close(fd); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } test/pseudo-ssh000077500000000000000000000004451322621430500140560ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: LGPL-2.1+ # A tool that we can use like ssh, but instead of connecting anywhere simply # invokes the specified command locally. To be used via $CASYNC_SSH_PATH for # testing the remoting protocol without actually doing anything remote. shift 1 exec "$@" test/semaphore-run000077500000000000000000000033471322621430500145550ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: LGPL-2.1+ DEVPKGS="pkg-config liblzma-dev libcurl4-openssl-dev libssl-dev libacl1-dev libfuse-dev zlib1g-dev libzstd-dev libudev-dev" echo echo "============= Installing amd64 build dependencies ==============" set -eu ( set -x sudo apt-get install -y software-properties-common sudo add-apt-repository -y ppa:pitti/systemd-semaphore sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y $DEVPKGS rsync python3 meson python-sphinx ) echo echo "============= Building amd64 ==============" ( set -x meson build ninja -C build ) echo echo "============= Running amd64 tests as user ==============" ( set -x ninja -C build test ) echo echo "============= Running amd64 tests as root ==============" ( set -x sudo ninja -C build test ) echo echo "============= Installing i386 build dependencies ==============" # help apt to figure out replacing GI dependencies (we don't need them # anyway, but a lot of stuff is pre-installed in semaphore) ( set -x sudo apt-get purge --auto-remove -y python3-gi gir1.2-glib-2.0 ) # library -dev packages are not co-installable for multiple architectures, # so this can't go into the setup step ( set -x sudo apt-get install -y --no-install-recommends gcc-multilib $(echo "$DEVPKGS " | sed 's/\b /:i386 /g') ) echo echo "============= Building i386 ==============" ( set -x CFLAGS=-m32 LDFLAGS=-m32 meson build-i386 ninja -C build-i386 ) echo echo "============= Running i386 tests as user ==============" ( set -x linux32 ninja -C build-i386 test ) echo echo "============= Running i386 tests as root ==============" ( set -x sudo linux32 ninja -C build-i386 test ) test/test-cachunk.c000066400000000000000000000035731322621430500146000ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "cachunk.h" #include "def.h" /* #include "util.h" */ static void test_chunk_file(void) { uint8_t buffer[BUFFER_SIZE*4]; _cleanup_(realloc_buffer_free) ReallocBuffer rb = {}, rb2 = {}; const char *d; char *path; _cleanup_(safe_closep) int fd = -1; int r; assert(var_tmp_dir(&d) >= 0); path = strjoina(d, "/chunk-test.XXXXXX"); assert_se(dev_urandom(buffer, sizeof(buffer)) >= 0); fd = mkostemp(path, O_RDWR|O_CLOEXEC); assert_se(fd >= 0); assert_se(unlink(path) >= 0); r = ca_save_and_compress_fd(fd, CA_COMPRESSION_DEFAULT, buffer, sizeof(buffer)); assert_se(r >= 0); assert_se(lseek(fd, 0, SEEK_SET) == 0); r = ca_load_and_decompress_fd(fd, &rb); safe_close(r >= 0); assert_se(realloc_buffer_size(&rb) == sizeof(buffer)); assert_se(memcmp(realloc_buffer_data(&rb), buffer, sizeof(buffer)) == 0); realloc_buffer_empty(&rb); r = ca_compress(CA_COMPRESSION_DEFAULT, buffer, sizeof(buffer), &rb); assert_se(r >= 0); assert_se(lseek(fd, 0, SEEK_SET) == 0); assert_se(ftruncate(fd, 0) == 0); r = ca_save_and_decompress_fd(fd, realloc_buffer_data(&rb), realloc_buffer_size(&rb)); assert_se(r >= 0); realloc_buffer_empty(&rb); assert_se(lseek(fd, 0, SEEK_SET) == 0); r = ca_load_and_compress_fd(fd, CA_COMPRESSION_DEFAULT, &rb); assert_se(r >= 0); r = ca_decompress(realloc_buffer_data(&rb), realloc_buffer_size(&rb), &rb2); assert_se(r >= 0); assert_se(realloc_buffer_size(&rb2) == sizeof(buffer)); assert_se(memcmp(realloc_buffer_data(&rb2), buffer, sizeof(buffer)) == 0); } int main(int argc, char *argv[]) { test_chunk_file(); return 0; } test/test-cachunker-histogram.c000066400000000000000000000117661322621430500171250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include "util.h" #include "cachunker.h" #define BUFFER_SIZE (64*1024) #define CHUNKS_MAX 1000000 #define THREADS_MAX 16 #define RUNTIME_NSEC UINT64_C(2000000000) struct thread_info { pthread_t id; CaChunker chunker; unsigned *histogram; unsigned n_chunks; int random_fd; uint64_t until; }; static void* process(void *q) { struct thread_info *t = q; size_t previous = 0; for (;;) { uint8_t buffer[BUFFER_SIZE], *p; ssize_t l; if (t->until < now(CLOCK_MONOTONIC)) return NULL; l = read(t->random_fd, buffer, sizeof(buffer)); assert_se(l == sizeof(buffer)); p = buffer; for (;;) { size_t n; n = ca_chunker_scan(&t->chunker, p, l); if (n == (size_t) -1) { previous += l; break; } assert_se(n <= (size_t) l); assert_se(previous + n >= t->chunker.chunk_size_min); assert_se(previous + n <= t->chunker.chunk_size_max); t->histogram[previous + n] ++; t->n_chunks ++; p += n; l -= n; previous = 0; } } } static void draw(unsigned *histogram, size_t n) { #define N_BUCKETS 30 #define N_HEIGHT 69 unsigned buckets[N_BUCKETS] = {}; unsigned highest = 0; size_t i; for (i = 0; i < n; i++) buckets[i * N_BUCKETS / n] += histogram[i]; for (i = 0; i < N_BUCKETS; i++) if (buckets[i] > highest) highest = buckets[i]; for (i = 0; i < N_BUCKETS; i++) { unsigned k, j; k = buckets[i] * N_HEIGHT / highest; printf("%10zu ", (i+1) * n / N_BUCKETS -1); for (j = 0; j < k; j++) putchar('#'); putchar('\n'); } } static void run(size_t pick, size_t *ret_avg) { CaChunker chunker = CA_CHUNKER_INIT; unsigned *histogram, n_chunks = 0; struct thread_info threads[THREADS_MAX] = {}; uint64_t until, sum = 0; size_t i; int fd, r; ca_chunker_set_size(&chunker, 0, pick, 0); histogram = new0(unsigned, chunker.chunk_size_max+1); assert_se(histogram); log_info("Min/Avg/Max = %zu/%zu/%zu (discriminator=%zu)", chunker.chunk_size_min, chunker.chunk_size_avg, chunker.chunk_size_max, chunker.discriminator); fd = open("/dev/urandom", O_CLOEXEC|O_RDONLY); assert_se(fd >= 0); until = now(CLOCK_MONOTONIC) + RUNTIME_NSEC; for (i = 0; i < THREADS_MAX; i++) { threads[i].chunker = chunker; threads[i].histogram = new0(unsigned, chunker.chunk_size_max+1); assert_se(threads[i].histogram); threads[i].random_fd = fd; threads[i].until = until; r = pthread_create(&threads[i].id, NULL, process, threads + i); assert_se(r == 0); } for (i = 0; i < THREADS_MAX; i++) { size_t j; r = pthread_join(threads[i].id, NULL); assert_se(r == 0); for (j = 0; j <= chunker.chunk_size_max; j++) { histogram[j] += threads[i].histogram[j]; sum += threads[i].histogram[j] * j; } n_chunks += threads[i].n_chunks; free(threads[i].histogram); } log_info("Generated %u chunks.", n_chunks); log_info("Effective average is %" PRIu64 ".", sum / n_chunks); *ret_avg = sum / n_chunks; draw(histogram, chunker.chunk_size_max+1); free(histogram); safe_close(fd); return; } int main(int argc, char* argv[]) { size_t avg, start, end, step; if (argc > 1) { start = 4096; end = 1024*1024; step = 4*1024; } else { start = CA_CHUNK_SIZE_AVG_DEFAULT; end = CA_CHUNK_SIZE_AVG_DEFAULT+1; step = 1; } for (avg = start; avg < end; avg += step) { size_t effective_avg; double factor; run(avg, &effective_avg); factor = (double) effective_avg / (double) avg; printf("%zu\t%zu\t%g\n", avg, effective_avg, factor); log_error("Asked for average: %zu — Got average: %zu — Factor: %g", avg, effective_avg, factor); } return 0; } test/test-cachunker.c000066400000000000000000000147111322621430500151230ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include "cachunker.h" #include "util.h" static void test_rolling(void) { static const char buffer[] = "The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.\n" "When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.\n" "To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.\n" "For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.\n" "We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.\n" "Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.\n" "Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.\n" "The precise terms and conditions for copying, distribution and modification follow.\n"; const char *p = buffer; CaChunker x = CA_CHUNKER_INIT; assert(sizeof(buffer) > CA_CHUNKER_WINDOW_SIZE); ca_chunker_start(&x, buffer, CA_CHUNKER_WINDOW_SIZE); while (p < buffer + sizeof(buffer) - CA_CHUNKER_WINDOW_SIZE) { CaChunker y = CA_CHUNKER_INIT; uint32_t k, v; k = ca_chunker_roll(&x, p[0], p[CA_CHUNKER_WINDOW_SIZE]); v = ca_chunker_start(&y, p+1, CA_CHUNKER_WINDOW_SIZE); /* printf("%08x vs. %08x\n", k, v); */ assert_se(k == v); p++; } } static void test_chunk(void) { CaChunker x = CA_CHUNKER_INIT; uint8_t buffer[8*1024]; size_t acc = 0; int fd; unsigned count = 0; fd = open("/dev/urandom", O_CLOEXEC|O_RDONLY|O_NOCTTY); assert_se(fd >= 0); for (;;) { const uint8_t *p; size_t n; assert_se(read(fd, buffer, sizeof(buffer)) == sizeof(buffer)); p = buffer; n = sizeof(buffer); for (;;) { size_t k; k = ca_chunker_scan(&x, p, n); if (k == (size_t) -1) { acc += n; break; } acc += k; printf("%zu\n", acc); assert_se(acc >= x.chunk_size_min); assert_se(acc <= x.chunk_size_max); acc = 0; p += k, n -= k; count ++; if (count > 500) goto finish; } } finish: (void) close(fd); } static int test_set_size(void) { struct CaChunker x = CA_CHUNKER_INIT, y = CA_CHUNKER_INIT; ca_chunker_set_size(&y, 1024, 0, 0); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 0, 0, 16*1024); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 0, 4*1024, 0); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 0, 4*1024, 16*1024); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 1024, 4*1024, 16*1024); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 1024, 4*1024, 0); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 1024, 0, 16*1024); assert_se(y.chunk_size_min == 1024); assert_se(y.chunk_size_avg == 4*1024); assert_se(y.chunk_size_max == 16*1024); y = x; ca_chunker_set_size(&y, 128*1024, 0, 512*1024); assert_se(y.chunk_size_min == 128*1024); assert_se(y.chunk_size_avg == 256*1024); assert_se(y.chunk_size_max == 512*1024); return 0; } int main(int argc, char *argv[]) { test_rolling(); test_chunk(); test_set_size(); return 0; } test/test-cadigest.c000066400000000000000000000075651322621430500147540ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "util.h" #include "cadigest.h" #define TEST_SPEED_RUNTIME_NSEC (5U*NSEC_PER_SEC) static void test_speed(CaDigestType t) { uint32_t megabyte[1024*1024/sizeof(uint32_t)]; size_t k, c = 0; CaDigest *d; uint64_t n; /* Generate 1MB test data */ srand(0); for (k = 0; k < ELEMENTSOF(megabyte); k++) megabyte[k] = rand(); assert_se(ca_digest_new(t, &d) >= 0); n = now(CLOCK_MONOTONIC); while (n + TEST_SPEED_RUNTIME_NSEC > now(CLOCK_MONOTONIC)) { ca_digest_write(d, megabyte, sizeof(megabyte)); c++; } printf("%s: %zu MB/s\n", ca_digest_type_to_string(t), (size_t) ((c * NSEC_PER_SEC) / TEST_SPEED_RUNTIME_NSEC)); ca_digest_free(d); } int main(int argc, char *argv[]) { CaDigest *d; CaDigestType t; assert_se(ca_digest_new(CA_DIGEST_SHA256, &d) >= 0); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }, 32) == 0); ca_digest_reset(d); ca_digest_write(d, "foobar", 6); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xc3, 0xab, 0x8f, 0xf1, 0x37, 0x20, 0xe8, 0xad, 0x90, 0x47, 0xdd, 0x39, 0x46, 0x6b, 0x3c, 0x89, 0x74, 0xe5, 0x92, 0xc2, 0xfa, 0x38, 0x3d, 0x4a, 0x39, 0x60, 0x71, 0x4c, 0xae, 0xf0, 0xc4, 0xf2 }, 32) == 0); ca_digest_reset(d); ca_digest_write(d, "foo", 3); ca_digest_write(d, "bar", 3); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xc3, 0xab, 0x8f, 0xf1, 0x37, 0x20, 0xe8, 0xad, 0x90, 0x47, 0xdd, 0x39, 0x46, 0x6b, 0x3c, 0x89, 0x74, 0xe5, 0x92, 0xc2, 0xfa, 0x38, 0x3d, 0x4a, 0x39, 0x60, 0x71, 0x4c, 0xae, 0xf0, 0xc4, 0xf2 }, 32) == 0); d = ca_digest_free(d); assert_se(ca_digest_new(CA_DIGEST_SHA512_256, &d) >= 0); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xc6, 0x72, 0xb8, 0xd1, 0xef, 0x56, 0xed, 0x28, 0xab, 0x87, 0xc3, 0x62, 0x2c, 0x51, 0x14, 0x06, 0x9b, 0xdd, 0x3a, 0xd7, 0xb8, 0xf9, 0x73, 0x74, 0x98, 0xd0, 0xc0, 0x1e, 0xce, 0xf0, 0x96, 0x7a }, 32) == 0); ca_digest_reset(d); ca_digest_write(d, "foobar", 6); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xd0, 0x14, 0xc7, 0x52, 0xbc, 0x2b, 0xe8, 0x68, 0xe1, 0x63, 0x30, 0xf4, 0x7e, 0x0c, 0x31, 0x6a, 0x59, 0x67, 0xbc, 0xbc, 0x9c, 0x28, 0x6a, 0x45, 0x77, 0x61, 0xd7, 0x05, 0x5b, 0x92, 0x14, 0xce }, 32) == 0); ca_digest_reset(d); ca_digest_write(d, "foo", 3); ca_digest_write(d, "bar", 3); assert_se(memcmp(ca_digest_read(d), (const uint8_t[]) { 0xd0, 0x14, 0xc7, 0x52, 0xbc, 0x2b, 0xe8, 0x68, 0xe1, 0x63, 0x30, 0xf4, 0x7e, 0x0c, 0x31, 0x6a, 0x59, 0x67, 0xbc, 0xbc, 0x9c, 0x28, 0x6a, 0x45, 0x77, 0x61, 0xd7, 0x05, 0x5b, 0x92, 0x14, 0xce }, 32) == 0); d = ca_digest_free(d); for (t = 0; t < _CA_DIGEST_TYPE_MAX; t++) test_speed(t); return 0; } test/test-caencoder.c000066400000000000000000000152741322621430500151100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include #include "cadecoder.h" #include "caencoder.h" #include "caformat.h" static int encode(int dfd, int fd) { CaEncoder *e = NULL; uint64_t flags; int r; assert(dfd >= 0); assert(fd >= 0); printf("ENCODING...\n"); e = ca_encoder_new(); if (!e) { r = -ENOMEM; goto finish; } r = ca_encoder_set_base_fd(e, dfd); if (r < 0) goto finish; flags = CA_FORMAT_DEFAULT; if (geteuid() != 0) flags &= ~CA_FORMAT_WITH_PRIVILEGED; r = ca_encoder_set_feature_flags(e, flags); if (r < 0) goto finish; dfd = -1; for (;;) { int step; step = ca_encoder_step(e); if (step < 0) { r = step; goto finish; } switch (step) { case CA_ENCODER_FINISHED: r = 0; goto finish; case CA_ENCODER_NEXT_FILE: case CA_ENCODER_DONE_FILE: { char *path = NULL; mode_t mode; r = ca_encoder_current_path(e, &path); if (r < 0) goto finish; r = ca_encoder_current_mode(e, &mode); if (r < 0) goto finish; printf("%s %08o %s\n", step == CA_ENCODER_NEXT_FILE ? "→" : "←", mode, path); free(path); } /* Fall through */ case CA_ENCODER_PAYLOAD: case CA_ENCODER_DATA: { const void *p; size_t sz; ssize_t n; r = ca_encoder_get_data(e, &p, &sz); if (r == -ENODATA) break; if (r < 0) goto finish; n = write(fd, p, sz); if (n < 0) { r = -errno; goto finish; } break; } default: assert(false); } } finish: if (r == 0) { uint64_t offset; off_t foffset; r = ca_encoder_current_archive_offset(e, &offset); if (r < 0) goto finish; foffset = lseek(fd, 0, SEEK_CUR); if (foffset == (off_t) -1) { r = -errno; goto finish; } if ((off_t) offset != foffset) { r = -EIO; goto finish; } } ca_encoder_unref(e); if (fd >= 0) (void) close(fd); if (dfd >= 0) (void) close(dfd); return r; } static int decode(int fd) { CaDecoder *d = NULL; int r; assert(fd >= 0); printf("DECODING...\n"); d = ca_decoder_new(); if (!d) { r = -ENOMEM; goto finish; } r = ca_decoder_set_base_mode(d, S_IFDIR); if (r < 0) goto finish; for (;;) { int step; step = ca_decoder_step(d); if (step < 0) goto finish; switch (step) { case CA_DECODER_FINISHED: r = 0; goto finish; case CA_DECODER_STEP: break; case CA_DECODER_REQUEST: { uint8_t buffer[4096]; ssize_t n; n = read(fd, buffer, sizeof(buffer)); if (n < 0) { r = -errno; goto finish; } if (n == 0) r = ca_decoder_put_eof(d); else r = ca_decoder_put_data(d, buffer, n, NULL); if (r < 0) goto finish; break; } case CA_DECODER_DONE_FILE: case CA_DECODER_NEXT_FILE: { char *path = NULL; mode_t mode; r = ca_decoder_current_mode(d, &mode); if (r < 0) goto finish; r = ca_decoder_current_path(d, &path); if (r < 0) goto finish; printf("%s %08o %s\n", step == CA_DECODER_NEXT_FILE ? "→" : "←", mode, path); free(path); break; } case CA_DECODER_PAYLOAD: /* ignore for now */ break; } } finish: ca_decoder_unref(d); if (fd >= 0) (void) close(fd); return r; } int main(int argc, char *argv[]) { int fd = -1, dfd = -1, r; bool do_unlink = false; const char *d; char *t; assert(var_tmp_dir(&d) >= 0); t = strjoina(d, "/castream-test.XXXXXX"); dfd = open(argc > 1 ? argv[1] : ".", O_CLOEXEC|O_RDONLY|O_NOCTTY); if (dfd < 0) { r = -errno; goto finish; } fd = mkostemp(t, O_WRONLY|O_CLOEXEC); if (fd < 0) { r = -errno; goto finish; } do_unlink = true; log_error("Writing to: %s", t); r = encode(dfd, fd); dfd = fd = -1; if (r < 0) goto finish; fd = open(t, O_RDONLY|O_CLOEXEC); if (fd < 0) { r = -errno; goto finish; } r = decode(fd); fd = -1; finish: log_error("Done: %m"); if (fd >= 0) (void) close(fd); if (dfd >= 0) (void) close(dfd); if (do_unlink) assert_se(unlink(t) >= 0); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } test/test-caformat.c000066400000000000000000000151121322621430500147500ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "caformat-util.h" #include "caformat.h" #include "realloc-buffer.h" #include "util.h" int main(int argc, char *argv[]) { _cleanup_(realloc_buffer_free) ReallocBuffer buffer = {}; uint64_t frame_size = 0, skip_size = 0; _cleanup_(safe_closep) int fd = -1; int r; if (argc != 2) { log_error("Expected single filename parameter."); r = -EINVAL; goto finish; } fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) { log_error_errno(errno, "Failed to open %s: %m", argv[1]); goto finish; } for (;;) { CaFormatHeader *h; uint64_t sz; sz = realloc_buffer_size(&buffer); if (skip_size > 0 && sz > 0) { uint64_t t; t = MIN(skip_size, sz); realloc_buffer_advance(&buffer, t); skip_size -= t; continue; } if (frame_size < sizeof(CaFormatHeader)) frame_size = sizeof(CaFormatHeader); if (skip_size > 0 || sz < frame_size) { r = realloc_buffer_read(&buffer, fd); if (r < 0) { log_error_errno(r, "Failed to read: %m"); goto finish; } if (r == 0) { if (sz == 0) { r = 0; goto finish; } log_error("Premature end of file."); r = -EBADMSG; goto finish; } continue; } h = realloc_buffer_data(&buffer); printf(">>> Record <%s>\n", ca_format_type_name(read_le64(&h->type))); switch (read_le64(&h->type)) { case CA_FORMAT_ENTRY: { CaFormatEntry *entry; frame_size = read_le64(&h->size); if (sz < frame_size) continue; entry = (CaFormatEntry*) h; printf("\tMode: %08" PRIo64 "\n" "\tUID: " UID_FMT "\n" "\tGID: " GID_FMT "\n", read_le64(&entry->mode), (uid_t) read_le64(&entry->uid), (gid_t) read_le64(&entry->gid)); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_USER: { CaFormatUser *user; frame_size = read_le64(&h->size); if (sz < frame_size) continue; user = (CaFormatUser*) h; printf("\tUser: %s\n", user->name); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_GROUP: { CaFormatGroup *group; frame_size = read_le64(&h->size); if (sz < frame_size) continue; group = (CaFormatGroup*) h; printf("\tGroup: %s\n", group->name); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_PAYLOAD: frame_size = offsetof(CaFormatPayload, data); if (sz < frame_size) continue; skip_size = read_le64(&h->size) - frame_size; printf("\tPayload: %" PRIu64 "\n", skip_size); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; case CA_FORMAT_FILENAME: { CaFormatFilename *f; frame_size = read_le64(&h->size); if (sz < frame_size) continue; f = (CaFormatFilename*) h; printf("\tFilename: %s\n", f->name); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_SYMLINK: { CaFormatSymlink *symlink; frame_size = read_le64(&h->size); if (sz < frame_size) continue; symlink = (CaFormatSymlink*) h; printf("\tSymlink: %s\n", symlink->target); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_XATTR: { CaFormatXAttr *xattr; frame_size = read_le64(&h->size); if (sz < frame_size) continue; xattr = (CaFormatXAttr*) h; printf("\tXAttr: %s\n", (char*) xattr->name_and_value); realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; } case CA_FORMAT_FCAPS: case CA_FORMAT_GOODBYE: case CA_FORMAT_DEVICE: frame_size = read_le64(&h->size); if (sz < frame_size) continue; realloc_buffer_advance(&buffer, frame_size); frame_size = 0; break; default: log_error("Unknown record type."); r = -EBADMSG; goto finish; } } finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } test/test-caindex.c000066400000000000000000000016561322621430500145770ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "util.h" #include "caindex.h" int main(int argc, char*argv[]) { _cleanup_(ca_index_unrefp) CaIndex* index = NULL; int r; if (argc != 2) { fprintf(stderr, "Expected an index file as argument.\n"); return EXIT_FAILURE; } assert_se(index = ca_index_new_read()); assert_se(ca_index_set_path(index, argv[1]) >= 0); assert_se(ca_index_open(index) >= 0); for (;;) { CaChunkID id; uint64_t size; char ids[CA_CHUNK_ID_FORMAT_MAX]; r = ca_index_read_chunk(index, &id, NULL, &size); assert_se(r >= 0); if (r == 0) break; printf("%s (%" PRIu64 ")\n", ca_chunk_id_format(&id, ids), size); } printf("EOF\n"); return 0; } test/test-calc-digest.c000066400000000000000000000044211322621430500153340ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include #include #include #include "cadigest.h" #include "util.h" int main(int argc, char *argv[]) { CaDigest *digest = NULL; CaDigestType type; int fd = -1, r; const char *q, *path, *dt; size_t l; char *p = NULL; if (argc > 3) { log_error("Expected a two arguments: digest and file name."); r = -EINVAL; goto finish; } path = argc == 3 ? argv[2] : NULL; dt = argc >= 2 ? argv[1] : NULL; if (dt) { type = ca_digest_type_from_string(dt); if (type < 0) { log_error("Failed to parse digest name: %s", dt); r = -EINVAL; goto finish; } } else type = CA_DIGEST_SHA512_256; r = ca_digest_new(type, &digest); if (r < 0) { log_error("Failed to set up digest %s: %m", dt); goto finish; } if (path) { fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) { log_error_errno(errno, "Failed to open %s: %m", path); goto finish; } } else fd = STDIN_FILENO; for (;;) { uint8_t buffer[64*1024]; ssize_t n; n = read(fd, buffer, sizeof(buffer)); if (n < 0) { log_error_errno(errno, "Failed to read: %m"); goto finish; } if (n == 0) /* EOF */ break; ca_digest_write(digest, buffer, (size_t) n); } q = ca_digest_read(digest); l = ca_digest_get_size(digest); p = hexmem(q, l); if (!p) { r = log_oom(); goto finish; } fputs(p, stdout); fputc('\n', stdout); r = 0; finish: ca_digest_free(digest); if (fd > 2) safe_close(fd); free(p); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } test/test-camakebst.c000066400000000000000000000027231322621430500151120ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "camakebst.h" #include "util.h" #define TEST_MAX 9999 static size_t find_bst(const int b[], size_t n, int x) { size_t i = 0; for (;;) { if (i >= n) return (size_t) -1; if (b[i] == x) return i; if (x < b[i]) i = 2*i + 1; else if (x > b[i]) i = 2*i + 2; } } static void test_makebst_size(size_t n) { size_t i; int a[MAX(1U, n)], b[MAX(1U, n)]; for (i = 0; i < n; i++) { a[i] = (int) i; b[i] = -1; } ca_make_bst(a, n, sizeof(int), b); for (i = 0; i < n; i++) { assert_se(i*2+1 >= n || b[i] > b[i*2+1]); assert_se(i*2+2 >= n || b[i] < b[i*2+2]); } for (i = 0; i < n; i++) { size_t j; j = find_bst(b, n, (int) i); assert_se(j != (size_t) -1); assert_se(b[j] == (int) i); } assert_se(find_bst(b, n, -2) == (size_t) -1); assert_se(find_bst(b, n, -1) == (size_t) -1); assert_se(find_bst(b, n, n) == (size_t) -1); assert_se(find_bst(b, n, n+1) == (size_t) -1); } int main(int argc, char *argv[]) { size_t i; for (i = 0; i < TEST_MAX; i++) test_makebst_size(i); return 0; } test/test-caorigin.c000066400000000000000000000034321322621430500147510ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "caorigin.h" int main(int argc, char *argv[]) { CaOrigin *o, *p; CaLocation *a, *b, *c, *d; assert_se(ca_location_new("/foo", CA_LOCATION_ENTRY, 0, 22, &a) >= 0); assert_se(ca_location_new("/foo", CA_LOCATION_PAYLOAD, 0, 55, &b) >= 0); assert_se(ca_location_new("/foo", CA_LOCATION_PAYLOAD, 55, 77, &c) >= 0); assert_se(ca_location_new("/quux", CA_LOCATION_ENTRY, 0, 33, &d) >= 0); assert_se(ca_origin_new(&o) >= 0); assert_se(ca_origin_put(o, a) >= 0); assert_se(ca_origin_put(o, b) >= 0); assert_se(ca_origin_put(o, c) >= 0); assert_se(ca_origin_put(o, d) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_advance_bytes(o, 1) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_advance_bytes(o, 20) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_advance_bytes(o, 2) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_concat(o, o, UINT64_MAX) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_new(&p) >= 0); assert_se(ca_origin_put(p, b) >= 0); assert_se(ca_origin_put(p, b) >= 0); assert_se(ca_origin_put(p, c) >= 0); assert_se(ca_origin_dump(NULL, p) >= 0); assert_se(ca_origin_concat(o, p, UINT64_MAX) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); assert_se(ca_origin_concat(o, p, 56) >= 0); assert_se(ca_origin_dump(NULL, o) >= 0); ca_origin_unref(o); ca_origin_unref(p); ca_location_unref(a); ca_location_unref(b); ca_location_unref(c); ca_location_unref(d); return 0; } test/test-casync.c000066400000000000000000000061701322621430500144400ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include "caformat.h" #include "casync.h" #include "util.h" int main(int argc, char *argv[]) { const char *d; char *teststore, *testindex, *testtree; CaSync *s; int r, base_fd; CaChunkID digest; char t[CA_CHUNK_ID_FORMAT_MAX]; uint64_t flags; assert(var_tmp_dir(&d) >= 0); r = asprintf(&teststore, "%s/teststore.%" PRIx64, d, random_u64()); assert_se(r >= 0); asprintf(&testindex, "%s/testindex.%" PRIx64, d, random_u64()); assert_se(r >= 0); r = asprintf(&testtree, "%s/testtree.%" PRIx64, d, random_u64()); assert_se(r >= 0); assert_se(s = ca_sync_new_encode()); flags = CA_FORMAT_DEFAULT; if (geteuid() != 0) flags &= ~CA_FORMAT_WITH_PRIVILEGED; assert_se(ca_sync_set_feature_flags(s, flags) >= 0); base_fd = open(".", O_RDONLY|O_CLOEXEC|O_DIRECTORY); assert_se(base_fd >= 0); assert_se(ca_sync_set_base_fd(s, base_fd) >= 0); assert_se(ca_sync_enable_archive_digest(s, true) >= 0); assert_se(ca_sync_set_store_path(s, teststore) >= 0); assert_se(ca_sync_set_index_path(s, testindex) >= 0); for (;;) { r = ca_sync_step(s); assert_se(r >= 0); switch (r) { case CA_SYNC_FINISHED: { assert_se(ca_sync_get_archive_digest(s, &digest) >= 0); printf("%s\n", ca_chunk_id_format(&digest, t)); goto step2; } case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_NEXT_FILE: case CA_SYNC_DONE_FILE: break; default: assert_se(false); } } step2: ca_sync_unref(s); assert_se(s = ca_sync_new_decode()); (void) mkdir(testtree, 0777); base_fd = open(testtree, O_RDONLY|O_CLOEXEC|O_DIRECTORY); assert_se(base_fd >= 0); assert_se(ca_sync_set_base_fd(s, base_fd) >= 0); assert_se(ca_sync_enable_archive_digest(s, true) >= 0); assert_se(ca_sync_set_store_path(s, teststore) >= 0); assert_se(ca_sync_set_index_path(s, testindex) >= 0); for (;;) { r = ca_sync_step(s); assert_se(r >= 0); switch (r) { case CA_SYNC_FINISHED: { assert_se(ca_sync_get_archive_digest(s, &digest) >= 0); printf("%s\n", ca_chunk_id_format(&digest, t)); goto finish; } case CA_SYNC_STEP: case CA_SYNC_PAYLOAD: case CA_SYNC_NEXT_FILE: case CA_SYNC_DONE_FILE: break; default: assert_se(false); } } finish: ca_sync_unref(s); free(teststore); free(testindex); free(testtree); return 0; } test/test-cautil.c000066400000000000000000000145401322621430500144410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include "cautil.h" #include "util.h" static void test_locator_has_suffix(void) { assert_se(ca_locator_has_suffix(NULL, NULL)); assert_se(ca_locator_has_suffix("", "")); assert_se(ca_locator_has_suffix(NULL, "")); assert_se(ca_locator_has_suffix("", NULL)); assert_se(ca_locator_has_suffix("foo", NULL)); assert_se(ca_locator_has_suffix("foo", "")); assert_se(!ca_locator_has_suffix("", "foo")); assert_se(!ca_locator_has_suffix(NULL, "foo")); assert_se(ca_locator_has_suffix("foo.bar", ".bar")); assert_se(ca_locator_has_suffix("foo.bar", "bar")); assert_se(ca_locator_has_suffix("foo.bar", "ar")); assert_se(ca_locator_has_suffix("foo.bar", "r")); assert_se(ca_locator_has_suffix("foo.bar", "")); assert_se(!ca_locator_has_suffix("foo.bar", "foo")); assert_se(ca_locator_has_suffix("http://foobar.com/foo.bar", ".bar")); assert_se(!ca_locator_has_suffix("http://foobar.com/foo.bar", ".qux")); assert_se(ca_locator_has_suffix("http://foobar.com/foo.bar?miep=mup", ".bar")); assert_se(ca_locator_has_suffix("http://foobar.com/foo.bar?miep=.qux", ".bar")); assert_se(!ca_locator_has_suffix("http://foobar.com/foo.bar?miep=.qux", ".qux")); assert_se(ca_locator_has_suffix("http://foobar.com/foo.bar;miep=mup", ".bar")); assert_se(ca_locator_has_suffix("http://foobar.com/foo.bar;miep=.qux", ".bar")); assert_se(!ca_locator_has_suffix("http://foobar.com/foo.bar;miep=.qux", ".qux")); } static void test_strip_file_url_one(const char *a, const char *b) { char *s; s = ca_strip_file_url(a); assert_se(s); assert_se(streq(s, b)); free(s); } static void test_strip_file_url(void) { test_strip_file_url_one("/foo/bar", "/foo/bar"); test_strip_file_url_one("", ""); test_strip_file_url_one("file:///foobar.txt", "/foobar.txt"); test_strip_file_url_one("file:///foo%20bar.txt", "/foo bar.txt"); test_strip_file_url_one("file:///foo bar.txt", "/foo bar.txt"); test_strip_file_url_one("file:///foo%%xyz.txt", "/foo%%xyz.txt"); test_strip_file_url_one("file://localhost/piff.txt", "/piff.txt"); test_strip_file_url_one("file://elsewhere/piff.txt", "file://elsewhere/piff.txt"); test_strip_file_url_one("http://online.com/piff.txt", "http://online.com/piff.txt"); } static void test_classify_locator(void) { assert_se(ca_classify_locator(NULL) == _CA_LOCATOR_CLASS_INVALID); assert_se(ca_classify_locator("") == _CA_LOCATOR_CLASS_INVALID); assert_se(ca_classify_locator("x") == CA_LOCATOR_PATH); assert_se(ca_classify_locator(".") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("..") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("./") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("/") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("/foo") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("./foo") == CA_LOCATOR_PATH); assert_se(ca_classify_locator("http://foobar.com/xyz.txt") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://foobar.com/xyz.txt?piff=paff") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://user@foobar.com/xyz.txt?piff=paff") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://user@localhost:1234/xyz.txt?piff=paff") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://127.0.0.1:1234/xyz.txt?piff=paff") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://[::]:1234/xyz.txt?piff=paff") == CA_LOCATOR_URL); assert_se(ca_classify_locator("http://user@foobar.com") == CA_LOCATOR_URL); assert_se(ca_classify_locator("foobar:quux.txt") == CA_LOCATOR_SSH); assert_se(ca_classify_locator("lennart@foobar:quux.txt") == CA_LOCATOR_SSH); } static void test_locator_patch_last_component_one(const char *old, const char *last_component, int code, const char *result) { char *p; assert_se(ca_locator_patch_last_component(old, last_component, &p) == code); if (code >= 0) { assert_se(streq(p, result)); free(p); } } static void test_locator_patch_last_component(void) { test_locator_patch_last_component_one("", "quux", -EINVAL, NULL); test_locator_patch_last_component_one("foo", "quux", 0, "quux"); test_locator_patch_last_component_one("./miepf", "quux", 0, "./quux"); test_locator_patch_last_component_one("../miepf", "quux", 0, "../quux"); test_locator_patch_last_component_one("some/thing/miepf", "quux", 0, "some/thing/quux"); test_locator_patch_last_component_one("/some/thing/miepf", "quux", 0, "/some/thing/quux"); test_locator_patch_last_component_one("localhost:", "quux", 0, "quux"); test_locator_patch_last_component_one("localhost:miepf", "quux", 0, "localhost:quux"); test_locator_patch_last_component_one("localhost:miepf/waldo", "quux", 0, "localhost:miepf/quux"); test_locator_patch_last_component_one("localhost:/miepf", "quux", 0, "localhost:/quux"); test_locator_patch_last_component_one("localhost:/miepf/waldo", "quux", 0, "localhost:/miepf/quux"); test_locator_patch_last_component_one("http://example.com", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com?foo", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com/", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com/?foo", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com/miepf", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com/miepf?foo", "quux", 0, "http://example.com/quux"); test_locator_patch_last_component_one("http://example.com/miepf/more", "quux", 0, "http://example.com/miepf/quux"); test_locator_patch_last_component_one("http://example.com/miepf/more?foo", "quux", 0, "http://example.com/miepf/quux"); } int main(int argc, char *argv[]) { test_locator_has_suffix(); test_strip_file_url(); test_classify_locator(); test_locator_patch_last_component(); return 0; } test/test-fuse.sh.in000077500000000000000000000026631322621430500147250ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ PARAMS="--with=fuse" CASYNC_PROTOCOL_PATH=@top_builddir@ export CASYNC_PROTOCOL_PATH SCRATCH_DIR=${TMPDIR:-/var/tmp}/test-casync.$RANDOM mkdir -p $SCRATCH_DIR/src if [ `id -u` == 0 ] ; then cp -a @top_srcdir@ $SCRATCH_DIR/src else # If we lack privileges we use rsync rather than cp to copy, as it will just skip over device nodes rsync -a @top_srcdir@ $SCRATCH_DIR/src fi @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/src > $SCRATCH_DIR/test.digest @top_builddir@/casync $PARAMS make $SCRATCH_DIR/test.caidx $SCRATCH_DIR/src @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test.caidx.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.caidx.digest @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.caidx $SCRATCH_DIR/extract @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract > $SCRATCH_DIR/extract.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/extract.digest if [ `id -u` == 0 ] ; then modprobe fuse ||: if test -e /dev/fuse ; then MOUNT_PID=`@top_builddir@/notify-wait @top_builddir@/casync $PARAMS mount $SCRATCH_DIR/test.caidx $SCRATCH_DIR/mount` @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract > $SCRATCH_DIR/mount.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/mount.digest kill $MOUNT_PID umount $SCRATCH_DIR/mount ||: fi fi rm -rf $SCRATCH_DIR test/test-nbd.sh.in000077500000000000000000000033551322621430500145250ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ if [ -z "$1" ] ; then DIGEST=sha512-256 else DIGEST="$1" fi PARAMS="-v --digest=$DIGEST" CASYNC_PROTOCOL_PATH=@top_builddir@ export CASYNC_PROTOCOL_PATH SCRATCH_DIR=${TMPDIR:-/var/tmp}/test-casync.$RANDOM mkdir -p $SCRATCH_DIR dd if=/dev/urandom of=$SCRATCH_DIR/blob bs=102400 count=80 @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/blob > $SCRATCH_DIR/test.digest @top_builddir@/test-calc-digest $DIGEST $SCRATCH_DIR/blob > $SCRATCH_DIR/test.digest2 @top_builddir@/casync $PARAMS make $SCRATCH_DIR/test.caibx $SCRATCH_DIR/blob @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test.caibx > $SCRATCH_DIR/test.caibx.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.digest2 diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.caibx.digest @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.caibx $SCRATCH_DIR/blob2 @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.caibx --seed=$SCRATCH_DIR/blob2 $SCRATCH_DIR/blob3 @top_builddir@/test-calc-digest $DIGEST $SCRATCH_DIR/blob2 > $SCRATCH_DIR/extract.digest @top_builddir@/test-calc-digest $DIGEST $SCRATCH_DIR/blob3 > $SCRATCH_DIR/extract2.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/extract.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/extract2.digest if [ `id -u` == 0 ] ; then modprobe nbd ||: if test -e /dev/nbd0 ; then MKDEV_PID=`@top_builddir@/notify-wait @top_builddir@/casync $PARAMS mkdev $SCRATCH_DIR/test.caibx $SCRATCH_DIR/test-node` dd if=$SCRATCH_DIR/test-node bs=102400 count=80 | @top_builddir@/test-calc-digest $DIGEST > $SCRATCH_DIR/mkdev.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/mkdev.digest kill $MKDEV_PID fi fi rm -rf $SCRATCH_DIR test/test-script-gzip.sh.in000077500000000000000000000003121322621430500162230ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ # This is the same as test-script.sh, except that we force the compression # algorithm to be gzip. exec @top_builddir@/test-script.sh default gzip test/test-script-sha256.sh.in000077500000000000000000000004031322621430500162630ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ # This is the same as test-script.sh, except that we force the digest to be # sha256, in order to detect potential incompatibilities with the remoting # feature. exec @top_builddir@/test-script.sh sha256 test/test-script-xz.sh.in000077500000000000000000000003061322621430500157160ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ # This is the same as test-script.sh, except that we force the compression # algorithm to be xz. exec @top_builddir@/test-script.sh default xz test/test-script.sh.in000077500000000000000000000256051322621430500152700ustar00rootroot00000000000000#!/bin/bash -ex # SPDX-License-Identifier: LGPL-2.1+ if [ -z "$1" ] ; then DIGEST=sha512-256 else DIGEST="$1" fi if [ -z "$2" ] ; then COMPRESSION=zstd else COMPRESSION="$2" fi if [ `id -u` == 0 ] ; then PARAMS="-v --digest=$DIGEST --compression=$COMPRESSION" else PARAMS="-v --without=privileged --digest=$DIGEST --compression=$COMPRESSION" fi CASYNC_PROTOCOL_PATH=@top_builddir@ export CASYNC_PROTOCOL_PATH SCRATCH_DIR=${TMPDIR:-/var/tmp}/test-casync.$RANDOM mkdir -p $SCRATCH_DIR/src if [ `id -u` == 0 ] ; then cp -aT @top_srcdir@ $SCRATCH_DIR/src/casync else # If we lack privileges we use rsync rather than cp to copy, as it will just skip over device nodes rsync -a @top_srcdir@/ $SCRATCH_DIR/src/casync fi cd $SCRATCH_DIR/src @top_builddir@/casync $PARAMS list > $SCRATCH_DIR/test.list @top_builddir@/casync $PARAMS mtree > $SCRATCH_DIR/test.mtree @top_builddir@/casync $PARAMS digest > $SCRATCH_DIR/test.digest @top_builddir@/casync $PARAMS make $SCRATCH_DIR/test.catar @top_builddir@/casync $PARAMS list $SCRATCH_DIR/test.catar > $SCRATCH_DIR/test.catar.list @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/test.catar > $SCRATCH_DIR/test.catar.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test.catar > $SCRATCH_DIR/test.catar.digest @top_builddir@/test-calc-digest $DIGEST $SCRATCH_DIR/test.catar > $SCRATCH_DIR/test.catar.digest2 @top_builddir@/casync $PARAMS make $SCRATCH_DIR/test.caidx @top_builddir@/casync $PARAMS list $SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test.caidx.list @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test.caidx.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test.caidx.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.catar.list diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.caidx.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.catar.mtree diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.caidx.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.catar.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.catar.digest2 diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.caidx.digest ### Test Extraction @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.catar $SCRATCH_DIR/extract-catar @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.catar --seed=$SCRATCH_DIR/extract-catar --hardlink=no $SCRATCH_DIR/extract-catar2 @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.catar --seed=$SCRATCH_DIR/extract-catar --hardlink=yes $SCRATCH_DIR/extract-catar3 @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.caidx $SCRATCH_DIR/extract-caidx @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/test.caidx --seed=$SCRATCH_DIR/extract-caidx $SCRATCH_DIR/extract-caidx2 set +e diff -ur --no-dereference . $SCRATCH_DIR/extract-catar diff -ur --no-dereference . $SCRATCH_DIR/extract-catar2 diff -ur --no-dereference . $SCRATCH_DIR/extract-catar3 diff -ur --no-dereference . $SCRATCH_DIR/extract-caidx diff -ur --no-dereference . $SCRATCH_DIR/extract-caidx2 set -e @top_builddir@/casync $PARAMS list $SCRATCH_DIR/extract-catar > $SCRATCH_DIR/test.extract-catar.list @top_builddir@/casync $PARAMS list $SCRATCH_DIR/extract-catar2 > $SCRATCH_DIR/test.extract-catar2.list @top_builddir@/casync $PARAMS list $SCRATCH_DIR/extract-catar3 > $SCRATCH_DIR/test.extract-catar3.list @top_builddir@/casync $PARAMS list $SCRATCH_DIR/extract-caidx > $SCRATCH_DIR/test.extract-caidx.list @top_builddir@/casync $PARAMS list $SCRATCH_DIR/extract-caidx2 > $SCRATCH_DIR/test.extract-caidx2.list @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-catar > $SCRATCH_DIR/test.extract-catar.mtree @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-catar2 > $SCRATCH_DIR/test.extract-catar2.mtree @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-catar3 > $SCRATCH_DIR/test.extract-catar3.mtree @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-caidx > $SCRATCH_DIR/test.extract-caidx.mtree @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-caidx2 > $SCRATCH_DIR/test.extract-caidx2.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-catar > $SCRATCH_DIR/test.extract-catar.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-catar2 > $SCRATCH_DIR/test.extract-catar2.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-catar3 > $SCRATCH_DIR/test.extract-catar3.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-caidx > $SCRATCH_DIR/test.extract-caidx.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-caidx2 > $SCRATCH_DIR/test.extract-caidx2.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.extract-catar.list diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.extract-catar2.list diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.extract-catar3.list diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.extract-caidx.list diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test.extract-caidx2.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.extract-catar.mtree diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.extract-catar2.mtree diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.extract-catar3.mtree diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.extract-caidx.mtree diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test.extract-caidx2.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.extract-catar.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.extract-catar2.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.extract-catar3.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.extract-caidx.digest diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test.extract-caidx2.digest ### Test seeking @top_builddir@/casync $PARAMS make $SCRATCH_DIR/seek.catar @top_builddir@/casync $PARAMS make $SCRATCH_DIR/seek.caidx @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/seek.catar $SCRATCH_DIR/extract-seek-catar casync/test-files @top_builddir@/casync $PARAMS extract $SCRATCH_DIR/seek.caidx $SCRATCH_DIR/extract-seek-caidx casync/test-files @top_builddir@/casync $PARAMS mtree casync/test-files > $SCRATCH_DIR/original-seek.mtree @top_builddir@/casync $PARAMS digest casync/test-files > $SCRATCH_DIR/original-seek.digest @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-seek-catar/test-files > $SCRATCH_DIR/extract-seek-catar.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-seek-catar/test-files > $SCRATCH_DIR/extract-seek-catar.digest @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/extract-seek-caidx/test-files > $SCRATCH_DIR/extract-seek-caidx.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/extract-seek-caidx/test-files > $SCRATCH_DIR/extract-seek-caidx.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/seek.catar casync/test-files > $SCRATCH_DIR/extract-seek-catar-direct.digest @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/seek.caidx casync/test-files > $SCRATCH_DIR/extract-seek-caidx-direct.digest diff -q $SCRATCH_DIR/original-seek.digest $SCRATCH_DIR/extract-seek-catar.digest diff -q $SCRATCH_DIR/original-seek.digest $SCRATCH_DIR/extract-seek-caidx.digest diff -q $SCRATCH_DIR/original-seek.digest $SCRATCH_DIR/extract-seek-catar-direct.digest diff -q $SCRATCH_DIR/original-seek.digest $SCRATCH_DIR/extract-seek-caidx-direct.digest diff -q $SCRATCH_DIR/original-seek.mtree $SCRATCH_DIR/extract-seek-catar.mtree diff -q $SCRATCH_DIR/original-seek.mtree $SCRATCH_DIR/extract-seek-caidx.mtree ### Test SSH Remoting CASYNC_SSH_PATH=@top_srcdir@/test/pseudo-ssh export CASYNC_SSH_PATH CASYNC_REMOTE_PATH=@top_builddir@/casync export CASYNC_REMOTE_PATH @top_builddir@/casync $PARAMS list localhost:$SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test-remote.caidx.list @top_builddir@/casync $PARAMS mtree localhost:$SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test-remote.caidx.mtree @top_builddir@/casync $PARAMS digest localhost:$SCRATCH_DIR/test.caidx > $SCRATCH_DIR/test-remote.caidx.digest @top_builddir@/casync $PARAMS list localhost:$SCRATCH_DIR/test.catar > $SCRATCH_DIR/test-remote.catar.list @top_builddir@/casync $PARAMS mtree localhost:$SCRATCH_DIR/test.catar > $SCRATCH_DIR/test-remote.catar.mtree @top_builddir@/casync $PARAMS digest localhost:$SCRATCH_DIR/test.catar > $SCRATCH_DIR/test-remote.catar.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test-remote.caidx.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test-remote.caidx.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test-remote.caidx.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test-remote.catar.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test-remote.catar.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test-remote.catar.digest rm -rf $SCRATCH_DIR/default.castr @top_builddir@/casync $PARAMS make localhost:$SCRATCH_DIR/test2.caidx @top_builddir@/casync $PARAMS list $SCRATCH_DIR/test2.caidx > $SCRATCH_DIR/test2.caidx.list @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/test2.caidx > $SCRATCH_DIR/test2.caidx.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test2.caidx > $SCRATCH_DIR/test2.caidx.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test2.caidx.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test2.caidx.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test2.caidx.digest @top_builddir@/casync $PARAMS make localhost:$SCRATCH_DIR/test2.catar @top_builddir@/casync $PARAMS list $SCRATCH_DIR/test2.catar > $SCRATCH_DIR/test2.catar.list @top_builddir@/casync $PARAMS mtree $SCRATCH_DIR/test2.catar > $SCRATCH_DIR/test2.catar.mtree @top_builddir@/casync $PARAMS digest $SCRATCH_DIR/test2.catar > $SCRATCH_DIR/test2.catar.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test2.catar.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test2.catar.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test2.catar.digest ### Test HTTP Remoting HTTP_PORT=$((10000 + $$ % 10000)) CASYNC_PROTOCOL_PATH=@top_builddir@ export CASYNC_PROTOCOL_PATH HTTP_PID=`@top_builddir@/notify-wait @top_srcdir@/test/http-server.py $SCRATCH_DIR $HTTP_PORT` @top_builddir@/casync $PARAMS list http://localhost:$HTTP_PORT/test2.caidx > $SCRATCH_DIR/test3.caidx.list @top_builddir@/casync $PARAMS mtree http://localhost:$HTTP_PORT/test2.caidx > $SCRATCH_DIR/test3.caidx.mtree @top_builddir@/casync $PARAMS digest http://localhost:$HTTP_PORT/test2.caidx > $SCRATCH_DIR/test3.caidx.digest @top_builddir@/casync $PARAMS list http://localhost:$HTTP_PORT/test2.catar > $SCRATCH_DIR/test3.catar.list @top_builddir@/casync $PARAMS mtree http://localhost:$HTTP_PORT/test2.catar > $SCRATCH_DIR/test3.catar.mtree @top_builddir@/casync $PARAMS digest http://localhost:$HTTP_PORT/test2.catar > $SCRATCH_DIR/test3.catar.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test3.caidx.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test3.caidx.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test3.caidx.digest diff -q $SCRATCH_DIR/test.list $SCRATCH_DIR/test3.catar.list diff -q $SCRATCH_DIR/test.mtree $SCRATCH_DIR/test3.catar.mtree diff -q $SCRATCH_DIR/test.digest $SCRATCH_DIR/test3.catar.digest kill $HTTP_PID chmod -R u+rwx $SCRATCH_DIR rm -rf $SCRATCH_DIR test/test-util.c000066400000000000000000000040771322621430500141410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1+ */ #include #include "util.h" #define PART1 127 #define PART2 99 #define PART3 3333 #define PART4 4444 #define PART5 13 int main(int argc, char *argv[]) { uint8_t buffer[PART1 + PART2 + PART3 + PART4 + PART5]; uint8_t buffer2[sizeof(buffer)]; char *fn; int fd, p[2]; uint64_t n_punched; const char *d; assert_se(tmp_dir(&d) >= 0); fn = strjoina(d, "/zeroXXXXXX"); memzero(buffer, PART1); dev_urandom(buffer + PART1, PART2); memzero(buffer + PART1 + PART2, PART3); dev_urandom(buffer + PART1 + PART2 + PART3, PART4); memzero(buffer + PART1 + PART2 + PART3 + PART4, PART5); fd = mkostemp(fn, O_CLOEXEC); assert_se(fd >= 0); assert_se(unlink(fn) == 0); assert_se(loop_write_with_holes(fd, buffer, sizeof(buffer), &n_punched) >= 0); assert_se(n_punched >= PART1 + PART3); assert_se(lseek(fd, 0, SEEK_SET) == 0); assert_se(loop_read(fd, buffer2, sizeof(buffer2)) == sizeof(buffer2)); assert_se(memcmp(buffer, buffer2, sizeof(buffer)) == 0); memzero(buffer + PART1 + 1, PART2 - 2); assert_se(lseek(fd, PART1-1, SEEK_SET) == PART1-1); assert_se(loop_write_with_holes(fd, buffer + PART1 - 1, PART2 + 2, &n_punched) >= 0); assert_se(n_punched >= PART2 - 2); assert_se(lseek(fd, 0, SEEK_SET) == 0); assert_se(loop_read(fd, buffer2, sizeof(buffer2)) == sizeof(buffer2)); assert_se(memcmp(buffer, buffer2, sizeof(buffer)) == 0); fd = safe_close(fd); assert_se(pipe2(p, O_CLOEXEC) >= 0); assert_se(loop_write_with_holes(p[1], buffer, MIN(sizeof(buffer), (size_t) PIPE_BUF), &n_punched) >= 0); assert_se(n_punched == 0); p[1] = safe_close(p[1]); assert_se(loop_read(p[0], buffer2, sizeof(buffer2)) == MIN((ssize_t) sizeof(buffer2), PIPE_BUF)); p[0] = safe_close(p[0]); assert_se(memcmp(buffer, buffer2, MIN(sizeof(buffer), (size_t) PIPE_BUF)) == 0); return 0; }