Welcome to little lamb

Code » slicd » commit 8bf0e70

initial commit

author Olivier Brunel
2015-12-28 17:22:37 UTC
committer Olivier Brunel
2016-01-12 14:33:40 UTC

initial commit

.gitignore +17 -0
AUTHORS +2 -0
COPYING +674 -0
HISTORY +4 -0
INSTALL +24 -0
LICENSE +5 -0
Makefile +143 -0
README.md +72 -0
configure +404 -0
doc/footer.pod +20 -0
doc/miniexec.pod +113 -0
doc/setuid.pod +60 -0
doc/slicd-exec.pod +120 -0
doc/slicd-parser.pod +171 -0
doc/slicd-sched.pod +106 -0
doc/slicd.pod +59 -0
package/deps-build +1 -0
package/info +4 -0
package/modes +5 -0
package/targets.mak +28 -0
src/extra/deps-exe/miniexec +2 -0
src/extra/deps-exe/setuid +2 -0
src/extra/miniexec.c +201 -0
src/extra/setuid.c +129 -0
src/include/slicd/die.h +31 -0
src/include/slicd/err.h +44 -0
src/include/slicd/fields.h +59 -0
src/include/slicd/job.h +70 -0
src/include/slicd/parser.h +32 -0
src/include/slicd/sched.h +32 -0
src/include/slicd/slicd.h +39 -0
src/libslicd/bitarray_clearsetn.c +49 -0
src/libslicd/bitarray_firstclear_skip.c +57 -0
src/libslicd/bitarray_firstset_skip.c +57 -0
src/libslicd/deps-lib/slicd +12 -0
src/libslicd/errmsg.c +37 -0
src/libslicd/slicd_add_job_from_cronline.c +266 -0
src/libslicd/slicd_die_usage.c +37 -0
src/libslicd/slicd_die_version.c +41 -0
src/libslicd/slicd_free.c +33 -0
src/libslicd/slicd_job.c +131 -0
src/libslicd/slicd_job_next_run.c +149 -0
src/libslicd/slicd_load.c +117 -0
src/libslicd/slicd_save.c +61 -0
src/slicd/deps-exe/slicd-exec +3 -0
src/slicd/deps-exe/slicd-parser +2 -0
src/slicd/deps-exe/slicd-sched +3 -0
src/slicd/slicd-exec.c +846 -0
src/slicd/slicd-parser.c +394 -0
src/slicd/slicd-sched.c +340 -0
tools/gen-deps.sh +87 -0
tools/install.sh +64 -0

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..33e0624
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.swp
+*.bak
+*.o
+*.a
+*.lo
+*.so
+*.orig
+*.rej
+.dirstamp
+/package/deps.mak
+/config.mak
+/src/include/slicd/config.h
+/*.1
+/slicd-*
+/setuid
+/miniexec
+/libslicd.a
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..10e9f74
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+slicd is developed by Olivier Brunel
+See LICENSE for more
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  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
+them 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.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  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.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If 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 convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU 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
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..313e2d5
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,4 @@
+
+# 2015-12-??, v0.1.0
+
+ *  first alpha release
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0ee3380
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,24 @@
+Build Instructions
+------------------
+
+* Requirements
+  ------------
+
+  - A POSIX-compliant C development environment
+  - GNU make version 4.0 or later
+  - skalibs version 2.3.8.0 or later: http://skarnet.org/software/skalibs/
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7834bcf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,5 @@
+slicd is released under the GNU General Public License v3+
+  Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+  See COPYING
+
+Build system based on s6's by Laurent Bercot.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8d7c360
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,143 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+CC = $(error Please use ./configure first)
+
+STATIC_LIBS :=
+SHARED_LIBS :=
+INTERNAL_LIBS :=
+EXTRA_TARGETS :=
+DOC_TARGETS :=
+
+-include config.mak
+include package/targets.mak
+include package/deps.mak
+
+version_m := $(basename $(version))
+version_M := $(basename $(version_m))
+version_l := $(basename $(version_M))
+CPPFLAGS_ALL := -iquote src/include-local -Isrc/include $(CPPFLAGS)
+CFLAGS_ALL := $(CFLAGS) -pipe -Wall
+CFLAGS_SHARED := -fPIC
+LDFLAGS_ALL := $(LDFLAGS)
+LDFLAGS_SHARED := -shared
+LDLIBS_ALL := $(LDLIBS)
+REALCC = $(CROSS_COMPILE)$(CC)
+AR := $(CROSS_COMPILE)ar
+RANLIB := $(CROSS_COMPILE)ranlib
+STRIP := $(CROSS_COMPILE)strip
+INSTALL := ./tools/install.sh
+POD2MAN := pod2man
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+ALL_SCRIPTS := $(LIBEXEC_SCRIPTS_TARGET) $(BIN_SCRIPTS_TARGET)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_SCRIPTS) $(ALL_INCLUDES) $(DOC_TARGETS)
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(ALL_SCRIPTS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS)
+
+distclean: clean
+	@exec rm -f config.mak package/deps.mak src/include/${package}/config.h $(DOC_TARGETS)
+
+tgz: distclean $(DOC_TARGETS)
+	@./tools/gen-deps.sh > package/deps.mak 2> /dev/null && \
+	. package/info && \
+	rm -rf /tmp/$$package-$$version && \
+	cp -a . /tmp/$$package-$$version && \
+	cd /tmp && \
+	tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \
+	exec rm -rf /tmp/$$package-$$version
+
+strip: $(ALL_LIBS) $(ALL_BINS)
+ifneq ($(strip $(ALL_LIBS)),)
+	exec ${STRIP} -x -R .note -R .comment -R .note.GNU-stack $(ALL_LIBS)
+endif
+ifneq ($(strip $(ALL_BINS)),)
+	exec ${STRIP} -R .note -R .comment -R .note.GNU-stack $(ALL_BINS)
+endif
+
+install: install-dynlib install-libexec install-bin install-sbin install-lib install-include install-doc
+install-dynlib: $(SHARED_LIBS:lib%.so=$(DESTDIR)$(dynlibdir)/lib%.so)
+install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%) $(LIBEXEC_SCRIPTS_TARGET:%=$(DESTDIR)$(libexecdir)/%)
+install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%) $(BIN_SCRIPTS_TARGET:%=$(DESTDIR)$(bindir)/%)
+install-sbin: $(SBIN_TARGETS:%=$(DESTDIR)$(sbindir)/%)
+install-lib: $(STATIC_LIBS:lib%.a=$(DESTDIR)$(libdir)/lib%.a)
+install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h)
+install-doc: $(DOC_TARGETS:%=$(DESTDIR)/usr/share/man/man1/%)
+
+ifneq ($(exthome),)
+
+update:
+	exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome)
+
+global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so=$(DESTDIR)$(sproot)/library.so/lib%.so) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%) $(SBIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%)
+
+$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/%
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so: $(DESTDIR)$(dynlibdir)/lib%.so
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(exthome))/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $<.$(version) $@.$(version_m) && \
+	$(INSTALL) -l $<.$(version_m) $@.$(version_M) && \
+	$(INSTALL) -l $<.$(version_M) $@.$(version_l) && \
+	exec $(INSTALL) -l $<.$(version_l) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/% $(DESTDIR)$(sbindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)/usr/share/man/man1/%: %
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(EXTRA_LIBS) $(LDLIBS_ALL)
+
+lib%.a:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so:
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$@.$(version_l) $^
+
+$(ALL_SCRIPTS):
+	exec sed -e "s/@VERSION@/$(version)/g" -e "s/@BINDIR@/$(subst /,\/,$(bindir))/g" \
+		-e "s/@LIBEXECDIR@/$(subst /,\/,$(libexecdir))/g" $< > $@
+
+%.1: doc/%.pod doc/footer.pod
+	@exec cat $< doc/footer.pod > $(basename $@).pod
+	exec $(POD2MAN) --center="$(package)" --section=1 --release="$(version)" $(basename $@).pod > $@
+	@exec rm $(basename $@).pod
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-sbin install-lib install-include install-doc
+
+.DELETE_ON_ERROR:
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f68564b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,72 @@
+
+# slicd - simple lightweight Linux cron daemon
+
+slicd is a Linux cron daemon aiming to be small, simple and lightweight. It
+doesn't try to support every possible feature under the sun, and while not a
+requirement is perfectly fitted to run under a supervision suite.
+
+It comes as a few modules :
+
+- **slicd-parser** : to parse crontabs into one "compiled" file
+
+    The parser's job is to process all system & user crontabs and compile them
+    into a single file, in a ready-to-use format for the scheduler.
+
+    See slicd-parser(1) for information on supported format/syntax of crontabs.
+
+- **slicd-sched** : the scheduler, aka the actual cron daemon
+
+    The scheduler simply loads the compiled crontabs, determines the next time a
+    job needs to run and simply waits for it. It doesn't need to wake up every
+    minute (as most cron daemons do) and handles time changes (manual, NTP,
+    DST...) fine.
+
+    It also doesn't actually run anything, but simply prints on its stdout one
+    line for each job to run, in the form USERNAME:COMMAND LINE
+
+- **slicd-exec** : the exec daemon, to actually run jobs
+
+    The scheduler's stdout is aimed to be piped into this daemon's stdin, which
+    will handle forking and executing the command line. It will report all forks
+    & reaped children on its stdout, as well as anything printed on a child's
+    stdout or stderr.
+
+    It is important to note that it doesn't do any drop of privileges or
+    environment changes, instead you're supposed to do this making sure it execs
+    into the right tools; such as one to drop privileges to a specific user, one
+    to set up the correct environment, etc
+
+In addition, a few extra tools are provided, meant to be used alongside
+slicd-exec(1) :
+
+- **setuid** : drop privileges to the specified user
+- **miniexec** : minimal parsing & execing of command-line
+
+## Free Software
+
+slicd - Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+
+slicd is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+slicd 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+slicd (COPYING). If not, see http://www.gnu.org/licenses/
+
+## Want to know more?
+
+Some useful links if you're looking for more info:
+
+- [official site](http://jjacky.com/slicd "slicd @ jjacky.com")
+
+- [source code & issue tracker](https://github.com/jjk-jacky/slicd "slicd @ GitHub.com")
+
+- [PKGBUILD in AUR](https://aur.archlinux.org/packages/slicd "AUR: slicd")
+
+Plus, slicd comes with man pages.
diff --git a/configure b/configure
new file mode 100755
index 0000000..d8d62e8
--- /dev/null
+++ b/configure
@@ -0,0 +1,404 @@
+#!/bin/sh
+
+. package/info
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [TARGET]
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=DIR                  user executables [EPREFIX/bin]
+  --sbindir=DIR                 admin executables [EPREFIX/sbin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib/$package]
+  --includedir=DIR              C header files [PREFIX/include]
+
+ If no --prefix option is given, by default libdir (but not dynlibdir) will be
+ /usr/lib/$package, and includedir will be /usr/include.
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [PREFIX/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+ If no --prefix option is given, by default sysdeps will be fetched from
+ /usr/lib/skalibs/sysdeps.
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-cross=CROSS          prefix toolchain executable names with CROSS [none]
+
+EOF
+exit 0
+}
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO=$1
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $LDFLAGS_AUTO -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+CC_AUTO="$CC"
+CFLAGS_AUTO="$CFLAGS"
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -O2 $CPPFLAGS"
+LDFLAGS_AUTO="$LDFLAGS"
+LDFLAGS_NOSHARED=
+prefix=
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+sbindir='$exec_prefix/sbin'
+libdir='$prefix/lib/$package'
+includedir='$prefix/include'
+sysdeps='$prefix/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+slashpackage=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+cross="$CROSS_COMPILE"
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --sbindir=*) sbindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ; evenmorestatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) allstatic=true ; evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-cross=*) cross=${arg#*=} ;;
+    --enable-cross) cross= ;;
+    --disable-cross) cross= ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*|--build=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) ;;
+    *) target=$arg ;;
+  esac
+done
+
+# Add /usr in the default default case
+if test -z "$prefix" ; then
+  if test "$libdir" = '$prefix/lib/$package' ; then
+    libdir=/usr/lib/$package
+  fi
+  if test "$includedir" = '$prefix/include' ; then
+    includedir=/usr/include
+  fi
+  if test "$sysdeps" = '$prefix/lib/skalibs/sysdeps' ; then
+    sysdeps=/usr/lib/skalibs/sysdeps
+  fi
+fi
+
+# Expand installation directories
+stripdir prefix
+for i in exec_prefix dynlibdir libexecdir bindir sbindir libdir includedir sysdeps sproot skalibs ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${sproot}/package/prog/skalibs/sysdeps
+  fi
+  binprefix=${home}/command
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  libexecdir=$binprefix
+  bindir=$binprefix
+  sbindir=$binprefix
+  libdir=${home}/library
+  includedir=${home}/include
+  while read dep ; do
+    addincpath="$addincpath -I${sproot}${dep}/include"
+    vpaths="$vpaths ${sproot}${dep}/library"
+    addlibspath="$addlibspath -L${sproot}${dep}/library"
+    if $allstatic ; then : ; else
+      vpathd="$vpathd ${sproot}${dep}/library.so"
+      addlibdpath="$addlibdpath -L${sproot}${dep}/library.so"
+    fi
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+echo "checking for C compiler..."
+trycc ${cross}gcc
+trycc ${cross}c99
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO -c -o /dev/null "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "checking target system type..."
+test -n "$target" || target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+rt_lib=$(cat $sysdeps/rt.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+tainnow_lib=$(cat $sysdeps/tainnow.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS_AUTO -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CFLAGS_AUTO -Wa,--noexecstack
+tryflag CFLAGS_AUTO -fno-stack-protector
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS_AUTO -Wl,--hash-style=both
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+CPPFLAGS_AUTO="$CPPFLAGS_AUTO $addincpath"
+LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibspath"
+$allstatic || LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibdpath"
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+sbindir := $sbindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+RT_LIB := ${rt_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+UTIL_LIB := ${util_lib}
+
+CC := $CC_AUTO
+CFLAGS := $CFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS_AUTO
+LDFLAGS := $LDFLAGS_AUTO
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%a$vpaths
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  echo "DO_ALLSTATIC := 1"
+  vpathd=
+fi
+  echo "vpath lib%.so$vpathd"
+if $static ; then
+  echo "DO_STATIC := 1"
+else
+  echo "DO_STATIC :="
+fi
+if $shared ; then
+  echo "DO_SHARED := 1"
+else
+  echo "DO_SHARED :="
+fi
+
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$binprefix/\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix/\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$binprefix/\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir/\""
+fi
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/footer.pod b/doc/footer.pod
new file mode 100644
index 0000000..3acce22
--- /dev/null
+++ b/doc/footer.pod
@@ -0,0 +1,20 @@
+
+=head1 BUGS
+
+They're probably crawling somewhere in there... if you happen to catch one,
+(or more) report it and I'll do my best to squash it.
+
+=head1 REPOSITORY
+
+You can find the latest source code of B<slicd> as well as report bugs and/or
+suggest features on its GitHub repository, available at
+L<https://github.com/jjk-jacky/slicd>; or visit its official website at
+L<http://jjacky.com/slicd>
+
+=head1 AUTHOR
+
+=over
+
+=item Olivier Brunel <jjk@jjacky.com>
+
+=back
diff --git a/doc/miniexec.pod b/doc/miniexec.pod
new file mode 100644
index 0000000..8fc5c3d
--- /dev/null
+++ b/doc/miniexec.pod
@@ -0,0 +1,113 @@
+=head1 NAME
+
+miniexec - Parse and execute command line
+
+=head1 SYNOPSIS
+
+B<miniexec> I<ARG...>
+
+=head1 OPTIONS
+
+=over
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<miniexec>(1) is a small tool to parse its arguments and execute into the
+resulting command line. It is very simple, and aimed to be used from
+B<slicd-exec>(1) (after B<setuid>(1)) to avoid running a whole shell just to
+execute a simple command line.
+
+B<miniexec>(1) will construct the command line to execute into by parsing each
+of its argument in the following manner (similar to single-quote shell escaping) :
+
+- skip leading & ending blanks (spaces & tabulations)
+
+- anything in between single quotes is left as-in (the single quotes being
+  removed, obviously). A single quote cannot be used within, you need to escape
+  it outside, e.g:
+
+    'This is how it'\''s done.'
+
+- any backslash followed by another backslash will be replaced with a single
+  backslash
+
+- any backslash followed by a single quote will be replaced with a single quote
+
+- split on blanks
+
+=head2 Example
+
+For example, imagine you ran B<slicd-exec>(1) as such:
+
+    slicd-exec setuid %u miniexec
+
+Processing a line "bob:echo 'hello world'" read on its stdin, it would fork a
+new process and execute into a command-line made of 4 arguments:
+
+=over
+
+=item setuid
+
+=item bob
+
+=item miniexec
+
+=item echo 'hello world'
+
+=back
+
+Which in turn would have B<setuid>(1) drop privileges, then execute into:
+
+=over
+
+=item miniexec
+
+=item echo 'hello world'
+
+=back
+
+B<miniexec>(1) would then parse its arguments, and execute into the expected
+command line:
+
+=over
+
+=item echo
+
+=item hello world
+
+=back
+
+=head1 RETURN VALUE
+
+The following return values are possible:
+
+=over
+
+=item B<0> : success (only with B<--help> or B<--version>)
+
+=item B<131> : usage error (unknown option, etc)
+
+=item B<132> : I/O error (permission denied, etc)
+
+=item B<133> : memory error
+
+=item B<134> : parsing error
+
+=back
+
+Note that they are above the usual to help differentiate with return values from
+the program executed into.
+
+=head1 SEE ALSO
+
+B<slicd-parser>(1), B<slicd-sched>(1), B<slicd-exec>(1), B<setuid>(1)
diff --git a/doc/setuid.pod b/doc/setuid.pod
new file mode 100644
index 0000000..05c851b
--- /dev/null
+++ b/doc/setuid.pod
@@ -0,0 +1,60 @@
+=head1 NAME
+
+setuid - Execute a program as another user
+
+=head1 SYNOPSIS
+
+B<setuid> I<USERNAME> I<PROG...>
+
+=head1 OPTIONS
+
+=over
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<setuid>(1) is a small tool that will set its real and effective uid and gid to
+those of I<USERNAME>, as well as the list of supplementaty groups to the one of
+I<USERNAME>, then execute into I<PROG...>
+
+This is needed to drop privileges of the right user before executing into the
+command line of a cron job. It is intended to be used via B<slicd-exec>(1).
+
+=head1 NOTES
+
+B<setuid>(1) obviously requires to run as root in order to be able to drop
+privileges.
+
+=head1 RETURN VALUE
+
+The following return values are possible:
+
+=over
+
+=item B<0> : success (only with B<--help> or B<--version>)
+
+=item B<131> : usage error (unknown option, etc)
+
+=item B<132> : I/O error (permission denied, etc)
+
+=item B<134> : unknown user
+
+=item B<135> : failed to set uid/gid/supplementary groups
+
+=back
+
+Note that they are above the usual to help differentiate with return values from
+the program executed into.
+
+=head1 SEE ALSO
+
+B<slicd-parser>(1), B<slicd-sched>(1), B<slicd-exec>(1), B<miniexec>(1)
diff --git a/doc/slicd-exec.pod b/doc/slicd-exec.pod
new file mode 100644
index 0000000..562c67b
--- /dev/null
+++ b/doc/slicd-exec.pod
@@ -0,0 +1,120 @@
+=head1 NAME
+
+slicd-exec - Daemon to execute command-lines
+
+=head1 SYNOPSIS
+
+B<slicd-exec> [B<-R> I<FD>] I<ARG...>
+
+=head1 OPTIONS
+
+=over
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-R, --ready-fd> I<FD>
+
+Announce readiness by printing a LF (\n) on I<FD> when ready. This can be used
+to signal to your service manager when the daemon is actually ready.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<slicd-exec>(1) is a daemon that will read lines on its stdin, then fork &
+execute the specified command line. It is meant to be used alongside
+B<slicd-sched>(1) to actually run the cron jobs.
+
+The argument on its command-line are the ones that will be used to create a
+command line to execute into for each job.
+
+B<slicd-exec>(1) expect to read lines in the form I<USERNAME:COMMAND LINE> and
+for each one, will fork a new child and execute into a new command line. The
+stdout & stderr of the new child will be pipes and anything printed there will
+end up on B<slicd-exec>(1)'s stdout.
+
+It is important to note that it does not do anything else, specifically does not
+drop privileges or set up the environment. If this is needed, this should be
+done by execing into the right tools.
+
+=head2 Command-line construction
+
+When a new line is read, it is split in two : a username, and a command-line.
+B<slicd-exec>(1) will then fork & execute into a new command line, as specified
+on its own command line, with one single treatment: Any percent sign followed by
+another one (%%) will be replaced with a single percent sign (%), and any
+percent sign followed by a lowercase 'u' (%u) will be replaced with the
+username, as read from stdin.
+
+The command line (or argv) to execute into is then the resulting arguments, with
+one extra argument: the command-line as read from stdin.
+
+For example, a classic way to run B<slicd-exec>(1) would be:
+
+    slicd-exec setuid %u sh -c
+
+With this, for a line "bob:echo 'hello world'" read on its stdin, it will fork a
+new process and execute into a command-line made of 5 arguments:
+
+=over
+
+=item setuid
+
+=item bob
+
+=item sh
+
+=item -c
+
+=item echo 'hello world'
+
+=back
+
+B<setuid>(1) is a small tool that simply drops privileges to that of the
+username specified as first argument, then executes into the rest of its
+command line.
+
+In this case, into the shell, which will then parse & execute the command-line
+from the crontab.
+
+If you don't need a full shell to run your cron jobs, you can use
+B<miniexec>(1), a small tool that will simply parse/split its arguments then
+execute into the resulting command line.
+
+=head1 NOTES
+
+Remember that B<slicd-exec>(1) does not do any privileges drop or environment
+manipulation, those must be handled by what every job will exec into. If you're
+running a supervision suite (a-la daemontools) you probably have plenty of such
+tools to do everything you need.
+
+B<slicd-exec>(1) does not actually require root privileges to run. In fact, it
+could run as any user just fine, except that then every job will run as that
+user as well, and the one from the cronjob (i.e. read on stdin) has to be
+ignored.
+
+=head1 RETURN VALUE
+
+The following return values are possible:
+
+=over
+
+=item B<0> : success
+
+=item B<1> : usage error (unknown option, etc)
+
+=item B<2> : I/O error (permission denied, etc)
+
+=item B<3> : memory error
+
+=back
+
+=head1 SEE ALSO
+
+B<slicd-parser>(1), B<slicd-sched>(1), B<setuid>(1), B<miniexec>(1)
diff --git a/doc/slicd-parser.pod b/doc/slicd-parser.pod
new file mode 100644
index 0000000..9c84f53
--- /dev/null
+++ b/doc/slicd-parser.pod
@@ -0,0 +1,171 @@
+=head1 NAME
+
+slicd-parser - Parse crontabs and "compile" them
+
+=head1 SYNOPSIS
+
+B<slicd-parser> [B<-s> I<FILE>] [B<-u> I<DIR>] [B<-U> I<DIR>] -o I<FILE>
+
+=head1 OPTIONS
+
+=over
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-o, --output> I<FILE>
+
+Write compiled crontabs into I<FILE>. This can be I</dev/null> is you only want
+to check for parsing/syntax errors in the crontabs.
+
+This option is required.
+
+=item B<-s, --system> I<FILE>
+
+Parse I<FILE> as a system crontabs if it is a regular file; if it is a directory
+then parse every file it contains (not recursive) as one.
+
+A system crontab is one with an extra field (6th) before the command line, for
+the username to run command lines as.
+
+You can specify this option multiple times. At least one of B<--system>,
+B<--users> or B<--users-dirs> must be specified.
+
+=item B<-u, --users> I<DIR>
+
+Parse every file inside I<DIR> (one level, no recursion) as a user crontab, the
+name of the file being the username to execute command lines as.
+
+You can specify this option multiple times. At least one of B<--system>,
+B<--users> or B<--users-dirs> must be specified.
+
+=item B<-U, --users-dirs> I<DIR>
+
+Will go through every directory inside I<DIR> (one level), then parse every file
+within (one level, no recursion) as user crontabs. The name of the directory
+must be the username to run command lines as.
+
+You can specify this option multiple times. At least one of B<--system>,
+B<--users> or B<--users-dirs> must be specified.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<slicd-parser>(1) is a small tool that will read & parse the specified
+crontabs, and "compile" them into a single file in a ready-to-use format, that
+will be used by B<slicd-sched>(1).
+
+The format of the crontabs is mostly compatible with that of other cron daemons,
+with notable exception that no environment setting are supported.
+
+Any line starting with a pound sign (#) is ignored, to allow comments. Comments
+are not supported elsewhere, e.g. at the end of a line (any pound sign will then
+be part of the command line).
+
+Each line is made of 5 time-and-date fields, followed by a username field for
+system crontabs, then the command line.
+
+The time-and-date fields are, in order:
+
+    field               allowed values
+    -----               --------------
+    minute              0-59
+    hour                0-23
+    day                 1-31
+    month               1-12 (or names, see below)
+    day of the week     0-6 (0=Sunday, 1=Monday, etc; or names, see below)
+
+Range of values are allowed, separating two values with a hyphen (-). The
+specified range is inclusive, and must always have the lower value first.
+
+A field can contain either a single value, a single range, or a list. A list is
+a set of values or ranges, separated by commas.
+
+A field may also contain an asterisk (*) to mean all allowed values (i.e. same
+as "first-last" range).
+
+A step value can be used with ranges (or asterisk). Following the range (or
+asterisk) with "/I<n>" where I<n> is the number to move forward in the range.
+The lower value will always be matched, though the higher one might not.
+
+For example, using "10-20/2" would be the same as "10,12,14,16,18,20" and
+"10-20/3" be the same as "10,13,16,19" (note that 20 isn't a match).
+
+Note that unlike other daemons, B<slicd> does not wake every minute to check if
+a job matches the current time, instead it calculates the time for the next job
+to run, and waits until there. This is why any out of range value will be
+detected and reported as a parsing error (see L<RETURN VALUES> below).
+
+As indicated in the table above, for the 'month' and 'day of the week' fields
+names can also be used. Names are always the first 3 letters of the month/day
+(case does not matter).
+
+Names can be used in place of a single value or within ranges. You can for
+example use "jun-aug", "6-Aug" or "6-7,Aug" indifferently for the 'month' field.
+
+System crontabs have an extra field for the username to run under.
+
+The final field, or rest of the line, will be used as command line. See
+B<slicd-exec>(1) for more on how it will be parsed/processed.
+
+=head2 Special case: Using both 'day' & 'day of the week' fields
+
+If a restriction is set on the 'day of the week' field, i.e. the whole range
+wasn't included, the state of the first 6 days (1-6 in the field 'day') will be
+checked:
+
+- if all are set, the field 'day' will be treated as if '*' was used (i.e. any
+  restriction above will be ignored)
+
+- if none are set, this will result in a parsing error
+
+- if only some are set, it will be processed to mean the Nth of the 'day of the
+  week'. For example, with the 'day' field set to "2,4" and 'day of the week'
+  set to "Wed,Fri" then the job will run the 2nd Wednesday, 2nd Friday, 4th
+  Wednesday and 4th Friday of the month.
+
+  In addition to 1-5 for the first to fifth of the month, you can use 6 to mean
+  the last of the month. For example, with "2,6" as 'day' and "Mon" as 'day of
+  the week' the job would run the second and last Mondays.
+
+  Note that using e.g. "4,6" when if the fourth is also the last will only have
+  the job run once, on the fourth (or last) of the month, as expected.
+
+=head1 RETURN VALUE
+
+When parsing crontabs, any parsing error (e.g. out of range values specified,
+etc) will be reported on stderr, but the rest of the crontab, and other
+crontabs, will still be parsed. However, no output will be written; This is
+meant to allow to check for any such errors at once.
+
+The following return values are possible:
+
+=over
+
+=item B<0> : success
+
+=item B<1> : usage error (unknown option, etc)
+
+=item B<2> : I/O error (permission denied, etc)
+
+=item B<3> : memory error
+
+=item B<4> : no crontabs to parse specified (note that empty crontabs do not
+trigger this, only a lack any options B<--system>, B<--users> and
+B<--users-dirs>)
+
+=item B<5> : no output to write to specified
+
+=item B<6> : parsing errors occured
+
+=back
+
+=head1 SEE ALSO
+
+B<slicd-sched>(1), B<slicd-exec>(1)
diff --git a/doc/slicd-sched.pod b/doc/slicd-sched.pod
new file mode 100644
index 0000000..65d9935
--- /dev/null
+++ b/doc/slicd-sched.pod
@@ -0,0 +1,106 @@
+=head1 NAME
+
+slicd-sched - Scheduler, aka the cron daemon
+
+=head1 SYNOPSIS
+
+B<slicd-sched> [B<-R> I<FD>] I<FILE>
+
+=head1 OPTIONS
+
+=over
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-R, --ready-fd> I<FD>
+
+Announce readiness by printing a LF (\n) on I<FD> when ready. This can be used
+to signal to your service manager when the daemon is actually ready.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<slicd-sched>(1) is the actual cron daemon. It must be started with the path to
+a file of "compiled" crontabs, previously generated via B<slicd-parser>(1)
+
+It will load the compiled crontabs in memory, calculate the next runtime for
+every job, process them as needed and wait until the next time a job needs to
+run.
+
+Processing a job simply means that it will print on its stdout a line in the
+format I<USERNAME:COMMAND LINE> corresponding to the job to run. Its stdout is
+obviously meant to be redirected into B<slicd-exec>(1)'s stdin, which will take
+care of forking and executing the command line.
+
+Unlike most cron daemons, B<slicd-sched>(1) doesn't wake up every minute to
+check if a job definition matches & must be run; Instead, it calculate the next
+runtime for each job, and waits until the closest one.
+
+It does so via a timer set on the real-time system clock, which means any time
+changes (manual, NTP, daylight savings, etc) is handled properly. Specifically,
+if the time jumps backward, nothing happens (jobs are not re-run). If the time
+jumps forward, it will process every job that should have run during the
+(missed) interval, but only once.
+
+So a job said to run every minute would only be processed once, even for a jump
+forward of many hours.
+
+=head1 SIGNALS
+
+The following signals can be used :
+
+=over
+
+=item B<SIGHUP>
+
+Reload compiled crontabs. To avoid re-running jobs, the next jobs to run will
+only be calculated started at the next minute.
+
+=item B<SIGUSR1>
+
+Re-process jobs starting at the current minute.
+
+=item B<SIGINT>
+
+=item B<SIGTERM>
+
+Exit.
+
+=back
+
+=head1 NOTES
+
+Since it only prints to its stdout when a job needs to run, B<slicd-sched>(1)
+can run as any user, it doesn't require root privileges. Only B<slicd-exec>(1)
+does, so the forked children can drop privileges as needed.
+
+=head1 RETURN VALUE
+
+The following return values are possible:
+
+=over
+
+=item B<0> : success
+
+=item B<1> : usage error (unknown option, etc)
+
+=item B<2> : I/O error (permission denied, etc)
+
+=item B<3> : memory error
+
+=item B<4> : no crontabs specified
+
+=item B<5> : other errors (failed to get current time, set timer, etc)
+
+=back
+
+=head1 SEE ALSO
+
+B<slicd-parser>(1), B<slicd-exec>(1)
diff --git a/doc/slicd.pod b/doc/slicd.pod
new file mode 100644
index 0000000..af71065
--- /dev/null
+++ b/doc/slicd.pod
@@ -0,0 +1,59 @@
+=head1 NAME
+
+slicd - simple lightweight Linux cron daemon
+
+=head1 DESCRIPTION
+
+B<slicd> is a Linux cron daemon aiming to be small, simple and lightweight. It
+doesn't try to support every possible feature under the sun, and while not a
+requirement is perfectly fitted to run under a supervision suite.
+
+It comes as a few modules :
+
+=over
+
+=item B<slicd-parser> : to parse crontabs into one "compiled" file
+
+The parser's job is to process all system & user crontabs and compile them into
+a single file, in a ready-to-use format for the scheduler.
+
+See B<slicd-parser>(1) for information on supported format/syntax of crontabs.
+
+=item B<slicd-sched> : the scheduler, aka the actual cron daemon
+
+The scheduler simply loads the compiled crontabs, determines the next time a job
+needs to run and simply waits for it. It doesn't need to wake up every minute
+(as most cron daemons do) and handles time changes (manual, NTP, DST...) fine.
+
+It also doesn't actually run anything, but simply prints on its stdout one line
+for each job to run, in the form I<USERNAME:COMMAND LINE>
+
+=item B<slicd-exec> : the exec daemon, to actually run jobs
+
+The scheduler's stdout is aimed to be piped into this daemon's stdin, which will
+handle forking and executing the command line. It will report all forks & reaped
+children on its stdout, as well as anything printed on a child's stdout or
+stderr.
+
+It is important to note that it doesn't do any drop of privileges or environment
+changes, instead you're supposed to do this making sure it execs into the right
+tools; such as one to drop privileges to a specific user, one to set up the
+correct environment, etc
+
+=back
+
+In addition, a few extra tools are provided, meant to be used alongside
+B<slicd-exec>(1) :
+
+=over
+
+=item B<setuid> : drop privileges to the specified user
+
+=item B<miniexec> : minimal parsing & execing of command-line
+
+=back
+
+=head1 SEE ALSO
+
+B<slic-parser>(1), B<slicd-sched>(1), B<slicd-exec>(1), B<setuid>(1),
+B<miniexec>(1)
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..05d5af4
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1 @@
+/package/prog/skalibs
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..b9ec58e
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=slicd
+version=0.1.0
+category=admin
+package_macro_name=SLICD
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..655926e
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,5 @@
+slicd-exec              0755
+slicd-parser            0755
+slicd-sched             0755
+setuid                  0755
+miniexec                0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..6e6420e
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,28 @@
+BIN_TARGETS := \
+slicd-exec \
+slicd-parser \
+slicd-sched \
+setuid \
+miniexec
+
+DOC_TARGETS := \
+slicd.1 \
+slicd-parser.1 \
+slicd-sched.1 \
+slicd-exec.1 \
+setuid.1 \
+miniexec.1
+
+ifdef DO_ALLSTATIC
+LIBSLICD := libslicd.a
+else
+LIBSLICD := libslicd.so
+endif
+
+ifdef DO_SHARED
+SHARED_LIBS := libslicd.so
+endif
+
+ifdef DO_STATIC
+STATIC_LIBS := libslicd.a
+endif
diff --git a/src/extra/deps-exe/miniexec b/src/extra/deps-exe/miniexec
new file mode 100644
index 0000000..96c7a85
--- /dev/null
+++ b/src/extra/deps-exe/miniexec
@@ -0,0 +1,2 @@
+${LIBSLICD}
+-lskarnet
diff --git a/src/extra/deps-exe/setuid b/src/extra/deps-exe/setuid
new file mode 100644
index 0000000..96c7a85
--- /dev/null
+++ b/src/extra/deps-exe/setuid
@@ -0,0 +1,2 @@
+${LIBSLICD}
+-lskarnet
diff --git a/src/extra/miniexec.c b/src/extra/miniexec.c
new file mode 100644
index 0000000..5d7085d
--- /dev/null
+++ b/src/extra/miniexec.c
@@ -0,0 +1,201 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * miniexec.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include "slicd/config.h"
+
+#include <getopt.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/strerr2.h>
+#include <slicd/die.h>
+
+#define is_blank(c) \
+    (c == ' ' || c == '\t')
+
+enum
+{
+    RC_OK = 0,
+    RC_SYNTAX = 131,
+    RC_IO,
+    RC_MEMORY,
+    RC_PARSING
+};
+
+static void
+dieusage (int rc)
+{
+    slicd_die_usage (rc, "ARGS...",
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "miniexec";
+    stralloc sa = STRALLOC_ZERO;
+    genalloc ga = GENALLOC_ZERO;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "+hV", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'h':
+                dieusage (RC_OK);
+
+            case 'V':
+                slicd_die_version ();
+
+            default:
+                dieusage (RC_SYNTAX);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 1)
+        dieusage (RC_SYNTAX);
+
+    if (!stralloc_ready (&sa, 64))
+        strerr_diefu1sys (RC_MEMORY, "allocate memory");
+    if (!genalloc_ready (char *, &ga, argc + 1))
+        strerr_diefu1sys (RC_MEMORY, "allocate memory");
+
+    {
+        genalloc ga_idx = GENALLOC_ZERO;
+        char *p = NULL;
+        char *start = *argv;
+        int i;
+
+        while (*argv)
+        {
+            char *s;
+            int in_single = 0;
+
+            while (is_blank (*start))
+                ++start;
+
+            for (s = start; ; ++s)
+            {
+                if (*s == '\0')
+                {
+                    if (in_single)
+                        strerr_diefu3x (RC_PARSING, "parse arg '", *argv,
+                                "': unclosed single quote");
+
+                    if (!p)
+                    {
+                        if (s > start)
+                        {
+                            p = start;
+                            genalloc_append (char *, &ga, &p);
+                        }
+                    }
+                    else
+                    {
+                        stralloc_catb (&sa, start, s - start);
+                        stralloc_0 (&sa);
+                    }
+
+                    p = NULL;
+                    start = *++argv;
+                    break;
+                }
+                else if (*s == '\'')
+                {
+                    if (!in_single)
+                    {
+                        if (!p)
+                        {
+                            i = genalloc_len (char *, &ga);
+                            genalloc_append (int, &ga_idx, &i);
+                            p = (char *) (intptr_t) sa.len;
+                            genalloc_append (char *, &ga, &p);
+                        }
+                    }
+                    stralloc_catb (&sa, start, s - start);
+
+                    start = s + 1;
+                    in_single = !in_single;
+                }
+                else if (in_single)
+                    continue;
+                else if (is_blank (*s))
+                {
+                    if (!p)
+                    {
+                        i = genalloc_len (char *, &ga);
+                        genalloc_append (int, &ga_idx, &i);
+                        p = (char *) (intptr_t) sa.len;
+                        genalloc_append (char *, &ga, &p);
+                    }
+                    stralloc_catb (&sa, start, s - start);
+                    stralloc_0 (&sa);
+
+                    p = NULL;
+                    start = s + 1;
+                    break;
+                }
+                else if (*s == '\\' && (s[1] == '\\' || s[1] == '\''))
+                {
+                    if (!p)
+                    {
+                        i = genalloc_len (char *, &ga);
+                        genalloc_append (int, &ga_idx, &i);
+                        p = (char *) (intptr_t) sa.len;
+                        genalloc_append (char *, &ga, &p);
+                    }
+                    stralloc_catb (&sa, start, s - start);
+
+                    start = ++s;
+                }
+            }
+        }
+        for (i = 0; i < genalloc_len (int, &ga_idx); ++i)
+        {
+            int idx = genalloc_s (int, &ga_idx)[i];
+            unsigned int offset = (unsigned int) (intptr_t) genalloc_s (char *, &ga)[idx];
+            p = sa.s + offset;
+            byte_copy (ga.s + idx * sizeof (char *), sizeof (char *), &p);
+        }
+        genalloc_append (char *, &ga, argv);
+
+        genalloc_free (int, &ga_idx);
+        genalloc_shrink (char *, &ga);
+        stralloc_shrink (&sa);
+    }
+
+    pathexec ((char const * const *) ga.s);
+    strerr_dieexec (RC_IO, genalloc_s (char *, &ga)[0]);
+}
diff --git a/src/extra/setuid.c b/src/extra/setuid.c
new file mode 100644
index 0000000..09751ad
--- /dev/null
+++ b/src/extra/setuid.c
@@ -0,0 +1,129 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * setuid.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#define _BSD_SOURCE
+
+#include "slicd/config.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <pwd.h>
+#include <grp.h>
+#include <unistd.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr2.h>
+#include <slicd/die.h>
+
+enum
+{
+    RC_OK = 0,
+    RC_SYNTAX = 131,
+    RC_IO,
+    RC_MEMORY,
+    RC_UNKNOWN,
+    RC_OTHER
+};
+
+static void
+dieusage (int rc)
+{
+    slicd_die_usage (rc, "USERNAME PROG...",
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "setuid";
+    struct passwd *pw;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "+hV", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'h':
+                dieusage (RC_OK);
+
+            case 'V':
+                slicd_die_version ();
+
+            default:
+                dieusage (RC_SYNTAX);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc < 2)
+        dieusage (RC_SYNTAX);
+
+    pw = getpwnam (argv[0]);
+    if (!pw)
+        strerr_dief2x (RC_UNKNOWN, "unknown user: ", argv[0]);
+
+    {
+        gid_t gids[NGROUPS_MAX];
+        int n = 0;
+
+        errno = 0;
+        for (;;)
+        {
+            struct group *gr;
+            register char **member;
+
+            gr = getgrent ();
+            if (!gr)
+                break;
+            for (member = gr->gr_mem; *member; ++member)
+                if (str_equal (*member, argv[0]))
+                    break;
+            if (*member)
+                gids[n++] = gr->gr_gid;
+        }
+        endgrent ();
+        if (errno != 0)
+            strerr_diefu2sys (RC_OTHER, "get supplementary groups for ", argv[0]);
+        if (setgroups (n, gids) < 0)
+            strerr_diefu2sys (RC_OTHER, "set supplementary groups for ", argv[0]);
+    }
+    if (setgid (pw->pw_gid) < 0)
+        strerr_diefu1sys (RC_OTHER, "setgid");
+    if (setuid (pw->pw_uid) < 0)
+        strerr_diefu1sys (RC_OTHER, "setuid");
+
+    pathexec ((char const * const *) ++argv);
+    strerr_dieexec (RC_IO, argv[0]);
+}
diff --git a/src/include/slicd/die.h b/src/include/slicd/die.h
new file mode 100644
index 0000000..d3dc6a6
--- /dev/null
+++ b/src/include/slicd/die.h
@@ -0,0 +1,31 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * die.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_DIE_H
+#define SLICD_DIE_H
+
+extern char const *PROG;
+
+extern void slicd_die_usage (int rc, const char *usage, const char *details);
+extern void slicd_die_version (void);
+
+#endif /* SLICD_DIE_H */
diff --git a/src/include/slicd/err.h b/src/include/slicd/err.h
new file mode 100644
index 0000000..6674675
--- /dev/null
+++ b/src/include/slicd/err.h
@@ -0,0 +1,44 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * err.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_ERR_H
+#define SLICD_ERR_H
+
+enum
+{
+    SLICD_ERR_SUCCESS = 0,
+    SLICD_ERR_IO,
+    SLICD_ERR_MEMORY,
+    SLICD_ERR_NOT_EMPTY,
+    SLICD_ERR_NOT_IN_RANGE,
+    SLICD_ERR_INVALID_RANGE,
+    SLICD_ERR_UNKNOWN_NAME_FIELD,
+    SLICD_ERR_SYNTAX,
+    SLICD_ERR_DAYS_COMBO,
+    SLICD_ERR_IMPOSSIBLE_DATE,
+    SLICD_ERR_NO_USERNAME,
+    _SLICD_NB_ERR
+};
+
+extern const char const *slicd_errmsg[_SLICD_NB_ERR];
+
+#endif /* SLICD_ERR_H */
diff --git a/src/include/slicd/fields.h b/src/include/slicd/fields.h
new file mode 100644
index 0000000..c189741
--- /dev/null
+++ b/src/include/slicd/fields.h
@@ -0,0 +1,59 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * fields.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_FIELDS_H
+#define SLICD_FIELDS_H
+
+/* slicd_job_t contains a bitarray where bits are stored as follow:
+ * 60   minutes (0-59)
+ * 24   hours (0-23)
+ * 31   days (0-30)
+ * 12   months (0-11)
+ *  7   days of week (0-6; 0=sunday, 1=monday, etc)
+ *  1   days combo: set when both days & dow are set, i.e. the Nth dow
+ *
+ * 135 bits total; hence an array of 17 char-s (leaving 1 bit unused)
+ *
+ * Both DAYS & DOW set means that DAYS is limited to 0-6 which stands for the
+ * 1st, 2nd, ... till the 5th & then last DOW of the month.
+ */
+#define _SLICD_BITS_OFFSET_MINUTES      0
+#define _SLICD_BITS_OFFSET_HOURS        _SLICD_BITS_OFFSET_MINUTES + 60
+#define _SLICD_BITS_OFFSET_DAYS         _SLICD_BITS_OFFSET_HOURS + 24
+#define _SLICD_BITS_OFFSET_MONTHS       _SLICD_BITS_OFFSET_DAYS + 31
+#define _SLICD_BITS_OFFSET_DOW          _SLICD_BITS_OFFSET_MONTHS + 12
+#define _SLICD_BIT_DAYS_COMBO           _SLICD_BITS_OFFSET_DOW + 7
+
+static struct
+{
+    unsigned int adjust : 1; /* i.e. -1 to what is given, e.g. days are 1-31 but stored as 0-30 */
+    unsigned int max    : 8;
+    unsigned int offset : 23;
+} _slicd_fields[] = {
+    { 0, 59, _SLICD_BITS_OFFSET_MINUTES },
+    { 0, 23, _SLICD_BITS_OFFSET_HOURS },
+    { 1, 30, _SLICD_BITS_OFFSET_DAYS },
+    { 1, 11, _SLICD_BITS_OFFSET_MONTHS },
+    { 0, 6,  _SLICD_BITS_OFFSET_DOW }
+};
+
+#endif /* SLICD_FIELDS_H */
diff --git a/src/include/slicd/job.h b/src/include/slicd/job.h
new file mode 100644
index 0000000..7cf41b5
--- /dev/null
+++ b/src/include/slicd/job.h
@@ -0,0 +1,70 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * job.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_JOB_H
+#define SLICD_JOB_H
+
+#include <slicd/slicd.h>
+
+typedef struct
+{
+    unsigned int offset;
+    unsigned char bits[17];
+} slicd_job_t;
+
+typedef enum
+{
+    SLICD_MINUTES = 0,
+    SLICD_HOURS,
+    SLICD_DAYS,
+    SLICD_MONTHS,
+    SLICD_DAYS_OF_WEEK,
+    _SLICD_NB_FIELD
+} slicd_field_t;
+#define SLICD_DOW   SLICD_DAYS_OF_WEEK
+
+extern int slicd_job_has            (slicd_job_t    *job,
+                                     slicd_field_t   field,
+                                     int             which);
+
+extern int slicd_job_has_days_combo (slicd_job_t    *job);
+
+extern int slicd_job_set_days_combo (slicd_job_t    *job,
+                                     int             what);
+
+extern int slicd_job_clearset       (slicd_job_t    *job,
+                                     slicd_field_t   field,
+                                     int             from,
+                                     int             to,
+                                     int             what);
+#define slicd_job_clear(job,field,from,to) \
+    slicd_job_clearset (job, field, from, to, 0)
+#define slicd_job_set(job,field,from,to) \
+    slicd_job_clearset (job, field, from, to, 1)
+
+extern int slicd_job_first          (slicd_job_t    *job,
+                                     slicd_field_t   field,
+                                     int             from,
+                                     int             to,
+                                     int             what);
+
+#endif /* SLICD_JOB_H */
diff --git a/src/include/slicd/parser.h b/src/include/slicd/parser.h
new file mode 100644
index 0000000..cd1a0e3
--- /dev/null
+++ b/src/include/slicd/parser.h
@@ -0,0 +1,32 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * parser.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_PARSER_H
+#define SLICD_PARSER_H
+
+#include <slicd/slicd.h>
+
+extern int slicd_add_job_from_cronline (slicd_t     *slicd,
+                                        const char  *username,
+                                        const char  *cronline);
+
+#endif /* SLICD_PARSER_H */
diff --git a/src/include/slicd/sched.h b/src/include/slicd/sched.h
new file mode 100644
index 0000000..67be384
--- /dev/null
+++ b/src/include/slicd/sched.h
@@ -0,0 +1,32 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * sched.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_SCHED_H
+#define SLICD_SCHED_H
+
+#include <time.h>
+#include <slicd/job.h>
+
+extern time_t slicd_job_next_run (slicd_job_t   *job,
+                                  struct tm     *next);
+
+#endif /* SLICD_SCHED_H */
diff --git a/src/include/slicd/slicd.h b/src/include/slicd/slicd.h
new file mode 100644
index 0000000..e0c8f2d
--- /dev/null
+++ b/src/include/slicd/slicd.h
@@ -0,0 +1,39 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd.h
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef SLICD_H
+#define SLICD_H
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+
+typedef struct
+{
+    stralloc str;
+    genalloc jobs; /* slicd_job_t[] */
+} slicd_t;
+
+extern int slicd_load (slicd_t *slicd, const char *file);
+extern int slicd_save (slicd_t *slicd, const char *file);
+extern void slicd_free (slicd_t *slicd);
+
+#endif /* SLICD_H */
diff --git a/src/libslicd/bitarray_clearsetn.c b/src/libslicd/bitarray_clearsetn.c
new file mode 100644
index 0000000..1c927cb
--- /dev/null
+++ b/src/libslicd/bitarray_clearsetn.c
@@ -0,0 +1,49 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * bitarray_clearsetn.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+/* Based on bitarray_clearsetn.c from skalibs; ISC license
+ * Copyright (c) 2011-2015 Laurent Bercot <ska-skaware@skarnet.org> */
+
+#include <skalibs/bitarray.h>
+
+void _bitarray_clearsetn (register unsigned char *s,
+                          register unsigned int a,
+                          register unsigned int b,
+                          register unsigned int h)
+{
+ if (!b) return ;
+  b += a ;
+  if ((a >> 3) == ((b-1) >> 3))
+  {
+    register unsigned char mask = ((1 << (a & 7)) - 1) ^ ((1 << ((b & 7) ? b & 7 : 8)) - 1) ;
+    if (h) s[a>>3] |= mask ; else s[a>>3] &= ~mask ;
+  }
+  else
+  {
+    register unsigned char mask = ~((1 << (a & 7)) - 1) ;
+    register unsigned int i = (a>>3) + 1 ;
+    if (h) s[a>>3] |= mask ; else s[a>>3] &= ~mask ;
+    mask = h ? 0xff : 0x00 ;
+    for (; i < b>>3 ; i++) s[i] = mask ;
+    mask = (1 << (b & 7)) - 1 ;
+    if (h) s[b>>3] |= mask ; else s[b>>3] &= ~mask ;
+  }
+}
diff --git a/src/libslicd/bitarray_firstclear_skip.c b/src/libslicd/bitarray_firstclear_skip.c
new file mode 100644
index 0000000..97aae02
--- /dev/null
+++ b/src/libslicd/bitarray_firstclear_skip.c
@@ -0,0 +1,57 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * bitarray_firstclear_skip.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+/* Based on bitarray_firstclear.c from skalibs; ISC license
+ * Copyright (c) 2011-2015 Laurent Bercot <ska-skaware@skarnet.org> */
+
+#include <skalibs/bitarray.h>
+
+unsigned int
+_bitarray_firstclear_skip (register unsigned char const *s, unsigned int max, unsigned int skip)
+{
+    unsigned int n = bitarray_div8 (max);
+    register unsigned int i = bitarray_div8 (skip);
+
+    if (i && s[i - 1] != 0xffU)
+    {
+        register unsigned int j = skip;
+
+        skip = i << 3;
+        if (skip > max)
+            skip = max;
+        while ((j < skip) && bitarray_peek (s, j))
+            ++j;
+        if (j < skip)
+            return j;
+    }
+
+    for ( ; i < n ; ++i)
+        if (s[i] != 0xffU)
+            break;
+
+    if (i == n)
+        return max;
+
+    i <<= 3;
+    while ((i < max) && bitarray_peek (s, i))
+        ++i;
+    return i;
+}
diff --git a/src/libslicd/bitarray_firstset_skip.c b/src/libslicd/bitarray_firstset_skip.c
new file mode 100644
index 0000000..d6236cb
--- /dev/null
+++ b/src/libslicd/bitarray_firstset_skip.c
@@ -0,0 +1,57 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * bitarray_firstset_skip.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+/* Based on bitarray_firstset.c from skalibs; ISC license
+ * Copyright (c) 2011-2015 Laurent Bercot <ska-skaware@skarnet.org> */
+
+#include <skalibs/bitarray.h>
+
+unsigned int
+_bitarray_firstset_skip (register unsigned char const *s, unsigned int max, unsigned int skip)
+{
+    unsigned int n = bitarray_div8 (max);
+    register unsigned int i = bitarray_div8 (skip);
+
+    if (i && s[i - 1])
+    {
+        register unsigned int j = skip;
+
+        skip = i << 3;
+        if (skip > max)
+            skip = max;
+        while ((j < skip) && !bitarray_peek (s, j))
+            ++j;
+        if (j < skip)
+            return j;
+    }
+
+    for ( ; i < n ; ++i)
+        if (s[i])
+            break;
+
+    if (i == n)
+        return max;
+
+    i <<= 3;
+    while ((i < max) && !bitarray_peek (s, i))
+        ++i;
+    return i;
+}
diff --git a/src/libslicd/deps-lib/slicd b/src/libslicd/deps-lib/slicd
new file mode 100644
index 0000000..fb9cf1f
--- /dev/null
+++ b/src/libslicd/deps-lib/slicd
@@ -0,0 +1,12 @@
+bitarray_clearsetn.o
+bitarray_firstclear_skip.o
+bitarray_firstset_skip.o
+errmsg.o
+slicd_add_job_from_cronline.o
+slicd_die_usage.o
+slicd_die_version.o
+slicd_free.o
+slicd_job.o
+slicd_job_next_run.o
+slicd_load.o
+slicd_save.o
diff --git a/src/libslicd/errmsg.c b/src/libslicd/errmsg.c
new file mode 100644
index 0000000..e61e728
--- /dev/null
+++ b/src/libslicd/errmsg.c
@@ -0,0 +1,37 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * errmsg.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <slicd/err.h>
+
+const char const *slicd_errmsg[_SLICD_NB_ERR] = {
+    "",
+    "I/O error",
+    "Memory error",
+    "Not empty",
+    "Not in allowed range",
+    "Invalid range",
+    "Unknown name in field",
+    "Syntax error",
+    "Out of range days alongside days of the week",
+    "Impossible date matching (e.g. Feb 31)",
+    "No username specified"
+};
diff --git a/src/libslicd/slicd_add_job_from_cronline.c b/src/libslicd/slicd_add_job_from_cronline.c
new file mode 100644
index 0000000..c82890d
--- /dev/null
+++ b/src/libslicd/slicd_add_job_from_cronline.c
@@ -0,0 +1,266 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_add_job_from_cronline.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <errno.h>
+#include <slicd/slicd.h>
+#include <slicd/parser.h>
+#include <slicd/job.h>
+#include <slicd/fields.h>
+#include <slicd/err.h>
+
+
+static const char *names[_SLICD_NB_FIELD] = {
+    NULL,
+    NULL,
+    NULL,
+    "JanFebMarAprMayJunJulAugSepOctNovDec",
+    "SunMonTueWedThuFriSat",
+};
+
+#define is_blank(c) \
+    (c == ' ' || c == '\t')
+#define is_num(c) \
+    (c >= '0' && c <= '9')
+#define skip_blanks(s) \
+    for ( ; *s != '\0' && is_blank (*s); ++s) ;
+#define read_int(s,n) \
+    do {                            \
+        n = 0;                      \
+        for (;;)                    \
+        {                           \
+            if (!is_num (*s))       \
+                break;              \
+            n *= 10;                \
+            n += *s - '0';          \
+            ++s;                    \
+        }                           \
+    } while (0)
+#define in_interval(n,f,t) \
+    (n >= f && n <= t)
+
+static int
+parse_field_names (slicd_field_t field, const char **s)
+{
+    const char *n = names[field];
+    int max = _slicd_fields[field].max * 3;
+    int i = 'A' - 'a';
+    int p = 0;
+
+    for (p = 0; p <= max; p += 3)
+    {
+        if ( ((*s)[0] == n[p] || (*s)[0] == n[p] - i)
+                && ((*s)[1] == n[p + 1] || (*s)[1] == n[p + 1] + i)
+                && ((*s)[2] == n[p + 2] || (*s)[2] == n[p + 2] + i) )
+        {
+            *s += 3;
+            return (p / 3) + _slicd_fields[field].adjust;
+        }
+    }
+
+    return -1;
+}
+
+static int
+parse_interval (slicd_job_t *job, slicd_field_t field, const char **s)
+{
+    int min = _slicd_fields[field].adjust;
+    int max = min + _slicd_fields[field].max;
+    int from, to, step;
+
+again:
+    if (**s == '*')
+    {
+        from = min;
+        to = max;
+        ++*s;
+    }
+    else
+    {
+        if (is_num (**s))
+            read_int (*s, from);
+        else if (names[field])
+        {
+            from = parse_field_names (field, s);
+            if (from < 0)
+                return -SLICD_ERR_UNKNOWN_NAME_FIELD;
+        }
+        else
+            return -SLICD_ERR_SYNTAX;
+
+        if (!in_interval (from, min, max))
+            return -SLICD_ERR_NOT_IN_RANGE;
+
+        if (**s == '-')
+        {
+            ++*s;
+            if (is_num (**s))
+                read_int (*s, to);
+            else if (names[field])
+            {
+                to = parse_field_names (field, s);
+                if (to < 0)
+                    return -SLICD_ERR_UNKNOWN_NAME_FIELD;
+            }
+            else
+                return -SLICD_ERR_SYNTAX;
+
+            if (!in_interval (to, from, max))
+                return -SLICD_ERR_NOT_IN_RANGE;
+        }
+        else
+            to = from;
+
+        if (!is_blank (**s) && **s != '/' && **s != ',')
+            return -SLICD_ERR_SYNTAX;
+    }
+    if (**s == '/')
+    {
+        ++*s;
+        if (!is_num (**s))
+            return -SLICD_ERR_SYNTAX;
+
+        read_int (*s, step);
+    }
+    else
+        step = 1;
+
+    if (!is_blank (**s) && **s != ',')
+        return -SLICD_ERR_SYNTAX;
+
+    if (from == min && to == max && step == 1)
+        slicd_job_set (job, field, from, to);
+    else
+        for ( ; from <= to; from += step)
+            slicd_job_set (job, field, from, from);
+
+    if (**s == ',')
+    {
+        ++*s;
+        goto again;
+    }
+    skip_blanks (*s);
+
+    return 0;
+}
+
+int
+slicd_add_job_from_cronline (slicd_t     *slicd,
+                             const char  *username,
+                             const char  *cronline)
+{
+    slicd_job_t j = { 0, };
+    const char *s = cronline;
+    int r;
+    int i;
+
+    if (!s)
+        return -SLICD_ERR_SYNTAX;
+
+    skip_blanks (s);
+    if (*s == '\0' || *s == '#')
+        return 0;
+
+    for (i = 0; i < 5; ++i)
+    {
+        r = parse_interval (&j, i, &s);
+        if (r < 0)
+            return r;
+    }
+
+    /* special case: days combo */
+    if (slicd_job_first (&j, SLICD_DAYS_OF_WEEK, 0, 6, 0) <= 6)
+    {
+        /* there is a restriction on DOW, now let's check how the first 6 days
+         * are set:
+         * - all set    = same as '*', no restriction on DAYS
+         * - none set   = invalid
+         * - a mix      = DAYS_COMBO
+         */
+
+        if (slicd_job_first (&j, SLICD_DAYS, 1, 6, 0) == 7)
+            slicd_job_set (&j, SLICD_DAYS, 1, 31);
+        else if (slicd_job_first (&j, SLICD_DAYS, 1, 6, 1) == 7)
+            return -SLICD_ERR_DAYS_COMBO;
+        else
+            slicd_job_set_days_combo (&j, 1);
+    }
+
+    /* ensure date validity/coherence */
+    if (slicd_job_first (&j, SLICD_DAYS, 1, 29, 1) == 30)
+    {
+        /* none of the first 29 days are set, so we need to do some checking:
+         * if day 30 is set, that there's another month than Feb,
+         * else (i.e. day 31 is set) that there's at least one matching month
+         */
+        if (slicd_job_has (&j, SLICD_DAYS, 30))
+        {
+            if (!(slicd_job_has (&j, SLICD_MONTHS, 1)
+                        || slicd_job_first (&j, SLICD_MONTHS, 3, 12, 1) <= 12))
+                return -SLICD_ERR_IMPOSSIBLE_DATE;
+        }
+        else
+        {
+            if (!(slicd_job_has (&j, SLICD_MONTHS, 1)
+                        || slicd_job_has (&j, SLICD_MONTHS, 3)
+                        || slicd_job_has (&j, SLICD_MONTHS, 5)
+                        || slicd_job_has (&j, SLICD_MONTHS, 7)
+                        || slicd_job_has (&j, SLICD_MONTHS, 8)
+                        || slicd_job_has (&j, SLICD_MONTHS, 10)
+                        || slicd_job_has (&j, SLICD_MONTHS, 12)))
+                return -SLICD_ERR_IMPOSSIBLE_DATE;
+        }
+    }
+
+    {
+        int len;
+
+        if (username)
+            i = strlen (username);
+        else
+        {
+            username = s;
+            for (i = 0; *s != '\0' && !is_blank (*s); ++s, ++i)
+                ;
+            skip_blanks (s);
+        }
+        if (i == 0)
+            return -SLICD_ERR_NO_USERNAME;
+        if (*s == '\0')
+            return -SLICD_ERR_SYNTAX;
+
+        for (len = strlen (s); is_blank (s[len - 1]); --len)
+            ;
+
+        if (!stralloc_readyplus (&slicd->str, i + 1 + len + 1)
+                || !genalloc_readyplus (slicd_job_t, &slicd->jobs, 1))
+            return -SLICD_ERR_MEMORY;
+
+        j.offset = slicd->str.len;
+        stralloc_catb (&slicd->str, username, i);
+        stralloc_catb (&slicd->str, ":", 1);
+        stralloc_catb (&slicd->str, s, len);
+        stralloc_0 (&slicd->str);
+        genalloc_catb (slicd_job_t, &slicd->jobs, &j, 1);
+
+        return 1;
+    }
+}
diff --git a/src/libslicd/slicd_die_usage.c b/src/libslicd/slicd_die_usage.c
new file mode 100644
index 0000000..68a81db
--- /dev/null
+++ b/src/libslicd/slicd_die_usage.c
@@ -0,0 +1,37 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_die_usage.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <unistd.h>
+#include <skalibs/buffer.h>
+#include <slicd/die.h>
+
+void
+slicd_die_usage (int rc, const char *usage, const char *details)
+{
+    buffer_puts (buffer_1small, "Usage: ");
+    buffer_puts (buffer_1small, PROG);
+    buffer_puts (buffer_1small, " ");
+    buffer_puts (buffer_1small, usage);
+    buffer_puts (buffer_1small, "\n\n");
+    buffer_putsflush (buffer_1small, details);
+    _exit (rc);
+}
diff --git a/src/libslicd/slicd_die_version.c b/src/libslicd/slicd_die_version.c
new file mode 100644
index 0000000..ab6a9d0
--- /dev/null
+++ b/src/libslicd/slicd_die_version.c
@@ -0,0 +1,41 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_die_version.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include "slicd/config.h"
+
+#include <unistd.h>
+#include <skalibs/buffer.h>
+#include <slicd/die.h>
+
+void
+slicd_die_version (void)
+{
+    buffer_puts (buffer_1small, PROG);
+    buffer_puts (buffer_1small, " v" SLICD_VERSION "\n");
+    buffer_putsflush (buffer_1small,
+            "Copyright (C) 2015 Olivier Brunel - http://jjacky.com/slicd\n"
+            "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n"
+            "This is free software: you are free to change and redistribute it.\n"
+            "There is NO WARRANTY, to the extent permitted by law.\n"
+            );
+    _exit (0);
+}
diff --git a/src/libslicd/slicd_free.c b/src/libslicd/slicd_free.c
new file mode 100644
index 0000000..19c8eb5
--- /dev/null
+++ b/src/libslicd/slicd_free.c
@@ -0,0 +1,33 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_free.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <slicd/slicd.h>
+
+void
+slicd_free (slicd_t *slicd)
+{
+    if (slicd)
+    {
+        stralloc_free (&slicd->str);
+        genalloc_free (slicd_job_t, &slicd->jobs);
+    }
+}
diff --git a/src/libslicd/slicd_job.c b/src/libslicd/slicd_job.c
new file mode 100644
index 0000000..6642ed5
--- /dev/null
+++ b/src/libslicd/slicd_job.c
@@ -0,0 +1,131 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_job.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <skalibs/bitarray.h>
+#include <slicd/job.h>
+#include <slicd/fields.h>
+#include <slicd/err.h>
+
+extern void _bitarray_clearsetn (register unsigned char *s,
+                                 register unsigned int a,
+                                 register unsigned int b,
+                                 register unsigned int h);
+extern unsigned int _bitarray_firstset_skip (register unsigned char const *s,
+                                             unsigned int max,
+                                             unsigned int skip);
+extern unsigned int _bitarray_firstclear_skip (register unsigned char const *s,
+                                               unsigned int max,
+                                               unsigned int skip);
+
+#define ensure_in_range(field,n) \
+    if (n < 0 || n > _slicd_fields[field].max)  \
+        return -SLICD_ERR_NOT_IN_RANGE
+
+int
+slicd_job_has (slicd_job_t    *job,
+               slicd_field_t   field,
+               int             which)
+{
+    assert (job != NULL);
+    assert (field <= _SLICD_NB_FIELD);
+
+    if (_slicd_fields[field].adjust)
+        --which;
+    ensure_in_range (field, which);
+
+    return bitarray_isset (job->bits, _slicd_fields[field].offset + which);
+}
+
+int
+slicd_job_has_days_combo (slicd_job_t    *job)
+{
+    assert (job != NULL);
+    return bitarray_isset (job->bits, _SLICD_BIT_DAYS_COMBO);
+}
+
+int
+slicd_job_set_days_combo (slicd_job_t    *job,
+                          int             what)
+{
+    assert (job != NULL);
+
+    _bitarray_clearsetn (job->bits, _SLICD_BIT_DAYS_COMBO, 1, what);
+    return 0;
+}
+
+int
+slicd_job_clearset (slicd_job_t    *job,
+                    slicd_field_t   field,
+                    int             from,
+                    int             to,
+                    int             what)
+{
+    assert (job != NULL);
+    assert (field <= _SLICD_NB_FIELD);
+
+    if (_slicd_fields[field].adjust)
+    {
+        --from;
+        --to;
+    }
+    ensure_in_range (field, from);
+    ensure_in_range (field, to);
+    if (to < from)
+        return -SLICD_ERR_INVALID_RANGE;
+
+    _bitarray_clearsetn (job->bits, _slicd_fields[field].offset + from, to - from + 1, what);
+    return 0;
+}
+
+int
+slicd_job_first (slicd_job_t    *job,
+                 slicd_field_t   field,
+                 int             from,
+                 int             to,
+                 int             what)
+{
+    int r;
+
+    assert (job != NULL);
+    assert (field <= _SLICD_NB_FIELD);
+
+    if (_slicd_fields[field].adjust)
+    {
+        --from;
+        --to;
+    }
+    ensure_in_range (field, from);
+    ensure_in_range (field, to);
+    if (to < from)
+        return -SLICD_ERR_INVALID_RANGE;
+
+    from += _slicd_fields[field].offset;
+    to += _slicd_fields[field].offset;
+    if (what)
+        r = _bitarray_firstset_skip (job->bits, to + 1, from);
+    else
+        r = _bitarray_firstclear_skip (job->bits, to + 1, from);
+
+    return _slicd_fields[field].adjust + r - _slicd_fields[field].offset;
+}
diff --git a/src/libslicd/slicd_job_next_run.c b/src/libslicd/slicd_job_next_run.c
new file mode 100644
index 0000000..9860cde
--- /dev/null
+++ b/src/libslicd/slicd_job_next_run.c
@@ -0,0 +1,149 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_job_next_run.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <skalibs/bytestr.h>
+#include <slicd/sched.h>
+
+/* source: http://stackoverflow.com/a/11595914 */
+#define is_leap(year) \
+    ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))
+
+time_t
+slicd_job_next_run (slicd_job_t *job, struct tm *next)
+{
+    int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+    time_t time;
+    int n;
+
+    days[1] = is_leap (next->tm_year) ? 29 : 28;
+
+month:
+    n = slicd_job_first (job, SLICD_MONTHS, next->tm_mon + 1, 12, 1);
+    if (n > 12)
+    {
+bump_year:
+        ++next->tm_year;
+        days[1] = is_leap (next->tm_year) ? 29 : 28;
+        next->tm_mon = 0;
+        next->tm_mday = 1;
+        next->tm_hour = 0;
+        next->tm_min = 0;
+        goto month;
+    }
+    else if (n > next->tm_mon + 1)
+    {
+        next->tm_mon = n - 1;
+        next->tm_mday = 1;
+        next->tm_hour = 0;
+        next->tm_min = 0;
+    }
+
+day:
+    if (slicd_job_has_days_combo (job))
+        goto hour;
+
+    n = slicd_job_first (job, SLICD_DAYS, next->tm_mday, days[next->tm_mon], 1);
+    if (n > days[next->tm_mon])
+    {
+bump_month:
+        if (++next->tm_mon > 11)
+            goto bump_year;
+        next->tm_mday = 1;
+        next->tm_hour = 0;
+        next->tm_min = 0;
+        goto month;
+    }
+    else if (n > next->tm_mday)
+    {
+        next->tm_mday = n;
+        next->tm_hour = 0;
+        next->tm_min = 0;
+    }
+
+hour:
+    n = slicd_job_first (job, SLICD_HOURS, next->tm_hour, 23, 1);
+    if (n > 23)
+    {
+bump_day:
+        if (++next->tm_mday > days[next->tm_mon])
+            goto bump_month;
+        next->tm_hour = 0;
+        next->tm_min = 0;
+        goto day;
+    }
+    else if (n > next->tm_hour)
+    {
+        next->tm_hour = n;
+        next->tm_min = 0;
+    }
+
+/* minute: */
+    n = slicd_job_first (job, SLICD_MINUTES, next->tm_min, 59, 1);
+    if (n > 59)
+    {
+        if (++next->tm_hour > 23)
+            goto bump_day;
+        next->tm_min = 0;
+        goto hour;
+    }
+    else if (n > next->tm_min)
+        next->tm_min = n;
+
+    /* get tm_wday, plus return value on success */
+    time = mktime (next);
+    if (time == (time_t) -1)
+        return time;
+
+    if (!slicd_job_has (job, SLICD_DOW, next->tm_wday))
+        goto bump_day;
+
+    if (slicd_job_has_days_combo (job))
+    {
+        struct tm first;
+        int i;
+
+        byte_copy (&first, sizeof (first), next);
+        first.tm_mday = 1;
+        /* get tm_wday */
+        if (mktime (&first) == (time_t) -1)
+            return (time_t) -1;
+
+        /* let's get first.tm_mday to be the first next->tm_wday of the month */
+        first.tm_mday = next->tm_wday;
+        if (first.tm_mday < first.tm_wday)
+            first.tm_mday += 7;
+        first.tm_mday += 1 - first.tm_wday;
+
+        for (i = 1; i < 6; ++i, first.tm_mday += 7)
+            if (first.tm_mday == next->tm_mday)
+            {
+                if (slicd_job_has (job, SLICD_DAYS, i)
+                        || (first.tm_mday + 7 > days[next->tm_mon]
+                            && slicd_job_has (job, SLICD_DAYS, 6)))
+                    break;
+                else
+                    goto bump_day;
+            }
+    }
+
+    return time;
+}
diff --git a/src/libslicd/slicd_load.c b/src/libslicd/slicd_load.c
new file mode 100644
index 0000000..9ce9d17
--- /dev/null
+++ b/src/libslicd/slicd_load.c
@@ -0,0 +1,117 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_load.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <errno.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/uint32.h>
+#include <skalibs/djbunix.h>
+#include <slicd/slicd.h>
+#include <slicd/err.h>
+
+int
+slicd_load (slicd_t *slicd, const char *file)
+{
+    char buf[4];
+    int fd;
+    int r;
+    uint32 len, left;
+
+    if (slicd->str.len > 0 || slicd->jobs.len > 0)
+        return -SLICD_ERR_NOT_EMPTY;
+
+    fd = open_read (file);
+    if (fd < 0)
+        return -SLICD_ERR_IO;
+
+    len = 0;
+    left = 4;
+    while (left > 0)
+    {
+        r = fd_read (fd, buf + len, left);
+        if (r <= 0)
+        {
+            int e = (r == 0) ? EPROTO : errno;
+
+            fd_close (fd);
+            errno = e;
+            return -SLICD_ERR_IO;
+        }
+        left -= r;
+        len += r;
+    }
+
+    uint32_unpack (buf, &left);
+    if (!stralloc_ready_tuned (&slicd->str, left, 0, 0, 1))
+    {
+        r = -SLICD_ERR_MEMORY;
+        goto err;
+    }
+    while (left > 0)
+    {
+        r = fd_read (fd, slicd->str.s + slicd->str.len, left);
+        if (r <= 0)
+        {
+            if (r == 0)
+                errno = EPROTO;
+            r = -SLICD_ERR_IO;
+            goto err;
+        }
+        slicd->str.len += r;
+        left -= r;
+    }
+
+    for (;;)
+    {
+        if (!stralloc_readyplus (&slicd->jobs, 1024))
+        {
+            r = -SLICD_ERR_MEMORY;
+            goto err;
+        }
+        r = fd_read (fd, slicd->jobs.s + slicd->jobs.len, 1024);
+        if (r < 0)
+        {
+            r = -SLICD_ERR_IO;
+            goto err;
+        }
+        else if (r == 0)
+        {
+            stralloc_shrink (&slicd->jobs);
+            fd_close (fd);
+            return 0;
+        }
+        slicd->jobs.len += r;
+    }
+
+err:
+    {
+        int e = errno;
+
+        slicd->str.len = 0;
+        stralloc_shrink (&slicd->str);
+        slicd->jobs.len = 0;
+        stralloc_shrink (&slicd->jobs);
+
+        fd_close (fd);
+        errno = e;
+        return r;
+    }
+}
diff --git a/src/libslicd/slicd_save.c b/src/libslicd/slicd_save.c
new file mode 100644
index 0000000..ddaf661
--- /dev/null
+++ b/src/libslicd/slicd_save.c
@@ -0,0 +1,61 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd_save.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/uint32.h>
+#include <skalibs/djbunix.h>
+#include <slicd/slicd.h>
+#include <slicd/err.h>
+
+int
+slicd_save (slicd_t *slicd, const char *file)
+{
+    char buf[4];
+    int fd;
+    struct iovec iov[3];
+
+    fd = open3 (file, O_WRONLY | O_NONBLOCK | O_TRUNC | O_CREAT, 0600);
+    if (fd < 0)
+        return -SLICD_ERR_IO;
+
+    uint32_pack (buf, (uint32) slicd->str.len);
+    iov[0].iov_base = buf;
+    iov[0].iov_len = 4;
+    iov[1].iov_base = slicd->str.s;
+    iov[1].iov_len = slicd->str.len;
+    iov[2].iov_base = slicd->jobs.s;
+    iov[2].iov_len = slicd->jobs.len;
+
+    if (fd_writev (fd, iov, 3) < 0)
+    {
+        int e = errno;
+
+        fd_close (fd);
+        errno = e;
+        return -SLICD_ERR_IO;
+    }
+
+    fd_close (fd);
+    return 0;
+}
diff --git a/src/slicd/deps-exe/slicd-exec b/src/slicd/deps-exe/slicd-exec
new file mode 100644
index 0000000..96a227d
--- /dev/null
+++ b/src/slicd/deps-exe/slicd-exec
@@ -0,0 +1,3 @@
+${LIBSLICD}
+-lskarnet
+${TAINOW_LIB}
diff --git a/src/slicd/deps-exe/slicd-parser b/src/slicd/deps-exe/slicd-parser
new file mode 100644
index 0000000..96c7a85
--- /dev/null
+++ b/src/slicd/deps-exe/slicd-parser
@@ -0,0 +1,2 @@
+${LIBSLICD}
+-lskarnet
diff --git a/src/slicd/deps-exe/slicd-sched b/src/slicd/deps-exe/slicd-sched
new file mode 100644
index 0000000..96a227d
--- /dev/null
+++ b/src/slicd/deps-exe/slicd-sched
@@ -0,0 +1,3 @@
+${LIBSLICD}
+-lskarnet
+${TAINOW_LIB}
diff --git a/src/slicd/slicd-exec.c b/src/slicd/slicd-exec.c
new file mode 100644
index 0000000..26742a1
--- /dev/null
+++ b/src/slicd/slicd-exec.c
@@ -0,0 +1,846 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd-exec.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include "slicd/config.h"
+
+#include <getopt.h>
+#include <errno.h>
+#include <skalibs/uint.h>
+#include <skalibs/uint32.h>
+#include <skalibs/sig.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/buffer.h>
+#include <skalibs/iopause.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/tai.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/strerr2.h>
+#include <slicd/die.h>
+
+enum
+{
+    RC_OK = 0,
+    RC_SYNTAX,
+    RC_IO,
+    RC_MEMORY
+};
+
+enum
+{
+    TYPE_DIRECT,
+    TYPE_USERNAME,
+    TYPE_COMBINE,
+    TYPE_COMBINE_LAST,
+    TYPE_PARTIAL,
+    TYPE_PARTIAL_LAST
+};
+
+struct arg
+{
+    unsigned char type;
+    char *str;
+    int len;
+};
+
+struct child
+{
+    pid_t pid;
+    int fd_out;
+    int fd_err;
+    stralloc sa_out;
+    stralloc sa_err;
+};
+
+typedef void (*process_line_fn) (int fd, char *line);
+
+static genalloc ga_iop = GENALLOC_ZERO;
+static genalloc ga_child = GENALLOC_ZERO;
+static int exiting = 0;
+static int looping = 1;
+static stralloc sa_in = STRALLOC_ZERO;
+static struct arg *args;
+static int len_args;
+static int n_args;
+
+#define ga_remove(type, ga, i)     do {         \
+    int len = (ga)->len / sizeof (type);        \
+    int c = len - (i) - 1;                      \
+    if (c > 0)                                  \
+        memmove (genalloc_s (type, (ga)) + (i), genalloc_s (type, (ga)) + (i) + 1, c * sizeof (type)); \
+    genalloc_setlen (type, (ga), len - 1);    \
+} while (0)
+
+static void close_fd (int fd);
+static void process_line (int i_c, int fd, char *line, int len);
+
+static inline void
+start_exiting (void)
+{
+    if (exiting)
+        return;
+    exiting = 1;
+    close_fd (0);
+    if (genalloc_len (struct child, &ga_child) == 0)
+        looping = 0;
+}
+
+static void
+iop_remove_fd (int fd)
+{
+    int i;
+
+    for (i = 0; i < genalloc_len (iopause_fd, &ga_iop); ++i)
+    {
+        if (genalloc_s (iopause_fd, &ga_iop)[i].fd == fd)
+        {
+            ga_remove (iopause_fd, &ga_iop, i);
+            break;
+        }
+    }
+}
+
+static inline int
+get_child_from_fd (int fd)
+{
+    int i;
+
+    for (i = 0; i < genalloc_len (struct child, &ga_child); ++i)
+        if (genalloc_s (struct child, &ga_child)[i].fd_out == fd
+                || genalloc_s (struct child, &ga_child)[i].fd_err == fd)
+            return i;
+    return -1;
+}
+
+static void
+close_fd (int fd)
+{
+
+    fd_close (fd);
+    iop_remove_fd (fd);
+
+    if (fd == 0)
+        start_exiting ();
+    else
+    {
+        struct child *child;
+        int i;
+
+        i = get_child_from_fd (fd);
+        if (i < 0)
+        {
+            char buf[UINT32_FMT];
+            uint32 u;
+
+            u = (uint32) fd;
+            buf[uint32_fmt (buf, u)] = '\0';
+            strerr_warnwu2x ("to find child for fd#", buf);
+            return;
+        }
+        child = &genalloc_s (struct child, &ga_child)[i];
+
+        if (fd == child->fd_out)
+        {
+            if (child->sa_out.len > 0)
+                process_line (i, fd, child->sa_out.s, child->sa_out.len);
+            child->fd_out = -1;
+        }
+        else
+        {
+            if (child->sa_err.len > 0)
+                process_line (i, fd, child->sa_err.s, child->sa_err.len);
+            child->fd_err = -1;
+        }
+    }
+}
+
+static void
+new_child (char *line, int len)
+{
+    char buf[UINT32_FMT];
+    uint32 u;
+    int p_int[2];
+    int p_out[2];
+    int p_err[2];
+    char c;
+    pid_t pid;
+    int n;
+
+    n = byte_chr (line, len, ':');
+    if (n == 0 || n >= len - 1)
+    {
+        strerr_warnw3x ("invalid data on stdin: '", line, "'");
+        return;
+    }
+
+    if (pipecoe (p_int) < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "': failed to set up pipes");
+        return;
+    }
+    if (pipe (p_out) < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "': failed to set up pipes");
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        return;
+    }
+    else if (ndelay_on (p_out[0]) < 0 || coe (p_out[0]) < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "': failed to set up pipes");
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_out[0]);
+        fd_close (p_out[1]);
+        return;
+    }
+    if (pipe (p_err) < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "': failed to set up pipes");
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_out[0]);
+        fd_close (p_out[1]);
+        return;
+    }
+    else if (ndelay_on (p_err[0]) < 0 || coe (p_err[0]) < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "': failed to set up pipes");
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_out[0]);
+        fd_close (p_out[1]);
+        fd_close (p_err[0]);
+        fd_close (p_err[1]);
+        return;
+    }
+
+    pid = fork ();
+    if (pid < 0)
+    {
+        strerr_warnwu3sys ("fork for '", line, "'");
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_out[0]);
+        fd_close (p_out[1]);
+        fd_close (p_err[0]);
+        fd_close (p_err[1]);
+        return;
+    }
+    else if (pid == 0)
+    {
+        char * argv[n_args + 2];
+        stralloc sa = STRALLOC_ZERO;
+        int i;
+
+        PROG = "slicd-exec (child)";
+
+        argv[n_args] = line + n + 1;
+        line[n] = '\0';
+        n = 0;
+        for (i = 0; i < len_args; ++i)
+        {
+            switch (args[i].type)
+            {
+                case TYPE_DIRECT:
+                    argv[n] = args[i].str;
+                    ++n;
+                    break;
+
+                case TYPE_USERNAME:
+                    argv[n] = line;
+                    ++n;
+                    break;
+
+                case TYPE_COMBINE:
+                case TYPE_COMBINE_LAST:
+                    stralloc_catb (&sa, args[i].str, args[i].len);
+                    stralloc_cats (&sa, line);
+                    if (args[i].type == TYPE_COMBINE_LAST)
+                        ++n;
+                    break;
+
+                case TYPE_PARTIAL:
+                case TYPE_PARTIAL_LAST:
+                    stralloc_catb (&sa, args[i].str, args[i].len);
+                    if (args[i].type == TYPE_PARTIAL_LAST)
+                        ++n;
+                    break;
+            }
+        }
+        argv[++n] = NULL;
+
+        selfpipe_finish ();
+        fd_close (p_int[0]);
+        fd_close (p_out[0]);
+        fd_close (p_err[0]);
+
+        fd_close (1);
+        fd_close (2);
+        if (fd_move2 (1, p_out[1], 2, p_err[1]) < 0)
+        {
+            u = (uint32) errno;
+            fd_write (p_int[1], "p", 1);
+            uint32_pack (buf, u);
+            fd_write (p_int[1], buf, 4);
+            errno = (int) u;
+            strerr_diefu1sys (RC_IO, "set up pipes");
+        }
+
+        pathexec ((char const * const *) argv);
+        /* if it fails... */
+        u = (uint32) errno;
+        fd_write (p_int[1], "e", 1);
+        uint32_pack (buf, u);
+        fd_write (p_int[1], buf, 4);
+        errno = (int) u;
+        strerr_dieexec (RC_IO, argv[0]);
+    }
+
+    {
+        struct child child = { 0, };
+        iopause_fd iop;
+
+        child.pid = pid;
+        child.fd_out = p_out[0];
+        child.fd_err = p_err[0];
+        genalloc_append (struct child, &ga_child, &child);
+
+        iop.fd = child.fd_out;
+        iop.events = IOPAUSE_READ;
+        genalloc_append (iopause_fd, &ga_iop, &iop);
+
+        iop.fd = child.fd_err;
+        iop.events = IOPAUSE_READ;
+        genalloc_append (iopause_fd, &ga_iop, &iop);
+    }
+
+    buffer_putsnoflush (buffer_1small, "Forked PID#");
+    u = (uint32) pid;
+    buf[uint32_fmt (buf, u)] = '\0';
+    buffer_putsnoflush (buffer_1small, buf);
+    buffer_putsnoflush (buffer_1small, " for job '");
+    buffer_putsnoflush (buffer_1small, line);
+    buffer_putsflush (buffer_1small, "'\n");
+
+    fd_close (p_int[1]);
+    fd_close (p_out[1]);
+    fd_close (p_err[1]);
+    switch (fd_read (p_int[0], &c, 1))
+    {
+        case 0:     /* it worked */
+            break;
+
+        case 1:     /* child failed to exec */
+            {
+                char b[4];
+                uint32 e = 0;
+
+                if (fd_read (p_int[0], b, 4) == 4)
+                    uint32_unpack (b, &e);
+
+                if (e > 0)
+                {
+                    errno = e;
+                    strerr_warnw4sys ("PID#", buf, ": failed to ",
+                            (c == 'p') ? "set up pipes" : "exec");
+                }
+                else
+                    strerr_warnw4x ("PID#", buf, ": failed to ",
+                            (c == 'p') ? "set up pipes" : "exec");
+            }
+            break;
+
+        case -1:    /* internal failure */
+            strerr_warnw3sys ("PID#", buf, ": failed to read internal pipe");
+            break;
+    }
+    fd_close (p_int[0]);
+}
+
+static void
+process_line (int i_c, int fd, char *line, int len)
+{
+    struct child *child = &genalloc_s (struct child, &ga_child)[i_c];
+    char buf[UINT32_FMT];
+    uint32 u;
+
+    u = (uint32) child->pid;
+    buf[uint32_fmt (buf, u)] = '\0';
+
+    buffer_putsnoflush (buffer_1small, "PID#");
+    buffer_putsnoflush (buffer_1small, buf);
+    buffer_putsnoflush (buffer_1small, " ");
+    buffer_putsnoflush (buffer_1small, (fd == child->fd_out) ? "stdout" : "stderr");
+    buffer_putsnoflush (buffer_1small, ": ");
+    buffer_putnoflush (buffer_1small, line, len);
+    buffer_putsflush (buffer_1small, "\n");
+}
+
+static void
+handle_fd (int fd, int all)
+{
+    stralloc *sa;
+    int i_c = 0; /* to silence warning */
+    int r;
+    int close = 0;
+
+    if (fd == 0)
+        sa = &sa_in;
+    else
+    {
+        i_c = get_child_from_fd (fd);
+        if (i_c < 0)
+        {
+            char buf[UINT32_FMT];
+            uint32 u;
+
+            u = (uint32) fd;
+            buf[uint32_fmt (buf, u)] = '\0';
+            strerr_warnwu3x ("to find child for fd#", buf, "; closing");
+
+            fd_close (fd);
+            iop_remove_fd (fd);
+            return;
+        }
+        if (fd == genalloc_s (struct child, &ga_child)[i_c].fd_out)
+            sa = &genalloc_s (struct child, &ga_child)[i_c].sa_out;
+        else
+            sa = &genalloc_s (struct child, &ga_child)[i_c].sa_err;
+    }
+
+    for (;;)
+    {
+        if (!stralloc_readyplus (sa, 1024))
+            strerr_diefu1sys (RC_MEMORY, "allocate memory");
+
+        r = sanitize_read (fd_read (fd, sa->s + sa->len, 1024));
+        if (r < 0)
+        {
+            if (errno != EPIPE)
+            {
+                char buf[UINT32_FMT];
+                uint32 u;
+                int e = errno;
+
+                close_fd (fd);
+                errno = e;
+
+                u = (uint32) fd;
+                buf[uint32_fmt (buf, u)] = '\0';
+                if (fd == 0)
+                    strerr_warnwu1sys ("read from stdin");
+                else
+                {
+                    struct child *child = &genalloc_s (struct child, &ga_child)[i_c];
+                    char buf[UINT32_FMT];
+                    uint32 u;
+
+                    u = (uint32) child->pid;
+                    buf[uint32_fmt (buf, u)] = '\0';
+                    strerr_warnwu4sys ("read from ",
+                            (fd == child->fd_out) ? "stdout" : "stderr",
+                            " of PID#", buf);
+                }
+                return;
+            }
+            close = 1;
+        }
+        else
+            sa->len += r;
+
+        while (sa->len > 0)
+        {
+            int l;
+
+            r = byte_chr (sa->s, sa->len, '\n');
+            if (r == sa->len)
+                break;
+
+            sa->s[r] = '\0';
+            if (fd == 0)
+                new_child (sa->s, r);
+            else
+                process_line (i_c, fd, sa->s, r);
+            l = sa->len - r - 1;
+            if (l > 0)
+                byte_copy (sa->s, l, sa->s + r + 1);
+            sa->len = l;
+        }
+
+        if (!all || close)
+            break;
+    }
+
+    if (close)
+        close_fd (fd);
+}
+
+static inline void
+handle_sigchld (void)
+{
+    for (;;)
+    {
+        struct child *child;
+        char buf[20 + UINT32_FMT];
+        uint32 u;
+        int wstat;
+        int r;
+        int i;
+
+        r = wait_nohang (&wstat);
+        if (r < 0)
+        {
+            if (errno != ECHILD)
+                strerr_warnwu1sys ("reap zombies (waitpid)");
+            break;
+        }
+        else if (r == 0)
+            break;
+
+        u = (uint32) r;
+        buf[20 + uint32_fmt (buf + 20, u)] = '\0';
+
+        for (i = 0; i < genalloc_len (struct child, &ga_child); ++i)
+            if (genalloc_s (struct child, &ga_child)[i].pid == r)
+                break;
+        if (i >= genalloc_len (struct child, &ga_child))
+        {
+            strerr_warnwu2x ("find child for reaped PID#", buf + 20);
+            continue;
+        }
+        child = &genalloc_s (struct child, &ga_child)[i];
+
+        if (child->fd_out > 0)
+            handle_fd (child->fd_out, 1);
+        if (child->fd_err > 0)
+            handle_fd (child->fd_err, 1);
+        stralloc_free (&child->sa_out);
+        stralloc_free (&child->sa_err);
+        ga_remove (struct child, &ga_child, i);
+
+        if (WIFEXITED (wstat))
+        {
+            byte_copy (buf, 9, "exitcode ");
+            buf[9 + uint32_fmt (buf + 9, (uint32) WEXITSTATUS (wstat))] = '\0';
+        }
+        else
+        {
+            const char *name;
+
+            name = sig_name (WTERMSIG (wstat));
+            byte_copy (buf, 10, "signal SIG");
+            byte_copy (buf + 10, strlen (name) + 1, name);
+        }
+
+        buffer_putsnoflush (buffer_1small, "PID#");
+        buffer_putsnoflush (buffer_1small, buf + 20);
+        buffer_putsnoflush (buffer_1small, " reaped, ");
+        buffer_putsnoflush (buffer_1small, buf);
+        buffer_putsflush (buffer_1small, "\n");
+    }
+
+    if (exiting && genalloc_len (struct child, &ga_child) == 0)
+        looping = 0;
+}
+
+static void
+handle_signals (void)
+{
+    /* signals */
+    for (;;)
+    {
+        char c;
+
+        c = selfpipe_read ();
+        switch (c)
+        {
+            case -1:
+                strerr_diefu1sys (RC_IO, "selfpipe_read");
+
+            case 0:
+                return;
+
+            case SIGINT:
+            case SIGTERM:
+                start_exiting ();
+                break;
+
+            case SIGCHLD:
+                handle_sigchld ();
+                break;
+
+            default:
+                strerr_dief1x (RC_IO, "internal error: invalid selfpipe_read value");
+        }
+    }
+    return;
+}
+
+static int
+parse_arg (struct arg *arg, char *s, int len, int in_arg)
+{
+    int r;
+
+    r = byte_chr (s, len, '%');
+    if (r >= len - 1)
+    {
+        if (!in_arg)
+            arg->type = TYPE_DIRECT;
+        else
+        {
+            arg->type = TYPE_PARTIAL_LAST;
+            arg->len = len;
+        }
+        arg->str = s;
+    }
+    else if (r == 0 && len == 2 && s[1] == 'u')
+    {
+        if (!in_arg)
+            arg->type = TYPE_USERNAME;
+        else
+        {
+            arg->type = TYPE_COMBINE_LAST;
+            arg->str = s;
+            arg->len = r;
+        }
+    }
+    else if (r == len - 2)
+    {
+        if (in_arg || (s[r + 1] == 'u' || s[r + 1] == '%'))
+        {
+            if (s[r + 1] == 'u')
+            {
+                arg->type = TYPE_COMBINE_LAST;
+                arg->len = r;
+            }
+            else
+            {
+                arg->type = TYPE_PARTIAL_LAST;
+                arg->len = r + ((!in_arg || s[r + 1] == '%') ? 1 : 2);
+            }
+        }
+        else
+            arg->type = TYPE_DIRECT;
+
+        arg->str = s;
+    }
+    else
+    {
+        if (s[r + 1] == 'u')
+        {
+            arg->type = TYPE_COMBINE;
+            arg->len = r;
+        }
+        else
+        {
+            arg->type = TYPE_PARTIAL;
+            arg->len = r + ((s[r + 1] == '%') ? 1 : 2);
+        }
+
+        arg->str = s;
+        return r + 2;
+    }
+
+    return 0;
+}
+
+static void
+dieusage (int rc)
+{
+    slicd_die_usage (rc, "[OPTION...] ARG...",
+            " -R, --ready-fd FD             Prints LF ('\\n') on FD once ready\n"
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "slicd-exec";
+    unsigned int fd_ready = 0;
+    int r;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "ready-fd",           required_argument,  NULL,   'R' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "hR:V", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'h':
+                dieusage (RC_OK);
+
+            case 'R':
+                if (!uint0_scan (optarg, &fd_ready) || fd_ready <= 2)
+                    dieusage (RC_SYNTAX);
+                break;
+
+            case 'V':
+                slicd_die_version ();
+
+            default:
+                dieusage (RC_SYNTAX);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc == 0)
+        dieusage (RC_SYNTAX);
+
+    {
+        iopause_fd iop;
+        sigset_t set;
+
+        iop.fd = selfpipe_init ();
+        if (iop.fd < 0)
+            strerr_diefu1sys (RC_IO, "init selfpipe");
+        iop.events = IOPAUSE_READ;
+        genalloc_append (iopause_fd, &ga_iop, &iop);
+
+        sigemptyset (&set);
+        sigaddset (&set, SIGTERM);
+        sigaddset (&set, SIGINT);
+        sigaddset (&set, SIGCHLD);
+        if (selfpipe_trapset (&set) < 0)
+            strerr_diefu1sys (RC_IO, "trap signals");
+
+        iop.fd = 0;
+        iop.events = IOPAUSE_READ;
+        genalloc_append (iopause_fd, &ga_iop, &iop);
+
+        if (ndelay_on (0) < 0)
+            strerr_diefu1sys (RC_IO, "set stdin non-blocking");
+    }
+
+    tain_now_g ();
+
+    {
+        struct arg _args[argc];
+        genalloc ga_args = GENALLOC_ZERO;
+        int in_arg = 0;
+        int i;
+
+        n_args = argc;
+        args = _args;
+        for (i = 0; i < argc; ++i)
+        {
+            struct arg arg;
+            char *s = argv[i];
+            int len = strlen (s);
+
+again:
+            r = parse_arg (&arg, s, len, in_arg);
+
+            if (arg.type != TYPE_COMBINE && arg.type != TYPE_PARTIAL)
+            {
+                in_arg = 0;
+                if (args)
+                    byte_copy (&_args[i], sizeof (struct arg), &arg);
+                else
+                    genalloc_append (struct arg, &ga_args, &arg);
+                continue;
+            }
+
+            in_arg = 1;
+            if (args)
+            {
+                if (!genalloc_ready (struct arg, &ga_args, argc + 1))
+                    strerr_diefu1sys (RC_MEMORY, "allocate memory parsing args");
+                if (i > 0)
+                    byte_copy (ga_args.s, sizeof (struct arg) * i, args);
+                genalloc_setlen (struct arg, &ga_args, i);
+
+                args = NULL;
+            }
+
+            genalloc_append (struct arg, &ga_args, &arg);
+
+            s += r;
+            len -= r;
+            goto again;
+        }
+        if (args)
+            len_args = argc;
+        else
+        {
+            args = genalloc_s (struct arg, &ga_args);
+            len_args = genalloc_len (struct arg, &ga_args);
+        }
+
+        if (fd_ready > 0)
+        {
+            if (fd_write (fd_ready, "\n", 1) < 0)
+                strerr_warnwu1sys ("announce readiness");
+            fd_close (fd_ready);
+        }
+
+        while (looping)
+        {
+            r = iopause_g (genalloc_s (iopause_fd, &ga_iop),
+                    genalloc_len (iopause_fd, &ga_iop), NULL);
+            if (r < 0)
+                strerr_diefu1sys (RC_IO, "iopause");
+
+            for (i = genalloc_len (iopause_fd, &ga_iop) - 1; i > 0; --i)
+            {
+                iopause_fd *iop = &genalloc_s (iopause_fd, &ga_iop)[i];
+
+                if (iop->revents & IOPAUSE_READ)
+                    handle_fd (iop->fd, 0);
+                else if (iop->revents & IOPAUSE_EXCEPT)
+                    close_fd (iop->fd);
+            }
+
+            if (genalloc_s (iopause_fd, &ga_iop)[0].revents & IOPAUSE_READ)
+                handle_signals ();
+            else if (genalloc_s (iopause_fd, &ga_iop)[0].revents & IOPAUSE_EXCEPT)
+                strerr_dief1sys (RC_IO, "trouble with selfpipe");
+        }
+
+        genalloc_free (struct arg, &ga_args);
+    }
+
+    stralloc_free (&sa_in);
+    genalloc_free (iopause_fd, &ga_iop);
+    genalloc_free (struct child, &ga_child);
+
+    return RC_OK;
+}
diff --git a/src/slicd/slicd-parser.c b/src/slicd/slicd-parser.c
new file mode 100644
index 0000000..9653394
--- /dev/null
+++ b/src/slicd/slicd-parser.c
@@ -0,0 +1,394 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd-parser.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#define _BSD_SOURCE
+
+#include "slicd/config.h"
+
+#include <getopt.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/direntry.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <slicd/slicd.h>
+#include <slicd/parser.h>
+#include <slicd/err.h>
+#include <slicd/die.h>
+
+enum
+{
+    RC_OK = 0,
+    RC_SYNTAX,
+    RC_IO,
+    RC_MEMORY,
+    RC_NO_CRONTABS,
+    RC_NO_OUTPUT,
+    RC_ERRORS
+};
+
+enum type
+{
+    TYPE_SYSTEM = 0,
+    TYPE_USER,
+    TYPE_USER_DIR
+};
+
+struct crontab
+{
+    enum type type;
+    const char *path;
+};
+
+static int rc = RC_OK;
+
+typedef void (*scandir_cb) (const char *path, const char *name, void *data);
+static void scan_dir (const char *path, int files, scandir_cb callback, void *data);
+
+static const char *userfile = "";
+static stralloc sa = STRALLOC_ZERO;
+
+static slicd_t slicd = { 0, };
+
+static void
+do_parse (const char *user, const char *file, unsigned int *line, int eof)
+{
+    char *s = sa.s;
+    int len = sa.len;
+
+    for (;;)
+    {
+        int l;
+        int r;
+
+        if (len <= 0 || eof == 2)
+            break;
+
+        l = byte_chr (s, len, '\n');
+        if (l == len)
+        {
+            if (!eof)
+                break;
+            eof = 2;
+        }
+        else
+            s[l] = '\0';
+
+        r = slicd_add_job_from_cronline (&slicd, user, s);
+        if (r < 0)
+        {
+            char buf[UINT_FMT];
+
+            buf[uint_fmt (buf, *line)] = '\0';
+            if (-r == SLICD_ERR_MEMORY)
+                strerr_diefu6sys (RC_MEMORY, "parse '", file, "' line ", buf, ": ",
+                        slicd_errmsg[-r]);
+            else
+            {
+                rc = RC_ERRORS;
+                strerr_warnwu6x ("parse '", file, "' line ", buf, ": ", slicd_errmsg[-r]);
+            }
+        }
+
+        ++*line;
+        s += l + 1;
+        len -= l + 1;
+    }
+
+    if (s > sa.s)
+    {
+        if (len > 0)
+            byte_copy (sa.s, len, s);
+        else
+            len = 0;
+        sa.len = len;
+    }
+}
+
+static void
+parse_file (const char *path, const char *name, void *data)
+{
+    const char *username = data;
+    int l_path = strlen (path);
+    int l_name = strlen (name);
+    char buf[l_path + 1 + l_name + 1];
+    int fd;
+    int r;
+    unsigned int line = 1;
+
+    if (username == userfile)
+        username = name;
+
+    byte_copy (buf, l_path, path);
+    buf[l_path] = '/';
+    byte_copy (buf + l_path + 1, l_name + 1, name);
+
+    fd = open_read (buf);
+    if (fd < 0)
+        strerr_diefu3sys (RC_IO, "open file '", buf, "'");
+
+    for (;;)
+    {
+        if (!stralloc_readyplus (&sa, 1024))
+        {
+            int e = errno;
+
+            fd_close (fd);
+            errno = e;
+            strerr_diefu3sys (RC_MEMORY, "allocate memory while reading '", buf, "'");
+        }
+
+        r = fd_read (fd, sa.s + sa.len, 1024);
+        if (r < 0)
+        {
+            int e = errno;
+
+            fd_close (fd);
+            errno = e;
+            strerr_diefu3sys (RC_IO, "read '", buf, "'");
+        }
+        else if (r == 0)
+        {
+            fd_close (fd);
+            do_parse (username, buf, &line, 1);
+            return;
+        }
+        sa.len += r;
+
+        do_parse (username, buf, &line, 0);
+    }
+}
+
+static void
+scan_userdirs (const char *path, const char *name, void *data)
+{
+    int l_path = strlen (path);
+    int l_name = strlen (name);
+    char buf[l_path + 1 + l_name + 1];
+
+    byte_copy (buf, l_path, path);
+    buf[l_path] = '/';
+    byte_copy (buf + l_path + 1, l_name + 1, name);
+
+    scan_dir (buf, 1, parse_file, (void *) name);
+}
+
+static void
+scan_dir (const char *path, int files, scandir_cb callback, void *data)
+{
+    DIR *dir;
+    int e = 0;
+
+    dir = opendir (path);
+    if (!dir)
+        strerr_diefu3sys (RC_IO, "open directory '", path, "'");
+
+    for (;;)
+    {
+        direntry *d;
+
+        errno = 0;
+        d = readdir (dir);
+        if (!d)
+        {
+            e = errno;
+            break;
+        }
+        if (d->d_name[0] == '.'
+                && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+            continue;
+        if ((files && d->d_type != DT_REG) || (!files && d->d_type != DT_DIR))
+            continue;
+
+        callback (path, d->d_name, data);
+    }
+    dir_close (dir);
+
+    if (e > 0)
+    {
+        errno = e;
+        strerr_diefu3sys (RC_IO, "read directory '", path, "'");
+    }
+}
+
+static void
+dieusage (int rc)
+{
+    slicd_die_usage (rc, "OPTION...",
+            " -s, --system FILE             Parse FILE for system crontabs\n"
+            " -u, --users DIR               Parse DIR for user crontabs\n"
+            " -U, --users-dirs DIR          Parse DIR for directories of user crontabs\n"
+            " -o, --output FILE             Write compiled crontabs to FILE\n"
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "slicd-parser";
+    const char *output = NULL;
+    struct crontab crontab[argc / 2];
+    int nb = 0;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "output",             required_argument,  NULL,   'o' },
+            { "system",             required_argument,  NULL,   's' },
+            { "users-dirs",         required_argument,  NULL,   'U' },
+            { "users",              required_argument,  NULL,   'u' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "ho:s:U:u:V", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'h':
+                dieusage (RC_OK);
+
+            case 'o':
+                output = optarg;
+                break;
+
+            case 's':
+                crontab[nb].type = TYPE_SYSTEM;
+                crontab[nb].path = optarg;
+                ++nb;
+                break;
+
+            case 'U':
+                crontab[nb].type = TYPE_USER_DIR;
+                crontab[nb].path = optarg;
+                ++nb;
+                break;
+
+            case 'u':
+                crontab[nb].type = TYPE_USER;
+                crontab[nb].path = optarg;
+                ++nb;
+                break;
+
+            case 'V':
+                slicd_die_version ();
+
+            default:
+                dieusage (RC_SYNTAX);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc > 0)
+        dieusage (RC_SYNTAX);
+    if (nb == 0)
+        strerr_dief1x (RC_NO_CRONTABS, "no crontabs specified");
+    if (!output)
+        strerr_dief1x (RC_NO_OUTPUT, "no output file specified");
+
+    {
+        int i;
+
+        for (i = 0; i < nb; ++i)
+        {
+            struct stat st;
+
+            if (stat (crontab[i].path, &st) < 0)
+            {
+                rc = RC_ERRORS;
+                strerr_warnwu3sys ("stat '", crontab[i].path, "'");
+                continue;
+            }
+
+            switch (crontab[i].type)
+            {
+                case TYPE_SYSTEM:
+                    if (S_ISREG (st.st_mode))
+                    {
+                        int len = strlen (crontab[i].path);
+                        char buf[len + 1];
+                        int l;
+
+                        byte_copy (buf, len + 1, crontab[i].path);
+
+                        l = byte_rchr (buf, len, '/');
+                        if (l == len)
+                            l = 0;
+                        else
+                            buf[l++] = '\0';
+
+                        parse_file ((l) ? buf : ".", buf + l, NULL);
+                    }
+                    else if (S_ISDIR (st.st_mode))
+                        scan_dir (crontab[i].path, 1, parse_file, NULL);
+                    else
+                    {
+                        rc = RC_ERRORS;
+                        strerr_warnwu3x ("cannot parse system crontab(s) '",
+                                crontab[i].path, "': not a file nor directory");
+                        continue;
+                    }
+                    break;
+
+                case TYPE_USER:
+                    if (!S_ISDIR (st.st_mode))
+                    {
+                        rc = RC_ERRORS;
+                        strerr_warnwu3x ("cannot parse user crontabs from '",
+                                crontab[i].path, "': not a directory");
+                        continue;
+                    }
+                    scan_dir (crontab[i].path, 1, parse_file, (void *) userfile);
+                    break;
+
+                case TYPE_USER_DIR:
+                    if (!S_ISDIR (st.st_mode))
+                    {
+                        rc = RC_ERRORS;
+                        strerr_warnwu3x ("cannot parse user crontabs from '",
+                                crontab[i].path, "': not a directory");
+                        continue;
+                    }
+                    scan_dir (crontab[i].path, 0, scan_userdirs, NULL);
+                    break;
+            }
+        }
+    }
+
+    if (rc != RC_OK)
+        strerr_dief1x (rc, "Errors occured; Aborting");
+    if (slicd_save (&slicd, output) < 0)
+        strerr_diefu3sys (RC_IO, "save compiled crontabs to '", output, "'");
+
+    stralloc_free (&sa);
+    return rc;
+}
diff --git a/src/slicd/slicd-sched.c b/src/slicd/slicd-sched.c
new file mode 100644
index 0000000..1069929
--- /dev/null
+++ b/src/slicd/slicd-sched.c
@@ -0,0 +1,340 @@
+/*
+ * slicd - Copyright (C) 2015 Olivier Brunel
+ *
+ * slicd-sched.c
+ * Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>
+ *
+ * This file is part of slicd.
+ *
+ * slicd is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * slicd 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * slicd. If not, see http://www.gnu.org/licenses/
+ */
+
+#include "slicd/config.h"
+
+#include <getopt.h>
+#include <errno.h>
+#include <sys/timerfd.h>
+#include <skalibs/uint.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/tai.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <slicd/slicd.h>
+#include <slicd/sched.h>
+#include <slicd/err.h>
+#include <slicd/die.h>
+
+enum
+{
+    RC_OK = 0,
+    RC_SYNTAX,
+    RC_IO,
+    RC_MEMORY,
+    RC_NO_CRONTABS,
+    RC_OTHER
+};
+
+enum
+{
+    DO_NOTHING = 0,
+    DO_EXIT,
+    DO_RELOAD,
+    DO_RESET
+};
+
+static slicd_t slicd = { 0, };
+
+#define job_str(job)        (slicd.str.s + job->offset)
+
+static int
+handle_signals (const char *file)
+{
+    /* signals */
+    for (;;)
+    {
+        char c;
+
+        c = selfpipe_read ();
+        switch (c)
+        {
+            case -1:
+                strerr_diefu1sys (RC_IO, "selfpipe_read");
+
+            case 0:
+                break;
+
+            case SIGHUP:
+                {
+                    slicd_t new = { 0, };
+                    int r;
+
+                    r = slicd_load (&new, file);
+                    if (r < 0)
+                        strerr_warnwu3sys ("reload compiled crontabs from '", file, "'");
+                    else
+                    {
+                        slicd_free (&slicd);
+                        byte_copy (&slicd, sizeof (slicd), &new);
+                        strerr_warn3x ("Compiled crontabs reloaded from '", file, "'");
+                        return DO_RELOAD;
+                    }
+                }
+                break;
+
+            case SIGUSR1:
+                return DO_RESET;
+
+            case SIGINT:
+            case SIGTERM:
+                return DO_EXIT;
+
+            default:
+                strerr_dief1x (RC_IO, "internal error: invalid selfpipe_read value");
+        }
+    }
+    return DO_NOTHING;
+}
+
+static void
+dieusage (int rc)
+{
+    slicd_die_usage (rc, "[OPTION...] FILE",
+            " -R, --ready-fd FD             Prints LF ('\\n') on FD once ready\n"
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "slicd-sched";
+    struct itimerspec its = { 0, };
+    iopause_fd iop[2];
+    sigset_t set;
+    unsigned int fd_ready = 0;
+    int r;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "ready-fd",           required_argument,  NULL,   'R' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "hR:V", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'h':
+                dieusage (RC_OK);
+
+            case 'R':
+                if (!uint0_scan (optarg, &fd_ready) || fd_ready <= 2)
+                    dieusage (RC_SYNTAX);
+                break;
+
+            case 'V':
+                slicd_die_version ();
+
+            default:
+                dieusage (RC_SYNTAX);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc != 1)
+        dieusage (RC_SYNTAX);
+
+    r = slicd_load (&slicd, argv[0]);
+    if (r < 0)
+        strerr_warnwu3sys ("load compiled crontabs from '", argv[0], "'");
+
+    if (ndelay_on (1) < 0)
+        strerr_diefu1sys (RC_IO, "set stdout non-blocking");
+
+    iop[0].fd = selfpipe_init ();
+    if (iop[0].fd < 0)
+        strerr_diefu1sys (RC_IO, "init selfpipe");
+    iop[0].events = IOPAUSE_READ;
+
+    sigemptyset (&set);
+    sigaddset (&set, SIGTERM);
+    sigaddset (&set, SIGINT);
+    sigaddset (&set, SIGHUP);
+    sigaddset (&set, SIGUSR1);
+    if (selfpipe_trapset (&set) < 0)
+        strerr_diefu1sys (RC_IO, "trap signals");
+
+    iop[1].fd = timerfd_create (CLOCK_REALTIME, TFD_NONBLOCK);
+    if (iop[1].fd < 0)
+        strerr_diefu1sys (RC_OTHER, "create timerfd");
+    iop[1].events = IOPAUSE_READ;
+    /* force processing jobs before the first iopause */
+    iop[1].revents = IOPAUSE_READ;
+
+    tain_now_g ();
+
+    if (fd_ready > 0)
+    {
+        if (fd_write (fd_ready, "\n", 1) < 0)
+            strerr_warnwu1sys ("announce readiness");
+        fd_close (fd_ready);
+    }
+
+    for (;;)
+    {
+        if (iop[1].revents & IOPAUSE_READ)
+        {
+            struct tm cur_tm, next_tm, since_tm, *tm;
+            time_t cur_time, next_time, since_time;
+            int i;
+
+            if (clock_gettime (CLOCK_REALTIME, &its.it_interval) < 0)
+                strerr_diefu1sys (RC_OTHER, "get current time");
+            cur_time = its.it_interval.tv_sec;
+            its.it_interval.tv_sec = 0;
+            its.it_interval.tv_nsec = 0;
+
+            tm = localtime (&cur_time);
+            if (!tm)
+                strerr_diefu1x (RC_OTHER, "break down time");
+            byte_copy (&cur_tm, sizeof (cur_tm), tm);
+            cur_time -= cur_tm.tm_sec;
+            cur_tm.tm_sec = 0;
+
+            since_time = cur_time;
+            byte_copy (&since_tm, sizeof (since_tm), &cur_tm);
+
+            /* is there a timerfd set? i.e. did we wait for some time, if so we
+             * might need to handle some time changes.
+             * Any time change shouldn't affect us, since our timerfd is based
+             * on CLOCK_REALTIME and simply "adjust" as needed. But, time might
+             * have jumped forwad and we "missed" our deadline...
+             * We'll also account for the case of the time going back right
+             * after our timerfd expired, just in case.
+             */
+            if (its.it_value.tv_sec > 0)
+            {
+                /* post-DO_RELOAD, wait for next minute to avoid rerunning jobs */
+                if (its.it_value.tv_nsec > 0)
+                {
+                    its.it_value.tv_nsec = 0;
+                    next_time = cur_time + 60;
+                    goto timer;
+                }
+                else if (cur_time > its.it_value.tv_sec)
+                {
+                    /* we're ahead of the plan, we need to trigger anything that
+                     * we "missed" */
+                    since_time = its.it_value.tv_sec;
+                    tm = localtime (&since_time);
+                    if (!tm)
+                        strerr_diefu1x (RC_OTHER, "break down time");
+                    byte_copy (&since_tm, sizeof (since_tm), tm);
+                }
+                else if (cur_time < its.it_value.tv_sec)
+                {
+                    /* we're early somehow; just wait it out... */
+                    goto pause;
+                }
+            }
+
+            next_time = (time_t) -1;
+            for (i = 0; i < genalloc_len (slicd_job_t, &slicd.jobs); ++i)
+            {
+                slicd_job_t *job = &genalloc_s (slicd_job_t, &slicd.jobs)[i];
+                struct tm job_tm;
+                time_t job_time;
+
+                byte_copy (&job_tm, sizeof (job_tm), &since_tm);
+next_run:
+                job_time = slicd_job_next_run (job, &job_tm);
+                if (job_time == (time_t) -1)
+                {
+                    strerr_warnwu3x ("get next run for job '", job_str (job), "'");
+                    continue;
+                }
+
+                if (job_time <= cur_time)
+                {
+                    buffer_puts (buffer_1small, job_str (job));
+                    if (!buffer_putsflush (buffer_1small, "\n"))
+                        strerr_diefu1sys (RC_IO, "write to stdout");
+
+                    /* now calculate the "actual" next run, using cur_tm in case
+                     * since_tm was set earlier (time jump) */
+                    byte_copy (&job_tm, sizeof (job_tm), &cur_tm);
+                    ++job_tm.tm_min;
+                    /* call mktime() to update job_tm, as one more minute
+                     * might be a new hour, day, etc */
+                    if (mktime (&job_tm) == (time_t) -1)
+                    {
+                        strerr_warnwu3x ("get next run for job '", job_str (job), "'");
+                        continue;
+                    }
+                    goto next_run;
+                }
+                else if (next_time == (time_t) -1 || job_time < next_time)
+                {
+                    byte_copy (&next_tm, sizeof (next_tm), &job_tm);
+                    next_time = job_time;
+                }
+            }
+
+timer:
+            its.it_value.tv_sec = (next_time == (time_t) -1) ? 0 : next_time;
+            r = timerfd_settime (iop[1].fd, TFD_TIMER_ABSTIME, &its, NULL);
+            if (r < 0)
+                strerr_diefu1sys (RC_OTHER, "set timerfd");
+        }
+        else if (iop[1].revents & IOPAUSE_EXCEPT)
+            strerr_dief1sys (RC_IO, "trouble with timerfd");
+
+pause:
+        r = iopause_g (iop, 2, NULL);
+        if (r < 0)
+            strerr_diefu1sys (RC_IO, "iopause");
+
+        if (iop[0].revents & IOPAUSE_READ)
+        {
+            r = handle_signals (argv[0]);
+            if (r == DO_RELOAD)
+            {
+                iop[1].revents = IOPAUSE_READ;
+                its.it_value.tv_sec = 1;
+                its.it_value.tv_nsec = 1;
+            }
+            else if (r == DO_RESET)
+            {
+                iop[1].revents = IOPAUSE_READ;
+                its.it_value.tv_sec = 0;
+            }
+            else if (r == DO_EXIT)
+                break;
+        }
+        else if (iop[0].revents & IOPAUSE_EXCEPT)
+            strerr_dief1sys (RC_IO, "trouble with selfpipe");
+    }
+
+    slicd_free (&slicd);
+    return RC_OK;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..82fe633
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,87 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    while read dep ; do
+      deps="$deps src/$dir/$dep"
+    done < src/$dir/deps-lib/$file
+    echo "lib$file.a: $deps"
+    echo "lib${file}.so: $(echo "$deps" | sed 's/\.o/.lo/g')"
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      if echo $dep | grep -q '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps $dep"
+      fi
+    done < src/$dir/deps-exe/$file
+    echo "$file: private EXTRA_LIBS :=$libs"
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
+
+for file in $(ls -1 src/scripts) ; do
+    echo "$file: src/scripts/$file"
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi