diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 00000000..e9121e26
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,51 @@
+# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+#
+# This file is part of CLICON.
+#
+# CLICON 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.
+#
+# CLICON 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 CLICON; see the file COPYING. If not, see
+# .
+
+- New option: CLICON_DB_XML with new xml-based database. This makes more efficient
+ implementation of yang-based applications.
+- Changed yang type lookup to be lexically instead of dynamically scoped (as
+ they should) by using a cache (yang_type_cache).
+- Added support for yang 'max' range keyword
+- Added option: CLICON_YANG_MODULE_REVISION
+- Cleaned up xml translation functions in clicon_xml_map.c
+- Extended client/backend protocol with dumping a database config: CLICON_MSG_DBITEMS
+- Removed -a (appdir) config options. Use only -f .
+- Added INET (IPv4) communication between client and backend. Not only UNIX. Experimental.
+- Removed clicon_dbmatch.[ch]. Replaced with clicon_dbitems_match().
+- Replaced internal clicon_proto_ messaging API with clicon_rpc_.
+- Extended tree-based access functions: clicon_dbget_parent/children/descendents/xpath.
+- clicon_dbctrl: Renamed brief option from -P to -b and made it work with -m
+- Reference documentation using doxygen: make doc, make graphs
+- When entering CLI commands, they appear in the same order in xml and reload.
+- yang parse error using '+' in strings
+- New example: clicon_yang for stateless yang experiments (no backend)
+- Event subscription API: add/delete/each
+- Long-term mem-leak in chunk-code
+- xml parser did not support "" only ""
+- fixed error in event-loop if timers are used: events before now caused hanging
+
+R3.0.0 23 February 2015
+=======================
+This is a new major CLICON release.
+The changes from previous versions are too numerous to document.
+Major recent additions are:
+- Added python API
+- YANG modeling support
+- Updated commit callback API
+- Asynchronous notifications
+- db2txt
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..ae47ce6c
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,632 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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
+
+Note the following clarification from the GNU FAQ concerning plugins
+(CLIXON uses cli, netconf and backend plugins that comply to this):
+
+If the program dynamically links plug-ins, and they make function
+calls to each other and share data structures, we believe they form a
+single program, which must be treated as an extension of both the main
+program and the plug-ins. This means the plug-ins must be released
+under the GPL or a GPL-compatible free software license, and that the
+terms of the GPL must be followed when those plug-ins are distributed.
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 00000000..c4c6ac72
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,112 @@
+#
+# Makefile
+#
+#
+# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+#
+# This file is part of CLICON.
+#
+# CLICON 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.
+#
+# CLICON 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 CLICON; see the file COPYING. If not, see
+# .
+#
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+# abs_top_builddir is by default the absolute path of the builddir.
+includedir = @includedir@
+datadir = @datarootdir@
+localstatedir = @localstatedir@
+sysconfdir = @sysconfdir@
+
+VPATH = @srcdir@
+srcdir = @srcdir@
+libdir = @libdir@
+top_srcdir = @top_srcdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+#INSTALL = @INSTALL@
+
+INCLUDES = -I. -I@srcdir@ @INCLUDES@
+SHELL = /bin/sh
+
+SUBDIRS = lib apps include doc etc
+
+.PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status
+
+all: $(SUBDIRS) clicon.conf.cpp clicon.mk
+
+$(SUBDIRS):
+ (cd $@ && $(MAKE) $(MFLAGS) all)
+
+depend:
+ for i in $(SUBDIRS) example; \
+ do (cd $$i && $(MAKE) $(MFLAGS) depend); done
+
+# template clicon.conf file
+clicon.conf.cpp: clicon.conf.cpp.cpp
+ $(CPP) -P -x assembler-with-cpp -Dprefix=$(prefix) -Dlocalstatedir=$(localstatedir) -Dsysconfdir=$(sysconfdir) -Ddatadir=$(datadir) -Dlibdir=$(libdir) $< > $@
+
+clicon.mk: clicon.mk.cpp
+ $(CPP) -P -traditional-cpp -x assembler-with-cpp -Dprefix=$(prefix) -Dlocalstatedir=$(localstatedir) -Dsysconfdir=$(sysconfdir) -Ddatadir=$(datadir) -Dlibdir=$(libdir) $< > $@
+
+install: clicon.conf.cpp clicon.mk
+ for i in $(SUBDIRS); \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@); done; \
+ install -d -m 755 $(DESTDIR)$(datadir)/clicon
+ install -m 755 clicon.conf.cpp $(DESTDIR)$(datadir)/clicon
+ install -m 755 clicon.mk $(DESTDIR)$(datadir)/clicon
+ echo "Install for compilation by: make install-include"
+
+install-include:
+ for i in $(SUBDIRS); \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@); done; \
+ echo "To install example app: cd example; make; make install"
+
+uninstall:
+ for i in $(SUBDIRS) example; \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@); done;
+ rm -f $(datadir)/clicon/clicon.conf.cpp
+ rm -f $(datadir)/clicon/clicon.mk
+
+# Overrides SUBDIRS above
+#doc:
+# cd $@; $(MAKE) $(MFLAGS) $@
+
+config.status: configure
+ $(SHELL) config.status --recheck
+
+configure: configure.ac
+ cd $(srcdir) && autoconf
+
+clean:
+ for i in $(SUBDIRS) example; \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@); done;
+
+distclean:
+ rm -f Makefile TAGS config.status config.log *~ .depend
+ rm -rf Makefile autom4te.cache
+ rm -rf clicon.conf.cpp clicon.mk
+ for i in $(SUBDIRS) example; \
+ do (cd $$i && $(MAKE) $(MFLAGS) $@); done
+
+# Lines of code
+loc:
+ find . -name '*.[chyl]' -type f | xargs wc -l | tail -1 2> /dev/null
+
+TAGS:
+ find $(srcdir) -name '*.[chyl]' -type f | grep -v \.tab\.[ch] | grep -v lex.*.c | grep -v .yy.c | etags -
+
+
diff --git a/README b/README
new file mode 100644
index 00000000..86a25e7b
--- /dev/null
+++ b/README
@@ -0,0 +1,72 @@
+This README contains information for developers:
+1. How to document the code
+2. How to work in git (branching)
+3. How the meta-configure stuff works
+
+1. How to document the code
++++++++++++++++++++++++++++
+
+Create documentation:
+cd doc
+make doc
+make graphs # callgraphs
+
+/*! This is a small comment on one line
+ *
+ * This is a detailed description
+ * spanning several lines.
+ *
+ * Example usage:
+ * @code
+ * fn(a, &b);
+ * @endcode
+ *
+ * @param[in] src This is a description of the first parameter
+ * @param[in,out] dest This is a description of the second parameter
+ * @retval TRUE This is a description of the return value
+ * @retval FALSE This is a description of another return value
+ * @see See also this function
+ */
+
+2. How to work in git (branching)
++++++++++++++++++++++++++++++++++
+Baically follows: http://nvie.com/posts/a-successful-git-branching-model/
+only somewhat simplified:
+
+Do commits in develop branch. When done, merge with master.
+($ git checkout -b develop master # create develop)
+
+$ git checkout develop
+Switch to branch develop
+$ git add ..
+$ git commit ..
+$ git push origin develop
+Add/commit stuff here (and push)
+
+Ready for tagging
+-----------------
+(This is somewhat simplified - no release branch)
+$ ./bump-version.sh 3.6.0
+Files modified successfully, version bumped to 3.6.0
+$ git checkout master
+Switch to master
+$ git merge --no-ff develop
+Merge made by recursive.
+(Summary of changes)
+$ git tag -a 3.6.0
+
+3. How the meta-configure stuff works
++++++++++++++++++++++++++++++++++++++
+configure.ac --.
+ | .------> autoconf* -----> configure
+ [aclocal.m4] --+---+
+ | `-----> [autoheader*] --> [config.h.in]
+ [acsite.m4] ---'
+
+ .-------------> [config.cache]
+ configure* ------------+-------------> config.log
+ |
+ [config.h.in] -. v .-> [config.h] -.
+ +--> config.status* -+ +--> make*
+ Makefile.in ---' `-> Makefile ---'
+
diff --git a/README.develop b/README.develop
new file mode 100644
index 00000000..16593630
--- /dev/null
+++ b/README.develop
@@ -0,0 +1,68 @@
+This README contains information for developers:
+1. How to document the code
+2. How to work in git (branching)
+3. How the meta-configure stuff works
+
+1. How to document the code
++++++++++++++++++++++++++++
+
+/*! This is a small comment on one line
+ *
+ * This is a detailed description
+ * spanning several lines.
+ *
+ * Example usage:
+ * @code
+ * fn(a, &b);
+ * @endcode
+ *
+ * @param[in] src This is a description of the first parameter
+ * @param[in,out] dest This is a description of the second parameter
+ * @retval TRUE This is a description of the return value
+ * @retval FALSE This is a description of another return value
+ * @see See also this function
+ */
+
+
+2. How to work in git (branching)
++++++++++++++++++++++++++++++++++
+Baically follows: http://nvie.com/posts/a-successful-git-branching-model/
+only somewhat simplified:
+
+Do commits in develop branch. When done, merge with master.
+
+
+$ git checkout develop
+Switch to branch develop
+$ git add ..
+$ git commit ..
+$ git push origin develop
+Add/commit stuff here (and push)
+
+Ready for tagging
+-----------------
+(This is somewhat simplified - no release branch)
+$ ./bump-version.sh 3.6.0
+Files modified successfully, version bumped to 3.6.0
+$ git checkout master
+Switch to master
+$ git merge --no-ff develop
+Merge made by recursive.
+(Summary of changes)
+$ git tag -a 3.6.0
+
+3. How the meta-configure stuff works
++++++++++++++++++++++++++++++++++++++
+configure.ac --.
+ | .------> autoconf* -----> configure
+ [aclocal.m4] --+---+
+ | `-----> [autoheader*] --> [config.h.in]
+ [acsite.m4] ---'
+
+ .-------------> [config.cache]
+ configure* ------------+-------------> config.log
+ |
+ [config.h.in] -. v .-> [config.h] -.
+ +--> config.status* -+ +--> make*
+ Makefile.in ---' `-> Makefile ---'
+
diff --git a/README.doxygen b/README.doxygen
new file mode 100644
index 00000000..9ad563b4
--- /dev/null
+++ b/README.doxygen
@@ -0,0 +1,31 @@
+/*! This is a small comment on one line
+ *
+ * This is a detailed description
+ * spanning several lines.
+ *
+ * Example usage:
+ * @code
+ * fn(a, &b);
+ * @endcode
+ *
+ * @param[in] src This is a description of the first parameter
+ * @param[in,out] dest This is a description of the second parameter
+ * @retval TRUE This is a description of the return value
+ * @retval FALSE This is a description of another return value
+ * @see anotherfn()
+ * @note This is just an example
+ */
+
+/*! This is a documentation of a typedef or struct
+ *
+ * This is a detailed description
+ * spanning several lines.
+ *
+ * Example usage:
+ * @code
+ * struct foo f;
+ * @endcode
+ */
+typedef foo{
+ int a; /**< This is the a field*/
+};
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..472628ef
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+CLIXON
+======
+
+CLIXON is an automatic configuration manager where you from a YANG
+specification generate interactive CLI, NETCONF and embedded
+databases with transaction support.
+
+CLIXON is a fork of CLICON where legacy key specification has been
+replaced completely by YANG. This means that legacy CLICON
+applications such as CLICON/ROST does not run on CLIXON.
+
+Presentations and tutorial is found on the [CLICON project
+page](http://www.clicon.org)
+
+A typical installation is as follows:
+
+ > configure # Configure clixon to platform
+ > make # Compile
+ > sudo make install # Install libs, binaries, and config-files
+ > sudo make install-include # Install include files (for compiling)
+
+One example applications is provided, the IETF IP YANG datamodel with generated CLI and configuration interface. It all origins from work at
+[KTH](http://www.csc.kth.se/~olofh/10G_OSR)
+
+[CLIgen](http://www.cligen.se) is required for building CLIXON. If you need
+to build and install CLIgen:
+
+ git clone https://github.com/olofhagsand/cligen.git
+ cd cligen; configure; make; make install
+
+CLIXON is covered by GPLv3, and is also available with commercial license.
+
+See COPYING for license, CHANGELOG for recent changes.
+
+
+
+
diff --git a/apps/Makefile.in b/apps/Makefile.in
new file mode 100644
index 00000000..c33d0b20
--- /dev/null
+++ b/apps/Makefile.in
@@ -0,0 +1,68 @@
+#
+# Makefile
+#
+# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+#
+# This file is part of CLICON.
+#
+# CLICON 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.
+#
+# CLICON 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 CLICON; see the file COPYING. If not, see
+# .
+#
+#
+VPATH = @srcdir@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+SHELL = /bin/sh
+
+SUBDIRS = cli backend dbctrl netconf
+
+.PHONY: all clean depend install $(SUBDIRS)
+
+all: $(SUBDIRS)
+
+depend:
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+$(SUBDIRS):
+ (cd $@; $(MAKE) $(MFLAGS) all)
+
+install-include:
+ for i in $(SUBDIRS); \
+ do (cd $$i ; $(MAKE) $(MFLAGS) $@); done;
+
+install:
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+uninstall:
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+clean:
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+distclean: clean
+ rm -f Makefile *~ .depend
+ for i in $(SUBDIRS); \
+ do (cd $$i; $(MAKE) $(MFLAGS) $@); done
+
+tags:
+ find $(srcdir) -name '*.[chyl]' -print | etags -
diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in
new file mode 100644
index 00000000..aea109d1
--- /dev/null
+++ b/apps/backend/Makefile.in
@@ -0,0 +1,137 @@
+#
+# Makefile
+#
+# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+#
+# This file is part of CLICON.
+#
+# CLICON 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.
+#
+# CLICON 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 CLICON; see the file COPYING. If not, see
+# .
+#
+#
+VPATH = @srcdir@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+libdir = @libdir@
+sbindir = @sbindir@
+libexecdir = @libexecdir@
+localstatedir = @localstatedir@
+sysconfdir = @sysconfdir@
+includedir = @includedir@
+
+SH_SUFFIX = @SH_SUFFIX@
+CLICON_MAJOR = @CLICON_VERSION_MAJOR@
+CLICON_MINOR = @CLICON_VERSION_MINOR@
+
+# Use this clicon lib for linking
+CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
+# Location of system plugins
+CLICON_BACKEND_SYSDIR = $(libdir)/clicon/plugins/backend
+
+# For dependency. A little strange that we rely on it being built in the src dir
+# even though it may exist in $(libdir). But the new version may not have been installed yet.
+LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
+
+LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB) -lpthread
+CPPFLAGS = @CPPFLAGS@ -fPIC
+INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
+
+# Not accessible from plugin
+APPSRC = backend_main.c backend_socket.c backend_client.c \
+ backend_lock.c backend_commit.c backend_plugin.c
+
+APPOBJ = $(APPSRC:.c=.o)
+APPL = clicon_backend
+
+#SHLIB = clicon_backend
+MYNAME = clicon_backend
+MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
+MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
+MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
+
+# Accessible from plugin
+LIBSRC = clicon_backend_transaction.c clicon_backend_handle.c
+LIBOBJ = $(LIBSRC:.c=.o)
+
+all: $(MYLIB) $(APPL) test
+
+clean:
+ rm -f *.core $(APPL) $(APPOBJ) $(LIBOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
+
+distclean: clean
+ rm -f Makefile *~ .depend
+
+# Put demon in bin
+# Put other executables in libexec/
+# Also create a libexec/ directory for writeable/temporary files.
+# Put config file in etc/
+install: install-lib $(APPL)
+ install -d $(DESTDIR)$(sbindir)
+ install $(APPL) $(DESTDIR)$(sbindir)
+
+install-lib: $(MYLIB)
+ install -d $(DESTDIR)$(libdir)
+ install $(MYLIB) $(DESTDIR)$(libdir)
+ ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_config.so.2
+ ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_config.so
+ install -d $(DESTDIR)$(libdir)/clicon/plugins/backend
+
+uninstall:
+ rm -f $(sbindir)/$(APPL)
+ rm -f $(libdir)/$(MYLIB)
+ rm -f $(includedir)/clicon/*
+
+install-include: clicon_backend.h clicon_backend_api.h
+ install -d $(DESTDIR)$(includedir)/clicon
+ install -m 644 $^ $(DESTDIR)$(includedir)/clicon
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+.c.o:
+ $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLICON_BACKEND_SYSDIR=\"$(CLICON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $<
+
+# Just link test programs
+test.c :
+ echo "main(){}" > $@
+
+test: test.c $(LIBOBJ)
+ $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@
+
+$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS)
+ $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
+
+$(MYLIB): $(LIBOBJ)
+ $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ -lc $(LIBOBJ) -Wl,-soname=$(MYLIBSO)
+
+# link-name is needed for application linking, eg for clicon_cli and clicon_backend
+$(MYLIBLINK) : $(MYLIB)
+# ln -sf $(MYLIB) $(MYLIBSO)
+# ln -sf $(MYLIB) $@
+
+TAGS:
+ find . -name '*.[chyl]' -print | etags -
+
+depend:
+ $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
+
+#include .depend
+
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
new file mode 100644
index 00000000..4eada539
--- /dev/null
+++ b/apps/backend/backend_client.c
@@ -0,0 +1,1066 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "backend_commit.h"
+#include "backend_lock.h"
+#include "backend_plugin.h"
+#include "backend_client.h"
+#include "backend_handle.h"
+
+/*! Add client notification subscription. Ie send notify to this client when event occurs
+ * @param[in] ce Client entry struct
+ * @param[in] stream Notification stream name
+ * @param[in] format How to display event (see enum format_enum)
+ * @param[in] filter Filter, what to display, eg xpath for format=xml, fnmatch
+ *
+ * @see backend_notify - where subscription is made and notify call is made
+ */
+static struct client_subscription *
+client_subscription_add(struct client_entry *ce,
+ char *stream,
+ enum format_enum format,
+ char *filter)
+{
+ struct client_subscription *su = NULL;
+
+ if ((su = malloc(sizeof(*su))) == NULL){
+ clicon_err(OE_PLUGIN, errno, "malloc");
+ goto done;
+ }
+ memset(su, 0, sizeof(*su));
+ su->su_stream = strdup(stream);
+ su->su_format = format;
+ su->su_filter = strdup(filter);
+ su->su_next = ce->ce_subscription;
+ ce->ce_subscription = su;
+ done:
+ return su;
+}
+
+static struct client_entry *
+ce_find_bypid(struct client_entry *ce_list, int pid)
+{
+ struct client_entry *ce;
+
+ for (ce = ce_list; ce; ce = ce->ce_next)
+ if (ce->ce_pid == pid)
+ return ce;
+ return NULL;
+}
+
+static int
+client_subscription_delete(struct client_entry *ce,
+ struct client_subscription *su0)
+{
+ struct client_subscription *su;
+ struct client_subscription **su_prev;
+
+ su_prev = &ce->ce_subscription; /* this points to stack and is not real backpointer */
+ for (su = *su_prev; su; su = su->su_next){
+ if (su == su0){
+ *su_prev = su->su_next;
+ free(su->su_stream);
+ if (su->su_filter)
+ free(su->su_filter);
+ free(su);
+ break;
+ }
+ su_prev = &su->su_next;
+ }
+ return 0;
+}
+
+static struct client_subscription *
+client_subscription_find(struct client_entry *ce, char *stream)
+{
+ struct client_subscription *su = NULL;
+
+ for (su = ce->ce_subscription; su; su = su->su_next)
+ if (strcmp(su->su_stream, stream) == 0)
+ break;
+
+ return su;
+}
+
+/*! Remove client entry state
+ * Close down everything wrt clients (eg sockets, subscriptions)
+ * Finally actually remove client struct in handle
+ * @see backend_client_delete
+ */
+int
+backend_client_rm(clicon_handle h,
+ struct client_entry *ce)
+{
+ struct client_entry *c;
+ struct client_entry *c0;
+ struct client_entry **ce_prev;
+ struct client_subscription *su;
+
+ c0 = backend_client_list(h);
+ ce_prev = &c0; /* this points to stack and is not real backpointer */
+ for (c = *ce_prev; c; c = c->ce_next){
+ if (c == ce){
+ if (ce->ce_s){
+ event_unreg_fd(ce->ce_s, from_client);
+ close(ce->ce_s);
+ ce->ce_s = 0;
+ }
+ while ((su = ce->ce_subscription) != NULL)
+ client_subscription_delete(ce, su);
+ break;
+ }
+ ce_prev = &c->ce_next;
+ }
+ return backend_client_delete(h, ce); /* actually purge it */
+}
+
+
+/*! Internal message: Change entry set/delete in database xmldb variant
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_change(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ uint32_t len;
+ char *xk;
+ char *dbname;
+ enum operation_type op;
+ char *str = NULL;
+ char *candidate_db;
+ char *val=NULL;
+ yang_spec *yspec;
+
+ if (clicon_msg_change_decode(msg,
+ &dbname,
+ &op,
+ &xk,
+ &val,
+ &len,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ /* candidate is locked by other client */
+ if (strcmp(dbname, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0,
+ "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+
+ /* Update database */
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_XML, 0, "yang spec not found");
+ goto done;
+ }
+ if (xmldb_put_xkey(dbname, xk, val, yspec, op) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (str)
+ free(str);
+ return retval;
+}
+
+
+
+/*! Internal message: Change entries as XML
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_xmlput(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ char *dbname;
+ enum operation_type op;
+ cvec *cvv = NULL;
+ char *str = NULL;
+ char *xml = NULL;
+ yang_spec *ys;
+ char *candidate_db;
+ cxobj *xt;
+
+ if ((ys = clicon_dbspec_yang(h)) == NULL)
+ goto done;
+ if (clicon_msg_xmlput_decode(msg,
+ &dbname,
+ &op,
+ &xml,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ /* candidate is locked by other client */
+ if (strcmp(dbname, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0,
+ "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+ if (clicon_xml_parse_string(&xml, &xt) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if (xmldb_put(dbname, xt, ys, op) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (str)
+ free(str);
+ if (cvv)
+ cvec_free (cvv);
+ return retval;
+}
+
+/* Nr of snapshots. Can be made into a dynamic option */
+#define SNAPSHOTS_NR 30
+/*! dump old running_db to snapshot file #0. move all other checkpoints
+ * one step up
+ */
+int
+config_snapshot(clicon_handle h,
+ char *dbname,
+ char *dir)
+{
+ int retval = -1;
+ char filename0[MAXPATHLEN];
+ char filename1[MAXPATHLEN];
+ struct stat st;
+ int i;
+ FILE *f = NULL;
+ cxobj *xn;
+ yang_spec *yspec = clicon_dbspec_yang(h);
+
+ if (stat(dir, &st) < 0){
+ clicon_err(OE_CFG, errno, "%s: stat(%s): %s\n",
+ __FUNCTION__, dir, strerror(errno));
+ return -1;
+ }
+ if (!S_ISDIR(st.st_mode)){
+ clicon_err(OE_CFG, 0, "%s: %s: not directory\n",
+ __FUNCTION__, dir);
+ return -1;
+ }
+ for (i=SNAPSHOTS_NR-1; i>0; i--){
+ snprintf(filename0, MAXPATHLEN, "%s/%d",
+ dir,
+ i-1);
+ snprintf(filename1, MAXPATHLEN, "%s/%d",
+ dir,
+ i);
+ if (stat(filename0, &st) == 0)
+ if (rename(filename0, filename1) < 0){
+ clicon_err(OE_CFG, errno, "%s: rename(%s, %s): %s\n",
+ __FUNCTION__, filename0, filename1, strerror(errno));
+ return -1;
+ }
+ }
+ /* Make the most current snapshot */
+ snprintf(filename0, MAXPATHLEN, "%s/0", dir);
+ if ((f = fopen(filename0, "wb")) == NULL){
+ clicon_err(OE_CFG, errno, "Creating file %s", filename0);
+ return -1;
+ }
+ if (xmldb_get(dbname, "/", yspec, &xn) < 0)
+ goto done;
+ if (clicon_xml2file(f, xn, 0, 1) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (f != NULL)
+ fclose(f);
+ if (xn)
+ xml_free(xn);
+ return retval;
+}
+
+
+/*! Internal message: Dump/print database to file
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_save(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ char *filename;
+ char *archive_dir;
+ char *db;
+ uint32_t snapshot;
+ FILE *f = NULL;
+ cxobj *xn = NULL;
+ yang_spec *yspec;
+
+ if (clicon_msg_save_decode(msg,
+ &db,
+ &snapshot,
+ &filename,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if (snapshot){
+ if ((archive_dir = clicon_archive_dir(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined");
+ goto done;
+ }
+ if (config_snapshot(h, db, archive_dir) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+
+ goto done;
+ }
+ }
+ else{
+ if ((f = fopen(filename, "wb")) == NULL){
+ clicon_err(OE_CFG, errno, "Creating file %s", filename);
+ return -1;
+ }
+ yspec = clicon_dbspec_yang(h);
+ if (xmldb_get(db, "/", yspec, &xn) < 0)
+ goto done;
+ if (clicon_xml2file(f, xn, 0, 1) < 0)
+ goto done;
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (f != NULL)
+ fclose(f);
+ if (xn)
+ xml_free(xn);
+ return retval;
+}
+
+/*! Internal message: Load file into database
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_load(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+
+{
+ char *filename = NULL;
+ int retval = -1;
+ char *dbname = NULL;
+ int replace = 0;
+ char *candidate_db;
+ int fd = -1;
+ cxobj *xt = NULL;
+ cxobj *xn;
+ yang_spec *yspec;
+
+ if (clicon_msg_load_decode(msg,
+ &replace,
+ &dbname,
+ &filename,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ /* candidate is locked by other client */
+ if (strcmp(dbname, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0, "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+ if (replace){
+ if (unlink(dbname) < 0){
+ send_msg_err(s, OE_UNIX, 0, "rm %s %s", filename, strerror(errno));
+ goto done;
+ }
+ if (db_init(dbname) < 0)
+ goto done;
+ }
+
+ if ((fd = open(filename, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: open(%s)", __FUNCTION__, filename);
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if (clicon_xml_parse_file(fd, &xt, "") < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ yspec = clicon_dbspec_yang(h);
+ if ((xn = xml_child_i(xt, 0)) != NULL){
+ if (xmldb_put(dbname, xn, yspec, replace?OP_REPLACE:OP_MERGE) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (fd != -1)
+ close(fd);
+ if (xt)
+ xml_free(xt);
+ return retval;
+}
+
+/*! Internal message: Initialize database
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_initdb(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *filename1;
+ int retval = -1;
+ char *candidate_db;
+
+ if (clicon_msg_initdb_decode(msg,
+ &filename1,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ /* candidate is locked by other client */
+ if (strcmp(filename1, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0, "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+
+ if (db_init(filename1) < 0)
+ goto done;
+ /* Change mode if shared candidate. XXXX full rights for all is no good */
+ if (strcmp(filename1, candidate_db) == 0)
+ chmod(filename1, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Remove file
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_rm(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *filename1;
+ int retval = -1;
+ char *candidate_db;
+
+ if (clicon_msg_rm_decode(msg,
+ &filename1,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ /* candidate is locked by other client */
+ if (strcmp(filename1, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0, "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+
+ if (unlink(filename1) < 0){
+ send_msg_err(s, OE_UNIX, 0, "rm %s %s", filename1, strerror(errno));
+ goto done;
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Copy file from file1 to file2
+ * @param[in] h Clicon handle
+ * @param[in] s Socket where request arrived, and where replies are sent
+ * @param[in] pid Unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_copy(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *filename1;
+ char *filename2;
+ int retval = -1;
+ char *candidate_db;
+
+ if (clicon_msg_copy_decode(msg,
+ &filename1,
+ &filename2,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+
+ /* candidate is locked by other client */
+ if (strcmp(filename2, candidate_db) == 0 &&
+ db_islocked(h) &&
+ pid != db_islocked(h)){
+ send_msg_err(s, OE_DB, 0, "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+
+ if (file_cp(filename1, filename2) < 0){
+ send_msg_err(s, OE_UNIX, errno, "copy %s to %s", filename1, filename2);
+ goto done;
+ }
+ /* Change mode if shared candidate. XXXX full rights for all is no good */
+ if (strcmp(filename2, candidate_db) == 0)
+ chmod(filename2, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Lock database
+ * @param[in] h Clicon handle
+ * @param[in] s Client socket where request arrived, and where replies are sent
+ * @param[in] pid Client unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_lock(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *db;
+ int retval = -1;
+ char *candidate_db;
+
+
+ if (clicon_msg_lock_decode(msg,
+ &db,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+ if (strcmp(db, candidate_db)){
+ send_msg_err(s, OE_DB, 0, "can not lock %s, only %s",
+ db, candidate_db);
+ goto done;
+ }
+ if (db_islocked(h)){
+ if (pid == db_islocked(h))
+ ;
+ else{
+ send_msg_err(s, OE_DB, 0, "lock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+ }
+ else
+ db_lock(h, pid);
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Unlock database
+ * @param[in] h Clicon handle
+ * @param[in] s Client socket where request arrived, and where replies are sent
+ * @param[in] pid Client unix process id
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_unlock(clicon_handle h,
+ int s,
+ int pid,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *db;
+ int retval = -1;
+ char *candidate_db;
+
+ if (clicon_msg_unlock_decode(msg,
+ &db,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ send_msg_err(s, 0, 0, "candidate db not set");
+ goto done;
+ }
+
+ if (strcmp(db, candidate_db)){
+ send_msg_err(s, OE_DB, 0, "can not unlock %s, only %s",
+ db, clicon_candidate_db(h));
+ goto done;
+ }
+ if (db_islocked(h)){
+ if (pid == db_islocked(h))
+ db_unlock(h);
+ else{
+ send_msg_err(s, OE_DB, 0, "unlock failed: locked by %d", db_islocked(h));
+ goto done;
+ }
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Kill session (Kill the process)
+ * @param[in] h Clicon handle
+ * @param[in] s Client socket where request arrived, and where replies are sent
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_kill(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ uint32_t pid; /* other pid */
+ int retval = -1;
+ struct client_entry *ce;
+
+ if (clicon_msg_kill_decode(msg,
+ &pid,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ /* may or may not be in active client list, probably not */
+ if ((ce = ce_find_bypid(backend_client_list(h), pid)) != NULL)
+ backend_client_rm(h, ce);
+ if (kill (pid, 0) != 0 && errno == ESRCH) /* Nothing there */
+ ;
+ else{
+ killpg(pid, SIGTERM);
+ kill(pid, SIGTERM);
+#if 0 /* Hate sleeps we assume it died, see also 0 in next if.. */
+ sleep(1);
+#endif
+ }
+ if (1 || (kill (pid, 0) != 0 && errno == ESRCH)){ /* Nothing there */
+ /* clear from locks */
+ if (db_islocked(h) == pid)
+ db_unlock(h);
+ }
+ else{ /* failed to kill client */
+ send_msg_err(s, OE_DB, 0, "failed to kill %d", pid);
+ goto done;
+ }
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: Set debug level. This is global, not just for the session.
+ * @param[in] h Clicon handle
+ * @param[in] s Client socket where request arrived, and where replies are sent
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_debug(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ uint32_t level;
+
+ if (clicon_msg_debug_decode(msg,
+ &level,
+ label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
+
+ if (send_msg_ok(s) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Internal message: downcall backend plugin
+ * @param[in] h Clicon handle
+ * @param[in] s Client socket where request arrived, and where replies are sent
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_call(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ void *reply_data = NULL;
+ uint16_t reply_data_len = 0;
+ struct clicon_msg_call_req *req;
+
+ if (clicon_msg_call_decode(msg, &req, label) < 0) {
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+#ifdef notyet
+ if (!strlen(req->cr_plugin)) /* internal */
+ internal_function(req, &reply_data_len, &reply_data);
+ else
+#endif
+ if (plugin_downcall(h, req, &reply_data_len, &reply_data) < 0) {
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+
+ retval = send_msg_reply(s,CLICON_MSG_OK, (char *)reply_data, reply_data_len);
+ free(reply_data);
+
+ done:
+ return retval;
+}
+
+/*! Internal message: Create subscription for notifications
+ * @param[in] h Clicon handle
+ * @param[in] ce Client entry (from).
+ * @param[in] msg Message
+ * @param[in] label Memory chunk
+ * @retval 0 OK
+ * @retval -1 Error. Send error message back to client.
+ */
+static int
+from_client_subscription(clicon_handle h,
+ struct client_entry *ce,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int status;
+ enum format_enum format;
+ char *stream;
+ char *filter;
+ int retval = -1;
+ struct client_subscription *su;
+ clicon_log_notify_t *old;
+
+ if (clicon_msg_subscription_decode(msg,
+ &status,
+ &stream,
+ &format,
+ &filter,
+ label) < 0){
+ send_msg_err(ce->ce_s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+
+ if (status){
+ if ((su = client_subscription_add(ce, stream, format, filter)) == NULL){
+ send_msg_err(ce->ce_s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto done;
+ }
+ }
+ else{
+ if ((su = client_subscription_find(ce, stream)) != NULL)
+ client_subscription_delete(ce, su);
+ }
+ /* Avoid recursion when sending logs */
+ old = clicon_log_register_callback(NULL, NULL);
+ if (send_msg_ok(ce->ce_s) < 0)
+ goto done;
+ clicon_log_register_callback(old, h); /* XXX: old h */
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! An internal clicon message has arrived from a client. Receive and dispatch.
+ * @param[in] s Socket where message arrived. read from this.
+ * @param[in] arg Client entry (from).
+ * @retval 0 OK
+ * @retval -1 Error Terminates backend and is never called). Instead errors are
+ * propagated back to client.
+ */
+int
+from_client(int s, void* arg)
+{
+ struct client_entry *ce = (struct client_entry *)arg;
+ clicon_handle h = ce->ce_handle;
+ struct clicon_msg *msg;
+ enum clicon_msg_type type;
+ int eof;
+
+ assert(s == ce->ce_s);
+ if (clicon_msg_rcv(ce->ce_s, &msg, &eof, __FUNCTION__) < 0)
+ goto done;
+ if (eof){
+ backend_client_rm(h, ce);
+ goto done;
+ }
+ type = ntohs(msg->op_type);
+ switch (type){
+ case CLICON_MSG_COMMIT:
+ if (from_client_commit(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_VALIDATE:
+ if (from_client_validate(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_CHANGE:
+ if (from_client_change(h, ce->ce_s, ce->ce_pid, msg,
+ (char *)__FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_XMLPUT:
+ if (from_client_xmlput(h, ce->ce_s, ce->ce_pid, msg,
+ (char *)__FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_SAVE:
+ if (from_client_save(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_LOAD:
+ if (from_client_load(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_RM:
+ if (from_client_rm(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_INITDB:
+ if (from_client_initdb(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_COPY:
+ if (from_client_copy(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_LOCK:
+ if (from_client_lock(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_UNLOCK:
+ if (from_client_unlock(h, ce->ce_s, ce->ce_pid, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_KILL:
+ if (from_client_kill(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_DEBUG:
+ if (from_client_debug(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_CALL:
+ if (from_client_call(h, ce->ce_s, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ case CLICON_MSG_SUBSCRIPTION:
+ if (from_client_subscription(h, ce, msg, __FUNCTION__) < 0)
+ goto done;
+ break;
+ default:
+ send_msg_err(s, OE_PROTO, 0, "Unexpected message: %d", type);
+ goto done;
+ }
+// retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+// return retval;
+ return 0; // -1 here terminates
+}
+
diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h
new file mode 100644
index 00000000..4a6ca209
--- /dev/null
+++ b/apps/backend/backend_client.h
@@ -0,0 +1,64 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+ */
+
+#ifndef _BACKEND_CLIENT_H_
+#define _BACKEND_CLIENT_H_
+
+/*
+ * Types
+ */
+/*
+ * Client entry.
+ * Keep state about every connected client.
+ */
+struct client_entry{
+ struct client_entry *ce_next; /* The clients linked list */
+ struct sockaddr ce_addr; /* The clients (UNIX domain) address */
+ int ce_s; /* stream socket to client */
+ int ce_nr; /* Client number (for dbg/tracing) */
+ int ce_stat_in; /* Nr of received msgs from client */
+ int ce_stat_out;/* Nr of sent msgs to client */
+ int ce_pid; /* Process id */
+ int ce_uid; /* User id of calling process */
+ clicon_handle ce_handle; /* clicon config handle (all clients have same?) */
+ struct client_subscription *ce_subscription; /* notification subscriptions */
+};
+
+/* Notification subscription info
+ * @see subscription in config_handle.c
+ */
+struct client_subscription{
+ struct client_subscription *su_next;
+ int su_s; /* stream socket */
+ enum format_enum su_format; /* format of notification stream */
+ char *su_stream;
+ char *su_filter;
+};
+
+/*
+ * Prototypes
+ */
+int backend_client_rm(clicon_handle h, struct client_entry *ce);
+int config_snapshot(clicon_handle h, char *dbname, char *dir);
+
+int from_client(int fd, void *arg);
+
+#endif /* _BACKEND_CLIENT_H_ */
diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c
new file mode 100644
index 00000000..d8b0524d
--- /dev/null
+++ b/apps/backend/backend_commit.c
@@ -0,0 +1,684 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_backend_transaction.h"
+#include "backend_plugin.h"
+#include "backend_handle.h"
+#include "backend_commit.h"
+#include "backend_client.h"
+
+/*! Key values are checked for validity independent of user-defined callbacks
+ *
+ * Key values are checked as follows:
+ * 1. If no value and default value defined, add it.
+ * 2. If no value and mandatory flag set in spec, report error.
+ * 3. Validate value versus spec, and report error if no match. Currently only int ranges and
+ * string regexp checked.
+ * See also db_lv_set() where defaults are also filled in. The case here for defaults
+ * are if code comes via XML/NETCONF.
+ * @param yspec Yang spec
+ * @param td Transaction data
+ */
+static int
+generic_validate(yang_spec *yspec,
+ transaction_data_t *td)
+{
+ int retval = -1;
+ cxobj *x1;
+ cxobj *x2;
+ int i;
+ yang_stmt *ys;
+
+ /* changed entries */
+ for (i=0; itd_clen; i++){
+ x1 = td->td_scvec[i]; /* source changed */
+ x2 = td->td_tcvec[i]; /* target changed */
+ ys = xml_spec(x1);
+ if (xml_yang_validate(x2, ys) < 0)
+ goto done;
+ }
+ /* deleted entries */
+ for (i=0; itd_dlen; i++){
+ x1 = td->td_dvec[i];
+ ys = xml_spec(x1);
+ if (yang_mandatory(ys)){
+ clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
+ xml_name(x1));
+ goto done;
+ }
+ }
+ /* added entries */
+ for (i=0; itd_alen; i++){
+ x2 = td->td_avec[i];
+ if (xml_yang_validate(x2, xml_spec(x2)) < 0)
+ goto done;
+ if (xml_apply(x2, CX_ELMNT,
+ (xml_applyfn_t*)xml_yang_validate, NULL) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Do a diff between candidate and running, then start a commit transaction
+ *
+ * The code reverts changes if the commit fails. But if the revert
+ * fails, we just ignore the errors and proceed. Maybe we should
+ * do something more drastic?
+ * @param[in] h Clicon handle
+ * @param[in] running The current database. The original backend state
+ * @param[in] candidate: The candidate database. The wanted backend state
+*/
+int
+candidate_commit(clicon_handle h,
+ char *candidate,
+ char *running)
+{
+ int retval = -1;
+ // int i, j;
+ // int failed = 0;
+ struct stat sb;
+ void *firsterr = NULL;
+ yang_spec *yspec;
+ transaction_data_t *td = NULL;
+
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
+ /* Sanity checks that databases exists. */
+ if (stat(running, &sb) < 0){
+ clicon_err(OE_DB, errno, "%s", running);
+ goto done;
+ }
+ if (stat(candidate, &sb) < 0){
+ clicon_err(OE_DB, errno, "%s", candidate);
+ goto done;
+ }
+
+ /* 1. Start transaction */
+ if ((td = transaction_new()) == NULL)
+ goto done;
+
+ /* 2. Parse xml trees */
+ if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
+ goto done;
+ if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
+ goto done;
+
+ /* 3. Compute differences */
+ if (xml_diff(yspec,
+ td->td_src,
+ td->td_target,
+ &td->td_dvec, /* removed: only in running */
+ &td->td_dlen,
+ &td->td_avec, /* added: only in candidate */
+ &td->td_alen,
+ &td->td_scvec, /* changed: original values */
+ &td->td_tcvec, /* changed: wanted values */
+ &td->td_clen) < 0)
+ goto done;
+ if (debug)
+ transaction_print(stderr, td);
+
+ /* 4. Call plugin transaction start callbacks */
+ if (plugin_transaction_begin(h, td) < 0)
+ goto done;
+
+ /* 5. Make generic validation on all new or changed data. */
+ if (generic_validate(yspec, td) < 0)
+ goto done;
+
+ /* 6. Call plugin transaction validate callbacks */
+ if (plugin_transaction_validate(h, td) < 0)
+ goto done;
+
+ /* 7. Call plugin transaction complete callbacks */
+ if (plugin_transaction_complete(h, td) < 0)
+ goto done;
+
+ /* 7. Call plugin transaction commit callbacks */
+ if (plugin_transaction_commit(h, td) < 0)
+ goto done;
+
+ /* 8. Copy running back to candidate in case end functions triggered
+ updates in running */
+ if (file_cp(running, candidate) < 0){
+ /* ignore errors or signal major setback ? */
+ clicon_err(OE_UNIX, errno, "file_cp(running, candidate)");
+ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
+ goto done;
+ }
+
+ /* 9. Call plugin transaction end callbacks */
+ plugin_transaction_end(h, td);
+
+#ifdef OBSOLETE
+ /* Find the differences between the two databases and store it in df vector. */
+ memset(&df, 0, sizeof(df));
+ if (db_diff(running, candidate,
+ __FUNCTION__,
+ clicon_dbspec_key(h),
+ &df
+ ) < 0)
+ goto done;
+
+ if (debug){
+ struct dbdiff_ent *dfe;
+ for (i=0; idfe_op,
+ cvec_name_get(dfe->dfe_vec1?dfe->dfe_vec1:dfe->dfe_vec2));
+ }
+ }
+ /* 1. Get commit processing to dbdiff vector: one entry per key that changed.
+ changes are registered as if they exist in the 1st(candidate) or
+ 2nd(running) dbs.
+ */
+ if (dbdep_commitvec(h, &df, &nvec, &ddvec) < 0)
+ goto done;
+
+ /* 2. Call transaction_begin hooks */
+ if (plugin_transaction_begin(h) < 0)
+ goto done;
+
+ /* call generic cv_validate() on all new or changed keys. */
+ if (generic_validate(yspec, NULL) < 0)
+ goto done;
+
+ /* user-defined validate callbacks registered in dbdep_validate */
+ // if (validate_db(h, nvec, ddvec, running, candidate) < 0)
+ // goto done;
+
+ /* Call plugin post-commit hooks */
+ if (plugin_transaction_complete(h) < 0)
+ goto done;
+
+ if (clicon_commit_order(h) == 0){
+ for (i=0; i < nvec; i++){ /* revert in opposite order */
+ dd = &ddvec[i];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ running, /* db1 */
+ candidate, /* db2 */
+ dd->dd_mkey1, /* key1 */
+ dd->dd_mkey2, /* key2 */
+ dd->dd_dbdiff->dfe_vec1, /* vec1 */
+ dd->dd_dbdiff->dfe_vec2, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ firsterr = clicon_err_save(); /* save this error */
+ failed++;
+ break;
+ }
+ }
+ if (!failed)
+ if (file_cp(candidate, running) < 0){ /* Commit here in case cp fails */
+ clicon_err(OE_UNIX, errno, "file_cp");
+ failed++;
+ }
+ /* Failed operation, start error handling: rollback in opposite order */
+ if (failed){
+ for (j=i-1; j>=0; j--){ /* revert in opposite order */
+ dd = &ddvec[j];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ switch (op){ /* reverse operation */
+ case CO_ADD:
+ op = CO_DELETE;
+ break;
+ case CO_DELETE:
+ op = CO_ADD;
+ break;
+ default:
+ break;
+ }
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ candidate, /* db1 */
+ running, /* db2 */
+ dd->dd_mkey2, /* key1 */
+ dd->dd_mkey1, /* key2 */
+ dd->dd_dbdiff->dfe_vec2, /* vec1 */
+ dd->dd_dbdiff->dfe_vec1, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ /* ignore errors or signal major setback ? */
+ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
+ continue;
+ }
+ }
+ goto done;
+ } /* error handling */
+ }
+ else { /* commit_order == 1 or 2 */
+ /* Now follows commit rules in order.
+ * 4. For all keys that are not in candidate but in running, delete key
+ * in reverse prio order
+ */
+ for (i = nvec-1; i >= 0; i--){
+ dd = &ddvec[i];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ /* original mode 2 where CHANGE=DEL/ADD */
+ if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
+ op = CO_DELETE;
+ if (op != CO_DELETE)
+ continue;
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ running, /* db1 */
+ candidate, /* db2 */
+ dd->dd_mkey1, /* key1 */
+ dd->dd_mkey2, /* key2 */
+ dd->dd_dbdiff->dfe_vec1, /* vec1 */
+ dd->dd_dbdiff->dfe_vec2, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ firsterr = clicon_err_save(); /* save this error */
+ break;
+ }
+ }
+ /* 5. Failed deletion, add the key value back to running */
+ if (i >= 0){ /* failed */
+ for (j=i+1; jdd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ /* original mode 2 where CHANGE=DEL/ADD */
+ if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
+ op = CO_DELETE;
+ if (op != CO_DELETE)
+ continue;
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ candidate, /* db1 */
+ running, /* db2 */
+ dd->dd_mkey2, /* key1 */
+ dd->dd_mkey1, /* key2 */
+ dd->dd_dbdiff->dfe_vec2, /* vec1 */
+ dd->dd_dbdiff->dfe_vec1, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ /* ignore errors or signal major setback ? */
+ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
+ continue;
+ }
+ }
+ goto done;
+ }
+ /*
+ * 6. For all added or changed keys
+ */
+ for (i=0; i < nvec; i++){
+ dd = &ddvec[i];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ if (op != CO_CHANGE && op != CO_ADD)
+ continue;
+ /* original mode 2 where CHANGE=DEL/ADD */
+ if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
+ op = CO_ADD;
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ running, /* db1 */
+ candidate, /* db2 */
+ dd->dd_mkey1, /* key1 */
+ dd->dd_mkey2, /* key2 */
+ dd->dd_dbdiff->dfe_vec1, /* vec1 */
+ dd->dd_dbdiff->dfe_vec2, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ firsterr = clicon_err_save(); /* save this error */
+ failed++;
+ break;
+ }
+ }
+ if (!failed) /* Commit here in case cp fails */
+ if (file_cp(candidate, running) < 0){
+ clicon_err(OE_UNIX, errno, "file_cp(candidate; running)");
+ failed++;
+ }
+ /* 10. Failed setting keys in running, first remove the keys set */
+ if (failed){ /* failed */
+ for (j=i-1; j>=0; j--){ /* revert in opposite order */
+ dd = &ddvec[j];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ if (op != CO_CHANGE && op != CO_ADD)
+ continue;
+ /* original mode 2 where CHANGE=DEL/ADD */
+ if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
+ op = CO_ADD;
+ if (op == CO_ADD) /* reverse op */
+ op = CO_DELETE;
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ candidate, /* db1 */
+ running, /* db2 */
+ dd->dd_mkey2, /* key1 */
+ dd->dd_mkey1, /* key2 */
+ dd->dd_dbdiff->dfe_vec2, /* vec1 */
+ dd->dd_dbdiff->dfe_vec1, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ /* ignore errors or signal major setback ? */
+ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
+ continue;
+ }
+ }
+ for (j=0; j < nvec; j++){ /* revert in opposite order */
+ dd = &ddvec[j];
+ dp = dd->dd_dep; /* op, callback, arg */
+ if ((dp->dp_type & TRANS_CB_COMMIT) == 0)
+ continue;
+ dfe = dd->dd_dbdiff; /* key1/key2/op */
+ op = dbdiff2commit_op(dfe->dfe_op);
+ /* original mode 2 where CHANGE=DEL/ADD */
+ if (clicon_commit_order(h) == 2 && op == CO_CHANGE)
+ op = CO_DELETE;
+ if (op != CO_DELETE)
+ continue;
+ op = CO_ADD;
+ if (plugin_commit_callback(h,
+ op, /* oper */
+ candidate, /* db1 */
+ running, /* db2 */
+ dd->dd_mkey2, /* key1 */
+ dd->dd_mkey1, /* key2 */
+ dd->dd_dbdiff->dfe_vec2, /* vec1 */
+ dd->dd_dbdiff->dfe_vec1, /* vec2 */
+ dp /* callback */
+ ) < 0){
+ /* ignore errors or signal major setback ? */
+ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
+ continue;
+ }
+ }
+ goto done;
+ }
+ } /* commit_order */
+#endif /* OBSOLETE */
+
+
+ retval = 0;
+ done:
+ /* In case of failure, call plugin transaction termination callbacks */
+ if (retval < 0 && td)
+ plugin_transaction_abort(h, td);
+ if (td)
+ transaction_free(td);
+ if (firsterr)
+ clicon_err_restore(firsterr);
+ return retval;
+}
+
+/*! Do a diff between candidate and running, then start a validate transaction
+ *
+ * @param[in] h Clicon handle
+ * @param[in] running The current database. The original backend state
+ * @param[in] candidate: The candidate database. The wanted backend state
+*/
+int
+candidate_validate(clicon_handle h,
+ char *candidate,
+ char *running)
+ {
+ int retval = -1;
+ struct stat sb;
+ yang_spec *yspec;
+ transaction_data_t *td = NULL;
+
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
+ /* Sanity checks that databases exists. */
+ if (stat(running, &sb) < 0){
+ clicon_err(OE_DB, errno, "%s", running);
+ goto done;
+ }
+ if (stat(candidate, &sb) < 0){
+ clicon_err(OE_DB, errno, "%s", candidate);
+ goto done;
+ }
+
+ /* 1. Start transaction */
+ if ((td = transaction_new()) == NULL)
+ goto done;
+
+ /* 2. Parse xml trees */
+ if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
+ goto done;
+ if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
+ goto done;
+
+ /* 3. Compute differences */
+ if (xml_diff(yspec,
+ td->td_src,
+ td->td_target,
+ &td->td_dvec, /* removed: only in running */
+ &td->td_dlen,
+ &td->td_avec, /* added: only in candidate */
+ &td->td_alen,
+ &td->td_scvec, /* changed: original values */
+ &td->td_tcvec, /* changed: wanted values */
+ &td->td_clen) < 0)
+ goto done;
+
+ if (debug)
+ transaction_print(stderr, td);
+
+ /* 4. Call plugin start transaction callbacks */
+ if (plugin_transaction_begin(h, td) < 0)
+ goto done;
+
+ /* 5. Make generic validation on all new or changed data. */
+ if (generic_validate(yspec, td) < 0)
+ goto done;
+
+ /* 6. Call plugin validate transaction callbacks */
+ if (plugin_transaction_validate(h, td) < 0)
+ goto done;
+
+ /* 7. Call plugin complete transaction callbacks */
+ if (plugin_transaction_complete(h, td) < 0)
+ goto done;
+
+ retval = 0;
+ done:
+ /* In case of failure, call plugin transaction termination callbacks */
+ if (retval < 0 && td)
+ plugin_transaction_abort(h, td);
+ if (td)
+ transaction_free(td);
+ return retval;
+ }
+
+
+/*! Handle an incoming commit message from a client.
+ * XXX: If commit succeeds and snapshot/startup fails, we have strange state:
+ * the commit has succeeded but an error message is returned.
+ */
+int
+from_client_commit(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ int retval = -1;
+ char *candidate;
+ char *running;
+ uint32_t snapshot;
+ uint32_t startup;
+ char *snapshot_0;
+ char *archive_dir;
+ char *startup_config;
+
+ if (clicon_msg_commit_decode(msg, &candidate, &running,
+ &snapshot, &startup, label) < 0)
+ goto err;
+
+ if (candidate_commit(h, candidate, running) < 0){
+ clicon_debug(1, "Commit %s failed", candidate);
+ retval = 0; /* We ignore errors from commit, but maybe
+ we should fail on fatal errors? */
+ goto err;
+ }
+ clicon_debug(1, "Commit %s", candidate);
+ if (snapshot){
+ if ((archive_dir = clicon_archive_dir(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined");
+ goto err;
+ }
+ if (config_snapshot(h, running, archive_dir) < 0)
+ goto err;
+ }
+
+ if (startup){
+ if ((archive_dir = clicon_archive_dir(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "startup set but clicon_archive_dir not defined");
+ goto err;
+ }
+ if ((startup_config = clicon_startup_config(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "startup set but startup_config not defined");
+ goto err;
+ }
+ snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", archive_dir);
+ if (file_cp(snapshot_0, startup_config) < 0){
+ clicon_err(OE_PROTO, errno, "%s: Error when creating startup",
+ __FUNCTION__);
+ goto err;
+ }
+ }
+ retval = 0;
+ if (send_msg_ok(s) < 0)
+ goto done;
+ goto done;
+ err:
+ /* XXX: more elaborate errstring? */
+ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
+ retval = -1;
+ done:
+ unchunk_group(__FUNCTION__);
+
+ return retval; /* may be zero if we ignoring errors from commit */
+} /* from_client_commit */
+
+
+
+/*
+ * Call backend plugin
+ */
+int
+from_client_validate(clicon_handle h,
+ int s,
+ struct clicon_msg *msg,
+ const char *label)
+{
+ char *dbname;
+ char *running_db;
+ int retval = -1;
+
+ if (clicon_msg_validate_decode(msg, &dbname, label) < 0){
+ send_msg_err(s, clicon_errno, clicon_suberrno,
+ clicon_err_reason);
+ goto err;
+ }
+
+ clicon_debug(1, "Validate %s", dbname);
+ if ((running_db = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto err;
+ }
+ if (candidate_validate(h, dbname, running_db) < 0){
+ clicon_debug(1, "Validate %s failed", dbname);
+ retval = 0; /* We ignore errors from commit, but maybe
+ we should fail on fatal errors? */
+ goto err;
+ }
+ retval = 0;
+ if (send_msg_ok(s) < 0)
+ goto done;
+ goto done;
+ err:
+ /* XXX: more elaborate errstring? */
+ if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
+ retval = -1;
+ done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+} /* from_client_validate */
diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h
new file mode 100644
index 00000000..1e7ffe25
--- /dev/null
+++ b/apps/backend/backend_commit.h
@@ -0,0 +1,34 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+
+#ifndef _BACKEND_COMMIT_H_
+#define _BACKEND_COMMIT_H_
+
+/*
+ * Prototypes
+ */
+int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
+int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
+int candidate_commit(clicon_handle h, char *candidate, char *running);
+
+#endif /* _BACKEND_COMMIT_H_ */
diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h
new file mode 100644
index 00000000..bee87133
--- /dev/null
+++ b/apps/backend/backend_handle.h
@@ -0,0 +1,43 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifndef _BACKEND_HANDLE_H_
+#define _BACKEND_HANDLE_H_
+
+
+/*
+ * Prototypes
+ * not exported.
+ */
+/* backend handles */
+clicon_handle backend_handle_init(void);
+
+int backend_handle_exit(clicon_handle h);
+
+struct client_entry *backend_client_add(clicon_handle h, struct sockaddr *addr);
+
+struct client_entry *backend_client_list(clicon_handle h);
+
+int backend_client_delete(clicon_handle h, struct client_entry *ce);
+
+#endif /* _BACKEND_HANDLE_H_ */
diff --git a/apps/backend/backend_lock.c b/apps/backend/backend_lock.c
new file mode 100644
index 00000000..8a33bbb4
--- /dev/null
+++ b/apps/backend/backend_lock.c
@@ -0,0 +1,95 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "backend_lock.h"
+
+/*
+ * Easy way out: store an integer for candidate db which contains
+ * the session-id of the client holding the lock.
+ * more general: any database
+ * we dont make any sanity check on who is locking.
+ */
+static int _db_locked = 0;
+
+/*
+ * db_lock
+ */
+int
+db_lock(clicon_handle h, int id)
+{
+ _db_locked = id;
+ clicon_debug(1, "%s: lock db by %u", __FUNCTION__, id);
+ return 0;
+}
+
+/*
+ * db_unlock
+ */
+int
+db_unlock(clicon_handle h)
+{
+ if (!_db_locked )
+ return 0;
+ _db_locked = 0;
+ return 0;
+}
+
+/*
+ * db_islocked
+ * returns id of locker
+ */
+int
+db_islocked(clicon_handle h)
+{
+ return _db_locked;
+}
+
diff --git a/apps/backend/backend_lock.h b/apps/backend/backend_lock.h
new file mode 100644
index 00000000..fa5c604f
--- /dev/null
+++ b/apps/backend/backend_lock.h
@@ -0,0 +1,37 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ * Database logical lock functions.
+ * Only one lock (candidate_db)
+ * Not persistent (needs another db)
+ */
+
+#ifndef _BACKEND_LOCK_H_
+#define _BACKEND_LOCK_H_
+
+/*
+ * Prototypes
+ */
+int db_lock(clicon_handle h, int id);
+int db_unlock(clicon_handle h);
+int db_islocked(clicon_handle h);
+
+#endif /* _BACKEND_LOCK_H_ */
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
new file mode 100644
index 00000000..a399ceec
--- /dev/null
+++ b/apps/backend/backend_main.c
@@ -0,0 +1,617 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_backend_handle.h"
+#include "backend_socket.h"
+#include "backend_client.h"
+#include "backend_commit.h"
+#include "backend_plugin.h"
+#include "backend_handle.h"
+
+/* Command line options to be passed to getopt(3) */
+#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc::rg:pt"
+
+/* Cannot use h after this */
+static int
+config_terminate(clicon_handle h)
+{
+ yang_spec *yspec;
+ char *pidfile = clicon_backend_pidfile(h);
+ char *sockpath = clicon_sock(h);
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ if ((yspec = clicon_dbspec_yang(h)) != NULL)
+ yspec_free(yspec);
+ plugin_finish(h);
+ if (pidfile)
+ unlink(pidfile);
+ if (sockpath)
+ unlink(sockpath);
+ backend_handle_exit(h); /* Cannot use h after this */
+ clicon_log_register_callback(NULL, NULL);
+ clicon_debug(1, "%s done", __FUNCTION__);
+ if (debug)
+ chunk_check(stderr, NULL);
+ return 0;
+}
+
+/*
+ config_sig_term
+ Unlink pidfile and quit
+*/
+static void
+config_sig_term(int arg)
+{
+ static int i=0;
+ if (i++ == 0)
+ clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
+ __PROGRAM__, __FUNCTION__, getpid(), arg);
+ clicon_exit_set(); /* checked in event_loop() */
+}
+
+/*
+ * usage
+ */
+static void
+usage(char *argv0, clicon_handle h)
+{
+ char *plgdir = clicon_backend_dir(h);
+ char *confsock = clicon_sock(h);
+ char *confpid = clicon_backend_pidfile(h);
+ char *startup = clicon_startup_config(h);
+ char *group = clicon_sock_group(h);
+
+ fprintf(stderr, "usage:%s\n"
+ "where options are\n"
+ " -h\t\tHelp\n"
+ " -D \tdebug\n"
+ " -f \tCLICON config file (mandatory)\n"
+ " -d \tSpecify backend plugin directory (default: %s)\n"
+ " -z\t\tKill other config daemon and exit\n"
+ " -F\t\tforeground\n"
+ " -1\t\tonce (dont wait for events)\n"
+ " -u \tconfig UNIX domain path / ip address (default: %s)\n"
+ " -P \tPid filename (default: %s)\n"
+ " -I\t\tInitialize running state database\n"
+ " -R\t\tCall plugin_reset() in plugins to reset system state in running db (use with -I)\n"
+ " -C\t\tCall plugin_reset() in plugins to reset system state in candidate db (use with -I)\n"
+ " -c []\tLoad specified application config. Default is\n"
+ " \t\"CLICON_STARTUP_CONFIG\" = %s\n"
+ " -r\t\tReload running database\n"
+ " -p \t\tPrint database yang specification\n"
+ " -t \t\tPrint alternate spec translation (eg if YANG print KEY, if KEY print YANG)\n"
+ " -g \tClient membership required to this group (default: %s)\n",
+ argv0,
+ plgdir ? plgdir : "none",
+ confsock ? confsock : "none",
+ confpid ? confpid : "none",
+ startup ? startup : "none",
+ group ? group : "none"
+ );
+ exit(0);
+}
+
+static int
+rundb_init(clicon_handle h, char *running_db)
+{
+ if (unlink(running_db) != 0 && errno != ENOENT) {
+ clicon_err(OE_UNIX, errno, "unlink");
+ return -1;
+ }
+ if (db_init(running_db) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*! Initialize running-config from file application configuration
+ *
+ * @param[in] h clicon handle
+ * @param[in] app_config_file clicon application configuration file
+ * @param[in] running_db Name of running db
+ * @retval 0 OK
+ * @retval -1 Error. clicon_err set
+ */
+static int
+rundb_main(clicon_handle h,
+ char *app_config_file,
+ char *running_db)
+{
+ char *tmp = NULL;
+ int retval = -1;
+ int fd = -1;
+ yang_spec *yspec;
+ cxobj *xt = NULL;
+ cxobj *xn;
+
+ if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
+ goto done;
+ if (file_cp(running_db, tmp) < 0){
+ clicon_err(OE_UNIX, errno, "file copy");
+ goto done;
+ }
+ if ((fd = open(app_config_file, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "open(%s)", app_config_file);
+ goto done;
+ }
+ if (clicon_xml_parse_file(fd, &xt, "") < 0)
+ goto done;
+ yspec = clicon_dbspec_yang(h);
+ if ((xn = xml_child_i(xt, 0)) != NULL)
+ if (xmldb_put(tmp, xn, yspec, OP_MERGE) < 0)
+ goto done;
+ if (candidate_commit(h, tmp, running_db) < 0)
+ goto done;
+ retval = 0;
+done:
+ if (tmp)
+ unlink(tmp);
+ if (xt)
+ xml_free(xt);
+ if (fd != -1)
+ close(fd);
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+
+static int
+candb_reset(clicon_handle h, char *running_db)
+{
+ int retval = -1;
+ char *tmp = NULL;
+
+ if ((tmp = clicon_tmpfile(__FUNCTION__)) == NULL)
+ goto done;
+ if (file_cp(running_db, tmp) < 0){
+ clicon_err(OE_UNIX, errno, "file copy");
+ goto done;
+ }
+ /* Request plugins to reset system state, eg initiate running from system
+ * -R
+ */
+ if (plugin_reset_state(h, tmp) < 0)
+ goto done;
+ if (candidate_commit(h, tmp, running_db) < 0)
+ goto done;
+ retval = 0;
+ done:
+ if (tmp)
+ unlink(tmp);
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+
+/*! Create backend server socket and register callback
+ */
+static int
+server_socket(clicon_handle h)
+{
+ int ss;
+
+ /* Open control socket */
+ if ((ss = config_socket_init(h)) < 0)
+ return -1;
+ /* ss is a server socket that the clients connect to. The callback
+ therefore accepts clients on ss */
+ if (event_reg_fd(ss, config_accept_client, h, "server socket") < 0) {
+ close(ss);
+ return -1;
+ }
+ return ss;
+}
+
+/*! Callback for CLICON log events
+ * If you make a subscription to CLICON stream, this function is called for every
+ * log event.
+ */
+static int
+config_log_cb(int level, char *msg, void *arg)
+{
+ size_t n;
+ char *ptr;
+ char *nptr;
+ char *newmsg = NULL;
+ int retval = -1;
+
+ /* backend_notify() will go through all clients and see if any has registered "CLICON",
+ and if so make a clicon_proto notify message to those clients. */
+
+
+ /* Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
+ At this stage all formatting is already done */
+ n = 0;
+ for(ptr=msg; *ptr; ptr++)
+ if (*ptr == '%')
+ n++;
+ if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) {
+ clicon_err(OE_UNIX, errno, "malloc");
+ return -1;
+ }
+ for(ptr=msg, nptr=newmsg; *ptr; ptr++) {
+ *nptr++ = *ptr;
+ if (*ptr == '%')
+ *nptr++ = '%';
+ }
+
+ retval = backend_notify(arg, "CLICON", level, newmsg);
+ free(newmsg);
+
+ return retval;
+}
+
+int
+main(int argc, char **argv)
+{
+ char c;
+ int zap;
+ int foreground;
+ int once;
+ int init_rundb;
+ char *running_db;
+ char *candidate_db;
+ int reload_running;
+ int reset_state_running;
+ int reset_state_candidate;
+ char *app_config_file = NULL;
+ char *config_group;
+ char *argv0 = argv[0];
+ char *tmp;
+ struct stat st;
+ clicon_handle h;
+ int help = 0;
+ int printspec = 0;
+ int printalt = 0;
+ int pid;
+ char *pidfile;
+ char *sock;
+ int sockfamily;
+
+ /* In the startup, logs to stderr & syslog and debug flag set later */
+ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
+ /* Initiate CLICON handle */
+ if ((h = backend_handle_init()) == NULL)
+ return -1;
+ if (config_plugin_init(h) != 0)
+ return -1;
+ foreground = 0;
+ once = 0;
+ zap = 0;
+ init_rundb = 0;
+ reload_running = 0;
+ reset_state_running = 0;
+ reset_state_candidate = 0;
+
+ /*
+ * Command-line options for help, debug, and config-file
+ */
+ opterr = 0;
+ optind = 1;
+ while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
+ switch (c) {
+ case '?':
+ case 'h':
+ /* Defer the call to usage() to later. Reason is that for helpful
+ text messages, default dirs, etc, are not set until later.
+ But this measn that we need to check if 'help' is set before
+ exiting, and then call usage() before exit.
+ */
+ help = 1;
+ break;
+ case 'D' : /* debug */
+ if (sscanf(optarg, "%d", &debug) != 1)
+ usage(argv[0], h);
+ break;
+ case 'f': /* config file */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
+ break;
+ }
+ /*
+ * Syslogs also to stderr, but later turn stderr off in daemon mode.
+ * error only to syslog. debug to syslog
+ */
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
+ clicon_debug_init(debug, NULL);
+
+ /* Find and read configfile */
+ if (clicon_options_main(h) < 0){
+ if (help)
+ usage(argv[0], h);
+ return -1;
+ }
+
+ /* Now run through the operational args */
+ opterr = 1;
+ optind = 1;
+ while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
+ switch (c) {
+ case 'D' : /* debug */
+ case 'f': /* config file */
+ break; /* see above */
+ case 'd': /* Plugin directory */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_BACKEND_DIR", optarg);
+ break;
+ case 'F' : /* foreground */
+ foreground = 1;
+ break;
+ case '1' : /* Quit after reading database once - dont wait for events */
+ once = 1;
+ break;
+ case 'z': /* Zap other process */
+ zap++;
+ break;
+ case 'u': /* config unix domain path / ip address */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_SOCK", optarg);
+ break;
+ case 'P': /* pidfile */
+ clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
+ break;
+ case 'I': /* Initiate running db */
+ init_rundb++;
+ break;
+ case 'R': /* Reset state directly into running */
+ reset_state_running++;
+ break;
+ case 'C': /* Reset state into candidate and then commit it */
+ reset_state_candidate++;
+ break;
+ case 'c': /* Load application config */
+ app_config_file = optarg ? optarg : clicon_startup_config(h);
+ if (app_config_file == NULL) {
+ fprintf(stderr, "Option \"CLICON_STARTUP_CONFIG\" not set\n");
+ return -1;
+ }
+ break;
+ case 'r': /* Reload running */
+ reload_running++;
+ break;
+ case 'g': /* config socket group */
+ clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
+ break;
+ case 'p' : /* Print spec */
+ printspec++;
+ break;
+ case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
+ printalt++;
+ break;
+ default:
+ usage(argv[0], h);
+ break;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* Defer: Wait to the last minute to print help message */
+ if (help)
+ usage(argv[0], h);
+
+ /* Check pid-file, if zap kil the old daemon, else return here */
+ if ((pidfile = clicon_backend_pidfile(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "pidfile not set");
+ goto done;
+ }
+ sockfamily = clicon_sock_family(h);
+ if ((sock = clicon_sock(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "sock not set");
+ goto done;
+ }
+ if (pidfile_get(pidfile, &pid) < 0)
+ return -1;
+ if (zap){
+ if (pid && pidfile_zapold(pid) < 0)
+ return -1;
+ if (lstat(pidfile, &st) == 0)
+ unlink(pidfile);
+ if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
+ unlink(sock);
+ exit(0);
+ }
+ else
+ if (pid){
+ clicon_err(OE_DEMON, 0, "Daemon already running with pid %d\n(Try killing it with %s -z)",
+ pid, argv0);
+ return -1; /* goto done deletes pidfile */
+ }
+
+ /* After this point we can goto done on error
+ * Here there is either no old process or we have killed it,..
+ */
+ if (lstat(pidfile, &st) == 0)
+ unlink(pidfile);
+ if (sockfamily==AF_UNIX && lstat(sock, &st) == 0)
+ unlink(sock);
+
+ /* Sanity check: config group exists */
+ if ((config_group = clicon_sock_group(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
+ return -1;
+ }
+
+ if (group_name2gid(config_group, NULL) < 0){
+ clicon_log(LOG_ERR, "'%s' does not seem to be a valid user group.\n"
+ "The config demon requires a valid group to create a server UNIX socket\n"
+ "Define a valid CLICON_SOCK_GROUP in %s or via the -g option\n"
+ "or create the group and add the user to it. On linux for example:"
+ " sudo groupadd %s\n"
+ " sudo usermod -a -G %s user\n",
+ config_group, clicon_configfile(h), config_group, config_group);
+ return -1;
+ }
+
+ /* Parse db spec file */
+ if (yang_spec_main(h, stdout, printspec) < 0)
+ goto done;
+
+ if ((running_db = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto done;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ goto done;
+ }
+ /* If running exists and reload_running set, make a copy to candidate */
+ if (reload_running){
+ if (stat(running_db, &st) && errno == ENOENT){
+ clicon_log(LOG_NOTICE, "%s: -r (reload running) option given but no running_db found, proceeding without", __PROGRAM__);
+ reload_running = 0; /* void it, so we dont commit candidate below */
+ }
+ else
+ if (file_cp(running_db, candidate_db) < 0){
+ clicon_err(OE_UNIX, errno, "FATAL: file_cp");
+ goto done;
+ }
+ }
+ /* Init running db
+ * -I
+ */
+ if (init_rundb || (stat(running_db, &st) && errno == ENOENT))
+ if (rundb_init(h, running_db) < 0)
+ goto done;
+
+ /* Initialize plugins
+ (also calls plugin_init() and plugin_start(argc,argv) in each plugin */
+ if (plugin_initiate(h) != 0)
+ goto done;
+
+ if (reset_state_candidate){
+ if (candb_reset(h, running_db) < 0)
+ goto done;
+ }
+ else
+ if (reset_state_running){
+ if (plugin_reset_state(h, running_db) < 0)
+ goto done;
+ }
+ /* Call plugin_start */
+ tmp = *(argv-1);
+ *(argv-1) = argv0;
+ if (plugin_start_hooks(h, argc+1, argv-1) < 0)
+ goto done;
+ *(argv-1) = tmp;
+
+ if (reload_running){
+ if (candidate_commit(h, candidate_db, running_db) < 0)
+ goto done;
+ }
+
+ /* Have we specified a config file to load? eg
+ -c
+ -r replace running (obsolete)
+ */
+ if (app_config_file)
+ if (rundb_main(h, app_config_file, running_db) < 0)
+ goto done;
+
+ /* Initiate the shared candidate. Maybe we should not do this? */
+ if (file_cp(running_db, candidate_db) < 0){
+ clicon_err(OE_UNIX, errno, "FATAL: file_cp");
+ goto done;
+ }
+ /* XXX Hack for now. Change mode so that we all can write. Security issue*/
+ chmod(candidate_db, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
+
+ if (once)
+ goto done;
+
+ /* Daemonize and initiate logging. Note error is initiated here to make
+ demonized errors OK. Before this stage, errors are logged on stderr
+ also */
+ if (foreground==0){
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
+ if (daemon(0, 0) < 0){
+ fprintf(stderr, "config: daemon");
+ exit(0);
+ }
+ }
+ /* Write pid-file */
+
+ if ((pid = pidfile_write(pidfile)) < 0)
+ goto done;
+
+ /* Register log notifications */
+ if (clicon_log_register_callback(config_log_cb, h) < 0)
+ goto done;
+ clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
+ if (set_signal(SIGTERM, config_sig_term, NULL) < 0){
+ clicon_err(OE_DEMON, errno, "Setting signal");
+ goto done;
+ }
+ if (set_signal(SIGINT, config_sig_term, NULL) < 0){
+ clicon_err(OE_DEMON, errno, "Setting signal");
+ goto done;
+ }
+
+ /* Initialize server socket */
+ if (server_socket(h) < 0)
+ goto done;
+
+ if (debug)
+ clicon_option_dump(h, debug);
+
+ if (event_loop() < 0)
+ goto done;
+ done:
+ clicon_log(LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid());
+ config_terminate(h); /* Cannot use h after this */
+
+ return 0;
+}
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
new file mode 100644
index 00000000..e06743a3
--- /dev/null
+++ b/apps/backend/backend_plugin.c
@@ -0,0 +1,751 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#define __USE_GNU /* strverscmp */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_backend_transaction.h"
+#include "backend_plugin.h"
+#include "backend_commit.h"
+
+/*
+ * Types
+ */
+/* Following are specific to backend. For common see clicon_plugin.h
+ * @note the following should match the prototypes in clicon_backend.h
+ */
+#define PLUGIN_RESET "plugin_reset"
+typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */
+
+#define PLUGIN_TRANS_BEGIN "transaction_begin"
+#define PLUGIN_TRANS_VALIDATE "transaction_validate"
+#define PLUGIN_TRANS_COMPLETE "transaction_complete"
+#define PLUGIN_TRANS_COMMIT "transaction_commit"
+#define PLUGIN_TRANS_END "transaction_end"
+#define PLUGIN_TRANS_ABORT "transaction_abort"
+
+typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */
+
+/* Backend (config) plugins */
+struct plugin {
+ char p_name[PATH_MAX]; /* Plugin name */
+ void *p_handle; /* Dynamic object handle */
+ plginit_t *p_init; /* Init */
+ plgstart_t *p_start; /* Start */
+ plgexit_t *p_exit; /* Exit */
+ plgreset_t *p_reset; /* Reset state */
+ trans_cb_t *p_trans_begin; /* Transaction start */
+ trans_cb_t *p_trans_validate; /* Transaction validation */
+ trans_cb_t *p_trans_complete; /* Transaction validation complete */
+ trans_cb_t *p_trans_commit; /* Transaction commit */
+ trans_cb_t *p_trans_end; /* Transaction completed */
+ trans_cb_t *p_trans_abort; /* Transaction aborted */
+};
+
+/*
+ * Local variables
+ */
+static int nplugins = 0;
+static struct plugin *plugins = NULL;
+
+/*! Find a plugin by name and return the dlsym handl
+ * Used by libclicon code to find callback funcctions in plugins.
+ * @param[in] h Clicon handle
+ * @param[in] h Name of plugin
+ * @retval handle Plugin handle if found
+ * @retval NULL Not found
+ */
+static void *
+config_find_plugin(clicon_handle h,
+ char *name)
+{
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++){
+ p = &plugins[i];
+ if (strcmp(p->p_name, name) == 0)
+ return p->p_handle;
+ }
+ return NULL;
+}
+
+/*! Initialize plugin code (not the plugins themselves)
+ * @param[in] h Clicon handle
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+config_plugin_init(clicon_handle h)
+{
+ find_plugin_t *fp = config_find_plugin;
+ clicon_hash_t *data = clicon_data(h);
+
+ /* Register CLICON_FIND_PLUGIN in data hash */
+ if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) {
+ clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN");
+ return -1;
+ }
+ return 0;
+}
+
+/*! Unload a plugin
+ * @param[in] h Clicon handle
+ * @param[in] plg Plugin structure
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+plugin_unload(clicon_handle h,
+ struct plugin *plg)
+{
+ char *error;
+
+ /* Call exit function is it exists */
+ if (plg->p_exit)
+ plg->p_exit(h);
+
+ dlerror(); /* Clear any existing error */
+ if (dlclose(plg->p_handle) != 0) {
+ error = (char*)dlerror();
+ clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error");
+ return -1;
+ /* Just report */
+ }
+ else
+ clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name);
+ return 0;
+}
+
+
+/*! Load a dynamic plugin and call its init-function
+ * @param[in] h Clicon handle
+ * @param[in] file The plugin (.so) to load
+ * @param[in] dlflags Arguments to dlopen(3)
+ * @param[in] label Chunk label
+ * @retval plugin Plugin struct
+ * @retval NULL Error
+ */
+static struct plugin *
+plugin_load (clicon_handle h,
+ char *file,
+ int dlflags,
+ const char *label)
+{
+ char *error;
+ void *handle;
+ char *name;
+ struct plugin *new;
+ plginit_t *initfun;
+
+ dlerror(); /* Clear any existing error */
+ if ((handle = dlopen (file, dlflags)) == NULL) {
+ error = (char*)dlerror();
+ clicon_err(OE_UNIX, 0, "dlopen: %s", error?error:"Unknown error");
+ return NULL;
+ }
+
+ initfun = dlsym(handle, PLUGIN_INIT);
+ if ((error = (char*)dlerror()) != NULL) {
+ clicon_err(OE_UNIX, 0, "dlsym: %s", error);
+ return NULL;
+ }
+
+ if (initfun(h) != 0) {
+ dlclose(handle);
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error",
+ file);
+ return NULL;
+ }
+
+ if ((new = chunk(sizeof(*new), label)) == NULL) {
+ clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno));
+ dlclose(handle);
+ return NULL;
+ }
+ memset(new, 0, sizeof(*new));
+ name = strrchr(file, '/') ? strrchr(file, '/')+1 : file;
+ clicon_debug(2, "Loading plugin '%s'.", name);
+ snprintf(new->p_name, sizeof(new->p_name), "%*s",
+ (int)strlen(name)-2, name);
+ new->p_handle = handle;
+ new->p_init = initfun;
+ if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_START);
+ if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_EXIT);
+ if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_RESET);
+ if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN);
+ if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE);
+ if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE);
+ if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT);
+ if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END);
+ if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL)
+ clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT);
+ clicon_debug(2, "Plugin '%s' loaded.\n", name);
+
+ return new;
+}
+
+/*! Request plugins to reset system state
+ * The system 'state' should be the same as the contents of running_db
+ * @param[in] h Clicon handle
+ * @param[in] dbname Name of database
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_reset_state(clicon_handle h,
+ char *dbname)
+{
+ int i;
+ struct plugin *p;
+
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ if (p->p_reset) {
+ clicon_debug(1, "Calling plugin_reset() for %s\n",
+ p->p_name);
+ if (((p->p_reset)(h, dbname)) < 0) {
+ clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n",
+ p->p_name);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*! Call plugin_start in all plugins
+ * @param[in] h Clicon handle
+ * @param[in] argc Command-line arguments
+ * @param[in] argv Command-line arguments
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_start_hooks(clicon_handle h,
+ int argc,
+ char **argv)
+{
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ if (p->p_start) {
+ optind = 0;
+ if (((p->p_start)(h, argc, argv)) < 0) {
+ clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n",
+ p->p_name);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*! Append plugin to list
+ * @param[in] p Plugin
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+plugin_append(struct plugin *p)
+{
+ struct plugin *new;
+
+ if ((new = rechunk(plugins, (nplugins+1) * sizeof (*p), NULL)) == NULL) {
+ clicon_err(OE_UNIX, errno, "chunk");
+ return -1;
+ }
+
+ memset (&new[nplugins], 0, sizeof(new[nplugins]));
+ memcpy (&new[nplugins], p, sizeof(new[nplugins]));
+ plugins = new;
+ nplugins++;
+
+ return 0;
+}
+
+/*! Load backend plugins found in a directory
+ * The plugins must have the '.so' suffix
+ * @param[in] h Clicon handle
+ * @param[in] dir Backend plugin directory
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+static int
+config_plugin_load_dir(clicon_handle h,
+ const char *dir)
+{
+ int retval = -1;
+ int i;
+ int np = 0;
+ int ndp;
+ struct stat st;
+ char *filename;
+ struct dirent *dp;
+ struct plugin *new;
+ struct plugin *p = NULL;
+ char *master;
+ char *master_plugin;
+
+ /* Format master plugin path */
+ if ((master_plugin = clicon_master_plugin(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set");
+ goto quit;
+ }
+ master = chunk_sprintf(__FUNCTION__, "%s.so", master_plugin);
+ if (master == NULL) {
+ clicon_err(OE_PLUGIN, errno, "chunk_sprintf master plugin");
+ goto quit;
+ }
+
+ /* Allocate plugin group object */
+ /* Get plugin objects names from plugin directory */
+ if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG, __FUNCTION__))<0)
+ goto quit;
+
+ /* reset num plugins */
+ np = 0;
+
+ /* Master plugin must be loaded first if it exists. */
+ filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, master);
+ if (filename == NULL) {
+ clicon_err(OE_UNIX, errno, "chunk");
+ goto quit;
+ }
+ if (stat(filename, &st) == 0) {
+ clicon_debug(1, "Loading master plugin '%.*s' ...",
+ (int)strlen(filename), filename);
+
+ new = plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL, __FUNCTION__);
+ if (new == NULL)
+ goto quit;
+ if (plugin_append(new) < 0)
+ goto quit;
+ }
+
+ /* Now load the rest */
+ for (i = 0; i < ndp; i++) {
+ if (strcmp(dp[i].d_name, master) == 0)
+ continue; /* Skip master now */
+ filename = chunk_sprintf(__FUNCTION__, "%s/%s", dir, dp[i].d_name);
+ clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename);
+ if (filename == NULL) {
+ clicon_err(OE_UNIX, errno, "chunk");
+ goto quit;
+ }
+ new = plugin_load (h, filename, RTLD_NOW, __FUNCTION__);
+ if (new == NULL)
+ goto quit;
+ if (plugin_append(new) < 0)
+ goto quit;
+ }
+
+ /* All good. */
+ retval = 0;
+
+quit:
+ if (retval != 0) {
+ if (p) {
+ while (--np >= 0)
+ plugin_unload (h, &p[np]);
+ unchunk(p);
+ }
+ }
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+
+/*! Load a plugin group.
+ * @param[in] h Clicon handle
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_initiate(clicon_handle h)
+{
+ char *dir;
+
+ /* First load CLICON system plugins */
+ if (config_plugin_load_dir(h, CLICON_BACKEND_SYSDIR) < 0)
+ return -1;
+
+ /* Then load application plugins */
+ if ((dir = clicon_backend_dir(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "backend_dir not defined");
+ return -1;
+ }
+ if (config_plugin_load_dir(h, dir) < 0)
+ return -1;
+
+ return 0;
+}
+
+/*! Unload and deallocate all backend plugins
+ * @param[in] h Clicon handle
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_finish(clicon_handle h)
+{
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ plugin_unload(h, p);
+ }
+ if (plugins)
+ unchunk(plugins);
+ nplugins = 0;
+ return 0;
+}
+
+/*! Call from frontend to function 'func' in plugin 'plugin'.
+ * Plugin function is supposed to populate 'retlen' and 'retarg' where
+ * 'retarg' is malloc:ed data if non-NULL.
+ * @param[in] h Clicon handle
+ * @param[in] req Clicon message containing information about the downcall
+ * @param[out] retlen Length of return value
+ * @param[out] ret Return value
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_downcall(clicon_handle h,
+ struct clicon_msg_call_req *req,
+ uint16_t *retlen,
+ void **retarg)
+{
+ int retval = -1;
+ int i;
+ downcall_cb funcp;
+ char name[PATH_MAX];
+ char *error;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ strncpy(name, p->p_name, sizeof(name)-1);
+ if (!strcmp(name+strlen(name)-3, ".so"))
+ name[strlen(name)-3] = '\0';
+ /* If no plugin is given or the plugin-name matches */
+ if (req->cr_plugin == NULL || strlen(req->cr_plugin)==0 ||
+ strcmp(name, req->cr_plugin) == 0) {
+ funcp = dlsym(p->p_handle, req->cr_func);
+ if ((error = (char*)dlerror()) != NULL) {
+ clicon_err(OE_PROTO, ENOENT,
+ "Function does not exist: %s()", req->cr_func);
+ return -1;
+ }
+ retval = funcp(h, req->cr_op, req->cr_arglen, req->cr_arg, retlen, retarg);
+ goto done;
+ }
+ }
+ clicon_err(OE_PROTO, ENOENT,"%s: %s(): Plugin does not exist: %s",
+ __FUNCTION__, req->cr_func, req->cr_plugin);
+ return -1;
+
+done:
+ return retval;
+}
+
+/*! Create and initialize transaction */
+transaction_data_t *
+transaction_new(void)
+{
+ transaction_data_t *td;
+ static uint64_t id = 0; /* Global transaction id */
+
+ if ((td = malloc(sizeof(*td))) == NULL){
+ clicon_err(OE_CFG, errno, "malloc");
+ return NULL;
+ }
+ memset(td, 0, sizeof(*td));
+ td->td_id = id++;
+ return td;
+}
+
+/*! Free transaction structure */
+int
+transaction_free(transaction_data_t *td)
+{
+ if (td->td_src)
+ xml_free(td->td_src);
+ if (td->td_target)
+ xml_free(td->td_target);
+ if (td->td_dvec)
+ free(td->td_dvec);
+ if (td->td_avec)
+ free(td->td_avec);
+ if (td->td_scvec)
+ free(td->td_scvec);
+ if (td->td_tcvec)
+ free(td->td_tcvec);
+ free(td);
+ return 0;
+}
+
+/* The plugin_transaction routines need access to struct plugin which is local to this file */
+
+/*! Call transaction_begin() in all plugins before a validate/commit.
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK
+ * @retval -1 Error: one of the plugin callbacks returned error
+ */
+int
+plugin_transaction_begin(clicon_handle h,
+ transaction_data_t *td)
+{
+ int i;
+ int retval = 0;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ if (p->p_trans_begin)
+ if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
+ __FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN);
+
+ break;
+ }
+
+ }
+ return retval;
+}
+
+/*! Call transaction_validate callbacks in all backend plugins
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK. Validation succeeded in all plugins
+ * @retval -1 Error: one of the plugin callbacks returned validation fail
+ */
+int
+plugin_transaction_validate(clicon_handle h,
+ transaction_data_t *td)
+{
+ int retval = 0;
+ int i;
+
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++){
+ p = &plugins[i];
+ if (p->p_trans_validate)
+ if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
+ __FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE);
+
+ break;
+ }
+ }
+ return retval;
+}
+
+/*! Call transaction_complete() in all plugins after validation (before commit)
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK
+ * @retval -1 Error: one of the plugin callbacks returned error
+ * @note Call plugins which have commit dependencies?
+ * @note Rename to transaction_complete?
+ */
+int
+plugin_transaction_complete(clicon_handle h,
+ transaction_data_t *td)
+{
+ int i;
+ int retval = 0;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++){
+ p = &plugins[i];
+ if (p->p_trans_complete)
+ if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
+ __FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE);
+
+ break;
+ }
+ }
+ return retval;
+}
+
+int
+plugin_transaction_revert(clicon_handle h,
+ transaction_data_t *td,
+ int nr)
+{
+ int retval = 0;
+ transaction_data_t tr; /* revert transaction */
+ int i;
+ struct plugin *p;
+
+ /* Create a new reversed transaction from the original where src and target
+ are swapped */
+ memcpy(&tr, td, sizeof(tr));
+ tr.td_src = td->td_target;
+ tr.td_target = td->td_src;
+ tr.td_dlen = td->td_alen;
+ tr.td_dvec = td->td_avec;
+ tr.td_alen = td->td_dlen;
+ tr.td_avec = td->td_dvec;
+ tr.td_scvec = td->td_tcvec;
+ tr.td_tcvec = td->td_scvec;
+
+ for (i = nr-1; i; i--){
+ p = &plugins[i];
+ if (p->p_trans_commit)
+ if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){
+ clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed",
+ p->p_name, PLUGIN_TRANS_COMMIT);
+ break;
+ }
+ }
+ return retval; /* ignore errors */
+}
+
+/*! Call transaction_commit callbacks in all backend plugins
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK
+ * @retval -1 Error: one of the plugin callbacks returned error
+ * If any of the commit callbacks fail by returning -1, a revert of the
+ * transaction is tried by calling the commit callbacsk with reverse arguments
+ * and in reverse order.
+ */
+int
+plugin_transaction_commit(clicon_handle h,
+ transaction_data_t *td)
+{
+ int retval = 0;
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++){
+ p = &plugins[i];
+ if (p->p_trans_commit)
+ if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
+ __FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT);
+ /* Make an effort to revert transaction */
+ plugin_transaction_revert(h, td, i);
+ break;
+ }
+ }
+ return retval;
+}
+
+/*! Call transaction_end() in all plugins after a successful commit.
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_transaction_end(clicon_handle h,
+ transaction_data_t *td)
+{
+ int retval = 0;
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ if (p->p_trans_end)
+ if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){
+ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */
+ clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error",
+ __FUNCTION__, p->p_name, PLUGIN_TRANS_END);
+
+ break;
+ }
+ }
+ return retval;
+}
+
+/*! Call transaction_abort() in all plugins after a failed validation/commit.
+ * @param[in] h Clicon handle
+ * @param[in] td Transaction data
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+plugin_transaction_abort(clicon_handle h,
+ transaction_data_t *td)
+{
+ int retval = 0;
+ int i;
+ struct plugin *p;
+
+ for (i = 0; i < nplugins; i++) {
+ p = &plugins[i];
+ if (p->p_trans_abort)
+ (p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */
+ }
+ return retval;
+}
+
+
diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h
new file mode 100644
index 00000000..f7707b4b
--- /dev/null
+++ b/apps/backend/backend_plugin.h
@@ -0,0 +1,72 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifndef _BACKEND_PLUGIN_H_
+#define _BACKEND_PLUGIN_H_
+
+/*
+ * Types
+ */
+
+
+/*! Transaction data
+ * Clicon internal, presented as void* to app's callback in the 'transaction_data'
+ * type in clicon_backend_api.h
+ * XXX: move to .c file?
+ */
+typedef struct {
+ uint64_t td_id; /* Transaction id */
+ void *td_arg; /* Callback argument */
+ cxobj *td_src; /* Source database xml tree */
+ cxobj *td_target; /* Target database xml tree */
+ cxobj **td_dvec; /* Delete xml vector */
+ size_t td_dlen; /* Delete xml vector length */
+ cxobj **td_avec; /* Add xml vector */
+ size_t td_alen; /* Add xml vector length */
+ cxobj **td_scvec; /* Source changed xml vector */
+ cxobj **td_tcvec; /* Target changed xml vector */
+ size_t td_clen; /* Changed xml vector length */
+} transaction_data_t;
+
+/*
+ * Prototypes
+ */
+int config_plugin_init(clicon_handle h);
+int plugin_initiate(clicon_handle h);
+int plugin_finish(clicon_handle h);
+
+int plugin_reset_state(clicon_handle h, char *dbname);
+int plugin_start_hooks(clicon_handle h, int argc, char **argv);
+int plugin_downcall(clicon_handle h, struct clicon_msg_call_req *req,
+ uint16_t *retlen, void **retarg);
+
+transaction_data_t * transaction_new(void);
+int transaction_free(transaction_data_t *);
+
+int plugin_transaction_begin(clicon_handle h, transaction_data_t *td);
+int plugin_transaction_validate(clicon_handle h, transaction_data_t *td);
+int plugin_transaction_complete(clicon_handle h, transaction_data_t *td);
+int plugin_transaction_commit(clicon_handle h, transaction_data_t *td);
+int plugin_transaction_end(clicon_handle h, transaction_data_t *td);
+int plugin_transaction_abort(clicon_handle h, transaction_data_t *td);
+
+#endif /* _BACKEND_PLUGIN_H_ */
diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c
new file mode 100644
index 00000000..5de10cbd
--- /dev/null
+++ b/apps/backend/backend_socket.c
@@ -0,0 +1,262 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef HAVE_SYS_UCRED_H
+#include
+#include
+#endif
+#define __USE_GNU
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+/* clicon */
+#include
+
+#include "backend_socket.h"
+#include "backend_client.h"
+#include "backend_handle.h"
+
+static int
+config_socket_init_ipv4(clicon_handle h, char *dst)
+{
+ int s;
+ struct sockaddr_in addr;
+ uint16_t port;
+
+ port = clicon_sock_port(h);
+
+ /* create inet socket */
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ clicon_err(OE_UNIX, errno, "socket");
+ return -1;
+ }
+// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ if (inet_pton(addr.sin_family, dst, &addr.sin_addr) != 1){
+ clicon_err(OE_UNIX, errno, "inet_pton: %s (Expected IPv4 address. Check settings of CLICON_SOCK_FAMILY and CLICON_SOCK)", dst);
+ goto err; /* Could check getaddrinfo */
+ }
+ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
+ goto err;
+ }
+ clicon_debug(1, "Listen on server socket at %s:%hu", dst, port);
+ if (listen(s, 5) < 0){
+ clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
+ goto err;
+ }
+ return s;
+ err:
+ close(s);
+ return -1;
+}
+
+/*! Open a socket and bind it to a file descriptor
+ *
+ * The socket is accessed via CLICON_SOCK option, has 770 permissions
+ * and group according to CLICON_SOCK_GROUP option.
+ */
+static int
+config_socket_init_unix(clicon_handle h, char *sock)
+{
+ int s;
+ struct sockaddr_un addr;
+ mode_t old_mask;
+ char *config_group;
+ gid_t gid;
+ struct stat st;
+
+ if (lstat(sock, &st) == 0 && unlink(sock) < 0){
+ clicon_err(OE_UNIX, errno, "%s: unlink(%s)", __FUNCTION__, sock);
+ return -1;
+ }
+ /* then find configuration group (for clients) and find its groupid */
+ if ((config_group = clicon_sock_group(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
+ return -1;
+ }
+ if (group_name2gid(config_group, &gid) < 0)
+ return -1;
+#if 0
+ if (gid == 0)
+ clicon_log(LOG_WARNING, "%s: No such group: %s\n", __FUNCTION__, config_group);
+#endif
+ /* create unix socket */
+ if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ clicon_err(OE_UNIX, errno, "%s: socket", __FUNCTION__);
+ return -1;
+ }
+// setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof(one));
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1);
+ old_mask = umask(S_IRWXO | S_IXGRP | S_IXUSR);
+ if (bind(s, (struct sockaddr *)&addr, SUN_LEN(&addr)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
+ umask(old_mask);
+ goto err;
+ }
+ umask(old_mask);
+ /* change socket path file group */
+ if (lchown(sock, -1, gid) < 0){
+ clicon_err(OE_UNIX, errno, "%s: lchown(%s, %s)", __FUNCTION__,
+ sock, config_group);
+ goto err;
+ }
+ clicon_debug(1, "Listen on server socket at %s", addr.sun_path);
+ if (listen(s, 5) < 0){
+ clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
+ goto err;
+ }
+ return s;
+ err:
+ close(s);
+ return -1;
+}
+
+int
+config_socket_init(clicon_handle h)
+{
+ char *sock;
+
+ if ((sock = clicon_sock(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
+ return -1;
+ }
+ switch (clicon_sock_family(h)){
+ case AF_UNIX:
+ return config_socket_init_unix(h, sock);
+ break;
+ case AF_INET:
+ return config_socket_init_ipv4(h, sock);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * config_accept_client
+ * XXX: credentials not properly implemented
+ */
+int
+config_accept_client(int fd, void *arg)
+{
+ int retval = -1;
+ clicon_handle h = (clicon_handle)arg;
+ int s;
+ struct sockaddr_un from;
+ socklen_t len;
+ struct client_entry *ce;
+#ifdef DONT_WORK /* XXX HAVE_SYS_UCRED_H */
+ struct xucred credentials; /* FreeBSD. */
+ socklen_t clen;
+#elif defined(SO_PEERCRED)
+ struct ucred credentials; /* Linux. */
+ socklen_t clen;
+#endif
+ char *config_group;
+ struct group *gr;
+ char *mem;
+ int i;
+
+ clicon_debug(1, "%s", __FUNCTION__);
+ len = sizeof(from);
+ if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__);
+ goto done;
+ }
+#if defined(SO_PEERCRED)
+ /* fill in the user data structure */
+ clen = sizeof(credentials);
+ if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){
+ clicon_err(OE_UNIX, errno, "%s: getsockopt", __FUNCTION__);
+ goto done;
+ }
+#endif
+ if ((ce = backend_client_add(h, (struct sockaddr*)&from)) == NULL)
+ goto done;
+#if defined(SO_PEERCRED)
+ ce->ce_pid = credentials.pid;
+ ce->ce_uid = credentials.uid;
+#endif
+ ce->ce_handle = h;
+
+ /* check credentials of caller (not properly implemented yet) */
+ if ((config_group = clicon_sock_group(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "clicon_sock_group option not set");
+ goto done;
+ }
+ if ((gr = getgrnam(config_group)) != NULL){
+ i = 0; /* one of mem should correspond to ce->ce_uid */
+ while ((mem = gr->gr_mem[i++]) != NULL)
+ ;
+ }
+
+#if 0
+ { /* XXX */
+ int ii;
+ struct client_entry *c;
+ for (c = ce_list, ii=0; c; c = c->ce_next, ii++);
+ clicon_debug(1, "Open client socket (nr:%d pid:%d [Total: %d])",
+ ce->ce_nr, ce->ce_pid, ii);
+ }
+#endif
+ ce->ce_s = s;
+
+ /*
+ * Here we register callbacks for actual data socket
+ */
+ if (event_reg_fd(s, from_client, (void*)ce, "client socket") < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+
diff --git a/apps/backend/backend_socket.h b/apps/backend/backend_socket.h
new file mode 100644
index 00000000..1af58ba8
--- /dev/null
+++ b/apps/backend/backend_socket.h
@@ -0,0 +1,33 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+
+#ifndef _BACKEND_SOCKET_H_
+#define _BACKEND_SOCKET_H_
+
+/*
+ * Prototypes
+ */
+int config_socket_init(clicon_handle h);
+int config_accept_client(int fd, void *arg);
+
+#endif /* _BACKEND_SOCKET_H_ */
diff --git a/apps/backend/clicon_backend.h b/apps/backend/clicon_backend.h
new file mode 100644
index 00000000..c49f1532
--- /dev/null
+++ b/apps/backend/clicon_backend.h
@@ -0,0 +1,92 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ * The exported interface to plugins. External apps (eg backend plugins) should
+ * only include this file.
+ * Internal code should not include this file
+ */
+
+#ifndef _CLICON_BACKEND_H_
+#define _CLICON_BACKEND_H_
+
+/*
+ * Use this constant to disable some prototypes that should not be visible outside the lib.
+ * This is an alternative to use separate internal include files.
+ */
+
+/* Common code (API and Backend daemon) */
+#include
+#include
+
+/*! Clicon Backend plugin callbacks: use these in your backend plugin code
+ */
+
+/*! Called when plugin loaded. Only mandadory callback. All others optional
+ * @see plginit_t
+ */
+int plugin_init(clicon_handle h);
+
+/* Called when backend started with cmd-line arguments from daemon call.
+ * @see plgstart_t
+ */
+int plugin_start(clicon_handle h, int argc, char **argv);
+
+/* Called just before plugin unloaded.
+ * @see plgexit_t
+ */
+int plugin_exit(clicon_handle h);
+
+/*! Reset system state to original state. Eg at reboot before running thru config.
+ * @see plgreset_t
+ */
+int plugin_reset(clicon_handle h, char *dbname);
+
+/*! Called before a commit/validate sequence begins. Eg setup state before commit
+ * @see trans_cb_t
+ */
+int transaction_begin(clicon_handle h, transaction_data td);
+
+/*! Validate.
+ * @see trans_cb_t
+ */
+int transaction_validate(clicon_handle h, transaction_data td);
+
+/* Called after a validation completed succesfully (but before commit).
+ * @see trans_cb_t
+ */
+int transaction_complete(clicon_handle h, transaction_data td);
+
+/* Commit.
+ * @see trans_cb_t
+ */
+int transaction_commit(clicon_handle h, transaction_data td);
+
+/* Called after a commit sequence completed succesfully.
+ * @see trans_cb_t
+ */
+int transaction_end(clicon_handle h, transaction_data td);
+
+/* Called if commit or validate sequence fails. After eventual rollback.
+ * @see trans_cb_t
+ */
+int transaction_abort(clicon_handle h, transaction_data td);
+
+#endif /* _CLICON_BACKEND_H_ */
diff --git a/apps/backend/clicon_backend_handle.c b/apps/backend/clicon_backend_handle.c
new file mode 100644
index 00000000..c12f86f5
--- /dev/null
+++ b/apps/backend/clicon_backend_handle.c
@@ -0,0 +1,353 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_backend_handle.h"
+#include "backend_client.h"
+#include "backend_handle.h"
+
+/* header part is copied from struct clicon_handle in lib/src/clicon_handle.c */
+
+#define CLICON_MAGIC 0x99aafabe
+
+#define handle(h) (assert(clicon_handle_check(h)==0),(struct backend_handle *)(h))
+
+/* Clicon_handle for backends.
+ * First part of this is header, same for clicon_handle and cli_handle.
+ * Access functions for common fields are found in clicon lib: clicon_options.[ch]
+ * This file should only contain access functions for the _specific_
+ * entries in the struct below.
+ */
+struct backend_handle {
+ int cb_magic; /* magic (HDR)*/
+ clicon_hash_t *cb_copt; /* clicon option list (HDR) */
+ clicon_hash_t *cb_data; /* internal clicon data (HDR) */
+ /* ------ end of common handle ------ */
+ struct client_entry *cb_ce_list; /* The client list */
+ int cb_ce_nr; /* Number of clients, just increment */
+ struct handle_subscription *cb_subscription; /* Event subscription list */
+};
+
+/*! Creates and returns a clicon config handle for other CLICON API calls
+ */
+clicon_handle
+backend_handle_init(void)
+{
+ return clicon_handle_init0(sizeof(struct backend_handle));
+}
+
+/*! Deallocates a backend handle, including all client structs
+ * @Note: handle 'h' cannot be used in calls after this
+ */
+int
+backend_handle_exit(clicon_handle h)
+{
+ struct client_entry *ce;
+
+ /* only delete client structs, not close sockets, etc, see backend_client_rm */
+ while ((ce = backend_client_list(h)) != NULL)
+ backend_client_delete(h, ce);
+ clicon_handle_exit(h); /* frees h and options */
+ return 0;
+}
+
+/*! Notify event and distribute to all registered clients
+ *
+ * @param[in] h Clicon handle
+ * @param[in] stream Name of event stream. CLICON is predefined as LOG stream
+ * @param[in] level Event level (not used yet)
+ * @param[in] event Actual message as text format
+ *
+ * Stream is a string used to qualify the event-stream. Distribute the
+ * event to all clients registered to this backend.
+ * XXX: event-log NYI.
+ * @see also subscription_add()
+ * @see also backend_notify_xml()
+ */
+int
+backend_notify(clicon_handle h, char *stream, int level, char *event)
+{
+ struct client_entry *ce;
+ struct client_subscription *su;
+ struct handle_subscription *hs;
+ int retval = -1;
+
+ /* First thru all clients(sessions), and all subscriptions and find matches */
+ for (ce = backend_client_list(h); ce; ce = ce->ce_next)
+ for (su = ce->ce_subscription; su; su = su->su_next)
+ if (strcmp(su->su_stream, stream) == 0){
+ if (fnmatch(su->su_filter, event, 0) == 0)
+ if (send_msg_notify(ce->ce_s, level, event) < 0)
+ goto done;
+ }
+ /* Then go thru all global (handle) subscriptions and find matches */
+ hs = NULL;
+ while ((hs = subscription_each(h, hs)) != NULL){
+ if (hs->hs_format != MSG_NOTIFY_TXT)
+ continue;
+ if (strcmp(hs->hs_stream, stream))
+ continue;
+ if (fnmatch(hs->hs_filter, event, 0) == 0)
+ if ((*hs->hs_fn)(h, event, hs->hs_arg) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Notify event and distribute to all registered clients
+ *
+ * @param[in] h Clicon handle
+ * @param[in] stream Name of event stream. CLICON is predefined as LOG stream
+ * @param[in] level Event level (not used yet)
+ * @param[in] event Actual message as xml tree
+ *
+ * Stream is a string used to qualify the event-stream. Distribute the
+ * event to all clients registered to this backend.
+ * XXX: event-log NYI.
+ * @see also subscription_add()
+ * @see also backend_notify()
+ */
+int
+backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x)
+{
+ struct client_entry *ce;
+ struct client_subscription *su;
+ int retval = -1;
+ cbuf *cb = NULL;
+ struct handle_subscription *hs;
+
+ /* Now go thru all clients(sessions), and all subscriptions and find matches */
+ for (ce = backend_client_list(h); ce; ce = ce->ce_next)
+ for (su = ce->ce_subscription; su; su = su->su_next)
+ if (strcmp(su->su_stream, stream) == 0){
+ if (strlen(su->su_filter)==0 || xpath_first(x, su->su_filter) != NULL){
+ if (cb==NULL){
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_PLUGIN, errno, "cbuf_new");
+ goto done;
+ }
+ if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
+ goto done;
+ }
+ if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0)
+ goto done;
+ }
+ }
+ /* Then go thru all global (handle) subscriptions and find matches */
+ /* XXX: x contains name==dk-ore, but filter is
+ id==/[userid=d2d5e46c-c6f9-42f3-9a69-fb52fe60940d] */
+ hs = NULL;
+ while ((hs = subscription_each(h, hs)) != NULL){
+ if (hs->hs_format != MSG_NOTIFY_XML)
+ continue;
+ if (strcmp(hs->hs_stream, stream))
+ continue;
+ if (strlen(hs->hs_filter)==0 || xpath_first(x, hs->hs_filter) != NULL)
+ if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+
+}
+
+struct client_entry *
+backend_client_add(clicon_handle h, struct sockaddr *addr)
+{
+ struct backend_handle *cb = handle(h);
+ struct client_entry *ce;
+
+ if ((ce = (struct client_entry *)malloc(sizeof(*ce))) == NULL){
+ clicon_err(OE_PLUGIN, errno, "malloc");
+ return NULL;
+ }
+ memset(ce, 0, sizeof(*ce));
+ ce->ce_nr = cb->cb_ce_nr++;
+ memcpy(&ce->ce_addr, addr, sizeof(*addr));
+ ce->ce_next = cb->cb_ce_list;
+ cb->cb_ce_list = ce;
+ return ce;
+}
+
+struct client_entry *
+backend_client_list(clicon_handle h)
+{
+ struct backend_handle *cb = handle(h);
+
+ return cb->cb_ce_list;
+}
+
+/*! Actually remove client from list
+ * See also backend_client_rm()
+ */
+int
+backend_client_delete(clicon_handle h, struct client_entry *ce)
+{
+ struct client_entry *c;
+ struct client_entry **ce_prev;
+ struct backend_handle *cb = handle(h);
+
+ ce_prev = &cb->cb_ce_list;
+ for (c = *ce_prev; c; c = c->ce_next){
+ if (c == ce){
+ *ce_prev = c->ce_next;
+ free(ce);
+ break;
+ }
+ ce_prev = &c->ce_next;
+ }
+ return 0;
+}
+
+/*! Add subscription given stream name, callback and argument
+ * @param[in] h Clicon handle
+ * @param[in] stream Name of event stream
+ * @param[in] format Expected format of event, eg text or xml
+ * @param[in] filter Filter to match event, depends on format, eg xpath for xml
+ * @param[in] fn Callback when event occurs
+ * @param[in] arg Argument to use with callback. Also handle when deleting
+ * Note that arg is not a real handle.
+ * @see subscription_delete
+ * @see subscription_each
+ */
+struct handle_subscription *
+subscription_add(clicon_handle h,
+ char *stream,
+ enum format_enum format,
+ char *filter,
+ subscription_fn_t fn,
+ void *arg)
+{
+ struct backend_handle *cb = handle(h);
+ struct handle_subscription *hs = NULL;
+
+ if ((hs = malloc(sizeof(*hs))) == NULL){
+ clicon_err(OE_PLUGIN, errno, "malloc");
+ goto done;
+ }
+ memset(hs, 0, sizeof(*hs));
+ hs->hs_stream = strdup(stream);
+ hs->hs_format = format;
+ hs->hs_filter = strdup(filter);
+ hs->hs_next = cb->cb_subscription;
+ hs->hs_fn = fn;
+ hs->hs_arg = arg;
+ cb->cb_subscription = hs;
+ done:
+ return hs;
+}
+
+/*! Delete subscription given stream name, callback and argument
+ * @param[in] h Clicon handle
+ * @param[in] stream Name of event stream
+ * @param[in] fn Callback when event occurs
+ * @param[in] arg Argument to use with callback and handle
+ * Note that arg is not a real handle.
+ * @see subscription_add
+ * @see subscription_each
+ */
+int
+subscription_delete(clicon_handle h,
+ char *stream,
+ subscription_fn_t fn,
+ void *arg)
+{
+ struct backend_handle *cb = handle(h);
+ struct handle_subscription *hs;
+ struct handle_subscription **hs_prev;
+
+ hs_prev = &cb->cb_subscription; /* this points to stack and is not real backpointer */
+ for (hs = *hs_prev; hs; hs = hs->hs_next){
+ /* XXX arg == hs->hs_arg */
+ if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
+ *hs_prev = hs->hs_next;
+ free(hs->hs_stream);
+ if (hs->hs_filter)
+ free(hs->hs_filter);
+ if (hs->hs_arg)
+ free(hs->hs_arg);
+ free(hs);
+ break;
+ }
+ hs_prev = &hs->hs_next;
+ }
+ return 0;
+}
+
+/*! Iterator over subscriptions
+ *
+ * NOTE: Never manipulate the child-list during operation or using the
+ * same object recursively, the function uses an internal field to remember the
+ * index used. It works as long as the same object is not iterated concurrently.
+ *
+ * @param[in] h clicon handle
+ * @param[in] hprev iterator, initialize with NULL
+ * @code
+ * clicon_handle h;
+ * struct handle_subscription *hs = NULL;
+ * while ((hs = subscription_each(h, hs)) != NULL) {
+ * ...
+ * }
+ * @endcode
+ */
+struct handle_subscription *
+subscription_each(clicon_handle h,
+ struct handle_subscription *hprev)
+{
+ struct backend_handle *cb = handle(h);
+ struct handle_subscription *hs = NULL;
+
+ if (hprev)
+ hs = hprev->hs_next;
+ else
+ hs = cb->cb_subscription;
+ return hs;
+}
diff --git a/apps/backend/clicon_backend_handle.h b/apps/backend/clicon_backend_handle.h
new file mode 100644
index 00000000..f760d50f
--- /dev/null
+++ b/apps/backend/clicon_backend_handle.h
@@ -0,0 +1,71 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ * Part of the external API to plugins. Applications should not include
+ * this file directly (only via clicon_backend.h).
+ * Internal code should include this
+ */
+
+#ifndef _CLICON_BACKEND_HANDLE_H_
+#define _CLICON_BACKEND_HANDLE_H_
+
+/*
+ * Types
+ */
+
+/*! Generic downcall registration.
+ * Enables any function to be called from (cli) frontend
+ * to backend. Like an RPC on application-level.
+ */
+typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
+ void *arg, uint16_t *retlen, void **retarg);
+
+/*
+ * Log for netconf notify function (config_client.c)
+ */
+int backend_notify(clicon_handle h, char *stream, int level, char *txt);
+int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x);
+
+/* subscription callback */
+typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg);
+
+/* Notification subscription info
+ * @see client_subscription in config_client.h
+ */
+struct handle_subscription{
+ struct handle_subscription *hs_next;
+ enum format_enum hs_format; /* format (enum format_enum) XXX not needed? */
+ char *hs_stream; /* name of notify stream */
+ char *hs_filter; /* filter, if format=xml: xpath, if text: fnmatch */
+ subscription_fn_t hs_fn; /* Callback when event occurs */
+ void *hs_arg; /* Callback argument */
+};
+
+struct handle_subscription *subscription_add(clicon_handle h, char *stream,
+ enum format_enum format, char *filter,
+ subscription_fn_t fn, void *arg);
+
+int subscription_delete(clicon_handle h, char *stream,
+ subscription_fn_t fn, void *arg);
+
+struct handle_subscription *subscription_each(clicon_handle h,
+ struct handle_subscription *hprev);
+#endif /* _CLICON_BACKEND_HANDLE_H_ */
diff --git a/apps/backend/clicon_backend_transaction.c b/apps/backend/clicon_backend_transaction.c
new file mode 100644
index 00000000..7f2461a3
--- /dev/null
+++ b/apps/backend/clicon_backend_transaction.c
@@ -0,0 +1,206 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+/*
+ * Note that the functions in this file are accessible from the plugins
+ */
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_backend_transaction.h"
+#include "backend_plugin.h"
+
+/* Access functions for transaction-data handle in callbacks
+ * Expressed in a transition from an current -> wanted state.
+ * For example, adding a database symbol 'a' in candidate and commiting
+ * would give running in source and 'a' and candidate in 'target'.
+ */
+/*! Get transaction id
+ * @param[in] td transaction_data
+ * @retval id transaction id
+ */
+uint64_t
+transaction_id(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_id;
+}
+
+/*! Get plugin/application specific callbackargument
+ * @param[in] td transaction_data
+ * @retval arg callback argument
+ * @note NYI
+ */
+void *
+transaction_arg(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_arg;
+}
+
+/*! Get Source database xml tree
+ * @param[in] td transaction_data
+ * @retval src source xml tree containing original state
+ */
+cxobj *
+transaction_src(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_src;
+}
+
+/*! Get target database xml tree
+ * @param[in] td transaction_data
+ * @retval xml target xml tree containing wanted state
+ */
+cxobj *
+transaction_target(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_target;
+}
+
+/*! Get delete xml vector, ie vector of xml nodes that are deleted src->target
+ * @param[in] td transaction_data
+ * @retval vec Vector of xml nodes
+ */
+cxobj **
+transaction_dvec(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_dvec;
+}
+
+/*! Get length of delete xml vector
+ * @param[in] td transaction_data
+ * @retval len Length of vector of xml nodes
+ * @see transaction_dvec
+ */
+size_t
+transaction_dlen(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_dlen;
+}
+
+/*! Get add xml vector, ie vector of xml nodes that are added src->target
+ * @param[in] td transaction_data
+ * @retval vec Vector of xml nodes
+ */
+cxobj **
+transaction_avec(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_avec;
+}
+
+/*! Get length of add xml vector
+ * @param[in] td transaction_data
+ * @retval len Length of vector of xml nodes
+ * @see transaction_avec
+ */
+size_t
+transaction_alen(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_alen;
+}
+
+/*! Get source changed xml vector, ie vector of xml nodes that changed
+ * @param[in] td transaction_data
+ * @retval vec Vector of xml nodes
+ * These are only nodes of type LEAF.
+ * For each node in this vector which contains the original value, there
+ * is a node in tcvec with the changed value
+ * @see transaction_dcvec
+ */
+cxobj **
+transaction_scvec(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_scvec;
+}
+
+/*! Get target changed xml vector, ie vector of xml nodes that changed
+ * @param[in] td transaction_data
+ * @retval vec Vector of xml nodes
+ * These are only nodes of type LEAF.
+ * For each node in this vector which contains the original value, there
+ * is a node in tcvec with the changed value
+ * @see transaction_scvec
+ */
+cxobj **
+transaction_tcvec(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_dvec;
+}
+
+/*! Get length of changed xml vector
+ * @param[in] td transaction_data
+ * @retval len Length of vector of xml nodes
+ * This is the length of both the src change vector and the target change vector
+ */
+size_t
+transaction_clen(transaction_data td)
+{
+ return ((transaction_data_t *)td)->td_clen;
+}
+
+int
+transaction_print(FILE *f,
+ transaction_data th)
+{
+ cxobj *xn;
+ int i;
+ transaction_data_t *td;
+
+ td = (transaction_data_t *)th;
+
+ fprintf(f, "Transaction id: 0x%llx\n", td->td_id);
+ fprintf(f, "Removed\n=========\n");
+ for (i=0; itd_dlen; i++){
+ xn = td->td_dvec[i];
+ clicon_xml2file(f, xn, 0, 1);
+ }
+ fprintf(f, "Added\n=========\n");
+ for (i=0; itd_alen; i++){
+ xn = td->td_avec[i];
+ clicon_xml2file(f, xn, 0, 1);
+ }
+ fprintf(stderr, "Changed\n=========\n");
+ for (i=0; itd_clen; i++){
+ xn = td->td_scvec[i];
+ clicon_xml2file(f, xn, 0, 1);
+ xn = td->td_tcvec[i];
+ clicon_xml2file(f, xn, 0, 1);
+ }
+ return 0;
+}
diff --git a/apps/backend/clicon_backend_transaction.h b/apps/backend/clicon_backend_transaction.h
new file mode 100644
index 00000000..1466662a
--- /dev/null
+++ b/apps/backend/clicon_backend_transaction.h
@@ -0,0 +1,60 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ * Part of the external API to plugins. Applications should not include
+ * this file directly (only via clicon_backend.h).
+ * Internal code should include this
+ */
+
+#ifndef _CLICON_BACKEND_TRANSACTION_H_
+#define _CLICON_BACKEND_TRANSACTION_H_
+
+/*
+ * Types
+ */
+
+/*! Generic downcall registration.
+ * Enables any function to be called from (cli) frontend
+ * to backend. Like an RPC on application-level.
+ */
+typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len,
+ void *arg, uint16_t *retlen, void **retarg);
+
+/* Transaction callback data accessors for client plugins
+ * (defined in config_dbdep.c)
+ * @see transaction_data_t internal structure
+ */
+typedef void *transaction_data;
+uint64_t transaction_id(transaction_data td);
+void *transaction_arg(transaction_data td);
+cxobj *transaction_src(transaction_data td);
+cxobj *transaction_target(transaction_data td);
+cxobj **transaction_dvec(transaction_data td);
+size_t transaction_dlen(transaction_data td);
+cxobj **transaction_avec(transaction_data td);
+size_t transaction_alen(transaction_data td);
+cxobj **transaction_scvec(transaction_data td);
+cxobj **transaction_tcvec(transaction_data td);
+size_t transaction_clen(transaction_data td);
+
+int transaction_print(FILE *f, transaction_data th);
+
+#endif /* _CLICON_BACKEND_TRANSACTION_H_ */
diff --git a/apps/backend/clicon_transaction_api.o b/apps/backend/clicon_transaction_api.o
new file mode 100644
index 00000000..3075b019
Binary files /dev/null and b/apps/backend/clicon_transaction_api.o differ
diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in
new file mode 100644
index 00000000..4e54b7c7
--- /dev/null
+++ b/apps/cli/Makefile.in
@@ -0,0 +1,134 @@
+#
+# Makefile
+#
+# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+#
+# This file is part of CLICON.
+#
+# CLICON 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.
+#
+# CLICON 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 CLICON; see the file COPYING. If not, see
+# .
+#
+#
+
+VPATH = @srcdir@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+prefix = @prefix@
+datarootdir = @datarootdir@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+libdir = @libdir@
+mandir = @mandir@
+libexecdir = @libexecdir@
+localstatedir = @localstatedir@
+sysconfdir = @sysconfdir@
+includedir = @includedir@
+
+SH_SUFFIX = @SH_SUFFIX@
+CLICON_MAJOR = @CLICON_VERSION_MAJOR@
+CLICON_MINOR = @CLICON_VERSION_MINOR@
+
+# Use this clicon lib for linking
+CLICON_LIB = libclicon.so.$(CLICON_MAJOR).$(CLICON_MINOR)
+# Location of system plugins
+CLICON_CLI_SYSDIR = $(libdir)/clicon/plugins/cli
+
+# For dependency. A little strange that we rely on it being built in the src dir
+# even though it may exist in $(libdir). But the new version may not have been installed yet.
+LIBDEPS = $(top_srcdir)/lib/src/$(CLICON_LIB)
+
+LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLICON_LIB) -lpthread
+CPPFLAGS = @CPPFLAGS@ -fPIC
+INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
+
+APPL = clicon_cli
+SRC = cli_main.c
+OBJS = $(SRC:.c=.o)
+
+MYNAME = clicon_cli
+MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
+MYLIB = $(MYLIBLINK).$(CLICON_MAJOR).$(CLICON_MINOR)
+MYLIBSO = $(MYLIBLINK).$(CLICON_MAJOR)
+
+LIBSRC = cli_plugin.c cli_common.c cli_handle.c cli_generate.c
+LIBOBJS = $(LIBSRC:.c=.o)
+
+all: $(MYLIB) $(APPL) test
+
+clean:
+ rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
+
+distclean: clean
+ rm -f Makefile *~ .depend
+
+# Put daemon in bin
+# Put other executables in libexec/
+# Also create a libexec/ directory for writeable/temporary files.
+# Put config file in etc/
+install: install-lib $(APPL)
+ install -d $(DESTDIR)$(bindir)
+ install $(APPL) $(DESTDIR)$(bindir)
+
+install-lib: $(MYLIB)
+ install -d $(DESTDIR)$(libdir)
+ install $(MYLIB) $(DESTDIR)$(libdir)
+ ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclicon_cli.so.2
+ ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclicon_cli.so
+ install -d $(DESTDIR)$(libdir)/clicon/plugins/cli
+
+install-include: clicon_cli.h clicon_cli_api.h
+ install -d $(DESTDIR)$(includedir)/clicon
+ install -m 644 $^ $(DESTDIR)$(includedir)/clicon
+
+uninstall:
+ rm -f $(bindir)/$(APPL)
+ rm -f $(libdir)/$(MYLIB)
+ rm -f $(includedir)/clicon/*
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+.c.o:
+ $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLICON_CLI_SYSDIR=\"$(CLICON_CLI_SYSDIR)\" $(CFLAGS) -c $<
+
+# Just link test programs
+test.c :
+ echo "main(){}" > $@
+
+test: test.c $(LIBOBJ)
+ $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. -l:$(MYLIB) $(LIBS) -o $@
+
+$(APPL): $(OBJS) $(MYLIBLINK) $(LIBDEPS)
+ $(CC) $(LDFLAGS) $(OBJS) -L. -l:$(MYLIB) $(LIBS) -o $@
+
+$(MYLIB) : $(LIBOBJS)
+ $(CC) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO)
+
+# link-name is needed for application linking, eg for clicon_cli and clicon_config
+$(MYLIBLINK) : $(MYLIB)
+# ln -sf $(MYLIB) $(MYLIBSO)
+# ln -sf $(MYLIB) $@
+
+TAGS:
+ find . -name '*.[chyl]' -print | etags -
+
+depend:
+ $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
+
+#include .depend
+
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
new file mode 100644
index 00000000..fc72b346
--- /dev/null
+++ b/apps/cli/cli_common.c
@@ -0,0 +1,1930 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#ifdef HAVE_CRYPT_H
+#include
+#endif
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_cli_api.h"
+
+#include "cli_common.h"
+
+static int xml2csv(FILE *f, cxobj *x, cvec *cvv);
+//static int xml2csv_raw(FILE *f, cxobj *x);
+
+/*! Initialize candidate database
+ * We have implemented these:
+ * shared - all users share a common candidate db
+ */
+int
+init_candidate_db(clicon_handle h, enum candidate_db_type type)
+{
+ int retval = -1;
+ struct stat sb;
+ char *running_db;
+ char *candidate_db;
+
+ if ((running_db = clicon_running_db(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: RUNNING_CANDIDATE_DB option not set", __FUNCTION__);
+ goto err;
+ }
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: CLICON_CANDIDATE_DB option not set", __FUNCTION__);
+ goto err;
+ }
+ cli_set_candidate_type(h, type);
+ switch(type){
+ case CANDIDATE_DB_NONE:
+ break;
+ case CANDIDATE_DB_PRIVATE:
+ if (lstat(candidate_db, &sb) < 0){
+ if (file_cp(running_db, candidate_db) < 0){
+ clicon_err(OE_UNIX, errno, "Error when copying %s to %s",
+ running_db, candidate_db);
+ unlink(candidate_db);
+ goto err;
+ }
+ }
+ break;
+ case CANDIDATE_DB_SHARED:
+ if (lstat(running_db, &sb) < 0){
+ clicon_err(OE_FATAL, 0, "Running db (%s) does not exist",
+ running_db);
+ goto err;
+ }
+ if (lstat(candidate_db, &sb) < 0){
+ if (cli_send2backend(h)) {
+ clicon_rpc_copy(h, running_db, candidate_db);
+ }
+ else
+ if (file_cp(running_db, candidate_db) < 0){
+ clicon_err(OE_UNIX, errno, "Error when copying %s to %s",
+ running_db, candidate_db);
+ goto err;
+ }
+ }
+ break;
+ case CANDIDATE_DB_CURRENT:
+ clicon_option_str_set(h, "CLICON_CANDIDATE_DB", running_db);
+ break;
+ }
+ retval = 0;
+ err:
+ return retval;
+}
+
+/*
+ * exit_candidate_db
+ * private canddidates should be removed
+ */
+int
+exit_candidate_db(clicon_handle h)
+{
+// struct stat sb;
+
+ switch(cli_candidate_type(h)){
+ case CANDIDATE_DB_PRIVATE:
+#if 0 /* XXX: maybe we should remove it, but I want several cli:s to edit it */
+ if (lstat(clicon_candidate_db(h), &sb) == 0)
+ unlink(clicon_candidate_db(h));
+#endif
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * cli_debug
+ * set debug level on stderr (not syslog).
+ * The level is either what is specified in arg as int argument.
+ * _or_ if a 'level' variable is present in vars use that value instead.
+ */
+int
+cli_debug(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ cg_var *cv;
+ int level;
+
+ if ((cv = cvec_find_var(vars, "level")) == NULL)
+ cv = arg;
+ level = cv_int32_get(cv);
+ /* cli */
+ clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */
+ /* config daemon */
+ if (cli_send2backend(h)) {
+ if (clicon_rpc_debug(h, level) < 0)
+ goto done;
+ }
+ done:
+ return 0;
+}
+
+
+void
+cli_signal_block(clicon_handle h)
+{
+ clicon_signal_block (SIGTSTP);
+ clicon_signal_block (SIGQUIT);
+ clicon_signal_block (SIGCHLD);
+ if (!clicon_quiet_mode(h))
+ clicon_signal_block (SIGINT);
+}
+
+void
+cli_signal_unblock(clicon_handle h)
+{
+ clicon_signal_unblock (SIGTSTP);
+ clicon_signal_unblock (SIGQUIT);
+ clicon_signal_unblock (SIGCHLD);
+ clicon_signal_unblock (SIGINT);
+}
+
+/*
+ * Flush pending signals for a given signal type
+ */
+void
+cli_signal_flush(clicon_handle h)
+{
+ /* XXX A bit rough. Use sigpending() and more clever logic ?? */
+
+ sigfn_t h1, h2, h3, h4;
+
+ set_signal (SIGTSTP, SIG_IGN, &h1);
+ set_signal (SIGQUIT, SIG_IGN, &h2);
+ set_signal (SIGCHLD, SIG_IGN, &h3);
+ set_signal (SIGINT, SIG_IGN, &h4);
+
+ cli_signal_unblock (h);
+
+ set_signal (SIGTSTP, h1, NULL);
+ set_signal (SIGQUIT, h2, NULL);
+ set_signal (SIGCHLD, h3, NULL);
+ set_signal (SIGINT, h4, NULL);
+
+ cli_signal_block (h);
+}
+
+
+/* Code for recording which CLI commands have been issued */
+
+static FILE *_recordf = NULL;
+static int _isrecording = 0;
+
+int
+isrecording(void)
+{
+ return _isrecording;
+}
+
+int
+cli_record(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ _isrecording = cv_int32_get(arg);
+ return 0;
+}
+
+static int
+record_open(void)
+{
+ char file[] = "/tmp/cli.record.XXXXXX";
+ int fd;
+
+ if ((fd = mkstemp(file)) < 0 || (_recordf = fdopen(fd, "w")) < 0) {
+ clicon_err(OE_UNIX, errno, "mkstemp/fdopen");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * record commands in file
+ */
+int
+record_command(char *str)
+{
+ if (_recordf==NULL)
+ if (record_open() < 0)
+ return -1;
+ fprintf(_recordf, "%s\n", str);
+ fflush(_recordf);
+ return 0;
+}
+
+
+/*
+ * Callback to set syntax mode
+ */
+int
+cli_set_mode(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ int retval = -1;
+ char *str = NULL;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ cli_set_syntax_mode(h, str);
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*
+ * XXX Application specific??
+ * cli_start_shell
+ * Start bash from cli callback
+ */
+int
+cli_start_shell(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ char *cmd;
+ struct passwd *pw;
+ int retval;
+ char bcmd[128];
+ cg_var *cv1 = cvec_i(vars, 1);
+
+ cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL);
+
+ if ((pw = getpwuid(getuid())) == NULL){
+ fprintf(stderr, "%s: getpwuid: %s\n",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ }
+ if (chdir(pw->pw_dir) < 0){
+ fprintf(stderr, "%s: chdir(%s): %s\n",
+ __FUNCTION__, pw->pw_dir, strerror(errno));
+ endpwent();
+ return -1;
+ }
+ endpwent();
+ cli_signal_flush(h);
+ cli_signal_unblock(h);
+ if (cmd){
+ snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd);
+ if ((retval = system(bcmd)) < 0){
+ cli_signal_block(h);
+ fprintf(stderr, "%s: system(bash -c): %s\n",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ }
+ }
+ else
+ if ((retval = system("bash -l")) < 0){
+ cli_signal_block(h);
+ fprintf(stderr, "%s: system(bash): %s\n",
+ __FUNCTION__, strerror(errno));
+ return -1;
+ }
+ cli_signal_block(h);
+#if 0 /* Allow errcodes from bash */
+ if (retval != 0){
+ fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval);
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+/*
+ * Generic quit callback
+ */
+int
+cli_quit(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ cli_set_exiting(h, 1);
+ return 0;
+}
+
+/*
+ * Generic commit callback
+ * if arg is 1, then snapshot and copy to startup config
+ */
+int
+cli_commit(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ int retval = -1;
+ int snapshot = arg?cv_int32_get(arg):0;
+ char *candidate;
+ char *running;
+
+ if ((running = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto done;
+ }
+ if ((candidate = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ goto done;
+ }
+ if ((retval = clicon_rpc_commit(h,
+ running,
+ candidate,
+ snapshot, /* snapshot */
+ snapshot)) < 0){ /* startup */
+ cli_output(stderr, "Commit failed. Edit and try again or discard changes\n");
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*
+ * Generic validatecallback
+ */
+int
+cli_validate(clicon_handle h, cvec *vars, cg_var *arg)
+{
+ char *candidate_db;
+ int retval = -1;
+
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ return -1;
+ }
+ if (cli_send2backend(h)) {
+ if ((retval = clicon_rpc_validate(h, candidate_db)) < 0)
+ cli_output(stderr, "Validate failed. Edit and try again or discard changes\n");
+ }
+ return retval;
+}
+
+
+
+
+/*! Completion callback primarily intended for automatically generated data model
+ *
+ * Returns an expand-type list of commands as used by cligen 'expand'
+ * functionality.
+ * arg is a string: " ".
+ * is either running or candidate
+ * matches a set of database keys following clicon_dbspec.
+ * Eg a[].b[] $!x $!y
+ * the last being the variable to expand for.
+ * Example:
+ * dbspec is a[].b[] $!x $!y
+ * clispec is a b (|;
+ * db contains entries:
+ * a.0 $x=5
+ * a.1 $x=10
+ * a.0.b.0 $x=5 $y=12
+ * a.0.b.1 $x=5 $y=20
+ * a.1.b.0 $x=10 $y=99
+ *
+ * The user types a 5 b > which produces the following output:
+ *
+ * 12
+ * 20
+ *
+ * Assume callback given in a cligen spec: a "
+ * @param[out] len len of return commands & helptxt
+ * @param[out] commands vector of function pointers to callback functions
+ * @param[out] helptxt vector of pointers to helptexts
+ * @see cli_expand_var_generate This is where arg is generated
+ */
+int
+expand_dbvar_dbxml(void *h,
+ char *name,
+ cvec *cvv,
+ cg_var *arg,
+ int *nr,
+ char ***commands,
+ char ***helptexts)
+{
+ char *dbname;
+ int nvec;
+ char **vec = NULL;
+ int retval = -1;
+ char *xkfmt;
+ char *str;
+ char *dbstr;
+ cxobj *xt = NULL;
+ char *xk = NULL;
+ int i;
+ int i0;
+ struct db_pair *pairs;
+ int npairs;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ /* In the example, str = "candidate a[].b[] $!x $!y" */
+ if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
+ goto done;
+ }
+ dbstr = vec[0];
+ if (strcmp(dbstr, "running") == 0)
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(dbstr, "candidate") == 0)
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "db not set");
+ goto done;
+ }
+ xkfmt = vec[1];
+ /* xkfmt = "/test/kalle/%s/lasse" --> "^/test/kalle/.* /lasse$" */
+ if (xmlkeyfmt2key2(xkfmt, cvv, &xk) < 0)
+ goto done;
+ if ((npairs = db_regexp(dbname, xk, __FUNCTION__, &pairs, 0)) < 0)
+ goto done;
+ i0 = *nr;
+ *nr += npairs;
+ if ((*commands = realloc(*commands, sizeof(char *) * (*nr))) == NULL) {
+ clicon_err(OE_UNDEF, errno, "realloc: %s", strerror (errno));
+ goto done;
+ }
+ for (i = 0; i < npairs; i++)
+ (*commands)[i0+i] = strdup(pairs[i].dp_val);
+
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+ if (xt)
+ xml_free(xt);
+ if (xk)
+ free(xk);
+ return retval;
+
+}
+#ifdef NOTUSED
+
+/*! Expand based on database key and variable value (of that key)
+ *
+ * Return an expand-type list of commands as used by cligen 'expand'
+ * functionality.
+ * arg is a string: " ".
+ * is either running or candidate
+ * matches a set of database keys
+ * is name of a variable occuring in the cli command string
+ * Example: "candidate ^Create.*$" GroupName"
+ * (See also expand_db_variable().
+ *
+ * Assume callback given in a cligen spec: a ",
+ str);
+ goto done;
+ }
+ dbstr = vec[0];
+ keystr = vec[1];
+ varstr = vec[2];
+ if (strcmp(dbstr, "running") == 0)
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(dbstr, "candidate") == 0)
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "dbname not set");
+ goto done;
+ }
+ if ((retval = expand_db_variable(h, dbname, keystr, varstr, nr, commands)) < 0)
+ goto done;
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+/*! Expand database variable
+ * Given a database, a basekey (pattern) and a variable, return an expand-type
+ * list of commands as used by cligen 'expand' functionality.
+ * @see expand_dbvar
+ */
+int
+expand_db_variable(clicon_handle h,
+ char *dbname,
+ char *basekey,
+ char *variable,
+ int *nr,
+ char ***commands)
+{
+ char *key;
+ int i;
+ int j;
+ int retval = -1;
+ cvec *cvv;
+ cg_var *cv = NULL;
+ char **tmp;
+ char *val = NULL;
+ cvec **cvvp = NULL;
+ size_t len = 0;
+
+ /* adhoc to detect regexp keys. If so, dont call db_gen_rxkey */
+ if (index(basekey, '^') == NULL){
+ if ((key = db_gen_rxkey(basekey, __FUNCTION__)) == NULL)
+ goto quit;
+ }
+ else
+ key = chunkdup(basekey, strlen(basekey)+1, __FUNCTION__);
+
+ if (clicon_dbitems(dbname, key, &cvvp, &len) < 0)
+ goto quit;
+ for (i = 0; i < len; i++) {
+ cvv = cvvp[i];
+ cv = NULL;
+ while ((cv = cvec_each(cvv, cv)) != NULL) {
+ if (strcmp(cv_name_get(cv), variable) != 0)
+ continue;
+ if ((val = cv2str_dup(cv)) == NULL)
+ goto quit;
+ /* Check if value already in vector? No duplicates */
+ for (j=0; j<*nr; j++)
+ if (strcmp(val, (*commands)[j]) == 0)
+ break;
+ if (j<*nr){
+ free(val);
+ val = NULL;
+ continue;
+ }
+ if ((tmp = realloc(*commands, sizeof(char *) * ((*nr)+1))) == NULL) {
+ clicon_err(OE_UNDEF, errno, "realloc: %s", strerror (errno));
+ goto quit;
+ }
+ *commands = tmp;
+ (*commands)[*nr] = val;
+ val = NULL;
+ (*nr)++;
+ break;
+ }
+ }
+ retval = 0;
+quit:
+ if (cvvp)
+ clicon_dbitems_free(cvvp, len);
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+/*! Pattern match in candidate_db
+ */
+int
+expand_db_symbol(clicon_handle h,
+ char *symbol,
+ int element,
+ int *nr,
+ char ***commands)
+{
+ int retval = -1;
+ char **tmp;
+ cvec **cvvp = NULL;
+ cvec *cvv;
+ size_t len = 0;
+ int i;
+ char *key;
+ int nvec;
+ int n;
+ char **vec = NULL;
+ char str[128];
+ char *dbname;
+
+ if ((dbname = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto done;
+ }
+ snprintf(str, sizeof(str), "^%s\\..", symbol);
+ if (clicon_rpc_dbitems(h, dbname, str, NULL, NULL, &cvvp, &len) < 0)
+ goto done;
+ for (i = 0; i < len; i++) {
+ cvv = cvvp[i];
+ key = cvec_name_get(cvv);
+ if ((vec = clicon_strsplit(key, ".", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_UNDEF, errno, "clicon_strsplit");
+ goto done;
+ }
+ /* Check if already exists */
+ for (n=0; n<*nr; n++)
+ if (strcmp((*commands)[n], vec[element]) == 0)
+ break; /* Already exists */
+ if (n<*nr)
+ continue;
+ /* Allocate new pointer */
+ if ((tmp = realloc(*commands, sizeof(char *) * ((*nr)+1))) == NULL) {
+ clicon_err(OE_UNDEF, errno, "realloc: %s", strerror (errno));
+ goto done;
+ }
+ *commands = tmp;
+ /* Duplicate string */
+ if (((*commands)[*nr] = strdup(vec[element])) == NULL) {
+ clicon_err(OE_UNDEF, errno, "strdup: %s", strerror (errno));
+ goto done;
+ }
+ (*nr)++;
+ }
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__) ;
+ if (cvvp)
+ clicon_dbitems_free(cvvp, len);
+ if (retval < 0 && *commands){
+ while ((*nr) >= 0)
+ free((*commands)[(*nr)--]);
+ free (*commands);
+ }
+ return retval;
+}
+#endif /* NOTUSED */
+
+/*
+ * expand_dir
+ * List files in a directory
+ */
+int
+expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat st;
+ char *str;
+ char *cmd;
+ int len;
+ int retval = -1;
+ struct passwd *pw;
+ char filename[MAXPATHLEN];
+
+ if ((dirp = opendir(dir)) == 0){
+ fprintf(stderr, "expand_dir: opendir(%s) %s\n",
+ dir, strerror(errno));
+ return -1;
+ }
+ *nr = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ if (
+#if 0
+ strcmp(dp->d_name, ".") != 0 &&
+ strcmp(dp->d_name, "..") != 0
+#else
+ dp->d_name[0] != '.'
+#endif
+ ) {
+ snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp->d_name);
+ if (lstat(filename, &st) == 0){
+ if ((st.st_mode & flags) == 0)
+ continue;
+
+#if EXPAND_RECURSIVE
+ if (S_ISDIR(st.st_mode)) {
+ int nrsav = *nr;
+ if(expand_dir(filename, nr, commands, detail) < 0)
+ goto quit;
+ while(nrsav < *nr) {
+ len = strlen(dp->d_name) + strlen((*commands)[nrsav]) + 2;
+ if((str = malloc(len)) == NULL) {
+ fprintf(stderr, "expand_dir: malloc: %s\n",
+ strerror(errno));
+ goto quit;
+ }
+ snprintf(str, len-1, "%s/%s",
+ dp->d_name, (*commands)[nrsav]);
+ free((*commands)[nrsav]);
+ (*commands)[nrsav] = str;
+
+ nrsav++;
+ }
+ continue;
+ }
+#endif
+ if ((cmd = strdup(dp->d_name)) == NULL) {
+ fprintf(stderr, "expand_dir: strdup: %s\n",
+ strerror(errno));
+ goto quit;
+ }
+ if (0 &&detail){
+ if ((pw = getpwuid(st.st_uid)) == NULL){
+ fprintf(stderr, "expand_dir: getpwuid(%d): %s\n",
+ st.st_uid, strerror(errno));
+ goto quit;
+ }
+ len = strlen(cmd) +
+ strlen(pw->pw_name) +
+#ifdef __FreeBSD__
+ strlen(ctime(&st.st_mtimespec.tv_sec)) +
+#else
+ strlen(ctime(&st.st_mtim.tv_sec)) +
+#endif
+
+ strlen("{ by }") + 1 /* \0 */;
+ if ((str=realloc(cmd, strlen(cmd)+len)) == NULL) {
+ fprintf(stderr, "expand_dir: malloc: %s\n",
+ strerror(errno));
+ goto quit;
+ }
+ snprintf(str + strlen(dp->d_name),
+ len - strlen(dp->d_name),
+ "{%s by %s}",
+#ifdef __FreeBSD__
+ ctime(&st.st_mtimespec.tv_sec),
+#else
+ ctime(&st.st_mtim.tv_sec),
+#endif
+
+ pw->pw_name
+ );
+ cmd = str;
+ }
+ if (((*commands) =
+ realloc(*commands, ((*nr)+1)*sizeof(char**))) == NULL){
+ perror("expand_dir: realloc");
+ goto quit;
+ }
+ (*commands)[(*nr)] = cmd;
+ (*nr)++;
+ if (*nr >= 128) /* Limit number of options */
+ break;
+ }
+ }
+ }
+ retval = 0;
+ quit:
+ closedir(dirp);
+ return retval;
+}
+
+/*! Compare two dbs using XML. Write to file and run diff
+ */
+static int
+compare_xmls(cxobj *xc1, cxobj *xc2, int astext)
+{
+ int fd;
+ FILE *f;
+ char filename1[MAXPATHLEN];
+ char filename2[MAXPATHLEN];
+ char cmd[MAXPATHLEN];
+ int retval = -1;
+ cxobj *xc;
+
+ snprintf(filename1, sizeof(filename1), "/tmp/cliconXXXXXX");
+ snprintf(filename2, sizeof(filename2), "/tmp/cliconXXXXXX");
+ if ((fd = mkstemp(filename1)) < 0){
+ clicon_err(OE_UNDEF, errno, "tmpfile: %s", strerror (errno));
+ goto done;
+ }
+ if ((f = fdopen(fd, "w")) == NULL)
+ goto done;
+ xc = NULL;
+ if (astext)
+ while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
+ xml2txt(f, xc, 0);
+ else
+ while ((xc = xml_child_each(xc1, xc, -1)) != NULL)
+ clicon_xml2file(f, xc, 0, 1);
+
+ fclose(f);
+ close(fd);
+
+ if ((fd = mkstemp(filename2)) < 0){
+ clicon_err(OE_UNDEF, errno, "mkstemp: %s", strerror (errno));
+ goto done;
+ }
+ if ((f = fdopen(fd, "w")) == NULL)
+ goto done;
+ xc = NULL;
+ if (astext)
+ while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
+ xml2txt(f, xc, 0);
+ else
+ while ((xc = xml_child_each(xc2, xc, -1)) != NULL)
+ clicon_xml2file(f, xc, 0, 1);
+ fclose(f);
+ close(fd);
+
+ snprintf(cmd, sizeof(cmd), "/usr/bin/diff -dU 1 %s %s | grep -v @@ | sed 1,2d", filename1, filename2);
+ if (system(cmd) < 0)
+ goto done;
+
+ retval = 0;
+ done:
+ unlink(filename1);
+ unlink(filename2);
+ return retval;
+}
+
+/*! Compare two dbs using XML. Write to file and run diff
+ * @param[in] h Clicon handle
+ * @param[in] cvv
+ * @param[in] arg arg: 0 as xml, 1: as text
+ */
+int
+compare_dbs(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ cxobj *xc1 = NULL; /* running xml */
+ cxobj *xc2 = NULL; /* candidate xml */
+ int retval = -1;
+ char *running;
+ char *candidate;
+
+ if ((running = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto done;
+ }
+ if ((candidate = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ goto done;
+ }
+ if (xmldb_get(running, "/", clicon_dbspec_yang(h), &xc1) < 0)
+ goto done;
+ if (xmldb_get(candidate, "/", clicon_dbspec_yang(h), &xc2) < 0)
+ goto done;
+ if (compare_xmls(xc1, xc2, arg?cv_int32_get(arg):0) < 0) /* astext? */
+ goto done;
+ retval = 0;
+ done:
+ if (xc1)
+ xml_free(xc1);
+ if (xc2)
+ xml_free(xc2);
+
+ return retval;
+}
+
+
+/*! Modify xml database frm a callback using xml key format strings
+ * @param[in] h Clicon handle
+ * @param[in] cvv Vector of cli string and instantiated variables
+ * @param[in] arg An xml key format string, eg /aaa/%s
+ * @param[in] op Operation to perform on database
+ * Cvv will contain forst the complete cli string, and then a set of optional
+ * instantiated variables.
+ * Example:
+ * cvv[0] = "set interfaces interface eth0 type bgp"
+ * cvv[1] = "eth0"
+ * cvv[2] = "bgp"
+ * arg = "/interfaces/interface/%s/type"
+ * op: OP_MERGE
+ * @see cli_callback_xmlkeyfmt_generate where arg is generated
+ */
+static int
+cli_dbxml(clicon_handle h,
+ cvec *cvv,
+ cg_var *arg,
+ enum operation_type op)
+{
+ int retval = -1;
+ char *str = NULL;
+ char *candidate;
+ char *running;
+ char *xkfmt; /* xml key format */
+ char *xk = NULL; /* xml key */
+ cg_var *cval;
+ char *val = NULL;
+ yang_spec *yspec;
+
+ /*
+ * clicon_rpc_xmlput(h, db, MERGE,"eth0hej");
+ * Wanted database content:
+ * /interfaces
+ * /interfaces/interface/eth0
+ * /interfaces/interface/eth0/name eth0
+ * /interfaces/interface/eth0/type hej
+ * Algorithm alt1:
+ * arg = "$1$2"
+ * Where is arg computed? In eg yang2cli_leaf, otherwise in yang_parse,..
+ * Create string using cbuf and save that.
+ */
+ if ((candidate = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ return -1;
+ }
+ if ((running = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ return -1;
+ }
+ xkfmt = cv_string_get(arg);
+ if (xmlkeyfmt2key(xkfmt, cvv, &xk) < 0)
+ goto done;
+ cval = cvec_i(cvv, cvec_len(cvv)-1);
+ if ((val = cv2str_dup(cval)) == NULL){
+ clicon_err(OE_UNIX, errno, "cv2str_dup");
+ goto done;
+ }
+ if (cli_send2backend(h)) {
+ if (clicon_rpc_change(h, candidate, op, xk, val) < 0)
+ goto done;
+ if (clicon_autocommit(h)) {
+ if (clicon_rpc_commit(h, running, candidate, 0, 0) < 0)
+ goto done;
+ }
+ }
+ else{
+ yspec = clicon_dbspec_yang(h);
+ if (xmldb_put_xkey(candidate, xk, val, yspec, op) < 0)
+ goto done;
+ if (clicon_autocommit(h))
+ clicon_log(LOG_WARNING, "Cant combine no backend and autocommit");
+ }
+ retval = 0;
+ done:
+ if (str)
+ free(str);
+ if (xk)
+ free(xk);
+ return retval;
+}
+
+int
+cli_set(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ int retval = 1;
+
+ if (cli_dbxml(h, cvv, arg, OP_REPLACE) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+int
+cli_merge(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ int retval = -1;
+
+ if (cli_dbxml(h, cvv, arg, OP_MERGE) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+int
+cli_del(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ int retval = -1;
+
+ if (cli_dbxml(h, cvv, arg, OP_REMOVE) < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Load a configuration file to candidate database
+ * Utility function used by cligen spec file
+ * @param[in] h CLICON handle
+ * @param[in] cvv Vector of variables (where is found)
+ * @param[in] arg A string: " (merge|replace)"
+ * is name of a variable occuring in "cvv" containing filename
+ * @note that "filename" is local on client filesystem not backend.
+ * @note file is assumed to have a dummy top-tag, eg
+ * @code
+ * # cligen spec
+ * load file , load_config_file("name2 merge");
+ * @endcode
+ * @see save_config_file
+ */
+int
+load_config_file(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ int ret = -1;
+ struct stat st;
+ char **vec;
+ char **vecp;
+ char *filename;
+ int replace;
+ char *dbname;
+ char *str;
+ cg_var *cv;
+ int nvec;
+ char *opstr;
+ char *varstr;
+ int fd = -1;
+ cxobj *xt = NULL;
+ cxobj *xn;
+ cxobj *x;
+ cbuf *cbxml;
+ yang_spec *yspec;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
+ goto done;
+ }
+ if (nvec != 2){
+ clicon_err(OE_PLUGIN, 0, "Arg syntax is ");
+ goto done;
+ }
+ varstr = vec[0];
+ opstr = vec[1];
+ if (strcmp(opstr, "merge") == 0)
+ replace = 0;
+ else
+ if (strcmp(opstr, "replace") == 0)
+ replace = 1;
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr);
+ goto done;
+ }
+ if ((cv = cvec_find_var(cvv, varstr)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
+ goto done;
+ }
+ if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){
+ cli_output(stderr, "Failed to resolve filename\n");
+ goto done;
+ }
+ filename = vecp[0];
+ if ((dbname = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ goto done;
+ }
+ if (stat(filename, &st) < 0){
+ clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s\n",
+ filename, strerror(errno));
+ goto done;
+ }
+ if (cli_send2backend(h)) {
+ /* Open and parse local file into xml */
+ if ((fd = open(filename, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: open(%s)", __FUNCTION__, filename);
+ goto done;
+ }
+ if (clicon_xml_parse_file(fd, &xt, "") < 0)
+ goto done;
+ if ((xn = xml_child_i(xt, 0)) != NULL){
+ if ((cbxml = cbuf_new()) == NULL)
+ goto done;
+ x = NULL;
+ while ((x = xml_child_each(xn, x, -1)) != NULL)
+ if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0)
+ goto done;
+ if (clicon_rpc_xmlput(h, dbname,
+ replace?OP_REPLACE:OP_MERGE,
+ cbuf_get(cbxml)) < 0)
+ goto done;
+ cbuf_free(cbxml);
+ }
+ }
+ else{
+ if (replace){
+ if (unlink(dbname) < 0){
+ clicon_err(OE_UNIX, 0, "rm %s %s", filename, strerror(errno));
+ goto done;
+ }
+ if (db_init(dbname) < 0)
+ goto done;
+ }
+ if ((fd = open(filename, O_RDONLY)) < 0){
+ clicon_err(OE_UNIX, errno, "%s: open(%s)", __FUNCTION__, filename);
+ goto done;
+ }
+ if (clicon_xml_parse_file(fd, &xt, "") < 0)
+ goto done;
+ yspec = clicon_dbspec_yang(h);
+ if ((xn = xml_child_i(xt, 0)) != NULL){
+ if (xmldb_put(dbname, xn, yspec, replace?OP_REPLACE:OP_MERGE) < 0)
+ goto done;
+ }
+ }
+ ret = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ if (fd != -1)
+ close(fd);
+ return ret;
+}
+
+/*! Copy database to local file
+ * Utility function used by cligen spec file
+ * @param[in] h CLICON handle
+ * @param[in] cvv variable vector (containing )
+ * @param[in] arg a string: " "
+ * is running or candidate
+ * is name of cligen variable in the "cvv" vector containing file name
+ * Note that "filename" is local on client filesystem not backend.
+ * The function can run without a local database
+ * @note The file is saved with dummy top-tag: clicon:
+ * @code
+ * save file , save_config_file("running name");
+ * @endcode
+ * @see load_config_file
+ */
+int
+save_config_file(clicon_handle h,
+ cvec *cvv,
+ cg_var *arg)
+{
+ int retval = -1;
+ char **vec;
+ char **vecp;
+ char *filename;
+ char *dbname;
+ cg_var *cv;
+ int nvec;
+ char *str;
+ char *dbstr;
+ char *varstr;
+ cxobj *xt = NULL;
+ FILE *f = NULL;
+ yang_spec *yspec;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
+ goto done;
+ }
+ if (nvec != 2){
+ clicon_err(OE_PLUGIN, 0, "Arg syntax is ");
+ goto done;
+ }
+ dbstr = vec[0];
+ varstr = vec[1];
+ if (strcmp(dbstr, "running") == 0)
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(dbstr, "candidate") == 0)
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "dbname not set");
+ goto done;
+ }
+
+ if ((cv = cvec_find_var(cvv, varstr)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
+ goto done;
+ }
+ if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){
+ cli_output(stderr, "Failed to resolve filename\n");
+ goto done;
+ }
+ filename = vecp[0];
+ yspec = clicon_dbspec_yang(h);
+ if (xmldb_get(dbname, "/", yspec, &xt) < 0)
+ goto done;
+ if ((f = fopen(filename, "wb")) == NULL){
+ clicon_err(OE_CFG, errno, "Creating file %s", filename);
+ goto done;
+ }
+ if (clicon_xml2file(f, xt, 0, 1) < 0)
+ goto done;
+ retval = 0;
+ /* Fall through */
+ done:
+ unchunk_group(__FUNCTION__);
+ if (xt)
+ xml_free(xt);
+ if (f != NULL)
+ fclose(f);
+ return retval;
+}
+
+/*! Delete all elements in a database
+ * Utility function used by cligen spec file
+ */
+int
+delete_all(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ char *dbname;
+ char *dbstr;
+ int retval = -1;
+
+ if (arg == NULL || (dbstr = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ if (strcmp(dbstr, "running") == 0)
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(dbstr, "candidate") == 0)
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "dbname not set");
+ goto done;
+ }
+ if (cli_send2backend(h)) {
+ clicon_rpc_rm(h, dbname);
+ clicon_rpc_initdb(h, dbname);
+ }
+ else{
+ if (unlink(dbname) < 0){
+ clicon_err(OE_FATAL, errno, "unlink(%s)", dbname);
+ goto done;
+ }
+ if (db_init(dbname) < 0)
+ goto done;
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Discard all changes in candidate and replace with running
+ * Utility function used by cligen spec file
+ */
+int
+discard_changes(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ char *running_db;
+ char *candidate_db;
+ int retval = -1;
+
+ if ((candidate_db = clicon_candidate_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "candidate db not set");
+ goto done;
+ }
+ if ((running_db = clicon_running_db(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "running db not set");
+ goto done;
+ }
+ clicon_rpc_copy(h, running_db, candidate_db);
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Generic function for showing configurations.
+ * the callback differs.
+ * @param[in] h CLICON handle
+ * @param[in] cvv Vector of variables (not needed)
+ * @param[in] arg A string:
+ * is either running or candidate
+ * xpath expression as in nertconf get-config
+ * @param fn
+ * @param fnarg
+ * @code
+ * # cligen spec
+ * show config id , show_conf_as("running interfaces/interface[name=eth*]");
+ * @endcode
+ */
+static int
+show_conf_xmldb_as(clicon_handle h,
+ cvec *cvv,
+ cg_var *arg,
+ cxobj **xt) /* top xml */
+{
+ int retval = -1;
+ char *dbname;
+ char **vec = NULL;
+ int nvec;
+ char *str;
+ char *xpath;
+ yang_spec *yspec;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
+ goto done;
+ }
+ if (nvec != 2){
+ clicon_err(OE_PLUGIN, 0, "format error \"%s\" - expected ", str);
+ goto done;
+ }
+ /* Dont get attr here, take it from arg instead */
+ if (strcmp(vec[0], "running") == 0) /* XXX: hardcoded */
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(vec[0], "candidate") == 0) /* XXX: hardcoded */
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", vec[0]);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "dbname not set");
+ goto done;
+ }
+ xpath = vec[1];
+ yspec = clicon_dbspec_yang(h);
+ if (xmldb_get(dbname, xpath, yspec, xt) < 0)
+ goto done;
+ retval = 0;
+done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+
+/*! Show a configuration database on stdout using XML format
+ * Utility function used by cligen spec file
+ */
+static int
+show_conf_as_xml1(clicon_handle h, cvec *cvv, cg_var *arg, int netconf)
+{
+ cxobj *xt = NULL;
+ cxobj *xc;
+ int retval = -1;
+
+ if (show_conf_xmldb_as(h, cvv, arg, &xt) < 0)
+ goto done;
+ if (netconf) /* netconf prefix */
+ fprintf(stdout, "\n");
+ xc = NULL; /* Dont print xt itself */
+ while ((xc = xml_child_each(xt, xc, -1)) != NULL)
+ clicon_xml2file(stdout, xc, netconf?2:0, 1);
+ if (netconf) /* netconf postfix */
+ fprintf(stdout, "]]>]]>\n");
+ retval = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ return retval;
+
+}
+
+/*! Show configuration as prettyprinted xml
+ * Utility function used by cligen spec file
+ */
+int
+show_conf_as_xml(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ return show_conf_as_xml1(h, cvv, arg, 0);
+}
+
+/*! Show configuration as prettyprinted xml with netconf hdr/tail
+ * Utility function used by cligen spec file
+ */
+int
+show_conf_as_netconf(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ return show_conf_as_xml1(h, cvv, arg, 1);
+}
+
+/*! Show configuration as JSON
+ * Utility function used by cligen spec file
+ */
+int
+show_conf_as_json(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ cxobj *xt = NULL;
+ cxobj *xc;
+ int retval = -1;
+
+ if (show_conf_xmldb_as(h, cvv, arg, &xt) < 0)
+ goto done;
+ xc = NULL; /* Dont print xt itself */
+ while ((xc = xml_child_each(xt, xc, -1)) != NULL)
+ xml2json(stdout, xc, 1);
+ retval = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ return retval;
+}
+
+int
+show_conf_xpath(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ int retval = -1;
+ char *dbname;
+ char **vec = NULL;
+ char *str;
+ char *xpath;
+ yang_spec *yspec;
+ cg_var *cv;
+ cxobj *xt = NULL;
+ cxobj **xv = NULL;
+ int xlen;
+ int i;
+
+ if (arg == NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ /* Dont get attr here, take it from arg instead */
+ if (strcmp(str, "running") == 0) /* XXX: hardcoded */
+ dbname = clicon_running_db(h);
+ else
+ if (strcmp(str, "candidate") == 0) /* XXX: hardcoded */
+ dbname = clicon_candidate_db(h);
+ else{
+ clicon_err(OE_PLUGIN, 0, "No such db name: %s", vec[0]);
+ goto done;
+ }
+ if (dbname == NULL){
+ clicon_err(OE_FATAL, 0, "dbname not set");
+ goto done;
+ }
+ cv = cvec_find_var(cvv, "xpath");
+ xpath = cv_string_get(cv);
+ yspec = clicon_dbspec_yang(h);
+ if (xmldb_get_xpath(dbname, xpath, yspec, &xt, &xv, &xlen) < 0)
+ goto done;
+ for (i=0; iop_type);
+ switch (type){
+ case CLICON_MSG_NOTIFY:
+ if (clicon_msg_notify_decode(reply, &level, &eventstr, __FUNCTION__) < 0)
+ goto done;
+ if (strcmp(format, SHOWAS_TXT) == 0){
+ fprintf(stdout, "%s", eventstr);
+ }
+ else
+ if (strcmp(format, SHOWAS_XML) == 0){
+ if (clicon_xml_parse_string(&eventstr, &xt) < 0)
+ goto done;
+ if ((xn = xml_child_i(xt, 0)) != NULL)
+ if (clicon_xml2file(stdout, xn, 0, 1) < 0)
+ goto done;
+ }
+ else
+ if (strcmp(format, SHOWAS_XML2TXT) == 0){
+ if (clicon_xml_parse_string(&eventstr, &xt) < 0)
+ goto done;
+ if ((xn = xml_child_i(xt, 0)) != NULL)
+ if (xml2txt(stdout, xn, 0) < 0)
+ goto done;
+ }
+ else
+ if (strcmp(format, SHOWAS_XML2JSON) == 0){
+ if (clicon_xml_parse_string(&eventstr, &xt) < 0)
+ goto done;
+ if ((xn = xml_child_i(xt, 0)) != NULL){
+ if (xml2json(stdout, xn, 0) < 0)
+ goto done;
+ }
+ }
+ break;
+ default:
+ clicon_err(OE_PROTO, 0, "%s: unexpected reply: %d",
+ __FUNCTION__, type);
+ goto done;
+ break;
+ }
+ retval = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ unchunk_group(__FUNCTION__); /* event allocated by chunk */
+ return retval;
+
+}
+
+
+/*! Make a notify subscription to backend and un/register callback for return messages.
+ *
+ * @param[in] h Clicon handle
+ * @param[in] cvv Not used
+ * @param[in] arg A string with []
+ * where is "0" or "1"
+ * and is XXX
+ * Example code: Start logging of mystream and show logs as xml
+ * @code
+ * cmd("comment"), cli_notify("mystream 1 xml");
+ * @endcode
+ * XXX: format is a memory leak
+ */
+int
+cli_notify(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ char *stream = NULL;
+ int retval = -1;
+ char **vec = NULL;
+ int nvec;
+ char *str;
+ int status;
+ char *formatstr = NULL;
+ enum format_enum format = MSG_NOTIFY_TXT;
+
+ if (arg==NULL || (str = cv_string_get(arg)) == NULL){
+ clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
+ goto done;
+ }
+ if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
+ goto done;
+ }
+ if (nvec < 2){
+ clicon_err(OE_PLUGIN, 0, "format error \"%s\" - expected ", str);
+ goto done;
+ }
+ stream = vec[0];
+ status = atoi(vec[1]);
+ if (nvec > 2){
+ formatstr = strdup(vec[2]); /* memory leak */
+ if (strcmp(formatstr, "SHOWAS_TXT") != 0)
+ format = MSG_NOTIFY_XML;
+ }
+ if (cli_notification_register(h,
+ stream,
+ format,
+ "",
+ status,
+ cli_notification_cb,
+ (void*)formatstr) < 0)
+ goto done;
+
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+/*! Register log notification stream
+ * @param[in] h Clicon handle
+ * @param[in] stream Event stream. CLICON is predefined, others are application-defined
+ * @param[in] filter Filter. For xml notification ie xpath: /[name=kalle]
+ * @param[in] status 0 for stop, 1 to start
+ * @param[in] fn Callback function called when notification occurs
+ * @param[in] arg Argumnent to function
+ */
+int
+cli_notification_register(clicon_handle h,
+ char *stream,
+ enum format_enum format,
+ char *filter,
+ int status,
+ int (*fn)(int, void*),
+ void *arg)
+{
+ int retval = -1;
+ char *logname;
+ void *p;
+ int s;
+ clicon_hash_t *cdat = clicon_data(h);
+ size_t len;
+ int s_exist = -1;
+
+ if ((logname = chunk_sprintf(__FUNCTION__, "log_socket_%s", stream)) == NULL){
+ clicon_err(OE_PLUGIN, errno, "%s: chunk_sprintf", __FUNCTION__);
+ goto done;
+ }
+ if ((p = hash_value(cdat, logname, &len)) != NULL)
+ s_exist = *(int*)p;
+
+ if (status){
+ if (s_exist!=-1){
+ clicon_err(OE_PLUGIN, 0, "%s: result log socket already exists", __FUNCTION__);
+ goto done;
+ }
+ if (clicon_rpc_subscription(h, status, stream, format, filter, &s) < 0)
+ goto done;
+ if (cligen_regfd(s, fn, arg) < 0)
+ goto done;
+ if (hash_add(cdat, logname, &s, sizeof(s)) == NULL)
+ goto done;
+ }
+ else{
+ if (s_exist != -1){
+ cligen_unregfd(s_exist);
+ }
+ hash_del(cdat, logname);
+ if (clicon_rpc_subscription(h, status, stream, format, filter, NULL) < 0)
+ goto done;
+
+ }
+ retval = 0;
+ done:
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+
+#ifdef notused
+/*! XML to CSV raw variant
+ * @see xml2csv
+ */
+static int
+xml2csv_raw(FILE *f, cxobj *x)
+{
+ cxobj *xc;
+ cxobj *xb;
+ int retval = -1;
+ int i = 0;
+
+ xc = NULL;
+ while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
+ if (xml_child_nr(xc)){
+ xb = xml_child_i(xc, 0);
+ if (xml_type(xb) == CX_BODY){
+ if (i++)
+ fprintf(f, ";");
+ fprintf(f, "%s", xml_value(xb));
+ }
+ }
+ }
+ fprintf(f, "\n");
+ retval = 0;
+ return retval;
+}
+#endif
+
+/*! Translate XML -> CSV commands
+ * Can only be made in a 'flat tree', ie on the form:
+ * B -->
+ * Type, A
+ * X, B
+ * @param[in] f Output file
+ * @param[in] x XML tree
+ * @param[in] cvv A vector of field names present in XML
+ * This means that only fields in x that are listed in cvv will be printed.
+ */
+static int
+xml2csv(FILE *f, cxobj *x, cvec *cvv)
+{
+ cxobj *xe, *xb;
+ int retval = -1;
+ cg_var *vs;
+
+ fprintf(f, "%s", xml_name(x));
+ xe = NULL;
+
+ vs = NULL;
+ while ((vs = cvec_each(cvv, vs))) {
+ if ((xe = xml_find(x, cv_name_get(vs))) == NULL){
+ fprintf(f, ";");
+ continue;
+ }
+ if (xml_child_nr(xe)){
+ xb = xml_child_i(xe, 0);
+ fprintf(f, ";%s", xml_value(xb));
+ }
+ }
+ fprintf(f, "\n");
+ retval = 0;
+ return retval;
+}
+
+
+static int
+show_conf_as_csv1(clicon_handle h, cvec *cvv0, cg_var *arg)
+{
+ cxobj *xt = NULL;
+ cxobj *xc;
+ int retval = -1;
+ cvec *cvv=NULL;
+ char *str;
+
+ if (show_conf_xmldb_as(h, cvv0, arg, &xt) < 0)
+ goto done;
+ xc = NULL; /* Dont print xt itself */
+ while ((xc = xml_child_each(xt, xc, -1)) != NULL){
+ if ((str = chunk_sprintf(__FUNCTION__, "%s[]", xml_name(xc))) == NULL)
+ goto done;
+#ifdef NOTYET /* yang-spec? */
+ if (ds==NULL && (ds = key2spec_key(dbspec, str)) != NULL){
+ cg_var *vs;
+ fprintf(stdout, "Type");
+ cvv = db_spec2cvec(ds);
+ vs = NULL;
+ while ((vs = cvec_each(cvv, vs)))
+ fprintf(stdout, ";%s", cv_name_get(vs));
+ fprintf(stdout, "\n");
+ } /* Now values just need to follow,... */
+#endif /* yang-spec? */
+ if (cvv== NULL)
+ goto done;
+ xml2csv(stdout, xc, cvv); /* csv syntax */
+ }
+ retval = 0;
+ done:
+ if (xt)
+ xml_free(xt);
+ unchunk_group(__FUNCTION__);
+ return retval;
+}
+
+int
+show_conf_as_csv(clicon_handle h, cvec *cvv, cg_var *arg)
+{
+ return show_conf_as_csv1(h, cvv, arg);
+}
diff --git a/apps/cli/cli_common.h b/apps/cli/cli_common.h
new file mode 100644
index 00000000..f047b1c2
--- /dev/null
+++ b/apps/cli/cli_common.h
@@ -0,0 +1,33 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifndef _CLI_COMMON_H_
+#define _CLI_COMMON_H_
+
+void cli_signal_block(clicon_handle h);
+void cli_signal_unblock(clicon_handle h);
+
+/* If you do not find a function here it may be in clicon_cli_api.h which is
+ the external API */
+
+#endif /* _CLI_COMMON_H_ */
diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c
new file mode 100644
index 00000000..245b9563
--- /dev/null
+++ b/apps/cli/cli_generate.c
@@ -0,0 +1,689 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ * Translation between database specs
+ * dbspec_key yang_spec CLIgen parse_tree
+ * +-------------+ key2yang +-------------+ yang2cli +-------------+
+ * | keyspec | -------------> | | ------------> | cli |
+ * | A[].B !$a | yang2key | list{key A;}| | syntax |
+ * +-------------+ <------------ +-------------+ +-------------+
+ */
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_cli_api.h"
+#include "cli_plugin.h"
+#include "cli_generate.h"
+
+/* This is the default callback function. But this is typically overwritten */
+#define GENERATE_CALLBACK "cli_set"
+
+/* variable expand function */
+#define GENERATE_EXPAND_LVEC "expand_dbvar_auto"
+#define GENERATE_EXPAND_XMLDB "expand_dbvar_dbxml"
+
+/*=====================================================================
+ * YANG generate CLI
+ *=====================================================================*/
+#if 0 /* examples/ntp */
+ ntp("Network Time Protocol"),cli_set("ntp");{
+ logging("Configure NTP message logging"),cli_set("ntp.logging");{
+ status (),cli_set("ntp.logging $status:bool");
+ }
+ server("Configure NTP Server") (("IPv4 address of peer")),cli_set("ntp.server[] $!ipv4addr:ipv4addr");
+ }
+#endif
+#if 0 /* examples/datamodel */
+
+WITH COMPLETION:
+ a (|),cli_set("a[] $!x");{
+ b,cli_set("a[].b $!x");{
+ y (|),cli_set("a[].b $!x $y");
+ }
+ z (|),cli_set("a[] $!x $z");
+ }
+
+#endif
+
+#ifndef HAVE_CLIGEN_MAX2STR /* XXX cligen 3.6 feature */
+
+/*! Print max value of a CLIgen variable type as string
+ * @param[in] type CLIgen variable type
+ * @param[out] str Max value printed in this string
+ * @param[in] size Length of 'str'
+ * @retval len How many bytes printed
+ * @see cvtype_max2str_dup
+ * You can use str=NULL to get the expected length.
+ * The number of (potentially if str=NULL) written bytes is returned.
+ */
+static int
+cvtype_max2str(enum cv_type type, char *str, size_t size)
+{
+ int len = 0;
+
+ switch (type){
+ case CGV_INT8:
+ len = snprintf(str, size, "%" PRId8, INT8_MAX);
+ break;
+ case CGV_INT16:
+ len = snprintf(str, size, "%" PRId16, INT16_MAX);
+ break;
+ case CGV_INT32:
+ len = snprintf(str, size, "%" PRId32, INT32_MAX);
+ break;
+ case CGV_INT64:
+ len = snprintf(str, size, "%" PRId64, INT64_MAX);
+ break;
+ case CGV_UINT8:
+ len = snprintf(str, size, "%" PRIu8, UINT8_MAX);
+ break;
+ case CGV_UINT16:
+ len = snprintf(str, size, "%" PRIu16, UINT16_MAX);
+ break;
+ case CGV_UINT32:
+ len = snprintf(str, size, "%" PRIu32, UINT32_MAX);
+ break;
+ case CGV_UINT64:
+ len = snprintf(str, size, "%" PRIu64, UINT64_MAX);
+ break;
+ case CGV_DEC64:
+ len = snprintf(str, size, "%" PRId64 ".0", INT64_MAX);
+ break;
+ case CGV_BOOL:
+ len = snprintf(str, size, "true");
+ break;
+ default:
+ break;
+ }
+ return len;
+}
+
+/*! Print max value of a CLIgen variable type as string
+ *
+ * The string should be freed after use.
+ * @param[in] type CLIgen variable type
+ * @retval str Malloced string containing value. Should be freed after use.
+ * @see cvtype_max2str
+ */
+static char *
+cvtype_max2str_dup(enum cv_type type)
+{
+ int len;
+ char *str;
+
+ if ((len = cvtype_max2str(type, NULL, 0)) < 0)
+ return NULL;
+ if ((str = (char *)malloc (len+1)) == NULL)
+ return NULL;
+ memset (str, '\0', len+1);
+ if ((cvtype_max2str(type, str, len+1)) < 0){
+ free(str);
+ return NULL;
+ }
+ return str;
+}
+#endif /* HAVE_CLIGEN_MAX2STR */
+
+/*! Create cligen variable expand entry with xmlkey format string as argument
+ * @param[in] h clicon handle
+ * @param[in] ys yang_stmt of the node at hand
+ * @param[in] cvtype Type of the cligen variable
+ * @param[in] cb0 The string where the result format string is inserted.
+ * @see expand_dbvar_dbxml This is where the expand string is used
+ */
+static int
+cli_expand_var_generate(clicon_handle h,
+ yang_stmt *ys,
+ enum cv_type cvtype,
+ cbuf *cb0)
+{
+ int retval = -1;
+ char *xkfmt = NULL;
+
+ if (yang2xmlkeyfmt(ys, &xkfmt) < 0)
+ goto done;
+ cprintf(cb0, "|<%s:%s %s(\"candidate %s\")>",
+ ys->ys_argument,
+ cv_type2str(cvtype),
+ GENERATE_EXPAND_XMLDB,
+ xkfmt);
+ retval = 0;
+ done:
+ if (xkfmt)
+ free(xkfmt);
+ return retval;
+}
+
+/*! Create callback with xmlkey format string as argument
+ * @param[in] h clicon handle
+ * @param[in] ys yang_stmt of the node at hand
+ * @param[in] cb0 The string where the result format string is inserted.
+ * @see cli_dbxml This is where the xmlkeyfmt string is used
+ */
+static int
+cli_callback_generate(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cb0)
+{
+ int retval = -1;
+ char *xkfmt = NULL;
+
+ if (yang2xmlkeyfmt(ys, &xkfmt) < 0)
+ goto done;
+ cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
+ retval = 0;
+ done:
+ if (xkfmt)
+ free(xkfmt);
+ return retval;
+}
+
+static int yang2cli_stmt(clicon_handle h, yang_stmt *ys,
+ cbuf *cb0,
+ enum genmodel_type gt,
+ int level);
+
+/*
+ * Check for completion (of already existent values), ranges (eg range[min:max]) and
+ * patterns, (eg regexp:"[0.9]*").
+ */
+static int
+yang2cli_var_sub(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cb0,
+ char *description,
+ enum cv_type cvtype,
+ yang_stmt *ytype, /* resolved type */
+ int options,
+ cg_var *mincv,
+ cg_var *maxcv,
+ char *pattern,
+ uint8_t fraction_digits
+ )
+{
+ int retval = -1;
+ char *type;
+ char *r;
+ yang_stmt *yi = NULL;
+ int i = 0;
+ char *cvtypestr;
+ int completion;
+
+ /* enumeration already gives completion */
+ if (cvtype == CGV_VOID){
+ retval = 0;
+ goto done;
+ }
+ type = ytype?ytype->ys_argument:NULL;
+ if (type)
+ completion = clicon_cli_genmodel_completion(h) &&
+ strcmp(type, "enumeration") != 0 &&
+ strcmp(type, "bits") != 0;
+ else
+ completion = clicon_cli_genmodel_completion(h);
+
+ if (completion)
+ cprintf(cb0, "(");
+ cvtypestr = cv_type2str(cvtype);
+ cprintf(cb0, "<%s:%s", ys->ys_argument, cvtypestr);
+#if 0
+ if (type && (strcmp(type, "identityref") == 0)){
+ yang_stmt *ybase;
+ if ((ybase = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL){
+ cprintf(cb0, " choice:");
+ i = 0;
+ /* for every found identity derived from base-type , do: */
+ {
+ if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
+ continue;
+ if (i)
+ cprintf(cb0, "|");
+ cprintf(cb0, "%s", yi->ys_argument);
+ i++;
+ }
+ }
+
+ }
+#endif
+ if (type && (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0)){
+ cprintf(cb0, " choice:");
+ i = 0;
+ while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
+ if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
+ continue;
+ if (i)
+ cprintf(cb0, "|");
+ cprintf(cb0, "%s", yi->ys_argument);
+ i++;
+ }
+ }
+ if (options & YANG_OPTIONS_FRACTION_DIGITS)
+ cprintf(cb0, " fraction-digits:%u", fraction_digits);
+ if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
+ cprintf(cb0, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
+ if (mincv){
+ if ((r = cv2str_dup(mincv)) == NULL){
+ clicon_err(OE_UNIX, errno, "cv2str_dup");
+ goto done;
+ }
+ cprintf(cb0, "%s:", r);
+ free(r);
+ }
+ if (maxcv != NULL){
+ if ((r = cv2str_dup(maxcv)) == NULL){
+ clicon_err(OE_UNIX, errno, "cv2str_dup");
+ goto done;
+ }
+
+ }
+ else{ /* Cligen does not have 'max' keyword in range so need to find actual
+ max value of type if yang range expression is 0..max */
+ if ((r = cvtype_max2str_dup(cvtype)) == NULL){
+ clicon_err(OE_UNIX, errno, "cvtype_max2str");
+ goto done;
+ }
+ }
+ cprintf(cb0, "%s]", r);
+ free(r);
+ }
+ if (options & YANG_OPTIONS_PATTERN)
+ cprintf(cb0, " regexp:\"%s\"", pattern);
+
+ cprintf(cb0, ">");
+ if (description)
+ cprintf(cb0, "(\"%s\")", description);
+ if (completion){
+ if (cli_expand_var_generate(h, ys, cvtype, cb0) < 0)
+ goto done;
+ if (description)
+ cprintf(cb0, "(\"%s\")", description);
+ cprintf(cb0, ")");
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Translate a yang leaf to cligen variable
+ * Make a type lookup and complete a cligen variable expression such as .
+ * One complication is yang union, that needs a recursion since it consists of sub-types.
+ * eg type union{ type int32; type string } --> (| )
+ */
+static int
+yang2cli_var(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cb0,
+ char *description)
+{
+ int retval = -1;
+ char *type; /* orig type */
+ yang_stmt *yrestype; /* resolved type */
+ char *restype; /* resolved type */
+ cg_var *mincv = NULL;
+ cg_var *maxcv = NULL;
+ char *pattern = NULL;
+ yang_stmt *yt = NULL;
+ yang_stmt *yrt;
+ uint8_t fraction_digits = 0;
+ enum cv_type cvtype;
+ int options = 0;
+ int i;
+
+ if (yang_type_get(ys, &type, &yrestype,
+ &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0)
+ goto done;
+ restype = yrestype?yrestype->ys_argument:NULL;
+ if (clicon_type2cv(type, restype, &cvtype) < 0)
+ goto done;
+ /* Note restype can be NULL here for example with unresolved hardcoded uuid */
+ if (restype && strcmp(restype, "union") == 0){
+ /* Union: loop over resolved type's sub-types */
+ cprintf(cb0, "(");
+ yt = NULL;
+ i = 0;
+ while ((yt = yn_each((yang_node*)yrestype, yt)) != NULL){
+ if (yt->ys_keyword != Y_TYPE)
+ continue;
+ if (i++)
+ cprintf(cb0, "|");
+ if (yang_type_resolve(ys, yt, &yrt,
+ &options, &mincv, &maxcv, &pattern, &fraction_digits) < 0)
+ goto done;
+ restype = yrt?yrt->ys_argument:NULL;
+ if (clicon_type2cv(type, restype, &cvtype) < 0)
+ goto done;
+ if ((retval = yang2cli_var_sub(h, ys, cb0, description, cvtype, yrt,
+ options, mincv, maxcv, pattern, fraction_digits)) < 0)
+
+ goto done;
+
+ }
+ cprintf(cb0, ")");
+ }
+ else
+ if ((retval = yang2cli_var_sub(h, ys, cb0, description, cvtype, yrestype,
+ options, mincv, maxcv, pattern, fraction_digits)) < 0)
+ goto done;
+
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*!
+ * @param[in] h Clicon handle
+ * @param[in] callback If set, include a "; cli_set()" callback, otherwise not.
+ */
+static int
+yang2cli_leaf(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cbuf,
+ enum genmodel_type gt,
+ int level,
+ int callback)
+{
+ yang_stmt *yd; /* description */
+ int retval = -1;
+ char *description = NULL;
+
+ /* description */
+ if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
+ description = yd->ys_argument;
+ cprintf(cbuf, "%*s", level*3, "");
+ if (gt == GT_VARS|| gt == GT_ALL){
+ cprintf(cbuf, "%s", ys->ys_argument);
+ if (yd != NULL)
+ cprintf(cbuf, "(\"%s\")", yd->ys_argument);
+ cprintf(cbuf, " ");
+ yang2cli_var(h, ys, cbuf, description);
+ }
+ else
+ yang2cli_var(h, ys, cbuf, description);
+ if (callback){
+ if (cli_callback_generate(h, ys, cbuf) < 0)
+ goto done;
+ cprintf(cbuf, ";\n");
+ }
+
+ retval = 0;
+ done:
+ return retval;
+}
+
+
+static int
+yang2cli_container(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cbuf,
+ enum genmodel_type gt,
+ int level)
+{
+ yang_stmt *yc;
+ yang_stmt *yd;
+ int i;
+ int retval = -1;
+
+ cprintf(cbuf, "%*s%s", level*3, "", ys->ys_argument);
+ if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
+ cprintf(cbuf, "(\"%s\")", yd->ys_argument);
+ if (cli_callback_generate(h, ys, cbuf) < 0)
+ goto done;
+ cprintf(cbuf, ";{\n");
+ for (i=0; iys_len; i++)
+ if ((yc = ys->ys_stmt[i]) != NULL)
+ if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
+ goto done;
+ cprintf(cbuf, "%*s}\n", level*3, "");
+ retval = 0;
+ done:
+ return retval;
+}
+
+static int
+yang2cli_list(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cbuf,
+ enum genmodel_type gt,
+ int level)
+{
+ yang_stmt *yc;
+ yang_stmt *yd;
+ yang_stmt *ykey;
+ yang_stmt *yleaf;
+ int i;
+ cg_var *cvi;
+ char *keyname;
+ cvec *cvk = NULL; /* vector of index keys */
+ int retval = -1;
+
+ cprintf(cbuf, "%*s%s", level*3, "", ys->ys_argument);
+ if ((yd = yang_find((yang_node*)ys, Y_DESCRIPTION, NULL)) != NULL)
+ cprintf(cbuf, "(\"%s\")", yd->ys_argument);
+ /* Loop over all key variables */
+ if ((ykey = yang_find((yang_node*)ys, Y_KEY, NULL)) == NULL){
+ clicon_err(OE_XML, 0, "List statement \"%s\" has no key", ys->ys_argument);
+ goto done;
+ }
+ /* The value is a list of keys: [ ]* */
+ if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
+ goto done;
+ cvi = NULL;
+ /* Iterate over individual keys */
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ if ((yleaf = yang_find((yang_node*)ys, Y_LEAF, keyname)) == NULL){
+ clicon_err(OE_XML, 0, "List statement \"%s\" has no key leaf \"%s\"",
+ ys->ys_argument, keyname);
+ goto done;
+ }
+ /* Print key variable now, and skip it in loop below
+ Note, only print callback on last statement
+ */
+ if (yang2cli_leaf(h, yleaf, cbuf, gt==GT_VARS?GT_NONE:gt, level+1,
+ cvec_next(cvk, cvi)?0:1) < 0)
+ goto done;
+ }
+
+ cprintf(cbuf, "{\n");
+ for (i=0; iys_len; i++)
+ if ((yc = ys->ys_stmt[i]) != NULL){
+ /* cvk is a cvec of strings containing variable names
+ yc is a leaf that may match one of the values of cvk.
+ */
+ cvi = NULL;
+ while ((cvi = cvec_each(cvk, cvi)) != NULL) {
+ keyname = cv_string_get(cvi);
+ if (strcmp(keyname, yc->ys_argument) == 0)
+ break;
+ }
+ if (cvi != NULL)
+ continue;
+ if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
+ goto done;
+ }
+ cprintf(cbuf, "%*s}\n", level*3, "");
+ retval = 0;
+ done:
+ if (cvk)
+ cvec_free(cvk);
+ return retval;
+}
+/*! Generate cli code for yang choice statement
+
+ Example:
+ choice interface-type {
+ container ethernet { ... }
+ container fddi { ... }
+ }
+ @Note Removes 'meta-syntax' from cli syntax. They are not shown when xml is
+ translated to cli. and therefore input-syntax != output syntax. Which is bad
+ */
+static int
+yang2cli_choice(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cbuf,
+ enum genmodel_type gt,
+ int level)
+{
+ int retval = -1;
+ yang_stmt *yc;
+ int i;
+
+ for (i=0; iys_len; i++)
+ if ((yc = ys->ys_stmt[i]) != NULL){
+ switch (yc->ys_keyword){
+ case Y_CASE:
+ if (yang2cli_stmt(h, yc, cbuf, gt, level+2) < 0)
+ goto done;
+ break;
+ case Y_CONTAINER:
+ case Y_LEAF:
+ case Y_LEAF_LIST:
+ case Y_LIST:
+ default:
+ if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
+ goto done;
+ break;
+ }
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+
+/*! Translate yang-stmt to CLIgen syntax.
+ */
+static int
+yang2cli_stmt(clicon_handle h,
+ yang_stmt *ys,
+ cbuf *cbuf,
+ enum genmodel_type gt,
+ int level /* indentation level for pretty-print */
+ )
+{
+ yang_stmt *yc;
+ int retval = -1;
+ int i;
+
+ if (yang_config(ys)){
+ switch (ys->ys_keyword){
+ case Y_GROUPING:
+ case Y_RPC:
+ case Y_AUGMENT:
+ return 0;
+ break;
+ case Y_CONTAINER:
+ if (yang2cli_container(h, ys, cbuf, gt, level) < 0)
+ goto done;
+ break;
+ case Y_LIST:
+ if (yang2cli_list(h, ys, cbuf, gt, level) < 0)
+ goto done;
+ break;
+ case Y_CHOICE:
+ if (yang2cli_choice(h, ys, cbuf, gt, level) < 0)
+ goto done;
+ break;
+ case Y_LEAF_LIST:
+ case Y_LEAF:
+ if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
+ goto done;
+ break;
+ default:
+ for (i=0; iys_len; i++)
+ if ((yc = ys->ys_stmt[i]) != NULL)
+ if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
+ goto done;
+ break;
+ }
+ }
+
+ retval = 0;
+ done:
+ return retval;
+
+}
+
+/*! Translate from a yang specification into a CLIgen syntax.
+ *
+ * Print a CLIgen syntax to cbuf string, then parse it.
+ * @param gt - how to generate CLI:
+ * VARS: generate keywords for regular vars only not index
+ * ALL: generate keywords for all variables including index
+ */
+int
+yang2cli(clicon_handle h,
+ yang_spec *yspec,
+ parse_tree *ptnew,
+ enum genmodel_type gt)
+{
+ cbuf *cbuf;
+ int i;
+ int retval = -1;
+ yang_stmt *ymod = NULL;
+ cvec *globals; /* global variables from syntax */
+
+ if ((cbuf = cbuf_new()) == NULL){
+ clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__);
+ goto done;
+ }
+ /* Traverse YANG specification: loop through statements */
+ for (i=0; iyp_len; i++)
+ if ((ymod = yspec->yp_stmt[i]) != NULL){
+ if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
+ goto done;
+ }
+ clicon_debug(1, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
+ /* Parse the buffer using cligen parser. XXX why this?*/
+ if ((globals = cvec_new(0)) == NULL)
+ goto done;
+ /* load cli syntax */
+ if (cligen_parse_str(cli_cligen(h), cbuf_get(cbuf),
+ "yang2cli", ptnew, globals) < 0)
+ goto done;
+ cvec_free(globals);
+ /* handle=NULL for global namespace, this means expand callbacks must be in
+ CLICON namespace, not in a cli frontend plugin. */
+ if (cligen_expand_str2fn(*ptnew, expand_str2fn, NULL) < 0)
+ goto done;
+ retval = 0;
+ done:
+ cbuf_free(cbuf);
+ return retval;
+}
diff --git a/apps/cli/cli_generate.h b/apps/cli/cli_generate.h
new file mode 100644
index 00000000..64665fe7
--- /dev/null
+++ b/apps/cli/cli_generate.h
@@ -0,0 +1,32 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ */
+
+#ifndef _CLI_GENERATE_H_
+#define _CLI_GENERATE_H_
+
+/*
+ * Prototypes
+ */
+int yang2cli(clicon_handle h, yang_spec *yspec, parse_tree *ptnew,
+ enum genmodel_type gt);
+
+#endif /* _CLI_GENERATE_H_ */
diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c
new file mode 100644
index 00000000..f89a3b44
--- /dev/null
+++ b/apps/cli/cli_handle.c
@@ -0,0 +1,295 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_cli_api.h"
+#include "cli_plugin.h"
+#include "cli_handle.h"
+
+#define CLICON_MAGIC 0x99aafabe
+
+#define handle(h) (assert(clicon_handle_check(h)==0),(struct cli_handle *)(h))
+#define cligen(h) (handle(h)->cl_cligen)
+
+/*
+ * cli_handle
+ * first part of this is header, same for clicon_handle and config_handle.
+ * Access functions for common fields are found in clicon lib: clicon_options.[ch]
+ * This file should only contain access functions for the _specific_
+ * entries in the struct below.
+ */
+struct cli_handle {
+ int cl_magic; /* magic (HDR)*/
+ clicon_hash_t *cl_copt; /* clicon option list (HDR) */
+ clicon_hash_t *cl_data; /* internal clicon data (HDR) */
+ /* ------ end of common handle ------ */
+ cligen_handle cl_cligen; /* cligen handle */
+
+ int cl_send2backend; /* Send changes to configuration daemon */
+ enum candidate_db_type cl_candidate_type;
+ cli_syntax_t *cl_stx; /* syntax structure */
+
+};
+
+/*
+ * cli_handle_init
+ * returns a clicon handle for other CLICON API calls
+ */
+clicon_handle
+cli_handle_init(void)
+{
+ struct cli_handle *cl;
+ cligen_handle clih = NULL;
+ clicon_handle h = NULL;
+
+ if ((cl = (struct cli_handle *)clicon_handle_init0(sizeof(struct cli_handle))) == NULL)
+ return NULL;
+
+ if ((clih = cligen_init()) == NULL){
+ clicon_handle_exit((clicon_handle)cl);
+ goto done;
+ }
+ cligen_userhandle_set(clih, cl);
+ cl->cl_cligen = clih;
+ cl->cl_candidate_type = CANDIDATE_DB_SHARED;
+ h = (clicon_handle)cl;
+ done:
+ return h;
+}
+
+/*
+ * cli_handle_exit
+ * frees clicon handle
+ */
+int
+cli_handle_exit(clicon_handle h)
+{
+ cligen_handle ch = cligen(h);
+
+ clicon_handle_exit(h); /* frees h and options */
+ cligen_exit(ch);
+ return 0;
+}
+
+
+/*----------------------------------------------------------
+ * cli-specific handle access functions
+ *----------------------------------------------------------*/
+/*! Send changes to configuration daemon or let client handle it itself. Default is 1 */
+int
+cli_set_send2backend(clicon_handle h, int send2backend)
+{
+ struct cli_handle *cl = handle(h);
+
+ cl->cl_send2backend = send2backend;
+ return 0;
+}
+
+/*! Get status of whether to send changes to configuration daemon. */
+int
+cli_send2backend(clicon_handle h)
+{
+ struct cli_handle *cl = handle(h);
+
+ return cl->cl_send2backend;
+}
+
+enum candidate_db_type
+cli_candidate_type(clicon_handle h)
+{
+ struct cli_handle *cl = handle(h);
+
+ return cl->cl_candidate_type;
+}
+
+int
+cli_set_candidate_type(clicon_handle h, enum candidate_db_type type)
+{
+ struct cli_handle *cl = handle(h);
+
+ cl->cl_candidate_type = type;
+ return 0;
+}
+
+/* Current syntax-group */
+cli_syntax_t *
+cli_syntax(clicon_handle h)
+{
+ struct cli_handle *cl = handle(h);
+ return cl->cl_stx;
+}
+
+int
+cli_syntax_set(clicon_handle h, cli_syntax_t *stx)
+{
+ struct cli_handle *cl = handle(h);
+ cl->cl_stx = stx;
+ return 0;
+}
+
+/*----------------------------------------------------------
+ * cligen access functions
+ *----------------------------------------------------------*/
+cligen_handle
+cli_cligen(clicon_handle h)
+{
+ return cligen(h);
+}
+
+/*
+ * cli_interactive and clicon_eval
+ */
+int
+cli_exiting(clicon_handle h)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_exiting(ch);
+}
+/*
+ * cli_common.c: cli_quit
+ * cli_interactive()
+ */
+int
+cli_set_exiting(clicon_handle h, int exiting)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_exiting_set(ch, exiting);
+}
+
+char
+cli_comment(clicon_handle h)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_comment(ch);
+}
+
+char
+cli_set_comment(clicon_handle h, char c)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_comment_set(ch, c);
+}
+
+char
+cli_tree_add(clicon_handle h, char *tree, parse_tree pt)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_tree_add(ch, tree, pt);
+}
+
+char *
+cli_tree_active(clicon_handle h)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_tree_active(ch);
+}
+
+int
+cli_tree_active_set(clicon_handle h, char *treename)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_tree_active_set(ch, treename);
+}
+
+parse_tree *
+cli_tree(clicon_handle h, char *name)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_tree(ch, name);
+}
+
+int
+cli_parse_file(clicon_handle h,
+ FILE *f,
+ char *name, /* just for errs */
+ parse_tree *pt,
+ cvec *globals)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_parse_file(ch, f, name, pt, globals);
+}
+
+int
+cli_susp_hook(clicon_handle h, cli_susphook_t *fn)
+{
+ cligen_handle ch = cligen(h);
+
+ /* This assume first arg of fn can be treated as void* */
+ return cligen_susp_hook(ch, (cligen_susp_cb_t*)fn);
+}
+
+char *
+cli_nomatch(clicon_handle h)
+{
+ cligen_handle ch = cligen(h);
+
+ return cligen_nomatch(ch);
+}
+
+int
+cli_prompt_set(clicon_handle h, char *prompt)
+{
+ cligen_handle ch = cligen(h);
+ return cligen_prompt_set(ch, prompt);
+}
+
+int
+cli_logsyntax_set(clicon_handle h, int status)
+{
+ cligen_handle ch = cligen(h);
+ return cligen_logsyntax_set(ch, status);
+}
diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h
new file mode 100644
index 00000000..be485acb
--- /dev/null
+++ b/apps/cli/cli_handle.h
@@ -0,0 +1,58 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifndef _CLI_HANDLE_H_
+#define _CLI_HANDLE_H_
+
+/*
+ * Prototypes
+ * Internal prototypes. For exported functions see clicon_cli_api.h
+ */
+char cli_tree_add(clicon_handle h, char *tree, parse_tree pt);
+
+int cli_parse_file(clicon_handle h,
+ FILE *f,
+ char *name, /* just for errs */
+ parse_tree *pt,
+ cvec *globals);
+
+char *cli_tree_active(clicon_handle h);
+
+int cli_tree_active_set(clicon_handle h, char *treename);
+
+parse_tree *cli_tree(clicon_handle h, char *name);
+
+int cli_susp_hook(clicon_handle h, cli_susphook_t *fn);
+
+char *cli_nomatch(clicon_handle h);
+
+int cli_prompt_set(clicon_handle h, char *prompt);
+
+int cli_logsyntax_set(clicon_handle h, int status);
+
+/* Internal functions for handling cli groups */
+
+cli_syntax_t *cli_syntax(clicon_handle h);
+int cli_syntax_set(clicon_handle h, cli_syntax_t *stx);
+
+#endif /* _CLI_HANDLE_H_ */
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
new file mode 100644
index 00000000..241385d4
--- /dev/null
+++ b/apps/cli/cli_main.c
@@ -0,0 +1,407 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#define __USE_GNU /* strverscmp */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* cligen */
+#include
+
+/* clicon */
+#include
+
+#include "clicon_cli_api.h"
+
+#include "cli_plugin.h"
+#include "cli_generate.h"
+#include "cli_common.h"
+#include "cli_handle.h"
+
+/* Command line options to be passed to getopt(3) */
+#define CLI_OPTS "hD:f:F:1u:d:m:cP:qpGLl:"
+
+static int
+cli_terminate(clicon_handle h)
+{
+ yang_spec *yspec;
+
+ if ((yspec = clicon_dbspec_yang(h)) != NULL)
+ yspec_free(yspec);
+ cli_plugin_finish(h);
+ exit_candidate_db(h);
+ cli_handle_exit(h);
+ return 0;
+}
+
+/*
+ * cli_sig_term
+ * Unlink pidfile and quit
+*/
+static void
+cli_sig_term(int arg)
+{
+ clicon_log(LOG_NOTICE, "%s: %u Terminated (killed by sig %d)",
+ __PROGRAM__, getpid(), arg);
+ exit(1);
+}
+
+/*
+ * Setup signal handlers
+ */
+static void
+cli_signal_init (clicon_handle h)
+{
+ cli_signal_block(h);
+ set_signal(SIGTERM, cli_sig_term, NULL);
+}
+
+static void
+cli_interactive(clicon_handle h)
+{
+ int res;
+ char *cmd;
+ char *new_mode;
+ int result;
+
+ /* Loop through all commands */
+ while(!cli_exiting(h)) {
+// save_mode =
+ new_mode = cli_syntax_mode(h);
+ if ((cmd = clicon_cliread(h)) == NULL) {
+ cli_set_exiting(h, 1); /* EOF */
+ break;
+ }
+ if ((res = clicon_parse(h, cmd, &new_mode, &result)) < 0)
+ break;
+ }
+}
+
+static void
+usage(char *argv0, clicon_handle h)
+{
+ char *confsock = clicon_sock(h);
+ char *plgdir = clicon_cli_dir(h);
+
+ fprintf(stderr, "usage:%s [options] [commands]\n"
+ "where commands is a CLI command or options passed to the main plugin\n"
+ "where options are\n"
+ "\t-h \t\tHelp\n"
+ "\t-D \tDebug\n"
+ "\t-f \tConfig-file (mandatory)\n"
+ "\t-F \tRead commands from file (default stdin)\n"
+ "\t-1\t\tDo not enter interactive mode\n"
+ "\t-u \tconfig UNIX domain path (default: %s)\n"
+ "\t-d \tSpecify plugin directory (default: %s)\n"
+ "\t-m \tSpecify plugin syntax mode\n"
+ "\t-c \t\tWrite to candidate db directly, not via config backend\n"
+ "\t-P \tWrite to private database\n"
+ "\t-q \t\tQuiet mode, dont print greetings or prompt, terminate on ctrl-C\n"
+ "\t-p \t\tPrint database yang specification\n"
+ "\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n"
+ "\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n"
+ "\t-l \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n",
+ argv0,
+ confsock ? confsock : "none",
+ plgdir ? plgdir : "none"
+ );
+ exit(1);
+}
+
+/*
+ */
+int
+main(int argc, char **argv)
+{
+ char c;
+ enum candidate_db_type dbtype;
+ char private_db[MAXPATHLEN];
+ int once;
+ char *tmp;
+ char *argv0 = argv[0];
+ clicon_handle h;
+ int printspec = 0;
+ int printgen = 0;
+ int logclisyntax = 0;
+ int help = 0;
+ char *treename;
+ char *running_db;
+ int logdst = CLICON_LOG_STDERR;
+
+ /* Defaults */
+
+ /* In the startup, logs to stderr & debug flag set later */
+ clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
+ /* Initiate CLICON handle */
+ if ((h = cli_handle_init()) == NULL)
+ goto done;
+ if (cli_plugin_init(h) != 0)
+ goto done;
+ dbtype = CANDIDATE_DB_SHARED;
+ once = 0;
+ private_db[0] = '\0';
+ cli_set_send2backend(h, 1); /* send changes to config daemon */
+ cli_set_comment(h, '#'); /* Default to handle #! clicon_cli scripts */
+
+ /*
+ * First-step command-line options for help, debug, config-file and log,
+ */
+ optind = 1;
+ opterr = 0;
+ while ((c = getopt(argc, argv, CLI_OPTS)) != -1)
+ switch (c) {
+ case '?':
+ case 'h':
+ /* Defer the call to usage() to later. Reason is that for helpful
+ text messages, default dirs, etc, are not set until later.
+ But this means that we need to check if 'help' is set before
+ exiting, and then call usage() before exit.
+ */
+ help = 1;
+ break;
+ case 'D' : /* debug */
+ if (sscanf(optarg, "%d", &debug) != 1)
+ usage(argv[0], h);
+ break;
+ case 'f': /* config file */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
+ break;
+ case 'l': /* Log destination: s|e|o */
+ switch (optarg[0]){
+ case 's':
+ logdst = CLICON_LOG_SYSLOG;
+ break;
+ case 'e':
+ logdst = CLICON_LOG_STDERR;
+ break;
+ case 'o':
+ logdst = CLICON_LOG_STDOUT;
+ break;
+ default:
+ usage(argv[0], h);
+ }
+ break;
+ }
+ /*
+ * Logs, error and debug to stderr or syslog, set debug level
+ */
+ clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
+
+ clicon_debug_init(debug, NULL);
+
+ /* Find and read configfile */
+ if (clicon_options_main(h) < 0){
+ if (help)
+ usage(argv[0], h);
+ return -1;
+ }
+
+ /* Now rest of options */
+ opterr = 0;
+ optind = 1;
+ while ((c = getopt(argc, argv, CLI_OPTS)) != -1){
+ switch (c) {
+ case 'D' : /* debug */
+ case 'f': /* config file */
+ case 'l': /* Log destination */
+ break; /* see above */
+ case 'F': /* read commands from file */
+ if (freopen(optarg, "r", stdin) == NULL){
+ cli_output(stderr, "freopen: %s\n", strerror(errno));
+ return -1;
+ }
+ break;
+ case '1' : /* Quit after reading database once - dont wait for events */
+ once = 1;
+ break;
+ case 'u': /* config unix domain path/ ip host */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_SOCK", optarg);
+ break;
+ case 'd': /* Plugin directory: overrides configfile */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_CLI_DIR", optarg);
+ break;
+ case 'm': /* CLI syntax mode */
+ if (!strlen(optarg))
+ usage(argv[0], h);
+ clicon_option_str_set(h, "CLICON_CLI_MODE", optarg);
+ break;
+ case 'c' : /* No config daemon (used in bootstrapping and file load) */
+ cli_set_send2backend(h, 0);
+ break;
+ case 'P' : /* load to private database with given name */
+ dbtype = CANDIDATE_DB_PRIVATE;
+ clicon_option_str_set(h, "CLICON_CANDIDATE_DB", optarg); /* override default */
+ break;
+ case 'q' : /* Quiet mode */
+ clicon_option_str_set(h, "CLICON_QUIET", "on");
+ break;
+ case 'p' : /* Print spec */
+ printspec++;
+ break;
+ case 'G' : /* Print generated CLI syntax */
+ printgen++;
+ break;
+ case 'L' : /* Debug print dynamic CLI syntax */
+ logclisyntax++;
+ break;
+ default:
+ usage(argv[0], h);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Defer: Wait to the last minute to print help message */
+ if (help)
+ usage(argv[0], h);
+
+ /* Setup signal handlers */
+ cli_signal_init(h);
+
+ /* Backward compatible mode, do not include keys in cgv-arrays in callbacks.
+ Should be 0 but default is 1 since all legacy apps use 1
+ Test legacy before shifting default to 0
+ */
+ cv_exclude_keys(clicon_cli_varonly(h));
+
+ /* Parse db specification as cli*/
+ if (yang_spec_main(h, stdout, printspec) < 0)
+ goto done;
+
+ /* Check plugin directory */
+ if (clicon_cli_dir(h) == NULL){
+ clicon_err(OE_PLUGIN, 0, "clicon_cli_dir not defined");
+ goto done;
+ }
+
+ /* Create tree generated from dataspec */
+ if (clicon_cli_genmodel(h)){
+ yang_spec *yspec; /* yang spec */
+ parse_tree pt = {0,}; /* cli parse tree */
+
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No YANG DB_SPEC");
+ goto done;
+ }
+ /* Create cli command tree from dbspec */
+ if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
+ goto done;
+
+ treename = chunk_sprintf(__FUNCTION__, "datamodel:%s", clicon_dbspec_name(h));
+ cli_tree_add(h, treename, pt);
+ if (printgen)
+ cligen_print(stdout, pt, 1);
+ }
+
+ /* Initialize cli syntax */
+ if (cli_syntax_load(h) < 0)
+ goto done;
+
+ /* Set syntax mode if specified from command-line or config-file. */
+ if (clicon_option_exists(h, "CLICON_CLI_MODE"))
+ if ((tmp = clicon_cli_mode(h)) != NULL)
+ if (cli_set_syntax_mode(h, tmp) == 0) {
+ fprintf(stderr, "FATAL: Failed to set syntax mode '%s'\n", tmp);
+ goto done;
+ }
+
+ if (!cli_syntax_mode(h)){
+ fprintf (stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n");
+ goto done;
+ }
+ if (cli_tree(h, cli_syntax_mode(h)) == NULL){
+ fprintf (stderr, "FATAL: No such cli mode: %s\n", cli_syntax_mode(h));
+ goto done;
+ }
+
+ /* Initialize databases */
+ if ((running_db = clicon_running_db(h)) == NULL)
+ goto done;
+
+ if (strlen(private_db))
+ clicon_option_str_set(h, "CLICON_CANDIDATE_DB", private_db);
+
+ if (!cli_send2backend(h))
+ if (db_init(running_db) < 0){
+ fprintf (stderr, "FATAL: Could not init running_db. (Run as root?)\n");
+ goto done;
+ }
+ /* A client does not have access to the candidate (and running)
+ databases if both these conditions are true:
+ 1. clicon_sock_family(h) == AF_INET[6]
+ 2. cli_send2backend(h) == 1
+ */
+ if (clicon_sock_family(h) == AF_UNIX || cli_send2backend(h)==0)
+ if (init_candidate_db(h, dbtype) < 0)
+ return -1;
+
+ if (logclisyntax)
+ cli_logsyntax_set(h, logclisyntax);
+
+ if (debug)
+ clicon_option_dump(h, debug);
+
+ /* Call start function in all plugins before we go interactive
+ Pass all args after the standard options to plugin_start
+ */
+
+ tmp = *(argv-1);
+ *(argv-1) = argv0;
+ cli_plugin_start(h, argc+1, argv-1);
+ *(argv-1) = tmp;
+
+ /* Launch interfactive event loop, unless -1 */
+ if (once == 0)
+ cli_interactive(h);
+ done:
+ // Gets in your face if we log on stderr
+ clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
+ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
+ cli_terminate(h);
+
+ return 0;
+}
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
new file mode 100644
index 00000000..0a57b69f
--- /dev/null
+++ b/apps/cli/cli_plugin.c
@@ -0,0 +1,1153 @@
+/*
+ *
+ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
+
+ This file is part of CLICON.
+
+ CLICON 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.
+
+ CLICON 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 CLICON; see the file COPYING. If not, see
+ .
+
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "clicon_config.h" /* generated by config & autoconf */
+#endif
+
+#include
+#include