From b13efce73e506b0feb4bb7c275c273a54ae6e716 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Tue, 22 Oct 2019 15:04:45 +0200 Subject: Change license and style, reorganise file, make makefile portable, and fix bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 3 +- COPYING | 15 - DEPENDENCIES | 1 - LICENSE | 683 +----------------------------------- Makefile | 139 ++------ README | 2 +- arg.h | 63 ++++ communication.c | 159 +++++++++ communication.h | 110 ++++++ config.mk | 12 + coopgammad.1 | 121 +++++++ coopgammad.c | 875 ++++++++++++++++++++++++++++++++++++++++++++++ doc/coopgammad.1 | 120 ------- servers-coopgamma.c | 524 ++++++++++++++++++++++++++++ servers-coopgamma.h | 76 ++++ servers-crtc.c | 312 +++++++++++++++++ servers-crtc.h | 75 ++++ servers-gamma.c | 381 +++++++++++++++++++++ servers-gamma.h | 60 ++++ servers-kernel.c | 363 ++++++++++++++++++++ servers-kernel.h | 64 ++++ servers-master.c | 370 ++++++++++++++++++++ servers-master.h | 17 + src/arg.h | 63 ---- src/communication.c | 175 ---------- src/communication.h | 137 -------- src/coopgammad.c | 895 ------------------------------------------------ src/servers/coopgamma.c | 558 ------------------------------ src/servers/coopgamma.h | 101 ------ src/servers/crtc.c | 325 ------------------ src/servers/crtc.h | 100 ------ src/servers/gamma.c | 398 --------------------- src/servers/gamma.h | 85 ----- src/servers/kernel.c | 384 --------------------- src/servers/kernel.h | 85 ----- src/servers/master.c | 394 --------------------- src/servers/master.h | 36 -- src/state.c | 638 ---------------------------------- src/state.h | 192 ----------- src/types/filter.c | 144 -------- src/types/filter.h | 138 -------- src/types/message.c | 572 ------------------------------- src/types/message.h | 160 --------- src/types/output.c | 335 ------------------ src/types/output.h | 308 ----------------- src/types/ramps.c | 98 ------ src/types/ramps.h | 102 ------ src/types/ring.c | 224 ------------ src/types/ring.h | 152 -------- src/util.c | 316 ----------------- src/util.h | 115 ------- state.c | 612 +++++++++++++++++++++++++++++++++ state.h | 167 +++++++++ types-filter.c | 125 +++++++ types-filter.h | 108 ++++++ types-message.c | 561 ++++++++++++++++++++++++++++++ types-message.h | 133 +++++++ types-output.c | 322 +++++++++++++++++ types-output.h | 275 +++++++++++++++ types-ramps.c | 86 +++++ types-ramps.h | 75 ++++ types-ring.c | 193 +++++++++++ types-ring.h | 131 +++++++ util.c | 274 +++++++++++++++ util.h | 81 +++++ 65 files changed, 6777 insertions(+), 8141 deletions(-) delete mode 100644 COPYING create mode 100644 arg.h create mode 100644 communication.c create mode 100644 communication.h create mode 100644 config.mk create mode 100644 coopgammad.1 create mode 100644 coopgammad.c delete mode 100644 doc/coopgammad.1 create mode 100644 servers-coopgamma.c create mode 100644 servers-coopgamma.h create mode 100644 servers-crtc.c create mode 100644 servers-crtc.h create mode 100644 servers-gamma.c create mode 100644 servers-gamma.h create mode 100644 servers-kernel.c create mode 100644 servers-kernel.h create mode 100644 servers-master.c create mode 100644 servers-master.h delete mode 100644 src/arg.h delete mode 100644 src/communication.c delete mode 100644 src/communication.h delete mode 100644 src/coopgammad.c delete mode 100644 src/servers/coopgamma.c delete mode 100644 src/servers/coopgamma.h delete mode 100644 src/servers/crtc.c delete mode 100644 src/servers/crtc.h delete mode 100644 src/servers/gamma.c delete mode 100644 src/servers/gamma.h delete mode 100644 src/servers/kernel.c delete mode 100644 src/servers/kernel.h delete mode 100644 src/servers/master.c delete mode 100644 src/servers/master.h delete mode 100644 src/state.c delete mode 100644 src/state.h delete mode 100644 src/types/filter.c delete mode 100644 src/types/filter.h delete mode 100644 src/types/message.c delete mode 100644 src/types/message.h delete mode 100644 src/types/output.c delete mode 100644 src/types/output.h delete mode 100644 src/types/ramps.c delete mode 100644 src/types/ramps.h delete mode 100644 src/types/ring.c delete mode 100644 src/types/ring.h delete mode 100644 src/util.c delete mode 100644 src/util.h create mode 100644 state.c create mode 100644 state.h create mode 100644 types-filter.c create mode 100644 types-filter.h create mode 100644 types-message.c create mode 100644 types-message.h create mode 100644 types-output.c create mode 100644 types-output.h create mode 100644 types-ramps.c create mode 100644 types-ramps.h create mode 100644 types-ring.c create mode 100644 types-ring.h create mode 100644 util.c create mode 100644 util.h diff --git a/.gitignore b/.gitignore index 72a83fb..4197d01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -bin/ -obj/ \#*\# .\#* *~ @@ -13,3 +11,4 @@ obj/ *.dvi *.ps *.info +/coopgammad diff --git a/COPYING b/COPYING deleted file mode 100644 index 900f61b..0000000 --- a/COPYING +++ /dev/null @@ -1,15 +0,0 @@ -coopgammad -- Cooperative gamma server -Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . diff --git a/DEPENDENCIES b/DEPENDENCIES index 7e4084b..c449ab4 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -18,4 +18,3 @@ INSTALL DEPENDENCIES: make coreutils - diff --git a/LICENSE b/LICENSE index 94a9ed0..c2c5e80 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,15 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +ISC License - 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. +© 2016, 2019 Mattias Andrée - Preamble +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile index 96fdd0f..065ed9e 100644 --- a/Makefile +++ b/Makefile @@ -1,115 +1,52 @@ -PREFIX = /usr -BINDIR = $(PREFIX)/bin -DATADIR = $(PREFIX)/share -MANDIR = $(DATADIR)/man -MAN1DIR = $(MANDIR)/man1 -LICENSEDIR = $(DATADIR)/licenses +.POSIX: -PKGNAME = coopgammad -COMMAND = coopgammad +CONFIGFILE = config.mk +include $(CONFIGFILE) -KERNEL = $(shell uname | tr '[A-Z]_' '[a-z]-') +XCPPFLAGS = -D'PKGNAME="$(PKGNAME)"' -D'COMMAND="$(COMMAND)"' -SRC = \ - coopgammad \ - util \ - communication \ - state \ - servers/master \ - servers/kernel \ - servers/crtc \ - servers/gamma \ - servers/coopgamma \ - types/filter \ - types/output \ - types/ramps \ - types/message \ - types/ring +PARTS =\ + communication\ + state\ + util\ + servers-master\ + servers-kernel\ + servers-crtc\ + servers-gamma\ + servers-coopgamma\ + types-filter\ + types-output\ + types-ramps\ + types-message\ + types-ring -OPTIMISE = -O2 +OBJ = $(PARTS:=.o) coopgammad.c -WARN = -Wall -Wextra -pedantic +HDR = $(PARTS:=.h) arg.h -CPP_linux = -DHAVE_LINUX_PROCFS -CPP_linux-libre = $(CPP_linux) +all: coopgammad +$(OBJ): $(@:.o=.c) $(HDR) -CCFLAGS = -std=c99 $(WARN) $(FFLAGS) $(OPTIMISE) -LDFLAGS = $(OPTIMISE) -lgamma -CPPFLAGS = -D'PKGNAME="$(PKGNAME)"' -D'COMMAND="$(COMMAND)"' -D_XOPEN_SOURCE=700 $(CPP_$(KERNEL)) -ifdef USE_VALGRIND -CPPFLAGS += -DUSE_VALGRIND -endif +.c.o: + $(CC) -c -o $@ $< $(XCPPFLAGS) $(CPPFLAGS) $(CFLAGS) +coopgammad: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) +install: coopgammad + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1" + cp -- coopgammad "$(DESTDIR)$(PREFIX)/bin/coopgammad" + cp -- coopgammad.1 "$(DESTDIR)$(MANPREFIX)/man1/coopgammad.1" -.PHONY: all -all: bin/coopgammad - -.PHONY: base -base: cmd - -.PHONY: cmd -cmd: bin/coopgammad - -bin/coopgammad: $(foreach S,$(SRC),obj/$(S).o) - @mkdir -p -- "$$(dirname -- "$@")" - $(CC) $(LDFLAGS) -o $@ $^ - -obj/%.o: src/%.c src/*.h src/*/*.h - @mkdir -p -- "$$(dirname -- "$@")" - $(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $< - - - -.PHONY: install -install: install-base install-doc - -.PHONY: install-base -install-base: install-cmd install-copyright - -.PHONY: install-copyright -install-copyright: install-license install-copying - -.PHONY: install-doc -install-doc: install-man - -.PHONY: install-cmd -install-cmd: bin/coopgammad - mkdir -p -- "$(DESTDIR)$(BINDIR)" - cp -- bin/coopgammad "$(DESTDIR)$(BINDIR)/$(COMMAND)" - chmod 0755 -- "$(DESTDIR)$(BINDIR)/$(COMMAND)" - -.PHONY: install-license -install-license: - mkdir -p -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)" - cp -- LICENSE "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE" - chmod 0644 -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE" - -.PHONY: install-copying -install-copying: - mkdir -p -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)" - cp -- COPYING "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING" - chmod 0644 -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING" - -.PHONY: install-man -install-man: - mkdir -p -- "$(DESTDIR)$(MAN1DIR)" - cp -- doc/coopgammad.1 "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1" - chmod 644 -- "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1" - - - -.PHONY: uninstall uninstall: - -rm -- "$(DESTDIR)$(MAN1DIR)/$(COMMAND).1" - -rm -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/COPYING" - -rm -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)/LICENSE" - -rmdir -- "$(DESTDIR)$(LICENSEDIR)/$(PKGNAME)" - -rm -- "$(DESTDIR)$(BINDIR)/$(COMMAND)" - + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/coopgammad.1" + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/coopgammad" - -.PHONY: clean clean: - -rm -r bin obj + -rm -rf -- coopgammad *.o *.su + +.SUFFIXES: +.SUFFIXES: .o .c +.PHONY: all install uninstall clean diff --git a/README b/README index 3954901..c996c26 100644 --- a/README +++ b/README @@ -100,4 +100,4 @@ RATIONALE SEE ALSO libcoopgamma(7), cg-tools(7), libgamma(7), - blueshift(1), mds-coopgamma(1). + blueshift(1), radharc(1), mds-coopgamma(1). diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..8418b38 --- /dev/null +++ b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define LNGARG() &argv[0][0] + +#endif diff --git a/communication.c b/communication.c new file mode 100644 index 0000000..13d5046 --- /dev/null +++ b/communication.c @@ -0,0 +1,159 @@ +/* See LICENSE file for copyright and license details. */ +#include "communication.h" +#include "state.h" +#include "servers-coopgamma.h" + +#include +#include +#include + + +/** + * Send a message + * + * @param conn The index of the connection + * @param buf The data to send + * @param n The size of `buf` + * @return Zero on success, -1 on error, 1 if disconncted + * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count + * as success (ECONNRESET cause 1 to be returned), + * and are handled appropriately. + */ +int +send_message(size_t conn, char *restrict buf, size_t n) +{ + struct ring *restrict ring = outbound + conn; + int fd = connections[conn]; + int saved_errno; + size_t ptr = 0; + ssize_t sent; + size_t chunksize = n; + size_t sendsize; + size_t old_n; + char *old_buf; + size_t old_ptr; + + while ((old_buf = ring_peek(ring, &old_n))) { + for (old_ptr = 0; old_ptr < n;) { + sendsize = old_n - old_ptr < chunksize ? old_n - old_ptr : chunksize; + sent = send(fd, old_buf + old_ptr, sendsize, MSG_NOSIGNAL); + if (sent < 0) { + if (errno == EPIPE) + errno = ECONNRESET; + if (errno != EMSGSIZE) + goto fail; + chunksize >>= 1; + if (!chunksize) + goto fail; + continue; + } + old_ptr += (size_t)sent; + ring_pop(ring, (size_t)sent); + } + } + + while (ptr < n) { + sendsize = n - ptr < chunksize ? n - ptr : chunksize; + sent = send(fd, buf + ptr, sendsize, MSG_NOSIGNAL); + if (sent < 0) { + if (errno == EPIPE) + errno = ECONNRESET; + if (errno != EMSGSIZE) + goto fail; + chunksize >>= 1; + if (!chunksize) + goto fail; + continue; + } + ptr += (size_t)sent; + } + + free(buf); + return 0; + +fail: + switch (errno) { + case EINTR: +#if defined(EAGAIN) + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN != EWOULDBLOCK) + case EWOULDBLOCK: +#endif + if (ring_push(ring, buf + ptr, n - ptr) < 0) + goto proper_fail; + free(buf); + return 0; + + case ECONNRESET: + free(buf); + if (connection_closed(fd) < 0) + return -1; + return 1; + + default: + break; + } + +proper_fail: + saved_errno = errno; + free(buf); + errno = saved_errno; + return -1; +} + + +/** + * Send a custom error without an error number + * + * @param conn The index of the connection + * @param message_id The ID of the message to which this message is a response + * @param desc The error description to send + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +int +(send_error)(size_t conn, const char *restrict message_id, const char *restrict desc) +{ + char *restrict buf; + size_t n; + + MAKE_MESSAGE(&buf, &n, 0, + "Command: error\n" + "In response to: %s\n" + "Error: custom\n" + "Length: %zu\n" + "\n" + "%s\n", + message_id, strlen(desc) + 1, desc); + + return send_message(conn, buf, n); +} + + +/** + * Send a standard error + * + * @param conn The index of the connection + * @param message_id The ID of the message to which this message is a response + * @param number The value of `errno`, 0 to indicate success + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +int +(send_errno)(size_t conn, const char *restrict message_id, int number) +{ + char *restrict buf; + size_t n; + + MAKE_MESSAGE(&buf, &n, 0, + "Command: error\n" + "In response to: %s\n" + "Error: %i\n" + "\n", + message_id, number); + + return send_message(conn, buf, n); +} diff --git a/communication.h b/communication.h new file mode 100644 index 0000000..b40022b --- /dev/null +++ b/communication.h @@ -0,0 +1,110 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef COMMUNICATION_H +#define COMMUNICATION_H + +#include +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Construct a message + * + * @param bufp:char** Output parameter for the buffer, must not have side-effects + * @param np:size_t* Output parameter for the size of the buffer sans `extra` + * @param extra:size_t The extra number for bytes to allocate to the buffer + * @param format:string-literal Message format string + * @param ... Message formatting arguments + */ +#define MAKE_MESSAGE(bufp, np, extra, format, ...)\ + do {\ + ssize_t m__;\ + snprintf(NULL, 0, format "%zn", __VA_ARGS__, &m__);\ + *(bufp) = malloc((size_t)(extra) + (size_t)m__ + (size_t)1);\ + if (!*(bufp))\ + return -1;\ + sprintf(*(bufp), format, __VA_ARGS__);\ + *(np) = (size_t)m__;\ + } while (0) + +/** + * Send a custom error without an error number + * + * @param ... The error description to send + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__)) + +/** + * Send a standard error + * + * @param ... The value of `errno`, 0 to indicate success + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__)) + +/** + * Send a message + * + * @param conn The index of the connection + * @param buf The data to send + * @param n The size of `buf` + * @return Zero on success, -1 on error, 1 if disconncted + * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count + * as success (ECONNRESET cause 1 to be returned), + * and are handled appropriately. + */ +int send_message(size_t conn, char *restrict buf, size_t n); + +/** + * Send a custom error without an error number + * + * @param conn The index of the connection + * @param message_id The ID of the message to which this message is a response + * @param desc The error description to send + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +GCC_ONLY(__attribute__((__nonnull__))) +int (send_error)(size_t conn, const char *restrict message_id, const char *restrict desc); + +/** + * Send a standard error + * + * @param conn The index of the connection + * @param message_id The ID of the message to which this message is a response + * @param number The value of `errno`, 0 to indicate success + * @return 1: Client disconnected + * 0: Success (possibily delayed) + * -1: An error occurred + */ +GCC_ONLY(__attribute__((__nonnull__))) +int (send_errno)(size_t conn, const char *restrict message_id, int number); + +/** + * Continue sending the queued messages + * + * @param conn The index of the connection + * @return Zero on success, -1 on error, 1 if disconncted + * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count + * as success (ECONNRESET cause 1 to be returned), + * and are handled appropriately. + */ +static inline int +continue_send(size_t conn) +{ + return send_message(conn, NULL, 0); +} + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..2208750 --- /dev/null +++ b/config.mk @@ -0,0 +1,12 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +# For class of preserved clut +PKGNAME = coopgammad +COMMAND = coopgammad + +CPPFLAGS = -D_XOPEN_SOURCE=700 -DUSE_VALGRIND +#CFLAGS = -std=c99 -Wall -O2 +#LDFLAGS = -lgamma -s +CFLAGS = -std=c99 -Wall -Og -g +LDFLAGS = -lgamma diff --git a/coopgammad.1 b/coopgammad.1 new file mode 100644 index 0000000..b8166b5 --- /dev/null +++ b/coopgammad.1 @@ -0,0 +1,121 @@ +.TH COOPGAMMAD 1 COOPGAMMAD +.SH "NAME" +coopgammad - Cooperative gamma server +.SH "SYNPOSIS" +.B coopgammad +.RB [ -m +.IR method ] +.RB [ -s +.IR site ] +.RB [ -fkpq ] +.SH "DESCRIPTION" +Programs that desire to change the gamma adjustment +on a display should use this program instead of +talking directly with the display server (unless +the display server is +.BR mds ). +By doing this, multiple programs can add filters to +the display without overriding each others effects. +.P +By using +.B coopgammad +to apply adjustments, you can select whether the +adjustment is persistent or shall be removed when +your program disconnects from +.BR coopgammad . +Even if the adjustment is persistent it can be +modified or removed later by another process. +.SH "OPTIONS" +.TP +.B -f +Don't fork the process to the background. +If used, you can still detect when the +process has been initialised be waiting +for its stdout to close. +.TP +.B -k +Do not close stderr when forking to the +background. +.TP +\fB-m\fP \fIMETHOD\fP +Adjustment method name or number. Recognised +names include: +.TS +tab(:); +l l. +\fBdummy\fP:Dummy method +\fBrandr\fP:X RAndR +\fBvidmode\fP:X VidMode +\fBdrm\fP:Linux DRM +\fBgdi\fP:Windows GDI +\fBquartz\fP:Quartz Core Graphics +.TE + +The adjustment methods are supported via +.BR libgamma (7). +Only methods that were enabled when +.B libgamma +as compiled will be supported. +.TP +.B -p +Add the current gamma adjustments to the +filter list at priority 0. Even if this +is not used, the gamma adjustments will +not change for an output until a filter +has been added for that output. +.TP +.B -q +If used once, print the selected adjustment +method for the first line to stdout, and +if site's have names for that method, print +the name of the selected site on the second +line to stdout. The second line can be omitted +if +.B -s +has not been used and the default site cannot +be find. + +If used at least twice, print the pathname +of the socket for the select method and site +combination to stdout. Under unusual +circumstances, the path may contain LF +characters, but it will always be terminated +by one extra LF to mark the end of the +printed line. +.TP +\fB-s\fP \fISITE\fP +Select the site to which to connect. +For example +.RB \(aq :0 \(aq, +for local display 0 when using +.BR X . +.SH "SIGNALS" +.TP +.B SIGUSR1 +Reexecute the process to an updated version. +.TP +.BR SIGUSR2 ", " SIGINFO " if available" +Dump the process state to standard error. +.TP +.B SIGRTMIN+0 +Disconnect from the display server or graphics +card. +.TP +.B SIGRTMIN+1 +Reconnect to the display server or graphics card. +.SH "RATIONALE" +After reading the description section, the need for +this should be obvious. +.P +I plan to reuse code written for this program when +implementing +.BR mds-coopgamma (1), +therefore, the protocol is overly complicated, +implementation-wise. +.SH "SEE ALSO" +.BR libcoopgamma (7), +.BR cg-tools (7), +.BR libgamma (7), +.BR blueshift (1), +.BR radharc (1), +.BR mds-coopgamma (1). diff --git a/coopgammad.c b/coopgammad.c new file mode 100644 index 0000000..50ba53b --- /dev/null +++ b/coopgammad.c @@ -0,0 +1,875 @@ +/* See LICENSE file for copyright and license details. */ +#include "arg.h" +#include "util.h" +#include "state.h" +#include "servers-master.h" +#include "servers-kernel.h" +#include "servers-crtc.h" +#include "servers-gamma.h" +#include "servers-coopgamma.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Number put in front of the marshalled data + * so the program an detect incompatible updates + */ +#define MARSHAL_VERSION 0 + + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + + +/** + * Lists all function recognised adjustment methods, + * will call macro X with the code for the each + * adjustment method as the first argument and + * corresponding name as the second argument + */ +#define LIST_ADJUSTMENT_METHODS\ + X(LIBGAMMA_METHOD_DUMMY, "dummy")\ + X(LIBGAMMA_METHOD_X_RANDR, "randr")\ + X(LIBGAMMA_METHOD_X_VIDMODE, "vidmode")\ + X(LIBGAMMA_METHOD_LINUX_DRM, "drm")\ + X(LIBGAMMA_METHOD_W32_GDI, "gdi")\ + X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz") + + +/** + * Used by initialisation functions as their return type. If a + * value not listed here is returned by such function, it is the + * exit value the process shall exit with as soon as possible. + */ +enum init_status { + /** + * Initialisation was successful + */ + INIT_SUCCESS = -1, + + /** + * Initialisation failed + */ + INIT_FAILURE = -2, + + /** + * Server is already running + */ + INIT_RUNNING = -3, +}; + + +/** + * The pathname of the PID file + */ +extern char *restrict pidpath; +char *restrict pidpath = NULL; + +/** + * The pathname of the socket + */ +extern char *restrict socketpath; +char *restrict socketpath = NULL; + + + +/** + * Called when the process receives + * a signal telling it to re-execute + * + * @param signo The received signal + */ +static void +sig_reexec(int signo) +{ + int saved_errno = errno; + reexec = 1; + signal(signo, sig_reexec); + errno = saved_errno; +} + + +/** + * Called when the process receives + * a signal telling it to terminate + * + * @param signo The received signal + */ +static void +sig_terminate(int signo) +{ + terminate = 1; + (void) signo; +} + + +/** + * Called when the process receives + * a signal telling it to disconnect + * from or reconnect to the site + * + * @param signo The received signal + */ +static void +sig_connection(int signo) +{ + int saved_errno = errno; + connection = signo - SIGRTMIN + 1; + signal(signo, sig_connection); + errno = saved_errno; +} + + +/** + * Called when the process receives + * a signal telling it to dump its + * state to stderr + * + * @param signo The received signal + */ +static void +sig_info(int signo) +{ + int saved_errno = errno; + char *env; + signal(signo, sig_info); + env = getenv("COOPGAMMAD_PIDFILE_TOKEN"); + fprintf(stderr, "PID file token: %s\n", env ? env : "(null)"); + fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)"); + fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)"); + state_dump(); + errno = saved_errno; +} + + +/** + * Parse adjustment method name (or stringised number) + * + * @param arg The adjustment method name (or stringised number) + * @return The adjustment method, -1 (negative) on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +get_method(const char *restrict arg) +{ +#if LIBGAMMA_METHOD_MAX > 5 +# warning libgamma has added more adjustment methods +#endif + + const char *restrict p; + +#define X(C, N) if (!strcmp(arg, N)) return C; + LIST_ADJUSTMENT_METHODS; +#undef X + + if (!*arg || (/* avoid overflow: */ strlen(arg) > 4)) + goto bad; + for (p = arg; *p; p++) + if ('0' > *p || *p > '9') + goto bad; + + return atoi(arg); + +bad: + fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg); + errno = 0; + return -1; +} + + +/** + * Set up signal handlers + * + * @return Zero on success, -1 on error + */ +static int +set_up_signals(void) +{ + if (signal(SIGUSR1, sig_reexec) == SIG_ERR || + signal(SIGUSR2, sig_info) == SIG_ERR || +#if defined(SIGINFO) + signal(SIGINFO, sig_info) == SIG_ERR || +#endif + signal(SIGTERM, sig_terminate) == SIG_ERR || + signal(SIGRTMIN + 0, sig_connection) == SIG_ERR || + signal(SIGRTMIN + 1, sig_connection) == SIG_ERR) + return -1; + return 0; +} + + +/** + * Fork the process to the background + * + * @param keep_stderr Keep stderr open? + * @return An `enum init_status` value or an exit value + */ +static enum init_status +daemonise(int keep_stderr) +{ + pid_t pid; + int fd = -1, saved_errno; + int notify_rw[2] = {-1, -1}; + char a_byte = 0; + ssize_t got; + + if (pipe(notify_rw) < 0) + goto fail; + if (notify_rw[0] <= STDERR_FILENO) + if ((notify_rw[0] = fcntl(notify_rw[0], F_DUPFD, STDERR_FILENO + 1)) < 0) + goto fail; + if (notify_rw[1] <= STDERR_FILENO) + if ((notify_rw[1] = fcntl(notify_rw[1], F_DUPFD, STDERR_FILENO + 1)) < 0) + goto fail; + + if ((pid = fork()) < 0) + goto fail; + if (pid > 0) { + /* Original process (parent): */ + waitpid(pid, NULL, 0); + close(notify_rw[1]), notify_rw[1] = -1; + got = read(notify_rw[0], &a_byte, 1); + if (got < 0) + goto fail; + close(notify_rw[0]); + errno = 0; + return got == 0 ? INIT_FAILURE : (enum init_status)0; + } + + /* Intermediary process (child): */ + close(notify_rw[0]), notify_rw[0] = -1; + if (setsid() < 0) + goto fail; + if ((pid = fork()) < 0) + goto fail; + if (pid > 0) { + /* Intermediary process (parent): */ + return (enum init_status)0; + } + + /* Daemon process (child): */ + + /* Replace std* with /dev/null */ + fd = open("/dev/null", O_RDWR); + if (fd < 0 || + dup2(fd, STDIN_FILENO) < 0 || + dup2(fd, STDOUT_FILENO) < 0 || + (keep_stderr && dup2(fd, STDERR_FILENO) < 0)) + goto fail; + if (fd > STDERR_FILENO) + close(fd); + fd = -1; + + /* Update PID file */ + fd = open(pidpath, O_WRONLY); + if (fd < 0) + goto fail; + if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) + goto fail; + close(fd); + fd = -1; + + /* Notify */ + if (write(notify_rw[1], &a_byte, 1) <= 0) + goto fail; + close(notify_rw[1]); + + return INIT_SUCCESS; +fail: + saved_errno = errno; + if (fd >= 0) + close(fd); + if (notify_rw[0] >= 0) + close(notify_rw[0]); + if (notify_rw[1] >= 0) + close(notify_rw[1]); + errno = saved_errno; + return INIT_FAILURE; +} + + +/** + * Initialise the process + * + * @param foreground Keep process in the foreground + * @param keep_stderr Keep stderr open + * @param query Was -q used, see `main` for description + * @return An `enum init_status` value or an exit value + */ +static enum init_status +initialise(int foreground, int keep_stderr, int query) +{ + struct rlimit rlimit; + size_t i, n; + sigset_t mask; + int s; + enum init_status r; + + /* Zero out some memory so it can be destroyed safely. */ + memset(&site, 0, sizeof(site)); + + if (!query) { + /* Close all file descriptors above stderr */ + if (getrlimit(RLIMIT_NOFILE, &rlimit) || rlimit.rlim_cur == RLIM_INFINITY) + n = 4 << 10; + else + n = (size_t)(rlimit.rlim_cur); + for (i = STDERR_FILENO + 1; i < n; i++) + close((int)i); + + /* Set umask, reset signal handlers, and reset signal mask */ + umask(0); + for (s = 1; s < _NSIG; s++) + signal(s, SIG_DFL); + if (sigfillset(&mask)) + perror(argv0); + else + sigprocmask(SIG_UNBLOCK, &mask, NULL); + } + + /* Get method */ + if (method < 0 && libgamma_list_methods(&method, 1, 0) < 1) + return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1; + + /* Go no further if we are just interested in the adjustment method and site */ + if (query) + return INIT_SUCCESS; + + /* Get site */ + if (initialise_site() < 0) + goto fail; + + /* Get PID file and socket pathname */ + if (!(pidpath = get_pidfile_pathname()) || + !(socketpath = get_socket_pathname())) + goto fail; + + /* Create PID file */ + if ((r = create_pidfile(pidpath)) < 0) { + free(pidpath); + pidpath = NULL; + if (r == -2) + return INIT_RUNNING; + goto fail; + } + + /* Get partitions and CRTC:s */ + if (initialise_crtcs() < 0) + goto fail; + + /* Get CRTC information */ + if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs)))) + goto fail; + if (initialise_gamma_info() < 0) + goto fail; + + /* Sort outputs */ + qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name); + + /* Load current gamma ramps */ + store_gamma(); + + /* Preserve current gamma ramps at priority=0 if -p */ + if (preserve && preserve_gamma() < 0) + goto fail; + + /* Create socket and start listening */ + if (create_socket(socketpath) < 0) + goto fail; + + /* Get the real pathname of the process's binary, in case + * it is relative, so we can re-execute without problem. */ + if (*argv0 != '/' && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL))) + goto fail; + + /* Change directory to / to avoid blocking umounting */ + if (chdir("/") < 0) + perror(argv0); + + /* Set up signal handlers */ + if (set_up_signals() < 0) + goto fail; + + /* Place in the background unless -f */ + if (!foreground) { + return daemonise(keep_stderr); + } else { + /* Signal the spawner that the service is ready */ + close(STDOUT_FILENO); + /* Avoid potential catastrophes that would occur if a library + * that is being used was so mindless as to write to stdout. */ + if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) + perror(argv0); + } + + return INIT_SUCCESS; +fail: + return INIT_FAILURE; +} + + +/** + * Deinitialise the process + * + * @param full Perform a full deinitialisation, shall be + * done iff the process is going to re-execute + */ +static void +destroy(int full) +{ + if (full) { + disconnect_all(); + close_socket(socketpath); + free(argv0_real); + if (outputs && connected) + restore_gamma(); + } + state_destroy(); + free(socketpath); + if (full && pidpath) + unlink(pidpath); + free(pidpath); +} + + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal the state of the process + * + * @param buf Output buffer for the marshalled data, + * `NULL` to only measure how large the + * buffer needs to be + * @return The number of marshalled bytes + */ +static size_t +marshal(void *restrict buf) +{ + size_t off = 0, n; + char *restrict bs = buf; + + if (bs) + *(int *)&bs[off] = MARSHAL_VERSION; + off += sizeof(int); + + n = strlen(pidpath) + 1; + if (bs) + memcpy(&bs[off], pidpath, n); + off += n; + + n = strlen(socketpath) + 1; + if (bs) + memcpy(&bs[off], socketpath, n); + off += n; + + off += state_marshal(bs ? &bs[off] : NULL); + + return off; +} + + +/** + * Unmarshal the state of the process + * + * @param buf Buffer with the marshalled data + * @return The number of marshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +static size_t +unmarshal(const void *restrict buf) +{ + size_t off = 0, n; + const char *restrict bs = buf; + + if (*(const int *)&bs[off] != MARSHAL_VERSION) { + fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0); + errno = 0; + return 0; + } + off += sizeof(int); + + n = strlen(&bs[off]) + 1; + if (!(pidpath = memdup(&bs[off], n))) + return 0; + off += n; + + n = strlen(&bs[off]) + 1; + if (!(socketpath = memdup(&bs[off], n))) + return 0; + off += n; + + off += n = state_unmarshal(&bs[off]); + if (!n) + return 0; + + return off; +} + + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + + +/** + * Do minimal initialisation, unmarshal the state of + * the process and merge with new state + * + * @param statefile The state file + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +restore_state(const char *restrict statefile) +{ + void *marshalled = NULL; + int fd = -1, saved_errno; + size_t r, n; + + if (set_up_signals() < 0) + goto fail; + + fd = open(statefile, O_RDONLY); + if (fd < 0) + goto fail; + + if (!(marshalled = nread(fd, &n))) + goto fail; + close(fd); + fd = -1; + unlink(statefile); + statefile = NULL; + + r = unmarshal(marshalled); + if (!r) + goto fail; + if (r != n) { + fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n", + argv0, n > r ? "larger" : "smaller", r, n); + errno = 0; + goto fail; + } + free(marshalled); + marshalled = NULL; + + if (connected) { + connected = 0; + if (reconnect() < 0) + goto fail; + } + + return 0; +fail: + saved_errno = errno; + if (fd >= 0) + close(fd); + free(marshalled); + errno = saved_errno; + return -1; +} + + +/** + * Reexecute the server + * + * Returns only on failure + * + * @return Pathname of file where the state is stored, + * `NULL` if the state is in tact + */ +static char * +reexecute(void) +{ + char *statefile = NULL; + char *statebuffer = NULL; + size_t buffer_size; + int fd = -1, saved_errno; + + statefile = get_state_pathname(); + if (!statefile) + goto fail; + + buffer_size = marshal(NULL); + statebuffer = malloc(buffer_size); + if (!statebuffer) + goto fail; + if (marshal(statebuffer) != buffer_size) { + fprintf(stderr, "%s: internal error\n", argv0); + errno = 0; + goto fail; + } + + fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd < 0) + goto fail; + + if (nwrite(fd, statebuffer, buffer_size) != buffer_size) + goto fail; + free(statebuffer); + statebuffer = NULL; + + if (close(fd) < 0) { + fd = -1; + if (errno != EINTR) + goto fail; + } + fd = -1; + + destroy(0); + + execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL); + free(argv0_real); + argv0_real = NULL; + return statefile; + +fail: + saved_errno = errno; + free(statebuffer); + if (fd >= 0) + close(fd); + if (statefile != NULL) { + unlink(statefile); + free(statefile); + } + errno = saved_errno; + return NULL; +} + + +/** + * Print the response for the -q option + * + * @param query See -q for `main`, must be atleast 1 + * @return Zero on success, -1 on error + */ +static int +print_method_and_site(int query) +{ + const char *restrict methodname = NULL; + char *p; + + if (query == 1) { + switch (method) { +#define X(C, N) case C: methodname = N; break; + LIST_ADJUSTMENT_METHODS; +#undef X + default: + if (printf("%i\n", method) < 0) + return -1; + break; + } + if (methodname) + if (printf("%s\n", methodname) < 0) + return -1; + } + + if (!sitename) + if ((sitename = libgamma_method_default_site(method))) + if (!(sitename = memdup(sitename, strlen(sitename) + 1))) + return -1; + + if (sitename) { + switch (method) { + case LIBGAMMA_METHOD_X_RANDR: + case LIBGAMMA_METHOD_X_VIDMODE: + if ((p = strrchr(sitename, ':'))) + if ((p = strchr(p, '.'))) + *p = '\0'; + break; + default: + break; + } + } + + if (sitename && query == 1) + if (printf("%s\n", sitename) < 0) + return -1; + + if (query == 2) { + site.method = method; + site.site = sitename; + sitename = NULL; + socketpath = get_socket_pathname(); + if (!socketpath) + return -1; + if (printf("%s\n", socketpath) < 0) + return -1; + } + + if (fflush(stdout)) + return -1; + return 0; +} + + +/** + * Print usage information and exit + */ +#if defined(__GNU__) || defined(__clang__) +__attribute__((__noreturn__)) +#endif +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-m method] [-s site] [-fkpq]\n", argv0); + exit(1); +} + + +#if defined(__clang__) +# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command" +#endif + + +/** + * Must not be started without stdin, stdout, or stderr (may be /dev/null) + * + * argv[0] must refer to the real command name or pathname, + * otherwise, re-execute will not work + * + * The process closes stdout when the socket has been created + * + * @signal SIGUSR1 Re-execute to updated process image + * @signal SIGUSR2 Dump the state of the process to standard error + * @signal SIGINFO Ditto + * @signal SIGTERM Terminate the process gracefully + * @signal SIGRTMIN+0 Disconnect from the site + * @signal SiGRTMIN+1 Reconnect to the site + * + * @param argc The number of elements in `argv` + * @param argv Command line arguments. Recognised options: + * -s SITENAME + * The site to which to connect + * -m METHOD + * Adjustment method name or adjustment method number + * -p + * Preserve current gamma ramps at priority 0 + * -f + * Do not fork the process into the background + * -k + * Keep stderr open + * -q + * Print the select (possiblity default) adjustment + * method on the first line in stdout, and the + * selected (possibility defasult) site on the second + * line in stdout, and exit. If the site name is `NULL`, + * the second line is omitted. This is indented to + * be used by clients to figure out to which instance + * of the service it should connect. Use twice to + * simply ge the socket pathname, an a terminating LF. + * By combining -q and -m you can enumerate the name + * of all recognised adjustment method, start from 0 + * and work up until a numerical adjustment method is + * returned. + * @return 0: Successful + * 1: An error occurred + * 2: Already running + */ +int +main(int argc, char *argv[]) +{ + int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r; + char *statefile = NULL, *statefile_ = NULL; + + ARGBEGIN { + case 's': + sitename = EARGF(usage()); + /* To simplify re-exec: */ + sitename = memdup(sitename, strlen(sitename) + 1); + if (!sitename) + goto fail; + break; + case 'm': + method = get_method(EARGF(usage())); + if (method < 0) + goto fail; + break; + case 'p': preserve = 1; break; + case 'f': foreground = 1; break; + case 'k': keep_stderr = 1; break; + case 'q': query = 1 + !!query; break; + case ' ': /* Internal, do not document */ + statefile = statefile_ = EARGF(usage()); + break; + default: + usage(); + } + ARGEND; + + if (argc > 0) + usage(); + +restart: + if (!statefile) { + switch ((r = initialise(foreground, keep_stderr, query))) { + case INIT_SUCCESS: break; + case INIT_RUNNING: rc = 2; /* fall through */ + case INIT_FAILURE: goto fail; + default: return r; + } + } else if (restore_state(statefile) < 0) { + goto fail; + } else { + if (statefile != statefile_) + free(statefile); + unlink(statefile); + statefile = NULL; + } + + if (query) { + if (print_method_and_site(query) < 0) + goto fail; + goto done; + } + +reenter_loop: + if (main_loop() < 0) + goto fail; + + if (reexec && !terminate) { + statefile = reexecute(); + if (statefile) { + perror(argv0); + fprintf(stderr, "%s: restoring state without re-executing\n", argv0); + reexec = 0; + goto restart; + } + perror(argv0); + fprintf(stderr, "%s: continuing without re-executing\n", argv0); + reexec = 0; + goto reenter_loop; + } + +done: + rc = 0; +deinit: + if (statefile) + unlink(statefile); + if (reexec) + free(statefile); + destroy(1); + return rc; + +fail: + if (errno) + perror(argv0); + goto deinit; +} diff --git a/doc/coopgammad.1 b/doc/coopgammad.1 deleted file mode 100644 index 58c8ae2..0000000 --- a/doc/coopgammad.1 +++ /dev/null @@ -1,120 +0,0 @@ -.TH COOPGAMMAD 1 COOPGAMMAD -.SH "NAME" -coopgammad - Cooperative gamma server -.SH "SYNPOSIS" -.B coopgammad -.RB [ -m -.IR method ] -.RB [ -s -.IR site ] -.RB [ -fkpq ] -.SH "DESCRIPTION" -Programs that desire to change the gamma adjustment -on a display should use this program instead of -talking directly with the display server (unless -the display server is -.BR mds ). -By doing this, multiple programs can add filters to -the display without overriding each others effects. -.P -By using -.B coopgammad -to apply adjustments, you can select whether the -adjustment is persistent or shall be removed when -your program disconnects from -.BR coopgammad . -Even if the adjustment is persistent it can be -modified or removed later by another process. -.SH "OPTIONS" -.TP -.B -f -Don't fork the process to the background. -If used, you can still detect when the -process has been initialised be waiting -for its stdout to close. -.TP -.B -k -Do not close stderr when forking to the -background. -.TP -\fB-m\fP \fIMETHOD\fP -Adjustment method name or number. Recognised -names include: -.TS -tab(:); -l l. -\fBdummy\fP:Dummy method -\fBrandr\fP:X RAndR -\fBvidmode\fP:X VidMode -\fBdrm\fP:Linux DRM -\fBgdi\fP:Windows GDI -\fBquartz\fP:Quartz Core Graphics -.TE - -The adjustment methods are supported via -.BR libgamma (7). -Only methods that were enabled when -.B libgamma -as compiled will be supported. -.TP -.B -p -Add the current gamma adjustments to the -filter list at priority 0. Even if this -is not used, the gamma adjustments will -not change for an output until a filter -has been added for that output. -.TP -.B -q -If used once, print the selected adjustment -method for the first line to stdout, and -if site's have names for that method, print -the name of the selected site on the second -line to stdout. The second line can be omitted -if -.B -s -has not been used and the default site cannot -be find. - -If used at least twice, print the pathname -of the socket for the select method and site -combination to stdout. Under unusual -circumstances, the path may contain LF -characters, but it will always be terminated -by one extra LF to mark the end of the -printed line. -.TP -\fB-s\fP \fISITE\fP -Select the site to which to connect. -For example -.RB \(aq :0 \(aq, -for local display 0 when using -.BR X . -.SH "SIGNALS" -.TP -.B SIGUSR1 -Reexecute the process to an updated version. -.TP -.BR SIGUSR2 ", " SIGINFO " if available" -Dump the process state to standard error. -.TP -.B SIGRTMIN+0 -Disconnect from the display server or graphics -card. -.TP -.B SIGRTMIN+1 -Reconnect to the display server or graphics card. -.SH "RATIONALE" -After reading the description section, the need for -this should be obvious. -.P -I plan to reuse code written for this program when -implementing -.BR mds-coopgamma (1), -therefore, the protocol is overly complicated, -implementation-wise. -.SH "SEE ALSO" -.BR libcoopgamma (7), -.BR cg-tools (7), -.BR libgamma (7), -.BR blueshift (1), -.BR mds-coopgamma (1). diff --git a/servers-coopgamma.c b/servers-coopgamma.c new file mode 100644 index 0000000..f14c0c4 --- /dev/null +++ b/servers-coopgamma.c @@ -0,0 +1,524 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-coopgamma.h" +#include "servers-gamma.h" +#include "state.h" +#include "communication.h" +#include "util.h" +#include "types-output.h" + +#include + +#include +#include +#include +#include + + +/** + * Apply a filter on top of another filter + * + * @param dest The output for the resulting ramp-trio, must be initialised + * @param application The red, green and blue ramps, as one single raw array, + * of the filter that should be applied + * @param depth -1: `float` stops + * -2: `double` stops + * Other: the number of bits of each (integral) stop + * @param base The CLUT on top of which the new filter should be applied, + * this can be the same pointer as `dest` + */ +static void +apply_filter(union gamma_ramps *dest, void *restrict application, int depth, union gamma_ramps *base) +{ + union gamma_ramps app; + size_t bytedepth; + size_t red_width, green_width, blue_width; + + if (depth == -1) + bytedepth = sizeof(float); + else if (depth == -2) + bytedepth = sizeof(double); + else + bytedepth = (size_t)depth / 8; + + red_width = (app.u8.red_size = base->u8.red_size) * bytedepth; + green_width = (app.u8.green_size = base->u8.green_size) * bytedepth; + blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth; + + app.u8.red = application; + app.u8.green = &app.u8.red[red_width]; + app.u8.blue = &app.u8.green[green_width]; + + if (dest != base) { + memcpy(dest->u8.red, base->u8.red, red_width); + memcpy(dest->u8.green, base->u8.green, green_width); + memcpy(dest->u8.blue, base->u8.blue, blue_width); + } + + switch (depth) { + case 8: + libclut_apply(&dest->u8, UINT8_MAX, uint8_t, &app.u8, UINT8_MAX, uint8_t, 1, 1, 1); + break; + + case 16: + libclut_apply(&dest->u16, UINT16_MAX, uint16_t, &app.u16, UINT16_MAX, uint16_t, 1, 1, 1); + break; + + case 32: + libclut_apply(&dest->u32, UINT32_MAX, uint32_t, &app.u32, UINT32_MAX, uint32_t, 1, 1, 1); + break; + + case 64: + libclut_apply(&dest->u64, UINT64_MAX, uint64_t, &app.u64, UINT64_MAX, uint64_t, 1, 1, 1); + break; + + case -1: + libclut_apply(&dest->f, 1.0f, float, &app.d, 1.0f, float, 1, 1, 1); + break; + + case -2: + libclut_apply(&dest->d, (double)1, double, &app.f, (double)1, double, 1, 1, 1); + break; + + default: + abort(); + } +} + + +/** + * Remove a filter from an output + * + * @param out The output + * @param filter The filter + * @return The index of the filter, `out->table_size` if not found + */ +static ssize_t +remove_filter(struct output *restrict out, struct filter *restrict filter) +{ + size_t i, n = out->table_size; + + for (i = 0; i < n; i++) + if (!strcmp(filter->class, out->table_filters[i].class)) + break; + + if (i == out->table_size) { + fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n", + argv0, out->name, filter->class); + return (ssize_t)(out->table_size); + } + + filter_destroy(&out->table_filters[i]); + libgamma_gamma_ramps8_destroy(&out->table_sums[i].u8); + + n = n - i - 1; + memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters))); + memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums))); + out->table_size--; + + return (ssize_t)i; +} + + +/** + * Add a filter to an output + * + * @param out The output + * @param filter The filter + * @return The index given to the filter, -1 on error + */ +static ssize_t +add_filter(struct output *restrict out, struct filter *restrict filter) +{ + size_t i, n = out->table_size; + int r = -1; + void *new; + + /* Remove? */ + if (filter->lifespan == LIFESPAN_REMOVE) + return remove_filter(out, filter); + + /* Update? */ + for (i = 0; i < n; i++) + if (!strcmp(filter->class, out->table_filters[i].class)) + break; + if (i != n) { + filter_destroy(&out->table_filters[i]); + out->table_filters[i] = *filter; + filter->class = NULL; + filter->ramps = NULL; + return (ssize_t)i; + } + + /* Add! */ + for (i = 0; i < n; i++) + if (filter->priority > out->table_filters[i].priority) + break; + + if (n == out->table_alloc) { + new = realloc(out->table_filters, (n + 10) * sizeof(*out->table_filters)); + if (!new) + return -1; + out->table_filters = new; + + new = realloc(out->table_sums, (n + 10) * sizeof(*out->table_sums)); + if (!new) + return -1; + out->table_sums = new; + + out->table_alloc += 10; + } + + memmove(&out->table_filters[i + 1], &out->table_filters[i], (n - i) * sizeof(*out->table_filters)); + memmove(&out->table_sums [i + 1], &out->table_sums [i], (n - i) * sizeof(*out->table_sums)); + out->table_size++; + + out->table_filters[i] = *filter; + filter->class = NULL; + filter->ramps = NULL; + + COPY_RAMP_SIZES(&out->table_sums[i].u8, out); + switch (out->depth) { + case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break; + case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break; + case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break; + case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break; + case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break; + case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break; + default: + abort(); + } + if (r < 0) + return -1; + + return (ssize_t)i; +} + + +/** + * Handle a closed connection + * + * @param client The file descriptor for the client + * @return Zero on success, -1 on error + */ +int +connection_closed(int client) +{ + size_t i, j, k; + int remove; + struct output *output; + ssize_t updated; + + for (i = 0; i < outputs_n; i++) { + output = outputs + i; + updated = -1; + for (j = k = 0; j < output->table_size; j += !remove, k++) { + if (j != k) { + output->table_filters[j] = output->table_filters[k]; + output->table_sums[j] = output->table_sums[k]; + } + remove = output->table_filters[j].client == client; + remove = remove && output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH; + if (remove) { + filter_destroy(&output->table_filters[j]); + libgamma_gamma_ramps8_destroy(&output->table_sums[j].u8); + output->table_size -= 1; + if (updated == -1) + updated = (ssize_t)j; + } + } + if (updated >= 0) + if (flush_filters(output, (size_t)updated) < 0) + return -1; + } + + return 0; +} + + +/** + * Handle a ‘Command: get-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @param coalesce The value of the ‘Coalesce’ header + * @param high_priority The value of the ‘High priority’ header + * @param low_priority The value of the ‘Low priority’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +int +handle_get_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc, + const char *restrict coalesce, const char *restrict high_priority, + const char *restrict low_priority) +{ + struct output *restrict output; + int64_t high, low; + int coal; + char *restrict buf; + size_t start, end, len, n, i; + char depth[3 * sizeof(output->depth) + 2]; + char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)]; + union gamma_ramps ramps; + + if (!crtc) return send_error("protocol error: 'CRTC' header omitted"); + if (!coalesce) return send_error("protocol error: 'Coalesce' header omitted"); + if (!high_priority) return send_error("protocol error: 'High priority' header omitted"); + if (!low_priority) return send_error("protocol error: 'Low priority' header omitted"); + + high = (int64_t)atoll(high_priority); + low = (int64_t)atoll(low_priority); + + if (!strcmp(coalesce, "yes")) + coal = 1; + else if (!strcmp(coalesce, "no")) + coal = 0; + else + return send_error("protocol error: recognised value for 'Coalesce' header"); + + output = output_find_by_name(crtc, outputs, outputs_n); + if (!output) + return send_error("selected CRTC does not exist"); + else if (output->supported == LIBGAMMA_NO) + return send_error("selected CRTC does not support gamma adjustments"); + + for (start = 0; start < output->table_size; start++) + if (output->table_filters[start].priority <= high) + break; + + for (end = output->table_size; end > 0; end--) + if (output->table_filters[end - 1].priority >= low) + break; + + switch (output->depth) { + case -2: strcpy(depth, "d"); break; + case -1: strcpy(depth, "f"); break; + default: + sprintf(depth, "%i", output->depth); + break; + } + + if (coal) { + *tables = '\0'; + n = output->ramps_size; + } else { + sprintf(tables, "Tables: %zu\n", end - start); + n = (sizeof(int64_t) + output->ramps_size) * (end - start); + for (i = start; i < end; i++) + n += strlen(output->table_filters[i].class) + 1; + } + + MAKE_MESSAGE(&buf, &n, n, + "In response to: %s\n" + "Depth: %s\n" + "Red size: %zu\n" + "Green size: %zu\n" + "Blue size: %zu\n" + "%s" + "Length: %zu\n" + "\n", + message_id, depth, output->red_size, output->green_size, + output->blue_size, tables, n); + + if (coal) { + if (!start && start < end) { + memcpy(&buf[n], output->table_sums[end - 1].u8.red, output->ramps_size); + } else { + if (make_plain_ramps(&ramps, output)) { + free(buf); + return -1; + } + for (i = start; i < end; i++) + apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps); + memcpy(&buf[n], ramps.u8.red, output->ramps_size); + libgamma_gamma_ramps8_destroy(&(ramps.u8)); + } + n += output->ramps_size; + } else { + for (i = start; i < end; i++) { +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + *(int64_t *)&buf[n] = output->table_filters[i].priority; +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + n += sizeof(int64_t); + len = strlen(output->table_filters[i].class) + 1; + memcpy(&buf[n], output->table_filters[i].class, len); + n += len; + memcpy(&buf[n], output->table_filters[i].ramps, output->ramps_size); + n += output->ramps_size; + } + } + + return send_message(conn, buf, n); +} + + +/** + * Handle a ‘Command: set-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @param priority The value of the ‘Priority’ header + * @param class The value of the ‘Class’ header + * @param lifespan The value of the ‘Lifespan’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +int +handle_set_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc, + const char *restrict priority, const char *restrict class, const char *restrict lifespan) +{ + struct message *restrict msg = inbound + conn; + struct output *restrict output = NULL; + struct filter filter; + char *restrict p; + char *restrict q; + int saved_errno; + ssize_t r; + + if (!crtc) return send_error("protocol error: 'CRTC' header omitted"); + if (!class) return send_error("protocol error: 'Class' header omitted"); + if (!lifespan) return send_error("protocol error: 'Lifespan' header omitted"); + + filter.client = connections[conn]; + filter.priority = !priority ? 0 : (int64_t)atoll(priority); + filter.ramps = NULL; + + output = output_find_by_name(crtc, outputs, outputs_n); + if (!output) + return send_error("CRTC does not exists"); + + p = strstr(class, "::"); + if (!p || p == class) + return send_error("protocol error: malformatted value for 'Class' header"); + q = strstr(p + 2, "::"); + if (!q || q == p) + return send_error("protocol error: malformatted value for 'Class' header"); + + if (!strcmp(lifespan, "until-removal")) + filter.lifespan = LIFESPAN_UNTIL_REMOVAL; + else if (!strcmp(lifespan, "until-death")) + filter.lifespan = LIFESPAN_UNTIL_DEATH; + else if (!strcmp(lifespan, "remove")) + filter.lifespan = LIFESPAN_REMOVE; + else + return send_error("protocol error: recognised value for 'Lifespan' header"); + + if (filter.lifespan == LIFESPAN_REMOVE) { + if (msg->payload_size) + fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with " + "Lifespan: remove\n", argv0); + if (priority) + fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with " + "Lifespan: remove\n", argv0); + } else if (msg->payload_size != output->ramps_size) { + return send_error("invalid payload: size of message payload does matched the expectancy"); + } else if (!priority) { + return send_error("protocol error: 'Priority' header omitted"); + } + + filter.class = memdup(class, strlen(class) + 1); + if (!filter.class) + goto fail; + + if (filter.lifespan != LIFESPAN_REMOVE) { + filter.ramps = memdup(msg->payload, msg->payload_size); + if (!filter.ramps) + goto fail; + } + + if ((r = add_filter(output, &filter)) < 0) + goto fail; + if (flush_filters(output, (size_t)r)) + goto fail; + + free(filter.class); + free(filter.ramps); + return send_errno(0); + +fail: + saved_errno = errno; + send_errno(saved_errno); + free(filter.class); + free(filter.ramps); + errno = saved_errno; + return -1; +} + + +/** + * Recalculate the resulting gamma and + * update push the new gamma ramps to the CRTC + * + * @param output The output + * @param first_updated The index of the first added or removed filter + * @return Zero on success, -1 on error + */ +int +flush_filters(struct output *restrict output, size_t first_updated) +{ + union gamma_ramps plain, *last; + size_t i; + + if (!first_updated) { + if (make_plain_ramps(&plain, output) < 0) + return -1; + last = &plain; + } else { + last = output->table_sums + (first_updated - 1); + } + + for (i = first_updated; i < output->table_size; i++) { + apply_filter(&output->table_sums[i], output->table_filters[i].ramps, output->depth, last); + last = output->table_sums + i; + } + + set_gamma(output, last); + + if (!first_updated) + libgamma_gamma_ramps8_destroy(&plain.u8); + + return 0; +} + + +/** + * Preserve current gamma ramps at priority 0 for all outputs + * + * @return Zero on success, -1 on error + */ +int +preserve_gamma(void) +{ + size_t i; + struct filter filter; + + for (i = 0; i < outputs_n; i++) { + filter.client = -1; + filter.priority = 0; + filter.class = NULL; + filter.lifespan = LIFESPAN_UNTIL_REMOVAL; + filter.ramps = NULL; + outputs[i].table_filters = calloc(4, sizeof(*outputs[i].table_filters)); + outputs[i].table_sums = calloc(4, sizeof(*outputs[i].table_sums)); + outputs[i].table_alloc = 4; + outputs[i].table_size = 1; + filter.class = memdup(PKGNAME"::"COMMAND"::preserved", sizeof(PKGNAME"::"COMMAND"::preserved")); + if (!filter.class) + return -1; + filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size); + if (!filter.ramps) + return -1; + outputs[i].table_filters[0] = filter; + COPY_RAMP_SIZES(&outputs[i].table_sums[0].u8, outputs + i); + if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size)) + return -1; + } + + return 0; +} diff --git a/servers-coopgamma.h b/servers-coopgamma.h new file mode 100644 index 0000000..584e760 --- /dev/null +++ b/servers-coopgamma.h @@ -0,0 +1,76 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVERS_COOPGAMMA_H +#define SERVERS_COOPGAMMA_H + +#include "types-output.h" + +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Handle a closed connection + * + * @param client The file descriptor for the client + * @return Zero on success, -1 on error + */ +int connection_closed(int client); + +/** + * Handle a ‘Command: get-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @param coalesce The value of the ‘Coalesce’ header + * @param high_priority The value of the ‘High priority’ header + * @param low_priority The value of the ‘Low priority’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +GCC_ONLY(__attribute__((__nonnull__(2)))) +int handle_get_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc, + const char *restrict coalesce, const char *restrict high_priority, + const char *restrict low_priority); + +/** + * Handle a ‘Command: set-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @param priority The value of the ‘Priority’ header + * @param class The value of the ‘Class’ header + * @param lifespan The value of the ‘Lifespan’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +GCC_ONLY(__attribute__((__nonnull__(2)))) +int handle_set_gamma(size_t conn, const char *restrict message_id, const char *restrict crtc, + const char *restrict priority, const char *restrict class, const char *restrict lifespan); + +/** + * Recalculate the resulting gamma and + * update push the new gamma ramps to the CRTC + * + * @param output The output + * @param first_updated The index of the first added or removed filter + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +int flush_filters(struct output *restrict output, size_t first_updated); + +/** + * Preserve current gamma ramps at priority 0 for all outputs + * + * @return Zero on success, -1 on error + */ +int preserve_gamma(void); + +#endif diff --git a/servers-crtc.c b/servers-crtc.c new file mode 100644 index 0000000..24018e1 --- /dev/null +++ b/servers-crtc.c @@ -0,0 +1,312 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-crtc.h" +#include "servers-gamma.h" +#include "servers-coopgamma.h" +#include "state.h" +#include "communication.h" +#include "util.h" + +#include +#include + + +/** + * Handle a ‘Command: enumerate-crtcs’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +int +handle_enumerate_crtcs(size_t conn, const char *restrict message_id) +{ + size_t i, n = 0, len; + char *restrict buf; + + for (i = 0; i < outputs_n; i++) + n += strlen(outputs[i].name) + 1; + + MAKE_MESSAGE(&buf, &n, n, + "Command: crtc-enumeration\n" + "In response to: %s\n" + "Length: %zu\n" + "\n", + message_id, n); + + for (i = 0; i < outputs_n; i++) { + len = strlen(outputs[i].name); + memcpy(&buf[n], outputs[i].name, len); + buf[n + len] = '\n'; + n += len + 1; + } + + return send_message(conn, buf, n); +} + + +/** + * Get the name of a CRTC + * + * @param info Information about the CRTC + * @param crtc libgamma's state for the CRTC + * @return The name of the CRTC, `NULL` on error + */ +char * +get_crtc_name(const libgamma_crtc_information_t *restrict info, const libgamma_crtc_state_t *restrict crtc) +{ + char *name; + if (!info->edid_error && info->edid) { + return libgamma_behex_edid(info->edid, info->edid_length); + } else if (!info->connector_name_error && info->connector_name) { + name = malloc(3 * sizeof(size_t) + strlen(info->connector_name) + 2); + if (name) + sprintf(name, "%zu.%s", crtc->partition->partition, info->connector_name); + return name; + } else { + name = malloc(2 * 3 * sizeof(size_t) + 2); + if (name) + sprintf(name, "%zu.%zu", crtc->partition->partition, crtc->crtc); + return name; + } +} + + +/** + * Initialise the site + * + * @return Zero on success, -1 on error + */ +int +initialise_site(void) +{ + char *restrict sitename_dup = NULL; + int gerror; + + if (sitename && !(sitename_dup = memdup(sitename, strlen(sitename) + 1))) + goto fail; + if ((gerror = libgamma_site_initialise(&site, method, sitename_dup))) + goto fail_libgamma; + + return 0; + +fail_libgamma: + sitename_dup = NULL; + libgamma_perror(argv0, gerror); + errno = 0; +fail: + free(sitename_dup); + return -1; +} + + +/** + * Get partitions and CRTC:s + * + * @return Zero on success, -1 on error + */ +int +initialise_crtcs(void) +{ + size_t i, j, n, n0; + int gerror; + + /* Get partitions */ + outputs_n = 0; + if (site.partitions_available) { + partitions = calloc(site.partitions_available, sizeof(*partitions)); + if (!partitions) + goto fail; + } + for (i = 0; i < site.partitions_available; i++) { + if ((gerror = libgamma_partition_initialise(&partitions[i], &site, i))) + goto fail_libgamma; + outputs_n += partitions[i].crtcs_available; + } + + /* Get CRTC:s */ + if (outputs_n) { + crtcs = calloc(outputs_n, sizeof(*crtcs)); + if (!crtcs) + goto fail; + } + for (i = 0, j = n = 0; i < site.partitions_available; i++) + for (n0 = n, n += partitions[i].crtcs_available; j < n; j++) + if ((gerror = libgamma_crtc_initialise(&crtcs[j], &partitions[i], j - n0))) + goto fail_libgamma; + + return 0; + +fail_libgamma: + libgamma_perror(argv0, gerror); + errno = 0; +fail: + return -1; +} + + +/** + * Merge the new state with an old state + * + * @param old_outputs The old `outputs` + * @param old_outputs_n The old `outputs_n` + * @return Zero on success, -1 on error + */ +int +merge_state(struct output *restrict old_outputs, size_t old_outputs_n) +{ + struct output *restrict new_outputs = NULL; + size_t new_outputs_n; + size_t i, j; + int cmp, is_same; + + /* How many outputs does the system now have? */ + i = j = new_outputs_n = 0; + while (i < old_outputs_n && j < outputs_n) { + cmp = strcmp(old_outputs[i].name, outputs[j].name); + if (cmp <= 0) + new_outputs_n++; + i += cmp >= 0; + j += cmp <= 0; + } + new_outputs_n += outputs_n - j; + + /* Allocate output state array */ + if (new_outputs_n > 0) { + new_outputs = calloc(new_outputs_n, sizeof(*new_outputs)); + if (!new_outputs) + return -1; + } + + /* Merge output states */ + i = j = new_outputs_n = 0; + while (i < old_outputs_n && j < outputs_n) { + is_same = 0; + cmp = strcmp(old_outputs[i].name, outputs[j].name); + if (!cmp) { + is_same = (old_outputs[i].depth == outputs[j].depth && + old_outputs[i].red_size == outputs[j].red_size && + old_outputs[i].green_size == outputs[j].green_size && + old_outputs[i].blue_size == outputs[j].blue_size); + } + if (is_same) { + new_outputs[new_outputs_n] = old_outputs[i]; + new_outputs[new_outputs_n].crtc = outputs[j].crtc; + memset(&old_outputs[i], 0, sizeof(*old_outputs)); + outputs[j].crtc = NULL; + output_destroy(&outputs[j]); + new_outputs_n++; + } else if (cmp <= 0) { + new_outputs[new_outputs_n++] = outputs[j]; + } + i += cmp >= 0; + j += cmp <= 0; + } + while (j < outputs_n) + new_outputs[new_outputs_n++] = outputs[j++]; + + /* Commit merge */ + free(outputs); + outputs = new_outputs; + outputs_n = new_outputs_n; + + return 0; +} + + +/** + * Disconnect from the site + * + * @return Zero on success, -1 on error + */ +int +disconnect(void) +{ + size_t i; + + if (!connected) + return 0; + connected = 0; + + for (i = 0; i < outputs_n; i++) { + outputs[i].crtc = NULL; + libgamma_crtc_destroy(&crtcs[i]); + } + free(crtcs); + crtcs = NULL; + + for (i = 0; i < site.partitions_available; i++) + libgamma_partition_destroy(&partitions[i]); + free(partitions); + partitions = NULL; + + libgamma_site_destroy(&site); + memset(&site, 0, sizeof(site)); + + return 0; +} + + +/** + * Reconnect to the site + * + * @return Zero on success, -1 on error + */ +int +reconnect(void) +{ + struct output *restrict old_outputs; + size_t i, old_outputs_n; + + if (connected) + return 0; + connected = 1; + + /* Remember old state */ + old_outputs = outputs, outputs = NULL; + old_outputs_n = outputs_n, outputs_n = 0; + + /* Get site */ + if (initialise_site() < 0) + goto fail; + + /* Get partitions and CRTC:s */ + if (initialise_crtcs() < 0) + goto fail; + + /* Get CRTC information */ + if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs)))) + goto fail; + if (initialise_gamma_info() < 0) + goto fail; + + /* Sort outputs */ + qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name); + + /* Load current gamma ramps */ + store_gamma(); + + /* Preserve current gamma ramps at priority=0 if -p */ + if (preserve && preserve_gamma() < 0) + goto fail; + + /* Merge state */ + if (merge_state(old_outputs, old_outputs_n) < 0) + goto fail; + for (i = 0; i < old_outputs_n; i++) + output_destroy(old_outputs + i); + free(old_outputs); + old_outputs = NULL; + old_outputs_n = 0; + + /* Reapply gamma ramps */ + reapply_gamma(); + + return 0; + +fail: + for (i = 0; i < old_outputs_n; i++) + output_destroy(&old_outputs[i]); + free(old_outputs); + return -1; +} diff --git a/servers-crtc.h b/servers-crtc.h new file mode 100644 index 0000000..08e4b02 --- /dev/null +++ b/servers-crtc.h @@ -0,0 +1,75 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVERS_CRTC_H +#define SERVERS_CRTC_H + +#include "types-output.h" + +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Handle a ‘Command: enumerate-crtcs’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +GCC_ONLY(__attribute__((__nonnull__))) +int handle_enumerate_crtcs(size_t conn, const char *restrict message_id); + +/** + * Get the name of a CRTC + * + * @param info Information about the CRTC + * @param crtc libgamma's state for the CRTC + * @return The name of the CRTC, `NULL` on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +char *get_crtc_name(const libgamma_crtc_information_t *restrict info, const libgamma_crtc_state_t *restrict crtc); + +/** + * Initialise the site + * + * @return Zero on success, -1 on error + */ +int initialise_site(void); + +/** + * Get partitions and CRTC:s + * + * @return Zero on success, -1 on error + */ +int initialise_crtcs(void); + +/** + * Merge the new state with an old state + * + * @param old_outputs The old `outputs` + * @param old_outputs_n The old `outputs_n` + * @return Zero on success, -1 on error + */ +int merge_state(struct output *restrict old_outputs, size_t old_outputs_n); + +/** + * Disconnect from the site + * + * @return Zero on success, -1 on error + */ +int disconnect(void); + +/** + * Reconnect to the site + * + * @return Zero on success, -1 on error + */ +int reconnect(void); + +#endif diff --git a/servers-gamma.c b/servers-gamma.c new file mode 100644 index 0000000..8907e8f --- /dev/null +++ b/servers-gamma.c @@ -0,0 +1,381 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-gamma.h" +#include "servers-crtc.h" +#include "state.h" +#include "communication.h" +#include "util.h" + +#include +#include + + +#if defined(__clang__) +# pragma GCC diagnostic ignored "-Wswitch-enum" +#endif + + +/** + * Handle a ‘Command: set-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +int +handle_get_gamma_info(size_t conn, const char *restrict message_id, const char *restrict crtc) +{ + struct output *restrict output; + char *restrict buf; + char depth[3 * sizeof(output->depth) + 2]; + const char *supported; + const char *colourspace; + char gamut[8 * (sizeof("White x: ") + 3 * sizeof(unsigned))]; + size_t n; + + if (!crtc) + return send_error("protocol error: 'CRTC' header omitted"); + + output = output_find_by_name(crtc, outputs, outputs_n); + if (!output) + return send_error("selected CRTC does not exist"); + + switch (output->depth) { + case -2: strcpy(depth, "d"); break; + case -1: strcpy(depth, "f"); break; + default: + sprintf(depth, "%i", output->depth); + break; + } + + switch (output->supported) { + case LIBGAMMA_YES: supported = "yes"; break; + case LIBGAMMA_NO: supported = "no"; break; + case LIBGAMMA_MAYBE: + default: supported = "maybe"; break; + } + + switch (output->colourspace) { + case COLOURSPACE_SRGB_SANS_GAMUT: + case COLOURSPACE_SRGB: colourspace = "Colour space: sRGB\n"; break; + case COLOURSPACE_RGB_SANS_GAMUT: + case COLOURSPACE_RGB: colourspace = "Colour space: RGB\n"; break; + case COLOURSPACE_NON_RGB: colourspace = "Colour space: non-RGB\n"; break; + case COLOURSPACE_GREY: colourspace = "Colour space: grey\n"; break; + case COLOURSPACE_UNKNOWN: + default: colourspace = ""; break; + } + + switch (output->colourspace) { + case COLOURSPACE_SRGB: + case COLOURSPACE_RGB: + sprintf(gamut, + "Red x: %u\n" "Red y: %u\n" + "Green x: %u\n" "Green y: %u\n" + "Blue x: %u\n" "Blue y: %u\n" + "White x: %u\n" "White y: %u\n", + output->red_x, output->red_y, output->green_x, output->green_y, + output->blue_x, output->blue_y, output->white_x, output->white_y); + break; + case COLOURSPACE_SRGB_SANS_GAMUT: + case COLOURSPACE_RGB_SANS_GAMUT: + case COLOURSPACE_NON_RGB: + case COLOURSPACE_GREY: + case COLOURSPACE_UNKNOWN: + default: + *gamut = '\0'; + break; + } + + MAKE_MESSAGE(&buf, &n, 0, + "In response to: %s\n" + "Cooperative: yes\n" /* In mds: say ‘no’, mds-coopgamma changes to ‘yes’.” */ + "Depth: %s\n" + "Red size: %zu\n" + "Green size: %zu\n" + "Blue size: %zu\n" + "Gamma support: %s\n" + "%s%s" + "\n", + message_id, depth, output->red_size, output->green_size, + output->blue_size, supported, gamut, colourspace); + + return send_message(conn, buf, n); +} + + +/** + * Set the gamma ramps on an output + * + * @param output The output + * @param ramps The gamma ramps + */ +void +set_gamma(const struct output *restrict output, const union gamma_ramps *restrict ramps) +{ + int r = 0; + + if (!connected) + return; + + switch (output->depth) { + case 8: r = libgamma_crtc_set_gamma_ramps8(output->crtc, ramps->u8); break; + case 16: r = libgamma_crtc_set_gamma_ramps16(output->crtc, ramps->u16); break; + case 32: r = libgamma_crtc_set_gamma_ramps32(output->crtc, ramps->u32); break; + case 64: r = libgamma_crtc_set_gamma_ramps64(output->crtc, ramps->u64); break; + case -1: r = libgamma_crtc_set_gamma_rampsf(output->crtc, ramps->f); break; + case -2: r = libgamma_crtc_set_gamma_rampsd(output->crtc, ramps->d); break; + default: + abort(); + } + + if (r) + libgamma_perror(argv0, r); /* Not fatal */ +} + + +/** + * Parse the EDID of a monitor + * + * @param output The output + * @param edid The EDID in binary format + * @param n The length of the EDID + */ +static void +parse_edid(struct output *restrict output, const unsigned char *restrict edid, size_t n) +{ + size_t i; + int analogue; + unsigned sum; + + output->red_x = output->green_x = output->blue_x = output->white_x = 0; + output->red_y = output->green_y = output->blue_y = output->white_y = 0; + output->colourspace = COLOURSPACE_UNKNOWN; + + if (!edid || n < 128) + return; + for (i = 0, sum = 0; i < 128; i++) + sum += (unsigned)edid[i]; + if ((sum & 0xFF) != 0) + return; + if (edid[0] || edid[7]) + return; + for (i = 1; i < 7; i++) + if (edid[i] != 0xFF) + return; + + analogue = !(edid[20] & 0x80); + if (!analogue) { + output->colourspace = COLOURSPACE_RGB; + } else { + switch ((edid[24] >> 3) & 3) { + case 0: output->colourspace = COLOURSPACE_GREY; break; + case 1: output->colourspace = COLOURSPACE_RGB; break; + case 2: output->colourspace = COLOURSPACE_NON_RGB; break; + default: output->colourspace = COLOURSPACE_UNKNOWN; break; + } + } + + if (output->colourspace != COLOURSPACE_RGB) + return; + + if (edid[24] & 4) + output->colourspace = COLOURSPACE_SRGB; + + output->red_x = (edid[25] >> 6) & 3; + output->red_y = (edid[25] >> 4) & 3; + output->green_x = (edid[25] >> 2) & 3; + output->green_y = (edid[25] >> 0) & 3; + output->blue_x = (edid[26] >> 6) & 3; + output->blue_y = (edid[26] >> 4) & 3; + output->white_x = (edid[26] >> 2) & 3; + output->white_y = (edid[26] >> 0) & 3; + + output->red_x |= ((unsigned)(edid[27])) << 2; + output->red_y |= ((unsigned)(edid[28])) << 2; + output->green_x |= ((unsigned)(edid[29])) << 2; + output->green_y |= ((unsigned)(edid[30])) << 2; + output->blue_x |= ((unsigned)(edid[31])) << 2; + output->blue_y |= ((unsigned)(edid[32])) << 2; + output->white_x |= ((unsigned)(edid[33])) << 2; + output->white_y |= ((unsigned)(edid[34])) << 2; + + if ((output->red_x == output->red_y) && + (output->red_x == output->green_x) && + (output->red_x == output->green_y) && + (output->red_x == output->blue_x) && + (output->red_x == output->blue_y) && + (output->red_x == output->white_x) && + (output->red_x == output->white_y)) { + if (output->colourspace == COLOURSPACE_SRGB) + output->colourspace = COLOURSPACE_SRGB_SANS_GAMUT; + else + output->colourspace = COLOURSPACE_RGB_SANS_GAMUT; + } +} + + +/** + * Store all current gamma ramps + * + * @return Zero on success, -1 on error + */ +int +initialise_gamma_info(void) +{ + libgamma_crtc_information_t info; + int saved_errno; + size_t i; + + for (i = 0; i < outputs_n; i++) { + libgamma_get_crtc_information(&info, crtcs + i, + LIBGAMMA_CRTC_INFO_EDID | + LIBGAMMA_CRTC_INFO_MACRO_RAMP | + LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT | + LIBGAMMA_CRTC_INFO_CONNECTOR_NAME); + + outputs[i].depth = info.gamma_depth_error ? 0 : info.gamma_depth; + outputs[i].red_size = info.gamma_size_error ? 0 : info.red_gamma_size; + outputs[i].green_size = info.gamma_size_error ? 0 : info.green_gamma_size; + outputs[i].blue_size = info.gamma_size_error ? 0 : info.blue_gamma_size; + outputs[i].supported = info.gamma_support_error ? 0 : info.gamma_support; + + if (info.gamma_support_error == LIBGAMMA_CRTC_INFO_NOT_SUPPORTED) + outputs[i].supported = LIBGAMMA_MAYBE; + + if (!outputs[i].depth || !outputs[i].red_size || + !outputs[i].green_size || !outputs[i].blue_size) + outputs[i].supported = 0; + + parse_edid(&outputs[i], info.edid_error ? NULL : info.edid, info.edid_error ? 0 : info.edid_length); + + outputs[i].name = get_crtc_name(&info, crtcs + i); + + saved_errno = errno; + outputs[i].name_is_edid = (!info.edid_error && info.edid); + outputs[i].crtc = &crtcs[i]; + + libgamma_crtc_information_destroy(&info); + outputs[i].ramps_size = outputs[i].red_size + outputs[i].green_size + outputs[i].blue_size; + + switch (outputs[i].depth) { + default: + outputs[i].depth = 64; + /* Fall through */ + case 8: + case 16: + case 32: + case 64: outputs[i].ramps_size *= (size_t)(outputs[i].depth / 8); break; + case -2: outputs[i].ramps_size *= sizeof(double); break; + case -1: outputs[i].ramps_size *= sizeof(float); break; + } + + errno = saved_errno; + if (!outputs[i].name) + return -1; + } + + return 0; +} + + +/** + * Store all current gamma ramps + */ +void +store_gamma(void) +{ + int gerror; + size_t i; + +#define LOAD_RAMPS(SUFFIX, MEMBER)\ + do {\ + libgamma_gamma_ramps##SUFFIX##_initialise(&outputs[i].saved_ramps.MEMBER);\ + gerror = libgamma_crtc_get_gamma_ramps##SUFFIX(outputs[i].crtc, &outputs[i].saved_ramps.MEMBER);\ + if (gerror) {\ + libgamma_perror(argv0, gerror);\ + outputs[i].supported = LIBGAMMA_NO;\ + libgamma_gamma_ramps##SUFFIX##_destroy(&outputs[i].saved_ramps.MEMBER);\ + memset(&outputs[i].saved_ramps.MEMBER, 0, sizeof(outputs[i].saved_ramps.MEMBER));\ + }\ + } while (0) + + for (i = 0; i < outputs_n; i++) { + if (outputs[i].supported == LIBGAMMA_NO) + continue; + + outputs[i].saved_ramps.u8.red_size = outputs[i].red_size; + outputs[i].saved_ramps.u8.green_size = outputs[i].green_size; + outputs[i].saved_ramps.u8.blue_size = outputs[i].blue_size; + + switch (outputs[i].depth) { + case 64: LOAD_RAMPS(64, u64); break; + case 32: LOAD_RAMPS(32, u32); break; + case 16: LOAD_RAMPS(16, u16); break; + case 8: LOAD_RAMPS( 8, u8); break; + case -2: LOAD_RAMPS(d, d); break; + case -1: LOAD_RAMPS(f, f); break; + default: /* impossible */ break; + } + } +} + + +/** + * Restore all gamma ramps + */ +void +restore_gamma(void) +{ + size_t i; + int gerror; + +#define RESTORE_RAMPS(SUFFIX, MEMBER)\ + do {\ + if (outputs[i].saved_ramps.MEMBER.red) {\ + gerror = libgamma_crtc_set_gamma_ramps##SUFFIX(outputs[i].crtc, outputs[i].saved_ramps.MEMBER);\ + if (gerror)\ + libgamma_perror(argv0, gerror);\ + }\ + } while (0) + + for (i = 0; i < outputs_n; i++) { + if (outputs[i].supported == LIBGAMMA_NO) + continue; + if (!outputs[i].saved_ramps.u8.red) + continue; + + switch (outputs[i].depth){ + case 64: RESTORE_RAMPS(64, u64); break; + case 32: RESTORE_RAMPS(32, u32); break; + case 16: RESTORE_RAMPS(16, u16); break; + case 8: RESTORE_RAMPS( 8, u8); break; + case -2: RESTORE_RAMPS(d, d); break; + case -1: RESTORE_RAMPS(f, f); break; + default: /* impossible */ break; + } + } +} + + +/** + * Reapplu all gamma ramps + */ +void +reapply_gamma(void) +{ + union gamma_ramps plain; + size_t i; + + /* Reapply gamma ramps */ + for (i = 0; i < outputs_n; i++) { + if (outputs[i].table_size > 0) { + set_gamma(&outputs[i], &outputs[i].table_sums[outputs[i].table_size - 1]); + } else { + make_plain_ramps(&plain, &outputs[i]); + set_gamma(&outputs[i], &plain); + libgamma_gamma_ramps8_destroy(&plain.u8); + } + } +} diff --git a/servers-gamma.h b/servers-gamma.h new file mode 100644 index 0000000..e522cf5 --- /dev/null +++ b/servers-gamma.h @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVERS_GAMMA_H +#define SERVERS_GAMMA_H + +#include "types-output.h" + +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Handle a ‘Command: set-gamma’ message + * + * @param conn The index of the connection + * @param message_id The value of the ‘Message ID’ header + * @param crtc The value of the ‘CRTC’ header + * @return Zero on success (even if ignored), -1 on error, + * 1 if connection closed + */ +GCC_ONLY(__attribute__((__nonnull__(2)))) +int handle_get_gamma_info(size_t conn, const char *restrict message_id, const char *restrict crtc); + +/** + * Set the gamma ramps on an output + * + * @param output The output + * @param ramps The gamma ramps + */ +GCC_ONLY(__attribute__((__nonnull__))) +void set_gamma(const struct output *restrict output, const union gamma_ramps *restrict ramps); + +/** + * Store all current gamma ramps + * + * @return Zero on success, -1 on error + */ +int initialise_gamma_info(void); + +/** + * Store all current gamma ramps + */ +void store_gamma(void); + +/** + * Restore all gamma ramps + */ +void restore_gamma(void); + +/** + * Reapplu all gamma ramps + */ +void reapply_gamma(void); + +#endif diff --git a/servers-kernel.c b/servers-kernel.c new file mode 100644 index 0000000..fd60aef --- /dev/null +++ b/servers-kernel.c @@ -0,0 +1,363 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-kernel.h" +#include "state.h" +#include "util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Get the pathname of the runtime file + * + * @param suffix The suffix for the file + * @return The pathname of the file, `NULL` on error + */ +GCC_ONLY(__attribute__((__malloc__, __nonnull__))) +static char * +get_pathname(const char *restrict suffix) +{ + const char *restrict rundir = getenv("XDG_RUNTIME_DIR"); + const char *restrict username = ""; + char *name = NULL; + char *p; + char *restrict rc; + struct passwd *restrict pw; + size_t n; + + if (sitename) { + name = memdup(sitename, strlen(sitename) + 1); + if (!name) + goto fail; + } else if ((name = libgamma_method_default_site(method))) { + name = memdup(name, strlen(name) + 1); + if (!name) + goto fail; + } + + if (name) { + switch (method) { + case LIBGAMMA_METHOD_X_RANDR: + case LIBGAMMA_METHOD_X_VIDMODE: + if ((p = strrchr(name, ':'))) + if ((p = strchr(p, '.'))) + *p = '\0'; + break; + default: + break; + } + } + + if (!rundir || !*rundir) + rundir = "/tmp"; + + if ((pw = getpwuid(getuid()))) + username = pw->pw_name ? pw->pw_name : ""; + + n = sizeof("/.coopgammad/~/.") + 3 * sizeof(int); + n += strlen(rundir) + strlen(username) + (name ? strlen(name) : 0) + strlen(suffix); + if (!(rc = malloc(n))) + goto fail; + sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s", + rundir, username, method, name ? "." : "", name ? name : "", suffix); + free(name); + return rc; + +fail: + free(name); + return NULL; +} + + +/** + * Get the pathname of the socket + * + * @return The pathname of the socket, `NULL` on error + */ +char * +get_socket_pathname(void) +{ + return get_pathname(".socket"); +} + + +/** + * Get the pathname of the PID file + * + * @return The pathname of the PID file, `NULL` on error + */ +char * +get_pidfile_pathname(void) +{ + return get_pathname(".pid"); +} + + +/** + * Get the pathname of the state file + * + * @return The pathname of the state file, `NULL` on error + */ +char * +get_state_pathname(void) +{ + return get_pathname(".state"); +} + + +/** + * Check whether a PID file is outdated + * + * @param pidpath The PID file + * @param token An environment variable (including both key and value) + * that must exist in the process if it is a coopgammad process + * @return -1: An error occurred + * 0: The service is already running + * 1: The PID file is outdated + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +is_pidfile_reusable(const char *restrict pidpath, const char *restrict token) +{ + /* PORTERS: /proc/$PID/environ is Linux specific */ + + char temp[sizeof("/proc//environ") + 3 * sizeof(long long int)]; + int fd = -1, saved_errno, tries = 0; + char *content = NULL; + char *p; + pid_t pid = 0; + size_t n; +#if defined(__linux__) || defined(HAVE_LINUX_PROCFS) + char *end; +#else + (void) token; +#endif + + /* Get PID */ +retry: + fd = open(pidpath, O_RDONLY); + if (fd < 0) + return -1; + content = nread(fd, &n); + if (!content) + goto fail; + close(fd); + fd = -1; + + if (!n) { + if (++tries > 1) + goto bad; + msleep(100); /* 1 tenth of a second */ + goto retry; + } + + if ('0' > content[0] || content[0] > '9') + goto bad; + if (content[0] == '0' && '0' <= content[1] && content[1] <= '9') + goto bad; + for (p = content; *p; p++) + if ('0' <= *p && *p <= '9') + pid = pid * 10 + (*p & 15); + else + break; + if (*p++ != '\n') + goto bad; + if (*p) + goto bad; + if ((size_t)(p - content) != n) + goto bad; + sprintf(temp, "%llu\n", (unsigned long long)pid); + if (strcmp(content, temp)) + goto bad; + free(content); + + /* Validate PID */ +#if defined(__linux__) || defined(HAVE_LINUX_PROCFS) + sprintf(temp, "/proc/%llu/environ", (unsigned long long int)pid); + fd = open(temp, O_RDONLY); + if (fd < 0) + return (errno == ENOENT || errno == EACCES) ? 1 : -1; + content = nread(fd, &n); + if (!content) + goto fail; + close(fd), fd = -1; + + for (end = &(p = content)[n]; p != end; p = &strchr(p, '\0')[1]) + if (!strcmp(p, token)) + return free(content), 0; + free(content); +#else + if (!kill(pid, 0) || errno == EINVAL) + return 0; +#endif + + return 1; + +bad: + fprintf(stderr, "%s: pid file contains invalid content: %s\n", argv0, pidpath); + errno = 0; + return -1; + +fail: + saved_errno = errno; + free(content); + if (fd >= 0) + close(fd); + errno = saved_errno; + return -1; +} + + +/** + * Create PID file + * + * @param pidpath The pathname of the PID file + * @return Zero on success, -1 on error, + * -2 if the service is already running + */ +int +create_pidfile(char *pidpath) +{ + int fd = -1, r, saved_errno; + char *p; + char *restrict token = NULL; + + /* Create token used to validate the service. */ + token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath)); + if (!token) + return -1; + sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath); +#if !defined(USE_VALGRIND) + if (putenv(token)) + goto putenv_fail; + /* `token` must not be free! */ +#else + { + static char static_token[sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + PATH_MAX]; + if (strlen(pidpath) > PATH_MAX) + abort(); + strcpy(static_token, token); + if (putenv(static_token)) + goto fail; + } +#endif + + /* Create PID file's directory. */ + for (p = pidpath; *p == '/'; p++); + while ((p = strchr(p, '/'))) { + *p = '\0'; + if (mkdir(pidpath, 0755) < 0) { + if (errno != EEXIST) { + *p = '/'; + goto fail; + } + } + *p++ = '/'; + } + + /* Create PID file. */ +retry: + fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644); + if (fd < 0) { + if (errno == EINTR) + goto retry; + if (errno != EEXIST) + return -1; + r = is_pidfile_reusable(pidpath, token); + if (r > 0) { + unlink(pidpath); + goto retry; + } else if (r < 0) { + goto fail; + } + fprintf(stderr, "%s: service is already running\n", argv0); + errno = 0; + return -2; + } + + /* Write PID to PID file. */ + if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) + goto fail; + + /* Done */ +#if defined(USE_VALGRIND) + free(token); +#endif + if (close(fd) < 0) + if (errno != EINTR) + return -1; + return 0; +#if !defined(USE_VALGRIND) +putenv_fail: + free(token); +#endif +fail: +#if defined(USE_VALGRIND) + free(token); +#endif + if (fd >= 0) { + saved_errno = errno; + close(fd); + unlink(pidpath); + errno = saved_errno; + } + return -1; +} + + +/** + * Create socket and start listening + * + * @param socketpath The pathname of the socket + * @return Zero on success, -1 on error + */ +int +create_socket(const char *socketpath) +{ + struct sockaddr_un address; + + address.sun_family = AF_UNIX; + if (strlen(socketpath) >= sizeof(address.sun_path)) { + errno = ENAMETOOLONG; + return -1; + } + strcpy(address.sun_path, socketpath); + unlink(socketpath); + socketfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (socketfd < 0) + return -1; + if (fchmod(socketfd, S_IRWXU) < 0) + return -1; + if (bind(socketfd, (struct sockaddr *)&address, (socklen_t)sizeof(address)) < 0) + return -1; + if (listen(socketfd, SOMAXCONN) < 0) + return -1; + + return 0; +} + + +/** + * Close and unlink the socket + * + * @param socketpath The pathname of the socket + */ +void +close_socket(const char *socketpath) +{ + if (socketfd >= 0) { + shutdown(socketfd, SHUT_RDWR); + close(socketfd); + unlink(socketpath); + } +} diff --git a/servers-kernel.h b/servers-kernel.h new file mode 100644 index 0000000..6839e9c --- /dev/null +++ b/servers-kernel.h @@ -0,0 +1,64 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVERS_KERNEL_H +#define SERVERS_KERNEL_H + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Get the pathname of the socket + * + * @return The pathname of the socket, `NULL` on error + */ +GCC_ONLY(__attribute__((__malloc__))) +char *get_socket_pathname(void); + +/** + * Get the pathname of the PID file + * + * @return The pathname of the PID file, `NULL` on error + */ +GCC_ONLY(__attribute__((__malloc__))) +char *get_pidfile_pathname(void); + +/** + * Get the pathname of the state file + * + * @return The pathname of the state file, `NULL` on error + */ +GCC_ONLY(__attribute__((__malloc__))) +char *get_state_pathname(void); + +/** + * Create PID file + * + * @param pidpath The pathname of the PID file + * @return Zero on success, -1 on error, + * -2 if the service is already running + */ +GCC_ONLY(__attribute__((__nonnull__))) +int create_pidfile(char *pidpath); + +/** + * Create socket and start listening + * + * @param socketpath The pathname of the socket + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +int create_socket(const char *socketpath); + +/** + * Close and unlink the socket + * + * @param socketpath The pathname of the socket + */ +GCC_ONLY(__attribute__((__nonnull__))) +void close_socket(const char *socketpath); + +#endif diff --git a/servers-master.c b/servers-master.c new file mode 100644 index 0000000..f6ebfee --- /dev/null +++ b/servers-master.c @@ -0,0 +1,370 @@ +/* See LICENSE file for copyright and license details. */ +#include "servers-master.h" +#include "servers-crtc.h" +#include "servers-gamma.h" +#include "servers-coopgamma.h" +#include "util.h" +#include "communication.h" +#include "state.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * All poll(3p) events that are not for writing + */ +#define NON_WR_POLL_EVENTS (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL) + + +/** + * Extract headers from an inbound message and pass + * them on to appropriate message handling function + * + * @param conn The index of the connection + * @param msg The inbound message + * @return 1: The connection as closed + * 0: Successful + * -1: Failure + */ +static int +dispatch_message(size_t conn, struct message *restrict msg) +{ + size_t i; + int r = 0; + const char *header; + const char *value; + const char *command = NULL; + const char *crtc = NULL; + const char *coalesce = NULL; + const char *high_priority = NULL; + const char *low_priority = NULL; + const char *priority = NULL; + const char *class = NULL; + const char *lifespan = NULL; + const char *message_id = NULL; + + for (i = 0; i < msg->header_count; i++) { + value = strstr((header = msg->headers[i]), ": ") + 2; + if (strstr(header, "Command: ") == header) command = value; + else if (strstr(header, "CRTC: ") == header) crtc = value; + else if (strstr(header, "Coalesce: ") == header) coalesce = value; + else if (strstr(header, "High priority: ") == header) high_priority = value; + else if (strstr(header, "Low priority: ") == header) low_priority = value; + else if (strstr(header, "Priority: ") == header) priority = value; + else if (strstr(header, "Class: ") == header) class = value; + else if (strstr(header, "Lifespan: ") == header) lifespan = value; + else if (strstr(header, "Message ID: ") == header) message_id = value; + else if (strstr(header, "Length: ") == header) ;/* Handled transparently */ + else + fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header); + } + + if (!command) { + fprintf(stderr, "%s: ignoring message without Command header\n", argv0); + + } else if (!message_id) { + fprintf(stderr, "%s: ignoring message without Message ID header\n", argv0); + + } else if (!strcmp(command, "enumerate-crtcs")) { + if (crtc || coalesce || high_priority || low_priority || priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: enumerate-crtcs message\n", argv0); + r = handle_enumerate_crtcs(conn, message_id); + + } else if (!strcmp(command, "get-gamma-info")) { + if (coalesce || high_priority || low_priority || priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma-info message\n", argv0); + r = handle_get_gamma_info(conn, message_id, crtc); + + } else if (!strcmp(command, "get-gamma")) { + if (priority || class || lifespan) + fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma message\n", argv0); + r = handle_get_gamma(conn, message_id, crtc, coalesce, high_priority, low_priority); + + } else if (!strcmp(command, "set-gamma")) { + if (coalesce || high_priority || low_priority) + fprintf(stderr, "%s: ignoring superfluous headers in Command: set-gamma message\n", argv0); + r = handle_set_gamma(conn, message_id, crtc, priority, class, lifespan); + + } else { + fprintf(stderr, "%s: ignoring unrecognised command: Command: %s\n", argv0, command); + } + + return r; +} + + +/** + * Sets the file descriptor set that includes + * the server socket and all connections + * + * The file descriptor will be ordered as in + * the array `connections`, `socketfd` will + * be last. + * + * @param fds Reference parameter for the array of file descriptors + * @param fdn Output parameter for the number of file descriptors + * @param fds_alloc Reference parameter for the allocation size of `fds`, in elements + * @return Zero on success, -1 on error + */ +static int +update_fdset(struct pollfd **restrict fds, nfds_t *restrict fdn, nfds_t *restrict fds_alloc) +{ + size_t i; + nfds_t j = 0; + void *new; + + if (connections_used + 1 > *fds_alloc) { + new = realloc(*fds, (connections_used + 1) * sizeof(**fds)); + if (!new) + return -1; + *fds = new; + *fds_alloc = connections_used + 1; + } + + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + (*fds)[j].fd = connections[i]; + (*fds)[j].events = NON_WR_POLL_EVENTS; + j++; + } + } + + (*fds)[j].fd = socketfd; + (*fds)[j].events = NON_WR_POLL_EVENTS; + j++; + + *fdn = j; + return 0; +} + + +/** + * Handle event on the server socket + * + * @return 1: New connection accepted + * 0: Successful + * -1: Failure + */ +static int +handle_server(void) +{ + int fd, flags, saved_errno; + void *new; + + fd = accept(socketfd, NULL, NULL); + if (fd < 0) { + switch (errno) { + case ECONNABORTED: + case EINVAL: + terminate = 1; + /* fall through */ + case EINTR: + return 0; + default: + return -1; + } + } + + flags = fcntl(fd, F_GETFL); + if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + goto fail; + + if (connections_ptr == connections_alloc) { + new = realloc(connections, (connections_alloc + 10) * sizeof(*connections)); + if (!new) + goto fail; + connections = new; + connections[connections_ptr] = fd; + + new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound)); + if (!new) + goto fail; + outbound = new; + ring_initialise(&outbound[connections_ptr]); + + new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound)); + if (!new) + goto fail; + inbound = new; + connections_alloc += 10; + if (message_initialise(&inbound[connections_ptr])) + goto fail; + } else { + connections[connections_ptr] = fd; + ring_initialise(&outbound[connections_ptr]); + if (message_initialise(&inbound[connections_ptr])) + goto fail; + } + + connections_ptr++; + while (connections_ptr < connections_used && connections[connections_ptr] >= 0) + connections_ptr++; + if (connections_used < connections_ptr) + connections_used = connections_ptr; + + return 1; +fail: + saved_errno = errno; + shutdown(fd, SHUT_RDWR); + close(fd); + errno = saved_errno; + return -1; +} + + +/** + * Handle event on a connection to a client + * + * @param conn The index of the connection + * @return 1: The connection as closed + * 0: Successful + * -1: Failure + */ +static int +handle_connection(size_t conn) +{ + struct message *restrict msg = &inbound[conn]; + int r, fd = connections[conn]; + +again: + errno = 0; + switch (message_read(msg, fd)) { + default: + break; + + case -1: + switch (errno) { + case EINTR: +#if defined(EAGAIN) + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN != EWOULDBLOCK) + case EWOULDBLOCK: +#endif + return 0; + default: + return -1; + case ECONNRESET:; + /* Fall through to `case -2` in outer switch */ + } + + case -2: + shutdown(fd, SHUT_RDWR); + close(fd); + connections[conn] = -1; + if (conn < connections_ptr) + connections_ptr = conn; + while (connections_used > 0 && connections[connections_used - 1] < 0) + connections_used -= 1; + message_destroy(msg); + ring_destroy(&outbound[conn]); + if (connection_closed(fd) < 0) + return -1; + return 1; + } + + if ((r = dispatch_message(conn, msg))) + return r; + + goto again; +} + + +/** + * Disconnect all clients + */ +void +disconnect_all(void) +{ + size_t i; + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + shutdown(connections[i], SHUT_RDWR); + close(connections[i]); + } + } +} + + +/** + * The program's main loop + * + * @return Zero on success, -1 on error + */ +int +main_loop(void) +{ + struct pollfd *fds = NULL; + nfds_t i, fdn = 0, fds_alloc = 0; + int r, update, do_read, do_write, fd; + size_t j; + + if (update_fdset(&fds, &fdn, &fds_alloc) < 0) + goto fail; + + while (!reexec && !terminate) { + if (connection) { + if ((connection == 1 ? disconnect() : reconnect()) < 0) { + connection = 0; + goto fail; + } + connection = 0; + } + + for (j = 0, i = 0; j < connections_used; j++) { + if (connections[j] >= 0) { + fds[i].revents = 0; + if (ring_have_more(outbound + j)) + fds[(size_t)i++ + j].events |= POLLOUT; + else + fds[(size_t)i++ + j].events &= ~POLLOUT; + } + } + fds[i].revents = 0; + + if (poll(fds, fdn, -1) < 0) { + if (errno == EAGAIN) + perror(argv0); + else if (errno != EINTR) + goto fail; + } + + update = 0; + for (i = 0; i < fdn; i++) { + do_read = fds[i].revents & NON_WR_POLL_EVENTS; + do_write = fds[i].revents & POLLOUT; + fd = fds[i].fd; + if (!do_read && !do_write) + continue; + + if (fd == socketfd) { + r = handle_server(); + } else { + for (j = 0; connections[j] != fd; j++); + r = do_read ? handle_connection(j) : 0; + } + + if (r >= 0 && do_write) + r |= continue_send(j); + if (r < 0) + goto fail; + update |= r > 0; + } + if (update && update_fdset(&fds, &fdn, &fds_alloc) < 0) + goto fail; + } + + free(fds); + return 0; + +fail: + free(fds); + return -1; +} diff --git a/servers-master.h b/servers-master.h new file mode 100644 index 0000000..8c49319 --- /dev/null +++ b/servers-master.h @@ -0,0 +1,17 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef SERVERS_MASTER_H +#define SERVERS_MASTER_H + +/** + * Disconnect all clients + */ +void disconnect_all(void); + +/** + * The program's main loop + * + * @return Zero on success, -1 on error + */ +int main_loop(void); + +#endif diff --git a/src/arg.h b/src/arg.h deleted file mode 100644 index 8418b38..0000000 --- a/src/arg.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copy me if you can. - * by 20h - */ - -#ifndef ARG_H__ -#define ARG_H__ - -/* use main(int argc, char *argv[]) */ -#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ - argv[0] && argv[0][0] == '-'\ - && argv[0][1];\ - argc--, argv++) {\ - char argc_;\ - char **argv_;\ - int brk_;\ - if (argv[0][1] == '-' && argv[0][2] == '\0') {\ - argv++;\ - argc--;\ - break;\ - }\ - for (brk_ = 0, argv[0]++, argv_ = argv;\ - argv[0][0] && !brk_;\ - argv[0]++) {\ - if (argv_ != argv)\ - break;\ - argc_ = argv[0][0];\ - switch (argc_) - -/* Handles obsolete -NUM syntax */ -#define ARGNUM case '0':\ - case '1':\ - case '2':\ - case '3':\ - case '4':\ - case '5':\ - case '6':\ - case '7':\ - case '8':\ - case '9' - -#define ARGEND }\ - } - -#define ARGC() argc_ - -#define ARGNUMF() (brk_ = 1, estrtonum(argv[0], 0, INT_MAX)) - -#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ - ((x), abort(), (char *)0) :\ - (brk_ = 1, (argv[0][1] != '\0')?\ - (&argv[0][1]) :\ - (argc--, argv++, argv[0]))) - -#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ - (char *)0 :\ - (brk_ = 1, (argv[0][1] != '\0')?\ - (&argv[0][1]) :\ - (argc--, argv++, argv[0]))) - -#define LNGARG() &argv[0][0] - -#endif diff --git a/src/communication.c b/src/communication.c deleted file mode 100644 index 9ede401..0000000 --- a/src/communication.c +++ /dev/null @@ -1,175 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "communication.h" -#include "state.h" -#include "servers/coopgamma.h" - -#include -#include -#include - - - -/** - * Send a message - * - * @param conn The index of the connection - * @param buf The data to send - * @param n The size of `buf` - * @return Zero on success, -1 on error, 1 if disconncted - * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count - * as success (ECONNRESET cause 1 to be returned), - * and are handled appropriately. - */ -int send_message(size_t conn, char* restrict buf, size_t n) -{ - struct ring* restrict ring = outbound + conn; - int fd = connections[conn]; - int saved_errno; - size_t ptr = 0; - ssize_t sent; - size_t chunksize = n; - size_t sendsize; - size_t old_n; - char* old_buf; - - while ((old_buf = ring_peek(ring, &old_n))) - { - size_t old_ptr = 0; - while (old_ptr < n) - { - sendsize = old_n - old_ptr < chunksize ? old_n - old_ptr : chunksize; - sent = send(fd, old_buf + old_ptr, sendsize, MSG_NOSIGNAL); - if (sent < 0) - { - if (errno == EPIPE) - errno = ECONNRESET; - if (errno != EMSGSIZE) - goto fail; - chunksize >>= 1; - if (chunksize == 0) - goto fail; - continue; - } - old_ptr += (size_t)sent; - ring_pop(ring, (size_t)sent); - } - } - - while (ptr < n) - { - sendsize = n - ptr < chunksize ? n - ptr : chunksize; - sent = send(fd, buf + ptr, sendsize, MSG_NOSIGNAL); - if (sent < 0) - { - if (errno == EPIPE) - errno = ECONNRESET; - if (errno != EMSGSIZE) - goto fail; - chunksize >>= 1; - if (chunksize == 0) - goto fail; - continue; - } - ptr += (size_t)sent; - } - - free(buf); - return 0; - - fail: - switch (errno) - { - case EINTR: - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif - if (ring_push(ring, buf + ptr, n - ptr) < 0) - goto proper_fail; - free(buf); - return 0; - case ECONNRESET: - free(buf); - if (connection_closed(fd) < 0) - return -1; - return 1; - default: - break; - } - proper_fail: - saved_errno = errno; - free(buf); - errno = saved_errno; - return -1; -} - - -/** - * Send a custom error without an error number - * - * @param conn The index of the connection - * @param message_id The ID of the message to which this message is a response - * @param desc The error description to send - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -int (send_error)(size_t conn, const char* restrict message_id, const char* restrict desc) -{ - char* restrict buf; - size_t n; - - MAKE_MESSAGE(&buf, &n, 0, - "Command: error\n" - "In response to: %s\n" - "Error: custom\n" - "Length: %zu\n" - "\n" - "%s\n", - message_id, strlen(desc) + 1, desc); - - return send_message(conn, buf, n); -} - - -/** - * Send a standard error - * - * @param conn The index of the connection - * @param message_id The ID of the message to which this message is a response - * @param number The value of `errno`, 0 to indicate success - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -int (send_errno)(size_t conn, const char* restrict message_id, int number) -{ - char* restrict buf; - size_t n; - - MAKE_MESSAGE(&buf, &n, 0, - "Command: error\n" - "In response to: %s\n" - "Error: %i\n" - "\n", - message_id, number); - - return send_message(conn, buf, n); -} - diff --git a/src/communication.h b/src/communication.h deleted file mode 100644 index 244ba9d..0000000 --- a/src/communication.h +++ /dev/null @@ -1,137 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef COMMUNICATION_H -#define COMMUNICATION_H - - -#include -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Construct a message - * - * @param bufp:char** Output parameter for the buffer, must not have side-effects - * @param np:size_t* Output parameter for the size of the buffer sans `extra` - * @param extra:size_t The extra number for bytes to allocate to the buffer - * @param format:string-literal Message format string - * @param ... Message formatting arguments - */ -#define MAKE_MESSAGE(bufp, np, extra, format, ...) \ - do \ - { \ - ssize_t m__; \ - snprintf(NULL, 0, format "%zn", __VA_ARGS__, &m__); \ - *(bufp) = malloc((size_t)(extra) + (size_t)m__ + (size_t)1); \ - if (*(bufp) == NULL) \ - return -1; \ - sprintf(*(bufp), format, __VA_ARGS__); \ - *(np) = (size_t)m__; \ - } \ - while (0) - -/** - * Send a custom error without an error number - * - * @param ... The error description to send - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__)) - -/** - * Send a standard error - * - * @param ... The value of `errno`, 0 to indicate success - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__)) - - - -/** - * Send a message - * - * @param conn The index of the connection - * @param buf The data to send - * @param n The size of `buf` - * @return Zero on success, -1 on error, 1 if disconncted - * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count - * as success (ECONNRESET cause 1 to be returned), - * and are handled appropriately. - */ -int send_message(size_t conn, char* restrict buf, size_t n); - -/** - * Send a custom error without an error number - * - * @param conn The index of the connection - * @param message_id The ID of the message to which this message is a response - * @param desc The error description to send - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -GCC_ONLY(__attribute__((nonnull))) -int (send_error)(size_t conn, const char* restrict message_id, const char* restrict desc); - -/** - * Send a standard error - * - * @param conn The index of the connection - * @param message_id The ID of the message to which this message is a response - * @param number The value of `errno`, 0 to indicate success - * @return 1: Client disconnected - * 0: Success (possibily delayed) - * -1: An error occurred - */ -GCC_ONLY(__attribute__((nonnull))) -int (send_errno)(size_t conn, const char* restrict message_id, int number); - -/** - * Continue sending the queued messages - * - * @param conn The index of the connection - * @return Zero on success, -1 on error, 1 if disconncted - * EINTR, EAGAIN, EWOULDBLOCK, and ECONNRESET count - * as success (ECONNRESET cause 1 to be returned), - * and are handled appropriately. - */ -static inline int continue_send(size_t conn) -{ - return send_message(conn, NULL, 0); -} - - - -#endif - diff --git a/src/coopgammad.c b/src/coopgammad.c deleted file mode 100644 index e81840e..0000000 --- a/src/coopgammad.c +++ /dev/null @@ -1,895 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "arg.h" -#include "util.h" -#include "state.h" -#include "servers/master.h" -#include "servers/kernel.h" -#include "servers/crtc.h" -#include "servers/gamma.h" -#include "servers/coopgamma.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * Number put in front of the marshalled data - * so the program an detect incompatible updates - */ -#define MARSHAL_VERSION 0 - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Lists all function recognised adjustment methods, - * will call macro X with the code for the each - * adjustment method as the first argument and - * corresponding name as the second argument - */ -#define LIST_ADJUSTMENT_METHODS \ - X(LIBGAMMA_METHOD_DUMMY, "dummy") \ - X(LIBGAMMA_METHOD_X_RANDR, "randr") \ - X(LIBGAMMA_METHOD_X_VIDMODE, "vidmode") \ - X(LIBGAMMA_METHOD_LINUX_DRM, "drm") \ - X(LIBGAMMA_METHOD_W32_GDI, "gdi") \ - X(LIBGAMMA_METHOD_QUARTZ_CORE_GRAPHICS, "quartz") - - - -/** - * Used by initialisation functions as their return type. If a - * value not listed here is returned by such function, it is the - * exit value the process shall exit with as soon as possible. - */ -enum init_status -{ - /** - * Initialisation was successful - */ - INIT_SUCCESS = -1, - - /** - * Initialisation failed - */ - INIT_FAILURE = -2, - - /** - * Server is already running - */ - INIT_RUNNING = -3, -}; - - - -/** - * The pathname of the PID file - */ -extern char* restrict pidpath; -char* restrict pidpath = NULL; - -/** - * The pathname of the socket - */ -extern char* restrict socketpath; -char* restrict socketpath = NULL; - - - -/** - * Called when the process receives - * a signal telling it to re-execute - * - * @param signo The received signal - */ -static void sig_reexec(int signo) -{ - int saved_errno = errno; - reexec = 1; - signal(signo, sig_reexec); - errno = saved_errno; -} - - -/** - * Called when the process receives - * a signal telling it to terminate - * - * @param signo The received signal - */ -static void sig_terminate(int signo) -{ - terminate = 1; - (void) signo; -} - - -/** - * Called when the process receives - * a signal telling it to disconnect - * from or reconnect to the site - * - * @param signo The received signal - */ -static void sig_connection(int signo) -{ - int saved_errno = errno; - connection = signo - SIGRTMIN + 1; - signal(signo, sig_connection); - errno = saved_errno; -} - - -/** - * Called when the process receives - * a signal telling it to dump its - * state to stderr - * - * @param signo The received signal - */ -static void sig_info(int signo) -{ - int saved_errno = errno; - char* env; - signal(signo, sig_info); - env = getenv("COOPGAMMAD_PIDFILE_TOKEN"); - fprintf(stderr, "PID file token: %s\n", env ? env : "(null)"); - fprintf(stderr, "PID file: %s\n", pidpath ? pidpath : "(null)"); - fprintf(stderr, "Socket path: %s\n", socketpath ? socketpath : "(null)"); - state_dump(); - errno = saved_errno; -} - - -/** - * Parse adjustment method name (or stringised number) - * - * @param arg The adjustment method name (or stringised number) - * @return The adjustment method, -1 (negative) on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int get_method(const char* restrict arg) -{ -#if LIBGAMMA_METHOD_MAX > 5 -# warning libgamma has added more adjustment methods -#endif - - const char* restrict p; - -#define X(C, N) if (!strcmp(arg, N)) return C; - LIST_ADJUSTMENT_METHODS; -#undef X - - if (!*arg || (/* avoid overflow: */ strlen(arg) > 4)) - goto bad; - for (p = arg; *p; p++) - if (('0' > *p) || (*p > '9')) - goto bad; - - return atoi(arg); - - bad: - fprintf(stderr, "%s: unrecognised adjustment method name: %s\n", argv0, arg); - errno = 0; - return -1; -} - - -/** - * Set up signal handlers - * - * @return Zero on success, -1 on error - */ -static int set_up_signals(void) -{ - if ((signal(SIGUSR1, sig_reexec) == SIG_ERR) || - (signal(SIGUSR2, sig_info) == SIG_ERR) || -#if defined(SIGINFO) - (signal(SIGINFO, sig_info) == SIG_ERR) || -#endif - (signal(SIGTERM, sig_terminate) == SIG_ERR) || - (signal(SIGRTMIN + 0, sig_connection) == SIG_ERR) || - (signal(SIGRTMIN + 1, sig_connection) == SIG_ERR)) - return -1; - return 0; -} - - -/** - * Fork the process to the background - * - * @param keep_stderr Keep stderr open? - * @return An `enum init_status` value or an exit value - */ -static enum init_status daemonise(int keep_stderr) -{ - pid_t pid; - int fd = -1, saved_errno; - int notify_rw[2] = { -1, -1 }; - char a_byte = 0; - ssize_t got; - - if (pipe(notify_rw) < 0) - goto fail; - if (notify_rw[0] <= STDERR_FILENO) - if ((notify_rw[0] = dup2atleast(notify_rw[0], STDERR_FILENO + 1)) < 0) - goto fail; - if (notify_rw[1] <= STDERR_FILENO) - if ((notify_rw[1] = dup2atleast(notify_rw[1], STDERR_FILENO + 1)) < 0) - goto fail; - - if ((pid = fork()) < 0) - goto fail; - if (pid > 0) - { - /* Original process (parent): */ - waitpid(pid, NULL, 0); - close(notify_rw[1]), notify_rw[1] = -1; - got = read(notify_rw[0], &a_byte, 1); - if (got < 0) - goto fail; - close(notify_rw[0]); - errno = 0; - return got == 0 ? INIT_FAILURE : (enum init_status)0; - } - - /* Intermediary process (child): */ - close(notify_rw[0]), notify_rw[0] = -1; - if (setsid() < 0) - goto fail; - if ((pid = fork()) < 0) - goto fail; - if (pid > 0) - { - /* Intermediary process (parent): */ - return (enum init_status)0; - } - - /* Daemon process (child): */ - - /* Replace std* with /dev/null */ - fd = open("/dev/null", O_RDWR); - if (fd < 0) - goto fail; -#define xdup2(s, d) do if (s != d) { close(d); if (dup2(s, d) < 0) goto fail; } while (0) - xdup2(fd, STDIN_FILENO); - xdup2(fd, STDOUT_FILENO); - if (keep_stderr) - xdup2(fd, STDERR_FILENO); - if (fd > STDERR_FILENO) - close(fd); - fd = -1; - - /* Update PID file */ - fd = open(pidpath, O_WRONLY); - if (fd < 0) - goto fail; - if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) - goto fail; - close(fd), fd = -1; - - /* Notify */ - if (write(notify_rw[1], &a_byte, 1) <= 0) - goto fail; - close(notify_rw[1]); - - return INIT_SUCCESS; - fail: - saved_errno = errno; - if (fd >= 0) close(fd); - if (notify_rw[0] >= 0) close(notify_rw[0]); - if (notify_rw[1] >= 0) close(notify_rw[1]); - errno = saved_errno; - return INIT_FAILURE; -} - - -/** - * Initialise the process - * - * @param foreground Keep process in the foreground - * @param keep_stderr Keep stderr open - * @param query Was -q used, see `main` for description - * @return An `enum init_status` value or an exit value - */ -static enum init_status initialise(int foreground, int keep_stderr, int query) -{ - struct rlimit rlimit; - size_t i, n; - sigset_t mask; - int s; - enum init_status r; - - /* Zero out some memory so it can be destoried safely. */ - memset(&site, 0, sizeof(site)); - - if (!query) - { - /* Close all file descriptors above stderr */ - if (getrlimit(RLIMIT_NOFILE, &rlimit) || (rlimit.rlim_cur == RLIM_INFINITY)) - n = 4 << 10; - else - n = (size_t)(rlimit.rlim_cur); - for (i = STDERR_FILENO + 1; i < n; i++) - close((int)i); - - /* Set umask, reset signal handlers, and reset signal mask */ - umask(0); - for (s = 1; s < _NSIG; s++) - signal(s, SIG_DFL); - if (sigfillset(&mask)) - perror(argv0); - else - sigprocmask(SIG_UNBLOCK, &mask, NULL); - } - - /* Get method */ - if ((method < 0) && (libgamma_list_methods(&method, 1, 0) < 1)) - return fprintf(stderr, "%s: no adjustment method available\n", argv0), -1; - - /* Go no further if we are just interested in the adjustment method and site */ - if (query) - return INIT_SUCCESS; - - /* Get site */ - if (initialise_site() < 0) - goto fail; - - /* Get PID file and socket pathname */ - if (!(pidpath = get_pidfile_pathname()) || - !(socketpath = get_socket_pathname())) - goto fail; - - /* Create PID file */ - if ((r = create_pidfile(pidpath)) < 0) - { - free(pidpath), pidpath = NULL; - if (r == -2) - return INIT_RUNNING; - goto fail; - } - - /* Get partitions and CRTC:s */ - if (initialise_crtcs() < 0) - goto fail; - - /* Get CRTC information */ - if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs)))) - goto fail; - if (initialise_gamma_info() < 0) - goto fail; - - /* Sort outputs */ - qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name); - - /* Load current gamma ramps */ - store_gamma(); - - /* Preserve current gamma ramps at priority=0 if -p */ - if (preserve && (preserve_gamma() < 0)) - goto fail; - - /* Create socket and start listening */ - if (create_socket(socketpath) < 0) - goto fail; - - /* Get the real pathname of the process's binary, in case - * it is relative, so we can re-execute without problem. */ - if ((*argv0 != '/') && strchr(argv0, '/') && !(argv0_real = realpath(argv0, NULL))) - goto fail; - - /* Change directory to / to avoid blocking umounting */ - if (chdir("/") < 0) - perror(argv0); - - /* Set up signal handlers */ - if (set_up_signals() < 0) - goto fail; - - /* Place in the background unless -f */ - if (foreground == 0) - return daemonise(keep_stderr); - else - { - /* Signal the spawner that the service is ready */ - close(STDOUT_FILENO); - /* Avoid potential catastrophes that would occur if a library - * that is being used was so mindless as to write to stdout. */ - if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) - perror(argv0); - } - - return INIT_SUCCESS; - fail: - return INIT_FAILURE; -} - - -/** - * Deinitialise the process - * - * @param full Perform a full deinitialisation, shall be - * done iff the process is going to re-execute - */ -static void destroy(int full) -{ - if (full) - { - disconnect_all(); - close_socket(socketpath); - free(argv0_real); - if ((outputs != NULL) && connected) - restore_gamma(); - } - state_destroy(); - free(socketpath); - if (full && (pidpath != NULL)) - unlink(pidpath); - free(pidpath); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal the state of the process - * - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how large the - * buffer needs to be - * @return The number of marshalled bytes - */ -static size_t marshal(void* restrict buf) -{ - size_t off = 0, n; - char* restrict bs = buf; - - if (bs != NULL) - *(int*)(bs + off) = MARSHAL_VERSION; - off += sizeof(int); - - n = strlen(pidpath) + 1; - if (bs != NULL) - memcpy(bs + off, pidpath, n); - off += n; - - n = strlen(socketpath) + 1; - if (bs != NULL) - memcpy(bs + off, socketpath, n); - off += n; - - off += state_marshal(bs ? bs + off : NULL); - - return off; -} - - -/** - * Unmarshal the state of the process - * - * @param buf Buffer with the marshalled data - * @return The number of marshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static size_t unmarshal(const void* restrict buf) -{ - size_t off = 0, n; - const char* restrict bs = buf; - - if (*(const int*)(bs + off) != MARSHAL_VERSION) - { - fprintf(stderr, "%s: re-executing to incompatible version, sorry about that\n", argv0); - errno = 0; - return 0; - } - off += sizeof(int); - - n = strlen(bs + off) + 1; - if (!(pidpath = memdup(bs + off, n))) - return 0; - off += n; - - n = strlen(bs + off) + 1; - if (!(socketpath = memdup(bs + off, n))) - return 0; - off += n; - - off += n = state_unmarshal(bs + off); - if (n == 0) - return 0; - - return off; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - - - -/** - * Do minimal initialisation, unmarshal the state of - * the process and merge with new state - * - * @param statefile The state file - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int restore_state(const char* restrict statefile) -{ - void* marshalled = NULL; - int fd = -1, saved_errno; - size_t r, n; - - if (set_up_signals() < 0) - goto fail; - - fd = open(statefile, O_RDONLY); - if (fd < 0) - goto fail; - - if (!(marshalled = nread(fd, &n))) - goto fail; - close(fd), fd = -1; - unlink(statefile), statefile = NULL; - - r = unmarshal(marshalled); - if (r == 0) - goto fail; - if (r != n) - { - fprintf(stderr, "%s: unmarshalled state file was %s than the unmarshalled state: read %zu of %zu bytes\n", - argv0, n > r ? "larger" : "smaller", r, n); - errno = 0; - goto fail; - } - free(marshalled), marshalled = NULL; - - if (connected) - { - connected = 0; - if (reconnect() < 0) - goto fail; - } - - return 0; - fail: - saved_errno = errno; - if (fd >= 0) - close(fd); - free(marshalled); - errno = saved_errno; - return -1; -} - - -/** - * Reexecute the server - * - * Returns only on failure - * - * @return Pathname of file where the state is stored, - * `NULL` if the state is in tact - */ -static char* reexecute(void) -{ - char* statefile = NULL; - char* statebuffer = NULL; - size_t buffer_size; - int fd = -1, saved_errno; - - statefile = get_state_pathname(); - if (statefile == NULL) - goto fail; - - buffer_size = marshal(NULL); - statebuffer = malloc(buffer_size); - if (statebuffer == NULL) - goto fail; - if (marshal(statebuffer) != buffer_size) - { - fprintf(stderr, "%s: internal error\n", argv0); - errno = 0; - goto fail; - } - - fd = open(statefile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); - if (fd < 0) - goto fail; - - if (nwrite(fd, statebuffer, buffer_size) != buffer_size) - goto fail; - free(statebuffer), statebuffer = NULL; - - if ((close(fd) < 0) && (fd = -1, errno != EINTR)) - goto fail; - fd = -1; - - destroy(0); - -#if !defined(USE_VALGRIND) - execlp(argv0_real ? argv0_real : argv0, argv0, "- ", statefile, NULL); -#else - execlp("valgrind", "valgrind", "--leak-check=full", argv0_real ? argv0_real : argv0, "- ", statefile, NULL); -#endif - saved_errno = errno; - free(argv0_real), argv0_real = NULL; - errno = saved_errno; - return statefile; - - fail: - saved_errno = errno; - free(statebuffer); - if (fd >= 0) - close(fd); - if (statefile != NULL) - unlink(statefile), free(statefile); - errno = saved_errno; - return NULL; -} - - - -/** - * Print the response for the -q option - * - * @param query See -q for `main`, must be atleast 1 - * @return Zero on success, -1 on error - */ -static int print_method_and_site(int query) -{ - const char* restrict methodname = NULL; - char* p; - - if (query == 1) - { - switch (method) - { -#define X(C, N) case C: methodname = N; break; - LIST_ADJUSTMENT_METHODS; -#undef X - default: - if (printf("%i\n", method) < 0) - return -1; - break; - } - if (methodname != NULL) - if (printf("%s\n", methodname) < 0) - return -1; - } - - if (sitename == NULL) - if ((sitename = libgamma_method_default_site(method))) - if (!(sitename = memdup(sitename, strlen(sitename) + 1))) - return -1; - - if (sitename != NULL) - switch (method) - { - case LIBGAMMA_METHOD_X_RANDR: - case LIBGAMMA_METHOD_X_VIDMODE: - if ((p = strrchr(sitename, ':'))) - if ((p = strchr(p, '.'))) - *p = '\0'; - break; - default: - break; - } - - if ((sitename != NULL) && (query == 1)) - if (printf("%s\n", sitename) < 0) - return -1; - - if (query == 2) - { - site.method = method; - site.site = sitename, sitename = NULL; - socketpath = get_socket_pathname(); - if (socketpath == NULL) - return -1; - if (printf("%s\n", socketpath) < 0) - return -1; - } - - if (fflush(stdout)) - return -1; - return 0; -} - - - -/** - * Print usage information and exit - */ -#if defined(__GNU__) || defined(__clang__) -__attribute__((noreturn)) -#endif -static void usage(void) -{ - printf("Usage: %s [-m method] [-s site] [-fkpq]\n", argv0); - exit(1); -} - - -#if defined(__clang__) -# pragma GCC diagnostic ignored "-Wdocumentation-unknown-command" -#endif - - -/** - * Must not be started without stdin, stdout, or stderr (may be /dev/null) - * - * argv[0] must refer to the real command name or pathname, - * otherwise, re-execute will not work - * - * The process closes stdout when the socket has been created - * - * @signal SIGUSR1 Re-execute to updated process image - * @signal SIGUSR2 Dump the state of the process to standard error - * @signal SIGINFO Ditto - * @signal SIGTERM Terminate the process gracefully - * @signal SIGRTMIN+0 Disconnect from the site - * @signal SiGRTMIN+1 Reconnect to the site - * - * @param argc The number of elements in `argv` - * @param argv Command line arguments. Recognised options: - * -s SITENAME - * The site to which to connect - * -m METHOD - * Adjustment method name or adjustment method number - * -p - * Preserve current gamma ramps at priority 0 - * -f - * Do not fork the process into the background - * -k - * Keep stderr open - * -q - * Print the select (possiblity default) adjustment - * method on the first line in stdout, and the - * selected (possibility defasult) site on the second - * line in stdout, and exit. If the site name is `NULL`, - * the second line is omitted. This is indented to - * be used by clients to figure out to which instance - * of the service it should connect. Use twice to - * simply ge the socket pathname, an a terminating LF. - * By combining -q and -m you can enumerate the name - * of all recognised adjustment method, start from 0 - * and work up until a numerical adjustment method is - * returned. - * @return 0: Successful - * 1: An error occurred - * 2: Already running - */ -int main(int argc, char** argv) -{ - int rc = 1, foreground = 0, keep_stderr = 0, query = 0, r; - char* statefile = NULL; - char* statefile_ = NULL; - - ARGBEGIN - { - case 's': - sitename = EARGF(usage()); - /* To simplify re-exec: */ - sitename = memdup(sitename, strlen(sitename) + 1); - if (sitename == NULL) - goto fail; - break; - case 'm': - method = get_method(EARGF(usage())); - if (method < 0) - goto fail; - break; - case 'p': preserve = 1; break; - case 'f': foreground = 1; break; - case 'k': keep_stderr = 1; break; - case 'q': query = 1 + !!query; break; - case ' ': /* Internal, do not document */ - statefile = statefile_ = EARGF(usage()); - break; - default: - usage(); - } - ARGEND; - if (argc > 0) - usage(); - - restart: - - if (statefile == NULL) - switch ((r = initialise(foreground, keep_stderr, query))) - { - case INIT_SUCCESS: break; - case INIT_RUNNING: rc = 2; /* fall through */ - case INIT_FAILURE: goto fail; - default: return r; - } - else if (restore_state(statefile) < 0) - goto fail; - else - { - if (statefile != statefile_) - free(statefile); - unlink(statefile), statefile = NULL; - } - - if (query) - { - if (print_method_and_site(query) < 0) - goto fail; - goto done; - } - - reenter_loop: - if (main_loop() < 0) - goto fail; - - if (reexec && !terminate) - { - if ((statefile = reexecute())) - { - perror(argv0); - fprintf(stderr, "%s: restoring state without re-executing\n", argv0); - reexec = 0; - goto restart; - } - perror(argv0); - fprintf(stderr, "%s: continuing without re-executing\n", argv0); - reexec = 0; - goto reenter_loop; - } - - done: - rc = 0; - deinit: - if (statefile) - unlink(statefile); - if (reexec) - free(statefile); - destroy(1); - return rc; - fail: - if (errno != 0) - perror(argv0); - goto deinit; -} - diff --git a/src/servers/coopgamma.c b/src/servers/coopgamma.c deleted file mode 100644 index 6e544be..0000000 --- a/src/servers/coopgamma.c +++ /dev/null @@ -1,558 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "coopgamma.h" -#include "gamma.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" -#include "../types/output.h" - -#include - -#include -#include -#include -#include - - - -/** - * Apply a filter on top of another filter - * - * @param dest The output for the resulting ramp-trio, must be initialised - * @param application The red, green and blue ramps, as one single raw array, - * of the filter that should be applied - * @param depth -1: `float` stops - * -2: `double` stops - * Other: the number of bits of each (integral) stop - * @param base The CLUT on top of which the new filter should be applied, - * this can be the same pointer as `dest` - */ -static void apply_filter(union gamma_ramps* restrict dest, void* restrict application, - int depth, union gamma_ramps* restrict base) -{ - union gamma_ramps app; - size_t bytedepth; - size_t red_width, green_width, blue_width; - - if (depth == -1) - bytedepth = sizeof(float); - else if (depth == -2) - bytedepth = sizeof(double); - else - bytedepth = (size_t)depth / 8; - - red_width = (app.u8.red_size = base->u8.red_size) * bytedepth; - green_width = (app.u8.green_size = base->u8.green_size) * bytedepth; - blue_width = (app.u8.blue_size = base->u8.blue_size) * bytedepth; - - app.u8.red = application; - app.u8.green = app.u8.red + red_width; - app.u8.blue = app.u8.green + green_width; - - if (dest != base) - { - memcpy(dest->u8.red, base->u8.red, red_width); - memcpy(dest->u8.green, base->u8.green, green_width); - memcpy(dest->u8.blue, base->u8.blue, blue_width); - } - - switch (depth) - { - case 8: - libclut_apply(&(dest->u8), UINT8_MAX, uint8_t, &(app.u8), UINT8_MAX, uint8_t, 1, 1, 1); - break; - case 16: - libclut_apply(&(dest->u16), UINT16_MAX, uint16_t, &(app.u16), UINT16_MAX, uint16_t, 1, 1, 1); - break; - case 32: - libclut_apply(&(dest->u32), UINT32_MAX, uint32_t, &(app.u32), UINT32_MAX, uint32_t, 1, 1, 1); - break; - case 64: - libclut_apply(&(dest->u64), UINT64_MAX, uint64_t, &(app.u64), UINT64_MAX, uint64_t, 1, 1, 1); - break; - case -1: - libclut_apply(&(dest->f), 1.0f, float, &(app.d), 1.0f, float, 1, 1, 1); - break; - case -2: - libclut_apply(&(dest->d), (double)1, double, &(app.f), (double)1, double, 1, 1, 1); - break; - default: - abort(); - } -} - - - -/** - * Remove a filter from an output - * - * @param out The output - * @param filter The filter - * @return The index of the filter, `out->table_size` if not found - */ -static ssize_t remove_filter(struct output* restrict out, struct filter* restrict filter) -{ - size_t i, n = out->table_size; - - for (i = 0; i < n; i++) - if (!strcmp(filter->class, out->table_filters[i].class)) - break; - - if (i == out->table_size) - { - fprintf(stderr, "%s: ignoring attempt to removing non-existing filter on CRTC %s: %s\n", - argv0, out->name, filter->class); - return (ssize_t)(out->table_size); - } - - filter_destroy(out->table_filters + i); - libgamma_gamma_ramps8_destroy(&(out->table_sums[i].u8)); - - n = n - i - 1; - memmove(out->table_filters + i, out->table_filters + i + 1, n * sizeof(*(out->table_filters))); - memmove(out->table_sums + i, out->table_sums + i + 1, n * sizeof(*(out->table_sums))); - out->table_size--; - - return (ssize_t)i; -} - - -/** - * Add a filter to an output - * - * @param out The output - * @param filter The filter - * @return The index given to the filter, -1 on error - */ -static ssize_t add_filter(struct output* restrict out, struct filter* restrict filter) -{ - size_t i, n = out->table_size; - int r = -1; - - /* Remove? */ - if (filter->lifespan == LIFESPAN_REMOVE) - return remove_filter(out, filter); - - /* Update? */ - for (i = 0; i < n; i++) - if (!strcmp(filter->class, out->table_filters[i].class)) - break; - if (i != n) - { - filter_destroy(out->table_filters + i); - out->table_filters[i] = *filter; - filter->class = NULL; - filter->ramps = NULL; - return (ssize_t)i; - } - - /* Add! */ - for (i = 0; i < n; i++) - if (filter->priority > out->table_filters[i].priority) - break; - - if (n == out->table_alloc) - { - void* new; - - new = realloc(out->table_filters, (n + 10) * sizeof(*(out->table_filters))); - if (new == NULL) - return -1; - out->table_filters = new; - - new = realloc(out->table_sums, (n + 10) * sizeof(*(out->table_sums))); - if (new == NULL) - return -1; - out->table_sums = new; - - out->table_alloc += 10; - } - - memmove(out->table_filters + i + 1, out->table_filters + i, (n - i) * sizeof(*(out->table_filters))); - memmove(out->table_sums + i + 1, out->table_sums + i, (n - i) * sizeof(*(out->table_sums))); - out->table_size++; - - out->table_filters[i] = *filter; - filter->class = NULL; - filter->ramps = NULL; - - COPY_RAMP_SIZES(&(out->table_sums[i].u8), out); - switch (out->depth) - { - case 8: r = libgamma_gamma_ramps8_initialise(&(out->table_sums[i].u8)); break; - case 16: r = libgamma_gamma_ramps16_initialise(&(out->table_sums[i].u16)); break; - case 32: r = libgamma_gamma_ramps32_initialise(&(out->table_sums[i].u32)); break; - case 64: r = libgamma_gamma_ramps64_initialise(&(out->table_sums[i].u64)); break; - case -1: r = libgamma_gamma_rampsf_initialise(&(out->table_sums[i].f)); break; - case -2: r = libgamma_gamma_rampsd_initialise(&(out->table_sums[i].d)); break; - default: - abort(); - } - if (r < 0) - return -1; - - return (ssize_t)i; -} - - -/** - * Handle a closed connection - * - * @param client The file descriptor for the client - * @return Zero on success, -1 on error - */ -int connection_closed(int client) -{ - size_t i, j, k; - int remove; - - for (i = 0; i < outputs_n; i++) - { - struct output* output = outputs + i; - ssize_t updated = -1; - for (j = k = 0; j < output->table_size; j += !remove, k++) - { - if (j != k) - { - output->table_filters[j] = output->table_filters[k]; - output->table_sums[j] = output->table_sums[k]; - } - remove = output->table_filters[j].client == client; - remove = remove && (output->table_filters[j].lifespan == LIFESPAN_UNTIL_DEATH); - if (remove) - { - filter_destroy(output->table_filters + j); - libgamma_gamma_ramps8_destroy(&(output->table_sums[j].u8)); - output->table_size -= 1; - if (updated == -1) - updated = (ssize_t)j; - } - } - if (updated >= 0) - if (flush_filters(output, (size_t)updated) < 0) - return -1; - } - - return 0; -} - - -/** - * Handle a ‘Command: get-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @param coalesce The value of the ‘Coalesce’ header - * @param high_priority The value of the ‘High priority’ header - * @param low_priority The value of the ‘Low priority’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc, - const char* restrict coalesce, const char* restrict high_priority, - const char* restrict low_priority) -{ - struct output* restrict output; - int64_t high, low; - int coal; - char* restrict buf; - size_t start, end, len, n, i; - char depth[3]; - char tables[sizeof("Tables: \n") + 3 * sizeof(size_t)]; - - if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted"); - if (coalesce == NULL) return send_error("protocol error: 'Coalesce' header omitted"); - if (high_priority == NULL) return send_error("protocol error: 'High priority' header omitted"); - if (low_priority == NULL) return send_error("protocol error: 'Low priority' header omitted"); - - high = (int64_t)atoll(high_priority); - low = (int64_t)atoll(low_priority); - - if (!strcmp(coalesce, "yes")) - coal = 1; - else if (!strcmp(coalesce, "no")) - coal = 0; - else - return send_error("protocol error: recognised value for 'Coalesce' header"); - - output = output_find_by_name(crtc, outputs, outputs_n); - if (output == NULL) - return send_error("selected CRTC does not exist"); - else if (output->supported == LIBGAMMA_NO) - return send_error("selected CRTC does not support gamma adjustments"); - - for (start = 0; start < output->table_size; start++) - if (output->table_filters[start].priority <= high) - break; - - for (end = output->table_size; end > 0; end--) - if (output->table_filters[end - 1].priority >= low) - break; - - switch (output->depth) - { - case -2: strcpy(depth, "d"); break; - case -1: strcpy(depth, "f"); break; - default: - sprintf(depth, "%i", output->depth); - break; - } - - if (coal) - { - *tables = '\0'; - n = output->ramps_size; - } - else - { - sprintf(tables, "Tables: %zu\n", end - start); - n = (sizeof(int64_t) + output->ramps_size) * (end - start); - for (i = start; i < end; i++) - n += strlen(output->table_filters[i].class) + 1; - } - - MAKE_MESSAGE(&buf, &n, n, - "In response to: %s\n" - "Depth: %s\n" - "Red size: %zu\n" - "Green size: %zu\n" - "Blue size: %zu\n" - "%s" - "Length: %zu\n" - "\n", - message_id, depth, output->red_size, output->green_size, - output->blue_size, tables, n); - - if (coal) - { - if ((start == 0) && (start < end)) - memcpy(buf + n, output->table_sums[end - 1].u8.red, output->ramps_size); - else - { - union gamma_ramps ramps; - if (make_plain_ramps(&ramps, output)) - { - int saved_errno = errno; - free(buf); - errno = saved_errno; - return -1; - } - for (i = start; i < end; i++) - apply_filter(&ramps, output->table_filters[i].ramps, output->depth, &ramps); - memcpy(buf + n, ramps.u8.red, output->ramps_size); - libgamma_gamma_ramps8_destroy(&(ramps.u8)); - } - n += output->ramps_size; - } - else - for (i = start; i < end; i++) - { -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - *(int64_t*)(buf + n) = output->table_filters[i].priority; -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - n += sizeof(int64_t); - len = strlen(output->table_filters[i].class) + 1; - memcpy(buf + n, output->table_filters[i].class, len); - n += len; - memcpy(buf + n, output->table_filters[i].ramps, output->ramps_size); - n += output->ramps_size; - } - - return send_message(conn, buf, n); -} - - -/** - * Handle a ‘Command: set-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @param priority The value of the ‘Priority’ header - * @param class The value of the ‘Class’ header - * @param lifespan The value of the ‘Lifespan’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc, - const char* restrict priority, const char* restrict class, const char* restrict lifespan) -{ - struct message* restrict msg = inbound + conn; - struct output* restrict output = NULL; - struct filter filter; - char* restrict p; - char* restrict q; - int saved_errno; - ssize_t r; - - if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted"); - if (class == NULL) return send_error("protocol error: 'Class' header omitted"); - if (lifespan == NULL) return send_error("protocol error: 'Lifespan' header omitted"); - - filter.client = connections[conn]; - filter.priority = priority == NULL ? 0 : (int64_t)atoll(priority); - filter.ramps = NULL; - - output = output_find_by_name(crtc, outputs, outputs_n); - if (output == NULL) - return send_error("CRTC does not exists"); - - p = strstr(class, "::"); - if ((p == NULL) || (p == class)) - return send_error("protocol error: malformatted value for 'Class' header"); - q = strstr(p + 2, "::"); - if ((q == NULL) || (q == p)) - return send_error("protocol error: malformatted value for 'Class' header"); - - if (!strcmp(lifespan, "until-removal")) - filter.lifespan = LIFESPAN_UNTIL_REMOVAL; - else if (!strcmp(lifespan, "until-death")) - filter.lifespan = LIFESPAN_UNTIL_DEATH; - else if (!strcmp(lifespan, "remove")) - filter.lifespan = LIFESPAN_REMOVE; - else - return send_error("protocol error: recognised value for 'Lifespan' header"); - - if (filter.lifespan == LIFESPAN_REMOVE) - { - if (msg->payload_size) - fprintf(stderr, "%s: ignoring superfluous payload on Command: set-gamma message with " - "Lifespan: remove\n", argv0); - if (priority != NULL) - fprintf(stderr, "%s: ignoring superfluous Priority header on Command: set-gamma message with " - "Lifespan: remove\n", argv0); - } - else if (msg->payload_size != output->ramps_size) - return send_error("invalid payload: size of message payload does matched the expectancy"); - else if (priority == NULL) - return send_error("protocol error: 'Priority' header omitted"); - - filter.class = memdup(class, strlen(class) + 1); - if (filter.class == NULL) - goto fail; - - if (filter.lifespan != LIFESPAN_REMOVE) - { - filter.ramps = memdup(msg->payload, msg->payload_size); - if (filter.ramps == NULL) - goto fail; - } - - if ((r = add_filter(output, &filter)) < 0) - goto fail; - if (flush_filters(output, (size_t)r)) - goto fail; - - free(filter.class); - free(filter.ramps); - return send_errno(0); - - fail: - saved_errno = errno; - send_errno(saved_errno); - free(filter.class); - free(filter.ramps); - errno = saved_errno; - return -1; -} - - - -/** - * Recalculate the resulting gamma and - * update push the new gamma ramps to the CRTC - * - * @param output The output - * @param first_updated The index of the first added or removed filter - * @return Zero on success, -1 on error - */ -int flush_filters(struct output* restrict output, size_t first_updated) -{ - union gamma_ramps plain; - union gamma_ramps* last; - size_t i; - - if (first_updated == 0) - { - if (make_plain_ramps(&plain, output) < 0) - return -1; - last = &plain; - } - else - last = output->table_sums + (first_updated - 1); - - for (i = first_updated; i < output->table_size; i++) - { - apply_filter(output->table_sums + i, output->table_filters[i].ramps, output->depth, last); - last = output->table_sums + i; - } - - set_gamma(output, last); - - if (first_updated == 0) - libgamma_gamma_ramps8_destroy(&(plain.u8)); - - return 0; -} - - - -/** - * Preserve current gamma ramps at priority 0 for all outputs - * - * @return Zero on success, -1 on error - */ -int preserve_gamma(void) -{ - size_t i; - - for (i = 0; i < outputs_n; i++) - { - struct filter filter = { - .client = -1, - .priority = 0, - .class = NULL, - .lifespan = LIFESPAN_UNTIL_REMOVAL, - .ramps = NULL - }; - outputs[i].table_filters = calloc(4, sizeof(*(outputs[i].table_filters))); - outputs[i].table_sums = calloc(4, sizeof(*(outputs[i].table_sums))); - outputs[i].table_alloc = 4; - outputs[i].table_size = 1; - filter.class = memdup(PKGNAME "::" COMMAND "::preserved", sizeof(PKGNAME "::" COMMAND "::preserved")); - if (filter.class == NULL) - return -1; - filter.ramps = memdup(outputs[i].saved_ramps.u8.red, outputs[i].ramps_size); - if (filter.ramps == NULL) - return -1; - outputs[i].table_filters[0] = filter; - COPY_RAMP_SIZES(&(outputs[i].table_sums[0].u8), outputs + i); - if (!gamma_ramps_unmarshal(outputs[i].table_sums, outputs[i].saved_ramps.u8.red, outputs[i].ramps_size)) - return -1; - } - - return 0; -} - diff --git a/src/servers/coopgamma.h b/src/servers/coopgamma.h deleted file mode 100644 index 5a9f7d9..0000000 --- a/src/servers/coopgamma.h +++ /dev/null @@ -1,101 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERVERS_COOPGAMMA_H -#define SERVERS_COOPGAMMA_H - - -#include "../types/output.h" - -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Handle a closed connection - * - * @param client The file descriptor for the client - * @return Zero on success, -1 on error - */ -int connection_closed(int client); - -/** - * Handle a ‘Command: get-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @param coalesce The value of the ‘Coalesce’ header - * @param high_priority The value of the ‘High priority’ header - * @param low_priority The value of the ‘Low priority’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -GCC_ONLY(__attribute__((nonnull(2)))) -int handle_get_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc, - const char* restrict coalesce, const char* restrict high_priority, - const char* restrict low_priority); - -/** - * Handle a ‘Command: set-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @param priority The value of the ‘Priority’ header - * @param class The value of the ‘Class’ header - * @param lifespan The value of the ‘Lifespan’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -GCC_ONLY(__attribute__((nonnull(2)))) -int handle_set_gamma(size_t conn, const char* restrict message_id, const char* restrict crtc, - const char* restrict priority, const char* restrict class, const char* restrict lifespan); - - -/** - * Recalculate the resulting gamma and - * update push the new gamma ramps to the CRTC - * - * @param output The output - * @param first_updated The index of the first added or removed filter - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -int flush_filters(struct output* restrict output, size_t first_updated); - - -/** - * Preserve current gamma ramps at priority 0 for all outputs - * - * @return Zero on success, -1 on error - */ -int preserve_gamma(void); - - -#endif - diff --git a/src/servers/crtc.c b/src/servers/crtc.c deleted file mode 100644 index ca10940..0000000 --- a/src/servers/crtc.c +++ /dev/null @@ -1,325 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "crtc.h" -#include "gamma.h" -#include "coopgamma.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" - -#include -#include - - - -/** - * Handle a ‘Command: enumerate-crtcs’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -int handle_enumerate_crtcs(size_t conn, const char* restrict message_id) -{ - size_t i, n = 0, len; - char* restrict buf; - - for (i = 0; i < outputs_n; i++) - n += strlen(outputs[i].name) + 1; - - MAKE_MESSAGE(&buf, &n, n, - "Command: crtc-enumeration\n" - "In response to: %s\n" - "Length: %zu\n" - "\n", - message_id, n); - - for (i = 0; i < outputs_n; i++) - { - len = strlen(outputs[i].name); - memcpy(buf + n, outputs[i].name, len); - buf[n + len] = '\n'; - n += len + 1; - } - - return send_message(conn, buf, n); -} - - -/** - * Get the name of a CRTC - * - * @param info Information about the CRTC - * @param crtc libgamma's state for the CRTC - * @return The name of the CRTC, `NULL` on error - */ -char* get_crtc_name(const libgamma_crtc_information_t* restrict info, - const libgamma_crtc_state_t* restrict crtc) -{ - if ((info->edid_error == 0) && (info->edid != NULL)) - return libgamma_behex_edid(info->edid, info->edid_length); - else if ((info->connector_name_error == 0) && (info->connector_name != NULL)) - { - char* name = malloc(3 * sizeof(size_t) + strlen(info->connector_name) + 2); - if (name != NULL) - sprintf(name, "%zu.%s", crtc->partition->partition, info->connector_name); - return name; - } - else - { - char* name = malloc(2 * 3 * sizeof(size_t) + 2); - if (name != NULL) - sprintf(name, "%zu.%zu", crtc->partition->partition, crtc->crtc); - return name; - } -} - - -/** - * Initialise the site - * - * @return Zero on success, -1 on error - */ -int initialise_site(void) -{ - char* restrict sitename_dup = NULL; - int gerror, saved_errno; - - if ((sitename != NULL) && !(sitename_dup = memdup(sitename, strlen(sitename) + 1))) - goto fail; - if ((gerror = libgamma_site_initialise(&site, method, sitename_dup))) - goto fail_libgamma; - - return 0; - fail_libgamma: - sitename_dup = NULL; - libgamma_perror(argv0, gerror); - errno = 0; - fail: - saved_errno = errno; - free(sitename_dup); - errno = saved_errno; - return -1; -} - - -/** - * Get partitions and CRTC:s - * - * @return Zero on success, -1 on error - */ -int initialise_crtcs(void) -{ - size_t i, j, n, n0; - int gerror; - - /* Get partitions */ - outputs_n = 0; - if (site.partitions_available) - if (!(partitions = calloc(site.partitions_available, sizeof(*partitions)))) - goto fail; - for (i = 0; i < site.partitions_available; i++) - { - if ((gerror = libgamma_partition_initialise(partitions + i, &site, i))) - goto fail_libgamma; - outputs_n += partitions[i].crtcs_available; - } - - /* Get CRTC:s */ - if (outputs_n) - if (!(crtcs = calloc(outputs_n, sizeof(*crtcs)))) - goto fail; - for (i = 0, j = n = 0; i < site.partitions_available; i++) - for (n0 = n, n += partitions[i].crtcs_available; j < n; j++) - if ((gerror = libgamma_crtc_initialise(crtcs + j, partitions + i, j - n0))) - goto fail_libgamma; - - return 0; - - fail_libgamma: - libgamma_perror(argv0, gerror); - errno = 0; - fail: - return -1; -} - - -/** - * Merge the new state with an old state - * - * @param old_outputs The old `outputs` - * @param old_outputs_n The old `outputs_n` - * @return Zero on success, -1 on error - */ -int merge_state(struct output* restrict old_outputs, size_t old_outputs_n) -{ - struct output* restrict new_outputs = NULL; - size_t new_outputs_n; - size_t i, j; - - /* How many outputs does the system now have? */ - i = j = new_outputs_n = 0; - while ((i < old_outputs_n) && (j < outputs_n)) - { - int cmp = strcmp(old_outputs[i].name, outputs[j].name); - if (cmp <= 0) - new_outputs_n++; - i += cmp >= 0; - j += cmp <= 0; - } - new_outputs_n += outputs_n - j; - - /* Allocate output state array */ - if (new_outputs_n > 0) - { - new_outputs = calloc(new_outputs_n, sizeof(*new_outputs)); - if (new_outputs == NULL) - return -1; - } - - /* Merge output states */ - i = j = new_outputs_n = 0; - while ((i < old_outputs_n) && (j < outputs_n)) - { - int is_same = 0, cmp = strcmp(old_outputs[i].name, outputs[j].name); - if (cmp == 0) - is_same = (old_outputs[i].depth == outputs[j].depth && - old_outputs[i].red_size == outputs[j].red_size && - old_outputs[i].green_size == outputs[j].green_size && - old_outputs[i].blue_size == outputs[j].blue_size); - if (is_same) - { - new_outputs[new_outputs_n] = old_outputs[i]; - new_outputs[new_outputs_n].crtc = outputs[j].crtc; - memset(old_outputs + i, 0, sizeof(*old_outputs)); - outputs[j].crtc = NULL; - output_destroy(outputs + j); - new_outputs_n++; - } - else if (cmp <= 0) - new_outputs[new_outputs_n++] = outputs[j]; - i += cmp >= 0; - j += cmp <= 0; - } - while (j < outputs_n) - new_outputs[new_outputs_n++] = outputs[j++]; - - /* Commit merge */ - free(outputs); - outputs = new_outputs; - outputs_n = new_outputs_n; - - return 0; -} - - - -/** - * Disconnect from the site - * - * @return Zero on success, -1 on error - */ -int disconnect(void) -{ - size_t i; - - if (!connected) - return 0; - connected = 0; - - for (i = 0; i < outputs_n; i++) - { - outputs[i].crtc = NULL; - libgamma_crtc_destroy(crtcs + i); - } - free(crtcs), crtcs = NULL; - for (i = 0; i < site.partitions_available; i++) - libgamma_partition_destroy(partitions + i); - free(partitions), partitions = NULL; - libgamma_site_destroy(&site); - memset(&site, 0, sizeof(site)); - - return 0; -} - - -/** - * Reconnect to the site - * - * @return Zero on success, -1 on error - */ -int reconnect(void) -{ - struct output* restrict old_outputs; - size_t i, old_outputs_n; - int saved_errno; - - if (connected) - return 0; - connected = 1; - - /* Remember old state */ - old_outputs = outputs, outputs = NULL; - old_outputs_n = outputs_n, outputs_n = 0; - - /* Get site */ - if (initialise_site() < 0) - goto fail; - - /* Get partitions and CRTC:s */ - if (initialise_crtcs() < 0) - goto fail; - - /* Get CRTC information */ - if (outputs_n && !(outputs = calloc(outputs_n, sizeof(*outputs)))) - goto fail; - if (initialise_gamma_info() < 0) - goto fail; - - /* Sort outputs */ - qsort(outputs, outputs_n, sizeof(*outputs), output_cmp_by_name); - - /* Load current gamma ramps */ - store_gamma(); - - /* Preserve current gamma ramps at priority=0 if -p */ - if (preserve && (preserve_gamma() < 0)) - goto fail; - - /* Merge state */ - if (merge_state(old_outputs, old_outputs_n) < 0) - goto fail; - for (i = 0; i < old_outputs_n; i++) - output_destroy(old_outputs + i); - free(old_outputs), old_outputs = NULL, old_outputs_n = 0; - - /* Reapply gamma ramps */ - reapply_gamma(); - - return 0; - - fail: - saved_errno = errno; - for (i = 0; i < old_outputs_n; i++) - output_destroy(old_outputs + i); - free(old_outputs); - errno = saved_errno; - return -1; -} - diff --git a/src/servers/crtc.h b/src/servers/crtc.h deleted file mode 100644 index 68239d4..0000000 --- a/src/servers/crtc.h +++ /dev/null @@ -1,100 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERVERS_CRTC_H -#define SERVERS_CRTC_H - - -#include "../types/output.h" - -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Handle a ‘Command: enumerate-crtcs’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -GCC_ONLY(__attribute__((nonnull))) -int handle_enumerate_crtcs(size_t conn, const char* restrict message_id); - -/** - * Get the name of a CRTC - * - * @param info Information about the CRTC - * @param crtc libgamma's state for the CRTC - * @return The name of the CRTC, `NULL` on error - */ -GCC_ONLY(__attribute__((nonnull))) -char* get_crtc_name(const libgamma_crtc_information_t* restrict info, - const libgamma_crtc_state_t* restrict crtc); - -/** - * Initialise the site - * - * @return Zero on success, -1 on error - */ -int initialise_site(void); - -/** - * Get partitions and CRTC:s - * - * @return Zero on success, -1 on error - */ -int initialise_crtcs(void); - -/** - * Merge the new state with an old state - * - * @param old_outputs The old `outputs` - * @param old_outputs_n The old `outputs_n` - * @return Zero on success, -1 on error - */ -int merge_state(struct output* restrict old_outputs, size_t old_outputs_n); - - -/** - * Disconnect from the site - * - * @return Zero on success, -1 on error - */ -int disconnect(void); - -/** - * Reconnect to the site - * - * @return Zero on success, -1 on error - */ -int reconnect(void); - - -#endif - diff --git a/src/servers/gamma.c b/src/servers/gamma.c deleted file mode 100644 index 17105d4..0000000 --- a/src/servers/gamma.c +++ /dev/null @@ -1,398 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "gamma.h" -#include "crtc.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" - -#include -#include - - - -#if defined(__clang__) -# pragma GCC diagnostic ignored "-Wswitch-enum" -#endif - - - -/** - * Handle a ‘Command: set-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc) -{ - struct output* restrict output; - char* restrict buf; - char depth[3]; - const char* supported; - const char* colourspace; - char gamut[8 * sizeof("White x: 1023")]; - size_t n; - - if (crtc == NULL) return send_error("protocol error: 'CRTC' header omitted"); - - output = output_find_by_name(crtc, outputs, outputs_n); - if (output == NULL) - return send_error("selected CRTC does not exist"); - - switch (output->depth) - { - case -2: strcpy(depth, "d"); break; - case -1: strcpy(depth, "f"); break; - default: - sprintf(depth, "%i", output->depth); - break; - } - - switch (output->supported) - { - case LIBGAMMA_YES: supported = "yes"; break; - case LIBGAMMA_NO: supported = "no"; break; - default: supported = "maybe"; break; - } - - switch (output->colourspace) - { - case COLOURSPACE_SRGB_SANS_GAMUT: - case COLOURSPACE_SRGB: colourspace = "Colour space: sRGB\n"; break; - case COLOURSPACE_RGB_SANS_GAMUT: - case COLOURSPACE_RGB: colourspace = "Colour space: RGB\n"; break; - case COLOURSPACE_NON_RGB: colourspace = "Colour space: non-RGB\n"; break; - case COLOURSPACE_GREY: colourspace = "Colour space: grey\n"; break; - default: colourspace = ""; break; - } - - switch (output->colourspace) - { - case COLOURSPACE_SRGB: - case COLOURSPACE_RGB: - sprintf(gamut, - "Red x: %u\n" - "Red y: %u\n" - "Green x: %u\n" - "Green y: %u\n" - "Blue x: %u\n" - "Blue y: %u\n" - "White x: %u\n" - "White y: %u\n", - output->red_x, output->red_y, output->green_x, output->green_y, - output->blue_x, output->blue_y, output->white_x, output->white_y); - break; - default: - *gamut = '\0'; - break; - } - - MAKE_MESSAGE(&buf, &n, 0, - "In response to: %s\n" - "Cooperative: yes\n" /* In mds: say ‘no’, mds-coopgamma changes to ‘yes’.” */ - "Depth: %s\n" - "Red size: %zu\n" - "Green size: %zu\n" - "Blue size: %zu\n" - "Gamma support: %s\n" - "%s%s" - "\n", - message_id, depth, output->red_size, output->green_size, - output->blue_size, supported, gamut, colourspace); - - return send_message(conn, buf, n); -} - - -/** - * Set the gamma ramps on an output - * - * @param output The output - * @param ramps The gamma ramps - */ -void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps) -{ - int r = 0; - - if (!connected) - return; - - switch (output->depth) - { - case 8: r = libgamma_crtc_set_gamma_ramps8(output->crtc, ramps->u8); break; - case 16: r = libgamma_crtc_set_gamma_ramps16(output->crtc, ramps->u16); break; - case 32: r = libgamma_crtc_set_gamma_ramps32(output->crtc, ramps->u32); break; - case 64: r = libgamma_crtc_set_gamma_ramps64(output->crtc, ramps->u64); break; - case -1: r = libgamma_crtc_set_gamma_rampsf(output->crtc, ramps->f); break; - case -2: r = libgamma_crtc_set_gamma_rampsd(output->crtc, ramps->d); break; - default: - abort(); - } - if (r) - libgamma_perror(argv0, r); /* Not fatal */ -} - - -/** - * Parse the EDID of a monitor - * - * @param output The output - * @param edid The EDID in binary format - * @param n The length of the EDID - */ -static void parse_edid(struct output* restrict output, const unsigned char* restrict edid, size_t n) -{ - size_t i; - int analogue; - unsigned sum; - - output->red_x = output->green_x = output->blue_x = output->white_x = 0; - output->red_y = output->green_y = output->blue_y = output->white_y = 0; - output->colourspace = COLOURSPACE_UNKNOWN; - - if ((edid == NULL) || (n < 128)) - return; - for (i = 0, sum = 0; i < 128; i++) - sum += (unsigned)edid[i]; - if ((sum & 0xFF) != 0) - return; - if ((edid[0] != 0) || (edid[7] != 0)) - return; - for (i = 1; i < 7; i++) - if (edid[i] != 0xFF) - return; - - analogue = !(edid[20] & 0x80); - if (!analogue) - output->colourspace = COLOURSPACE_RGB; - else - switch ((edid[24] >> 3) & 3) - { - case 0: output->colourspace = COLOURSPACE_GREY; break; - case 1: output->colourspace = COLOURSPACE_RGB; break; - case 2: output->colourspace = COLOURSPACE_NON_RGB; break; - default: output->colourspace = COLOURSPACE_UNKNOWN; break; - } - - if (output->colourspace != COLOURSPACE_RGB) - return; - - if (edid[24] & 4) - output->colourspace = COLOURSPACE_SRGB; - - output->red_x = (edid[25] >> 6) & 3; - output->red_y = (edid[25] >> 4) & 3; - output->green_x = (edid[25] >> 2) & 3; - output->green_y = (edid[25] >> 0) & 3; - output->blue_x = (edid[26] >> 6) & 3; - output->blue_y = (edid[26] >> 4) & 3; - output->white_x = (edid[26] >> 2) & 3; - output->white_y = (edid[26] >> 0) & 3; - - output->red_x |= ((unsigned)(edid[27])) << 2; - output->red_y |= ((unsigned)(edid[28])) << 2; - output->green_x |= ((unsigned)(edid[29])) << 2; - output->green_y |= ((unsigned)(edid[30])) << 2; - output->blue_x |= ((unsigned)(edid[31])) << 2; - output->blue_y |= ((unsigned)(edid[32])) << 2; - output->white_x |= ((unsigned)(edid[33])) << 2; - output->white_y |= ((unsigned)(edid[34])) << 2; - - if ((output->red_x == output->red_y) && - (output->red_x == output->green_x) && - (output->red_x == output->green_y) && - (output->red_x == output->blue_x) && - (output->red_x == output->blue_y) && - (output->red_x == output->white_x) && - (output->red_x == output->white_y)) - { - if (output->colourspace == COLOURSPACE_SRGB) - output->colourspace = COLOURSPACE_SRGB_SANS_GAMUT; - else - output->colourspace = COLOURSPACE_RGB_SANS_GAMUT; - } -} - - -/** - * Store all current gamma ramps - * - * @return Zero on success, -1 on error - */ -int initialise_gamma_info(void) -{ - libgamma_crtc_information_t info; - int saved_errno; - size_t i; - - for (i = 0; i < outputs_n; i++) - { - libgamma_get_crtc_information(&info, crtcs + i, - LIBGAMMA_CRTC_INFO_EDID | - LIBGAMMA_CRTC_INFO_MACRO_RAMP | - LIBGAMMA_CRTC_INFO_GAMMA_SUPPORT | - LIBGAMMA_CRTC_INFO_CONNECTOR_NAME); - outputs[i].depth = info.gamma_depth_error ? 0 : info.gamma_depth; - outputs[i].red_size = info.gamma_size_error ? 0 : info.red_gamma_size; - outputs[i].green_size = info.gamma_size_error ? 0 : info.green_gamma_size; - outputs[i].blue_size = info.gamma_size_error ? 0 : info.blue_gamma_size; - outputs[i].supported = info.gamma_support_error ? 0 : info.gamma_support; - if (info.gamma_support_error == LIBGAMMA_CRTC_INFO_NOT_SUPPORTED) - outputs[i].supported = LIBGAMMA_MAYBE; - if (outputs[i].depth == 0 || outputs[i].red_size == 0 || - outputs[i].green_size == 0 || outputs[i].blue_size == 0) - outputs[i].supported = 0; - parse_edid(outputs + i, info.edid_error ? NULL : info.edid, info.edid_error ? 0 : info.edid_length); - outputs[i].name = get_crtc_name(&info, crtcs + i); - saved_errno = errno; - outputs[i].name_is_edid = ((info.edid_error == 0) && (info.edid != NULL)); - outputs[i].crtc = crtcs + i; - libgamma_crtc_information_destroy(&info); - outputs[i].ramps_size = outputs[i].red_size + outputs[i].green_size + outputs[i].blue_size; - switch (outputs[i].depth) - { - default: - outputs[i].depth = 64; - /* Fall through */ - case 8: - case 16: - case 32: - case 64: outputs[i].ramps_size *= (size_t)(outputs[i].depth / 8); break; - case -2: outputs[i].ramps_size *= sizeof(double); break; - case -1: outputs[i].ramps_size *= sizeof(float); break; - } - errno = saved_errno; - if (outputs[i].name == NULL) - return -1; - } - - return 0; -} - - -/** - * Store all current gamma ramps - */ -void store_gamma(void) -{ - int gerror; - size_t i; - -#define LOAD_RAMPS(SUFFIX, MEMBER) \ - do \ - { \ - libgamma_gamma_ramps##SUFFIX##_initialise(&(outputs[i].saved_ramps.MEMBER)); \ - gerror = libgamma_crtc_get_gamma_ramps##SUFFIX(outputs[i].crtc, &(outputs[i].saved_ramps.MEMBER)); \ - if (gerror) \ - { \ - libgamma_perror(argv0, gerror); \ - outputs[i].supported = LIBGAMMA_NO; \ - libgamma_gamma_ramps##SUFFIX##_destroy(&(outputs[i].saved_ramps.MEMBER)); \ - memset(&(outputs[i].saved_ramps.MEMBER), 0, sizeof(outputs[i].saved_ramps.MEMBER)); \ - } \ - } \ - while (0) - - for (i = 0; i < outputs_n; i++) - { - if (outputs[i].supported == LIBGAMMA_NO) - continue; - - outputs[i].saved_ramps.u8.red_size = outputs[i].red_size; - outputs[i].saved_ramps.u8.green_size = outputs[i].green_size; - outputs[i].saved_ramps.u8.blue_size = outputs[i].blue_size; - - switch (outputs[i].depth) - { - case 64: LOAD_RAMPS(64, u64); break; - case 32: LOAD_RAMPS(32, u32); break; - case 16: LOAD_RAMPS(16, u16); break; - case 8: LOAD_RAMPS( 8, u8); break; - case -2: LOAD_RAMPS(d, d); break; - case -1: LOAD_RAMPS(f, f); break; - default: /* impossible */ break; - } - } -} - - -/** - * Restore all gamma ramps - */ -void restore_gamma(void) -{ - size_t i; - int gerror; - -#define RESTORE_RAMPS(SUFFIX, MEMBER) \ - do \ - if (outputs[i].saved_ramps.MEMBER.red != NULL) \ - { \ - gerror = libgamma_crtc_set_gamma_ramps##SUFFIX(outputs[i].crtc, outputs[i].saved_ramps.MEMBER); \ - if (gerror) \ - libgamma_perror(argv0, gerror); \ - } \ - while (0) - - for (i = 0; i < outputs_n; i++) - { - if (outputs[i].supported == LIBGAMMA_NO) - continue; - if (outputs[i].saved_ramps.u8.red == NULL) - continue; - - switch (outputs[i].depth) - { - case 64: RESTORE_RAMPS(64, u64); break; - case 32: RESTORE_RAMPS(32, u32); break; - case 16: RESTORE_RAMPS(16, u16); break; - case 8: RESTORE_RAMPS( 8, u8); break; - case -2: RESTORE_RAMPS(d, d); break; - case -1: RESTORE_RAMPS(f, f); break; - default: /* impossible */ break; - } - } -} - - -/** - * Reapplu all gamma ramps - */ -void reapply_gamma(void) -{ - union gamma_ramps plain; - size_t i; - - /* Reapply gamma ramps */ - for (i = 0; i < outputs_n; i++) - { - struct output* output = outputs + i; - if (output->table_size > 0) - set_gamma(output, output->table_sums + output->table_size - 1); - else - { - make_plain_ramps(&plain, output); - set_gamma(output, &plain); - libgamma_gamma_ramps8_destroy(&(plain.u8)); - } - } -} - diff --git a/src/servers/gamma.h b/src/servers/gamma.h deleted file mode 100644 index ac269e0..0000000 --- a/src/servers/gamma.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERVERS_GAMMA_H -#define SERVERS_GAMMA_H - - -#include "../types/output.h" - -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Handle a ‘Command: set-gamma’ message - * - * @param conn The index of the connection - * @param message_id The value of the ‘Message ID’ header - * @param crtc The value of the ‘CRTC’ header - * @return Zero on success (even if ignored), -1 on error, - * 1 if connection closed - */ -GCC_ONLY(__attribute__((nonnull(2)))) -int handle_get_gamma_info(size_t conn, const char* restrict message_id, const char* restrict crtc); - -/** - * Set the gamma ramps on an output - * - * @param output The output - * @param ramps The gamma ramps - */ -GCC_ONLY(__attribute__((nonnull))) -void set_gamma(const struct output* restrict output, const union gamma_ramps* restrict ramps); - - -/** - * Store all current gamma ramps - * - * @return Zero on success, -1 on error - */ -int initialise_gamma_info(void); - -/** - * Store all current gamma ramps - */ -void store_gamma(void); - -/** - * Restore all gamma ramps - */ -void restore_gamma(void); - - -/** - * Reapplu all gamma ramps - */ -void reapply_gamma(void); - - -#endif - diff --git a/src/servers/kernel.c b/src/servers/kernel.c deleted file mode 100644 index 1600d0a..0000000 --- a/src/servers/kernel.c +++ /dev/null @@ -1,384 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "kernel.h" -#include "../state.h" -#include "../util.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * Get the pathname of the runtime file - * - * @param suffix The suffix for the file - * @return The pathname of the file, `NULL` on error - */ -GCC_ONLY(__attribute__((malloc, nonnull))) -static char* get_pathname(const char* restrict suffix) -{ - const char* restrict rundir = getenv("XDG_RUNTIME_DIR"); - const char* restrict username = ""; - char* name = NULL; - char* p; - char* restrict rc; - struct passwd* restrict pw; - size_t n; - int saved_errno; - - if (sitename) - { - name = memdup(sitename, strlen(sitename) + 1); - if (name == NULL) - goto fail; - } - else if ((name = libgamma_method_default_site(method))) - { - name = memdup(name, strlen(name) + 1); - if (name == NULL) - goto fail; - } - - if (name != NULL) - switch (method) - { - case LIBGAMMA_METHOD_X_RANDR: - case LIBGAMMA_METHOD_X_VIDMODE: - if ((p = strrchr(name, ':'))) - if ((p = strchr(p, '.'))) - *p = '\0'; - break; - default: - break; - } - - if (!rundir || !*rundir) - rundir = "/tmp"; - - if ((pw = getpwuid(getuid()))) - username = pw->pw_name ? pw->pw_name : ""; - - n = sizeof("/.coopgammad/~/.") + 3 * sizeof(int); - n += strlen(rundir) + strlen(username) + ((name != NULL) ? strlen(name) : 0) + strlen(suffix); - if (!(rc = malloc(n))) - goto fail; - sprintf(rc, "%s/.coopgammad/~%s/%i%s%s%s", - rundir, username, method, name ? "." : "", name ? name : "", suffix); - free(name); - return rc; - - fail: - saved_errno = errno; - free(name); - errno = saved_errno; - return NULL; -} - - -/** - * Get the pathname of the socket - * - * @return The pathname of the socket, `NULL` on error - */ -char* get_socket_pathname(void) -{ - return get_pathname(".socket"); -} - - -/** - * Get the pathname of the PID file - * - * @return The pathname of the PID file, `NULL` on error - */ -char* get_pidfile_pathname(void) -{ - return get_pathname(".pid"); -} - - -/** - * Get the pathname of the state file - * - * @return The pathname of the state file, `NULL` on error - */ -char* get_state_pathname(void) -{ - return get_pathname(".state"); -} - - -/** - * Check whether a PID file is outdated - * - * @param pidpath The PID file - * @param token An environment variable (including both key and value) - * that must exist in the process if it is a coopgammad process - * @return -1: An error occurred - * 0: The service is already running - * 1: The PID file is outdated - */ -GCC_ONLY(__attribute__((nonnull))) -static int is_pidfile_reusable(const char* restrict pidpath, const char* restrict token) -{ - /* PORTERS: /proc/$PID/environ is Linux specific */ - - char temp[sizeof("/proc//environ") + 3 * sizeof(long long int)]; - int fd = -1, saved_errno, tries = 0; - char* content = NULL; - char* p; - pid_t pid = 0; - size_t n; -#if defined(HAVE_LINUX_PROCFS) - char* end; -#else - (void) token; -#endif - - /* Get PID */ - retry: - fd = open(pidpath, O_RDONLY); - if (fd < 0) - return -1; - content = nread(fd, &n); - if (content == NULL) - goto fail; - close(fd), fd = -1; - - if (n == 0) - { - if (++tries > 1) - goto bad; - msleep(100); /* 1 tenth of a second */ - goto retry; - } - - if (('0' > content[0]) || (content[0] > '9')) - goto bad; - if ((content[0] == '0') && ('0' <= content[1]) && (content[1] <= '9')) - goto bad; - for (p = content; *p; p++) - if (('0' <= *p) && (*p <= '9')) - pid = pid * 10 + (*p & 15); - else - break; - if (*p++ != '\n') - goto bad; - if (*p) - goto bad; - if ((size_t)(p - content) != n) - goto bad; - sprintf(temp, "%llu\n", (unsigned long long)pid); - if (strcmp(content, temp)) - goto bad; - free(content); - - /* Validate PID */ -#if defined(HAVE_LINUX_PROCFS) - sprintf(temp, "/proc/%llu/environ", (unsigned long long)pid); - fd = open(temp, O_RDONLY); - if (fd < 0) - return ((errno == ENOENT) || (errno == EACCES)) ? 1 : -1; - content = nread(fd, &n); - if (content == NULL) - goto fail; - close(fd), fd = -1; - - for (end = (p = content) + n; p != end; p = strchr(p, '\0') + 1) - if (!strcmp(p, token)) - return free(content), 0; - free(content); -#else - if ((kill(pid, 0) == 0) || (errno == EINVAL)) - return 0; -#endif - - return 1; - bad: - fprintf(stderr, "%s: pid file contains invalid content: %s\n", argv0, pidpath); - errno = 0; - return -1; - fail: - saved_errno = errno; - free(content); - if (fd >= 0) - close(fd); - errno = saved_errno; - return -1; -} - - -/** - * Create PID file - * - * @param pidpath The pathname of the PID file - * @return Zero on success, -1 on error, - * -2 if the service is already running - */ -int create_pidfile(char* pidpath) -{ - int fd = -1, r, saved_errno; - char* p; - char* restrict token = NULL; - - /* Create token used to validate the service. */ - token = malloc(sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + strlen(pidpath)); - if (token == NULL) - return -1; - sprintf(token, "COOPGAMMAD_PIDFILE_TOKEN=%s", pidpath); -#if !defined(USE_VALGRIND) - if (putenv(token)) - goto putenv_fail; - /* `token` must not be free! */ -#else - { - static char static_token[sizeof("COOPGAMMAD_PIDFILE_TOKEN=") + PATH_MAX]; - if (strlen(pidpath) > PATH_MAX) - abort(); - strcpy(static_token, token); - if (putenv(static_token)) - goto fail; - } -#endif - - /* Create PID file's directory. */ - for (p = pidpath; *p == '/'; p++); - while ((p = strchr(p, '/'))) - { - *p = '\0'; - if (mkdir(pidpath, 0755) < 0) - if (errno != EEXIST) - { - *p = '/'; - goto fail; - } - *p++ = '/'; - } - - /* Create PID file. */ - retry: - fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644); - if (fd < 0) - { - if (errno == EINTR) - goto retry; - if (errno != EEXIST) - return -1; - r = is_pidfile_reusable(pidpath, token); - if (r > 0) - { - unlink(pidpath); - goto retry; - } - else if (r < 0) - goto fail; - fprintf(stderr, "%s: service is already running\n", argv0); - errno = 0; - return -2; - } - - /* Write PID to PID file. */ - if (dprintf(fd, "%llu\n", (unsigned long long)getpid()) < 0) - goto fail; - - /* Done */ -#if defined(USE_VALGRIND) - free(token); -#endif - if (close(fd) < 0) - if (errno != EINTR) - return -1; - return 0; -#if !defined(USE_VALGRIND) - putenv_fail: - saved_errno = errno; - free(token); - errno = saved_errno; -#endif - fail: - saved_errno = errno; -#if defined(USE_VALGRIND) - free(token); -#endif - if (fd >= 0) - { - close(fd); - unlink(pidpath); - } - errno = saved_errno; - return -1; -} - - -/** - * Create socket and start listening - * - * @param socketpath The pathname of the socket - * @return Zero on success, -1 on error - */ -int create_socket(const char* socketpath) -{ - struct sockaddr_un address; - - address.sun_family = AF_UNIX; - if (strlen(socketpath) >= sizeof(address.sun_path)) - { - errno = ENAMETOOLONG; - return -1; - } - strcpy(address.sun_path, socketpath); - unlink(socketpath); - if ((socketfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) - return -1; - if (fchmod(socketfd, S_IRWXU) < 0) - return -1; - if (bind(socketfd, (struct sockaddr*)(&address), (socklen_t)sizeof(address)) < 0) - return -1; - if (listen(socketfd, SOMAXCONN) < 0) - return -1; - - return 0; -} - - -/** - * Close and unlink the socket - * - * @param socketpath The pathname of the socket - */ -void close_socket(const char* socketpath) -{ - if (socketfd >= 0) - { - shutdown(socketfd, SHUT_RDWR); - close(socketfd); - unlink(socketpath); - } -} - diff --git a/src/servers/kernel.h b/src/servers/kernel.h deleted file mode 100644 index 494e150..0000000 --- a/src/servers/kernel.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERVERS_KERNEL_H -#define SERVERS_KERNEL_H - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Get the pathname of the socket - * - * @return The pathname of the socket, `NULL` on error - */ -GCC_ONLY(__attribute__((malloc))) -char* get_socket_pathname(void); - -/** - * Get the pathname of the PID file - * - * @return The pathname of the PID file, `NULL` on error - */ -GCC_ONLY(__attribute__((malloc))) -char* get_pidfile_pathname(void); - -/** - * Get the pathname of the state file - * - * @return The pathname of the state file, `NULL` on error - */ -GCC_ONLY(__attribute__((malloc))) -char* get_state_pathname(void); - -/** - * Create PID file - * - * @param pidpath The pathname of the PID file - * @return Zero on success, -1 on error, - * -2 if the service is already running - */ -GCC_ONLY(__attribute__((nonnull))) -int create_pidfile(char* pidpath); - -/** - * Create socket and start listening - * - * @param socketpath The pathname of the socket - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -int create_socket(const char* socketpath); - -/** - * Close and unlink the socket - * - * @param socketpath The pathname of the socket - */ -GCC_ONLY(__attribute__((nonnull))) -void close_socket(const char* socketpath); - - -#endif - diff --git a/src/servers/master.c b/src/servers/master.c deleted file mode 100644 index 01b9bb5..0000000 --- a/src/servers/master.c +++ /dev/null @@ -1,394 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "master.h" -#include "crtc.h" -#include "gamma.h" -#include "coopgamma.h" -#include "../util.h" -#include "../communication.h" -#include "../state.h" - -#include -#include -#include -#include -#include -#include -#include -#include - - - -/** - * All poll(3p) events that are not for writing - */ -#define NON_WR_POLL_EVENTS (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP | POLLNVAL) - - -/** - * Extract headers from an inbound message and pass - * them on to appropriate message handling function - * - * @param conn The index of the connection - * @param msg The inbound message - * @return 1: The connection as closed - * 0: Successful - * -1: Failure - */ -static int dispatch_message(size_t conn, struct message* restrict msg) -{ - size_t i; - int r = 0; - const char* header; - const char* value; - const char* command = NULL; - const char* crtc = NULL; - const char* coalesce = NULL; - const char* high_priority = NULL; - const char* low_priority = NULL; - const char* priority = NULL; - const char* class = NULL; - const char* lifespan = NULL; - const char* message_id = NULL; - - for (i = 0; i < msg->header_count; i++) - { - value = strstr((header = msg->headers[i]), ": ") + 2; - if (strstr(header, "Command: ") == header) command = value; - else if (strstr(header, "CRTC: ") == header) crtc = value; - else if (strstr(header, "Coalesce: ") == header) coalesce = value; - else if (strstr(header, "High priority: ") == header) high_priority = value; - else if (strstr(header, "Low priority: ") == header) low_priority = value; - else if (strstr(header, "Priority: ") == header) priority = value; - else if (strstr(header, "Class: ") == header) class = value; - else if (strstr(header, "Lifespan: ") == header) lifespan = value; - else if (strstr(header, "Message ID: ") == header) message_id = value; - else if (strstr(header, "Length: ") == header) ;/* Handled transparently */ - else - fprintf(stderr, "%s: ignoring unrecognised header: %s\n", argv0, header); - } - - if (command == NULL) - fprintf(stderr, "%s: ignoring message without Command header\n", argv0); - else if (message_id == NULL) - fprintf(stderr, "%s: ignoring message without Message ID header\n", argv0); - else if (!strcmp(command, "enumerate-crtcs")) - { - if (crtc || coalesce || high_priority || low_priority || priority || class || lifespan) - fprintf(stderr, "%s: ignoring superfluous headers in Command: enumerate-crtcs message\n", argv0); - r = handle_enumerate_crtcs(conn, message_id); - } - else if (!strcmp(command, "get-gamma-info")) - { - if (coalesce || high_priority || low_priority || priority || class || lifespan) - fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma-info message\n", argv0); - r = handle_get_gamma_info(conn, message_id, crtc); - } - else if (!strcmp(command, "get-gamma")) - { - if (priority || class || lifespan) - fprintf(stderr, "%s: ignoring superfluous headers in Command: get-gamma message\n", argv0); - r = handle_get_gamma(conn, message_id, crtc, coalesce, high_priority, low_priority); - } - else if (!strcmp(command, "set-gamma")) - { - if (coalesce || high_priority || low_priority) - fprintf(stderr, "%s: ignoring superfluous headers in Command: set-gamma message\n", argv0); - r = handle_set_gamma(conn, message_id, crtc, priority, class, lifespan); - } - else - fprintf(stderr, "%s: ignoring unrecognised command: Command: %s\n", argv0, command); - - return r; -} - - -/** - * Sets the file descriptor set that includes - * the server socket and all connections - * - * The file descriptor will be ordered as in - * the array `connections`, `socketfd` will - * be last. - * - * @param fds Reference parameter for the array of file descriptors - * @param fdn Output parameter for the number of file descriptors - * @param fds_alloc Reference parameter for the allocation size of `fds`, in elements - * @return Zero on success, -1 on error - */ -static int update_fdset(struct pollfd** restrict fds, nfds_t* restrict fdn, nfds_t* restrict fds_alloc) -{ - size_t i; - nfds_t j = 0; - - if (connections_used + 1 > *fds_alloc) - { - void* new = realloc(*fds, (connections_used + 1) * sizeof(**fds)); - if (new == NULL) - return -1; - *fds = new; - *fds_alloc = connections_used + 1; - } - - for (i = 0; i < connections_used; i++) - if (connections[i] >= 0) - { - (*fds)[j].fd = connections[i]; - (*fds)[j].events = NON_WR_POLL_EVENTS; - j++; - } - - (*fds)[j].fd = socketfd; - (*fds)[j].events = NON_WR_POLL_EVENTS; - j++; - - *fdn = j; - return 0; -} - - -/** - * Handle event on the server socket - * - * @return 1: New connection accepted - * 0: Successful - * -1: Failure - */ -static int handle_server(void) -{ - int fd, flags, saved_errno; - - fd = accept(socketfd, NULL, NULL); - if (fd < 0) - switch (errno) - { - case EINTR: - return 0; - case ECONNABORTED: - case EINVAL: - terminate = 1; - return 0; - default: - return -1; - } - - flags = fcntl(fd, F_GETFL); - if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) - goto fail; - - if (connections_ptr == connections_alloc) - { - void* new; - - new = realloc(connections, (connections_alloc + 10) * sizeof(*connections)); - if (new == NULL) - goto fail; - connections = new; - connections[connections_ptr] = fd; - - new = realloc(outbound, (connections_alloc + 10) * sizeof(*outbound)); - if (new == NULL) - goto fail; - outbound = new; - ring_initialise(outbound + connections_ptr); - - new = realloc(inbound, (connections_alloc + 10) * sizeof(*inbound)); - if (new == NULL) - goto fail; - inbound = new; - connections_alloc += 10; - if (message_initialise(inbound + connections_ptr)) - goto fail; - } - else - { - connections[connections_ptr] = fd; - ring_initialise(outbound + connections_ptr); - if (message_initialise(inbound + connections_ptr)) - goto fail; - } - - connections_ptr++; - while ((connections_ptr < connections_used) && (connections[connections_ptr] >= 0)) - connections_ptr++; - if (connections_used < connections_ptr) - connections_used = connections_ptr; - - return 1; - fail: - saved_errno = errno; - shutdown(fd, SHUT_RDWR); - close(fd); - errno = saved_errno; - return -1; -} - - -/** - * Handle event on a connection to a client - * - * @param conn The index of the connection - * @return 1: The connection as closed - * 0: Successful - * -1: Failure - */ -static int handle_connection(size_t conn) -{ - struct message* restrict msg = inbound + conn; - int r, fd = connections[conn]; - - again: - switch (message_read(msg, fd)) - { - default: - break; - case -1: - switch (errno) - { - case EINTR: - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif - return 0; - default: - return -1; - case ECONNRESET:; - /* Fall throught to `case -2` in outer switch */ - } - case -2: - shutdown(fd, SHUT_RDWR); - close(fd); - connections[conn] = -1; - if (conn < connections_ptr) - connections_ptr = conn; - while ((connections_used > 0) && (connections[connections_used - 1] < 0)) - connections_used -= 1; - message_destroy(msg); - ring_destroy(outbound + conn); - if (connection_closed(fd) < 0) - return -1; - return 1; - } - - if ((r = dispatch_message(conn, msg))) - return r; - - goto again; -} - - - -/** - * Disconnect all clients - */ -void disconnect_all(void) -{ - size_t i; - for (i = 0; i < connections_used; i++) - if (connections[i] >= 0) - { - shutdown(connections[i], SHUT_RDWR); - close(connections[i]); - } -} - - -/** - * The program's main loop - * - * @return Zero on success, -1 on error - */ -int main_loop(void) -{ - struct pollfd* fds = NULL; - nfds_t i, fdn = 0, fds_alloc = 0; - int r, update, saved_errno; - size_t j; - - if (update_fdset(&fds, &fdn, &fds_alloc) < 0) - goto fail; - - while (!reexec && !terminate) - { - if (connection) - { - if ((connection == 1 ? disconnect() : reconnect()) < 0) - { - connection = 0; - goto fail; - } - connection = 0; - } - - for (j = 0, i = 0; j < connections_used; j++) - if (connections[j] >= 0) - { - fds[i].revents = 0; - if (ring_have_more(outbound + j)) - fds[(size_t)i++ + j].events |= POLLOUT; - else - fds[(size_t)i++ + j].events &= ~POLLOUT; - } - fds[i].revents = 0; - - if (poll(fds, fdn, -1) < 0) - { - if (errno == EAGAIN) - perror(argv0); - else if (errno != EINTR) - goto fail; - } - - update = 0; - for (i = 0; i < fdn; i++) - { - int do_read = fds[i].revents & NON_WR_POLL_EVENTS; - int do_write = fds[i].revents & POLLOUT; - int fd = fds[i].fd; - if (!do_read && !do_write) - continue; - - if (fd == socketfd) - r = handle_server(); - else - { - for (j = 0; connections[j] != fd; j++); - r = do_read ? handle_connection(j) : 0; - } - - if ((r >= 0) && do_write) - r |= continue_send(j); - if (r < 0) - goto fail; - update |= (r > 0); - } - if (update) - if (update_fdset(&fds, &fdn, &fds_alloc) < 0) - goto fail; - } - - free(fds); - return 0; - fail: - saved_errno = errno; - free(fds); - errno = saved_errno; - return -1; -} - diff --git a/src/servers/master.h b/src/servers/master.h deleted file mode 100644 index d7ef6e5..0000000 --- a/src/servers/master.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef SERVERS_MASTER_H -#define SERVERS_MASTER_H - - -/** - * Disconnect all clients - */ -void disconnect_all(void); - -/** - * The program's main loop - * - * @return Zero on success, -1 on error - */ -int main_loop(void); - - -#endif - diff --git a/src/state.c b/src/state.c deleted file mode 100644 index ff4f0e9..0000000 --- a/src/state.c +++ /dev/null @@ -1,638 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "state.h" -#include "util.h" - -#include -#include -#include -#include - - - -/** - * The name of the process - */ -char* restrict argv0; /* do not marshal */ - -/** - * The real pathname of the process's binary, - * `NULL` if `argv0` is satisfactory - */ -char* restrict argv0_real = NULL; - -/** - * Array of all outputs - */ -struct output* restrict outputs = NULL; - -/** - * The nubmer of elements in `outputs` - */ -size_t outputs_n = 0; - -/** - * The server socket's file descriptor - */ -int socketfd = -1; - -/** - * Has the process receive a signal - * telling it to re-execute? - */ -volatile sig_atomic_t reexec = 0; /* do not marshal */ - -/** - * Has the process receive a signal - * telling it to terminate? - */ -volatile sig_atomic_t terminate = 0; /* do not marshal */ - -/** - * Has the process receive a to - * disconnect from or reconnect to - * the site? 1 if disconnect, 2 if - * reconnect, 0 otherwise. - */ -volatile sig_atomic_t connection = 0; - -/** - * List of all client's file descriptors - * - * Unused slots, with index less than `connections_used`, - * should have the value -1 (negative) - */ -int* restrict connections = NULL; - -/** - * The number of elements allocated for `connections` - */ -size_t connections_alloc = 0; - -/** - * The index of the first unused slot in `connections` - */ -size_t connections_ptr = 0; - -/** - * The index of the last used slot in `connections`, plus 1 - */ -size_t connections_used = 0; - -/** - * The clients' connections' inbound-message buffers - */ -struct message* restrict inbound = NULL; - -/** - * The clients' connections' outbound-message buffers - */ -struct ring* restrict outbound = NULL; - -/** - * Is the server connect to the display? - * - * Set to true before the initial connection - */ -int connected = 1; - -/** - * The adjustment method, -1 for automatic - */ -int method = -1; - -/** - * The site's name, may be `NULL` - */ -char* restrict sitename = NULL; - -/** - * The libgamma site state - */ -libgamma_site_state_t site; /* do not marshal */ - -/** - * The libgamma partition states - */ -libgamma_partition_state_t* restrict partitions = NULL; /* do not marshal */ - -/** - * The libgamma CRTC states - */ -libgamma_crtc_state_t* restrict crtcs = NULL; /* do not marshal */ - -/** - * Preserve gamma ramps at priority 0? - */ -int preserve = 0; - - - -/** - * As part of a state dump, dump one or two gamma ramp-trios - * - * @param left The left ramps - * @param right The right ramps - * @param depth The gamma ramp type/depth - * @param have_right Print right ramps? - * @param indent Print indent - */ -static void ramps_dump(union gamma_ramps* left, union gamma_ramps* right, - signed depth, int have_right, const char* indent) -{ -#define STRINGISE(SIDE, CH, N, BUF) \ - do \ - if ((SIDE == NULL) || (SIDE->u8.CH == NULL)) \ - strcpy(BUF, "null"); \ - else if (i < N) \ - switch (depth) \ - { \ - case -2: snprintf(BUF, sizeof(BUF), "%lf", SIDE->d.CH[i]); break; \ - case -1: snprintf(BUF, sizeof(BUF), "%f", (double)(SIDE->f.CH[i])); break; \ - case 8: snprintf(BUF, sizeof(BUF), "%02" PRIx8, SIDE->u8.CH[i]); break; \ - case 16: snprintf(BUF, sizeof(BUF), "%04" PRIx16, SIDE->u16.CH[i]); break; \ - case 32: snprintf(BUF, sizeof(BUF), "%08" PRIx32, SIDE->u32.CH[i]); break; \ - case 64: snprintf(BUF, sizeof(BUF), "%16" PRIx64, SIDE->u64.CH[i]); break; \ - default: \ - strcpy(BUF, "corrupt state"); \ - break; \ - } \ - while (0) - - char lr[100], lg[100], lb[100], rr[100], rg[100], rb[100]; - size_t rn = left ? left->u8.red_size : right ? right->u8.red_size : 0; - size_t gn = left ? left->u8.green_size : right ? right->u8.green_size : 0; - size_t bn = left ? left->u8.blue_size : right ? right->u8.blue_size : 0; - size_t i, n = rn > gn ? rn : gn; - n = n > bn ? n : bn; - - for (i = 0; i < n; i++) - { - *lr = *lg = *lb = *rr = *rg = *rb = '\0'; - - STRINGISE(left, red, rn, lr); - STRINGISE(left, green, gn, lg); - STRINGISE(left, blue, bn, lb); - - if (have_right) - { - STRINGISE(right, red, rn, rr); - STRINGISE(right, green, gn, rg); - STRINGISE(right, blue, bn, rb); - } - - if (have_right) - fprintf(stderr, "%s%zu: %s, %s, %s :: %s, %s, %s\n", indent, i, lr, lg, lb, rr, rg, rb); - else - fprintf(stderr, "%s%zu: %s, %s, %s\n", indent, i, lr, lg, lb); - } -} - - -/** - * Dump the state to stderr - */ -void state_dump(void) -{ - size_t i, j; - fprintf(stderr, "argv0: %s\n", argv0 ? argv0 : "(null)"); - fprintf(stderr, "Realpath of argv0: %s\n", argv0_real ? argv0_real : "(null)"); - fprintf(stderr, "Calibrations preserved: %s\n", preserve ? "yes" : "no"); - fprintf(stderr, "Connected: %s\n", connected ? "yes" : "no"); - fprintf(stderr, "Socket FD: %i\n", socketfd); - fprintf(stderr, "Re-execution pending: %s\n", reexec ? "yes" : "no"); - fprintf(stderr, "Termination pending: %s\n", terminate ? "yes" : "no"); - if ((0 <= connection) && (connection <= 2)) - fprintf(stderr, "Pending connection change: %s\n", - connection == 0 ? "none" : connection == 1 ? "disconnect" : "reconnect"); - else - fprintf(stderr, "Pending connection change: %i (CORRUPT STATE)\n", connection); - fprintf(stderr, "Adjustment method: %i\n", method); - fprintf(stderr, "Site name: %s\n", sitename ? sitename : "(automatic)"); - fprintf(stderr, "Clients:\n"); - fprintf(stderr, " Next empty slot: %zu\n", connections_ptr); - fprintf(stderr, " Initialised slots: %zu\n", connections_used); - fprintf(stderr, " Allocated slots: %zu\n", connections_alloc); - if (connections == NULL) - fprintf(stderr, " File descriptor array is null\n"); - else - for (i = 0; i < connections_used; i++) - { - if (connections[i] < 0) - { - fprintf(stderr, " Slot %zu: empty\n", i); - continue; - } - fprintf(stderr, " Slot %zu:\n", i); - fprintf(stderr, " File descriptor: %i\n", connections[i]); - if (inbound == NULL) - fprintf(stderr, " Inbound message array is null\n"); - else - { - fprintf(stderr, " Inbound message:\n"); - fprintf(stderr, " Header array: %s\n", inbound[i].headers ? "non-null" : "null"); - fprintf(stderr, " Headers: %zu\n", inbound[i].header_count); - fprintf(stderr, " Payload buffer: %s\n", inbound[i].payload ? "non-null" : "null"); - fprintf(stderr, " Payload size: %zu\n", inbound[i].payload_size); - fprintf(stderr, " Payload write pointer: %zu\n", inbound[i].payload_ptr); - fprintf(stderr, " Message buffer: %s\n", inbound[i].buffer ? "non-null" : "null"); - fprintf(stderr, " Message buffer size: %zu\n", inbound[i].buffer_size); - fprintf(stderr, " Message buffer write pointer: %zu\n", inbound[i].buffer_ptr); - fprintf(stderr, " Read stage: %i\n", inbound[i].stage); - } - if (outbound == NULL) - fprintf(stderr, " Outbound message array is null\n"); - else - { - fprintf(stderr, " Ring buffer: %s\n", outbound[i].buffer ? "non-null" : "null"); - fprintf(stderr, " Head: %zu\n", outbound[i].end); - fprintf(stderr, " Tail: %zu\n", outbound[i].start); - fprintf(stderr, " Size: %zu\n", outbound[i].size); - } - } - fprintf(stderr, "Partition array: %s\n", partitions ? "non-null" : "null"); - fprintf(stderr, "CRTC array: %s\n", crtcs ? "non-null" : "null"); - fprintf(stderr, "Output:\n"); - fprintf(stderr, " Output count: %zu\n", outputs_n); - if (outputs == NULL) - fprintf(stderr, " Output array is null\n"); - else - for (i = 0; i < outputs_n; i++) - { - struct output* restrict out = outputs + i; - const char* str; - fprintf(stderr, " Output %zu:\n", i); - fprintf(stderr, " Depth: %i (%s)\n", out->depth, - out->depth == -1 ? "float" : - out->depth == -2 ? "double" : - out->depth == 8 ? "uint8_t" : - out->depth == 16 ? "uint16_t" : - out->depth == 32 ? "uint32_t" : - out->depth == 64 ? "uint64_t" : "CORRUPT STATE"); - fprintf(stderr, " Gamma supported: %s (%i)\n", - out->supported == LIBGAMMA_YES ? "yes" : - out->supported == LIBGAMMA_NO ? "no" : - out->supported == LIBGAMMA_MAYBE ? "maybe" : - "CORRUPT STATE", out->supported); - fprintf(stderr, " Name is EDID: %s\n", out->name_is_edid ? "yes" : "no"); - switch (out->colourspace) - { - case COLOURSPACE_UNKNOWN: str = "unknown"; break; - case COLOURSPACE_SRGB: str = "sRGB with explicit gamut"; break; - case COLOURSPACE_SRGB_SANS_GAMUT: str = "sRGB with implicit gamut (actually illegal)"; break; - case COLOURSPACE_RGB: str = "RGB other than sRGB, with unknown gamut"; break; - case COLOURSPACE_RGB_SANS_GAMUT: str = "RGB other than sRGB, with listed gamut"; break; - case COLOURSPACE_NON_RGB: str = "Non-RGB multicolour"; break; - case COLOURSPACE_GREY: str = "Monochrome or singlecolour scale"; break; - default: str = "CORRUPT STATE"; break; - } - fprintf(stderr, " Colourspace: %s (%i)\n", str, out->colourspace); - if ((out->colourspace == COLOURSPACE_SRGB) || (out->colourspace == COLOURSPACE_RGB)) - { - fprintf(stderr, " Red (x, y): (%u / 1024, %u / 1024)\n", out->red_x, out->red_y); - fprintf(stderr, " Green (x, y): (%u / 1024, %u / 1024)\n", out->green_x, out->green_y); - fprintf(stderr, " Blue (x, y): (%u / 1024, %u / 1024)\n", out->blue_x, out->blue_y); - fprintf(stderr, " White (x, y): (%u / 1024, %u / 1024)\n", out->white_x, out->white_y); - if (out->colourspace == COLOURSPACE_SRGB) - { - fprintf(stderr, " Expected red (x, y): (655 / 1024, 338 / 1024)\n"); - fprintf(stderr, " Expected green (x, y): (307 / 1024, 614 / 1024)\n"); - fprintf(stderr, " Expected blue (x, y): (154 / 1024, 61 / 1024)\n"); - fprintf(stderr, " Expected white (x, y): (320 / 1024, 337 / 1024)\n"); - } - } - if (out->supported) - { - fprintf(stderr, " Gamma ramp size:\n"); - fprintf(stderr, " Red: %zu stops\n", out->red_size); - fprintf(stderr, " Green: %zu stops\n", out->green_size); - fprintf(stderr, " Blue: %zu stops\n", out->blue_size); - fprintf(stderr, " Total: %zu bytes\n", out->ramps_size); - fprintf(stderr, " Name: %s\n", out->name ? out->name : "(null)"); - fprintf(stderr, " CRTC state: %s\n", out->crtc ? "non-null" : "null"); - fprintf(stderr, " Saved gamma ramps (stop: red, green, blue):\n"); - ramps_dump(&(out->saved_ramps), NULL, out->depth, 0, " "); - fprintf(stderr, " Filter table:\n"); - fprintf(stderr, " Filter count: %zu\n", out->table_size); - fprintf(stderr, " Slots allocated: %zu\n", out->table_alloc); - if (out->table_size > 0) - { - if (out->table_filters == NULL) - fprintf(stderr, " Filter table is null\n"); - if (out->table_sums == NULL) - fprintf(stderr, " Result table is null\n"); - } - for (j = 0; j < out->table_size; j++) - { - struct filter* restrict filter = out->table_filters ? out->table_filters + j : NULL; - fprintf(stderr, " Filter %zu:\n", j); - if (filter != NULL) - { - if (filter->lifespan == LIFESPAN_UNTIL_DEATH) - fprintf(stderr, " Client FD: %i\n", filter->client); - switch (filter->lifespan) - { - case LIFESPAN_REMOVE: str = "remove (ILLEGAL STATE)"; break; - case LIFESPAN_UNTIL_REMOVAL: str = "until-removal"; break; - case LIFESPAN_UNTIL_DEATH: str = "until-death"; break; - default: str = "CORRUPT STATE"; break; - } - fprintf(stderr, " Lifespan: %s (%i)\n", str, filter->lifespan); - fprintf(stderr, " Priority: %"PRIi64"\n", filter->priority); - fprintf(stderr, " Class: %s\n", filter->class ? filter->class : "(null)"); - str = "yes"; - if (filter->class == NULL) - str = "no, is NULL"; - else if (strchr(filter->class, '\n')) - str = "no, contains LF"; - else if (strstr(filter->class, "::") == NULL) - str = "no, does not contain \"::\""; - else if (strstr(strstr(filter->class, "::") + 2, "::") == NULL) - str = "no, contains only one \"::\""; - else if (verify_utf8(filter->class) < 0) - str = "no, not UTF-8"; - fprintf(stderr, " Class legal: %s\n", str); - if ((filter->ramps == NULL) && (filter->lifespan != LIFESPAN_REMOVE)) - fprintf(stderr, " Ramps are NULL\n"); - } - if (filter != NULL ? (filter->lifespan != LIFESPAN_REMOVE) : (out->table_sums != NULL)) - { - union gamma_ramps left; - size_t depth; - switch (out->depth) - { - case -2: depth = sizeof(double); break; - case -1: depth = sizeof(float); break; - case 8: case 16: case 32: case 64: - depth = (size_t)(out->depth) / 8; - break; - default: - goto corrupt_depth; - } - if (filter && filter->ramps) - { - left.u8.red_size = out->red_size; - left.u8.green_size = out->green_size; - left.u8.blue_size = out->blue_size; - left.u8.red = filter->ramps; - left.u8.green = left.u8.red + out->red_size * depth; - left.u8.blue = left.u8.green + out->green_size * depth; - } - fprintf(stderr," Ramps (stop: filter red, green, blue :: " - "composite red, geen, blue):\n"); - ramps_dump((filter && filter->ramps) ? &left : NULL, - out->table_sums ? out->table_sums + j : NULL, - out->depth, 1, " "); - corrupt_depth:; - } - } - } - } -} - - -/** - * Destroy the state - */ -void state_destroy(void) -{ - size_t i; - - for (i = 0; i < connections_used; i++) - if (connections[i] >= 0) - { - message_destroy(inbound + i); - ring_destroy(outbound + i); - } - free(inbound); - free(outbound); - free(connections); - - if (outputs != NULL) - for (i = 0; i < outputs_n; i++) - output_destroy(outputs + i); - free(outputs); - if (crtcs != NULL) - for (i = 0; i < outputs_n; i++) - libgamma_crtc_destroy(crtcs + i); - free(crtcs); - if (partitions != NULL) - for (i = 0; i < site.partitions_available; i++) - libgamma_partition_destroy(partitions + i); - free(partitions); - libgamma_site_destroy(&site); - - free(sitename); -} - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal the state - * - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how many bytes - * this buffer needs - * @return The number of marshalled bytes - */ -size_t state_marshal(void* restrict buf) -{ - size_t off = 0, i, n; - char* restrict bs = buf; - - if (argv0_real == NULL) - { - if (bs != NULL) - *(bs + off) = '\0'; - off += 1; - } - else - { - n = strlen(argv0_real) + 1; - if (bs != NULL) - memcpy(bs + off, argv0_real, n); - off += n; - } - - if (bs != NULL) - *(size_t*)(bs + off) = outputs_n; - off += sizeof(size_t); - - for (i = 0; i < outputs_n; i++) - off += output_marshal(outputs + i, bs ? bs + off : NULL); - - if (bs != NULL) - *(int*)(bs + off) = socketfd; - off += sizeof(int); - - if (bs != NULL) - *(sig_atomic_t*)(bs + off) = connection; - off += sizeof(sig_atomic_t); - - if (bs != NULL) - *(int*)(bs + off) = connected; - off += sizeof(int); - - if (bs != NULL) - *(size_t*)(bs + off) = connections_ptr; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = connections_used; - off += sizeof(size_t); - - if (bs != NULL) - memcpy(bs + off, connections, connections_used * sizeof(*connections)); - off += connections_used * sizeof(*connections); - - for (i = 0; i < connections_used; i++) - if (connections[i] >= 0) - { - off += message_marshal(inbound + i, bs ? bs + off : NULL); - off += ring_marshal(outbound + i, bs ? bs + off : NULL); - } - - if (bs != NULL) - *(int*)(bs + off) = method; - off += sizeof(int); - - if (bs != NULL) - *(int*)(bs + off) = sitename != NULL; - off += sizeof(int); - if (sitename != NULL) - { - n = strlen(sitename) + 1; - if (bs != NULL) - memcpy(bs + off, sitename, n); - off += n; - } - - if (bs != NULL) - *(int*)(bs + off) = preserve; - off += sizeof(int); - - return off; -} - - -/** - * Unmarshal the state - * - * @param buf Buffer for the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -size_t state_unmarshal(const void* restrict buf) -{ - size_t off = 0, i, n; - const char* restrict bs = buf; - - connections = NULL; - inbound = NULL; - - if (*(bs + off)) - { - n = strlen(bs + off) + 1; - if (!(argv0_real = memdup(bs + off, n))) - return 0; - off += n; - } - else - off += 1; - - outputs_n = *(const size_t*)(bs + off); - off += sizeof(size_t); - - outputs = calloc(outputs_n, sizeof(*outputs)); - if (outputs == NULL) - return 0; - - for (i = 0; i < outputs_n; i++) - { - off += n = output_unmarshal(outputs + i, bs + off); - if (n == 0) - return 0; - } - - socketfd = *(const int*)(bs + off); - off += sizeof(int); - - connection = *(const sig_atomic_t*)(bs + off); - off += sizeof(sig_atomic_t); - - connected = *(const int*)(bs + off); - off += sizeof(int); - - connections_ptr = *(const size_t*)(bs + off); - off += sizeof(size_t); - - connections_alloc = connections_used = *(const size_t*)(bs + off); - off += sizeof(size_t); - - if (connections_alloc > 0) - { - connections = memdup(bs + off, connections_alloc * sizeof(*connections)); - if (connections == NULL) - return 0; - off += connections_used * sizeof(*connections); - - inbound = malloc(connections_alloc * sizeof(*inbound)); - if (inbound == NULL) - return 0; - } - - for (i = 0; i < connections_used; i++) - if (connections[i] >= 0) - { - off += n = message_unmarshal(inbound + i, bs + off); - if (n == 0) - return 0; - off += n = ring_unmarshal(outbound + i, bs + off); - if (n == 0) - return 0; - } - - method = *(const int*)(bs + off); - off += sizeof(int); - - if (*(const int*)(bs + off)) - { - off += sizeof(int); - n = strlen(bs + off) + 1; - if (!(sitename = memdup(bs + off, n))) - return 0; - off += n; - } - else - off += sizeof(int); - - preserve = *(const int*)(bs + off); - off += sizeof(int); - - return off; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - diff --git a/src/state.h b/src/state.h deleted file mode 100644 index 1cbe482..0000000 --- a/src/state.h +++ /dev/null @@ -1,192 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef STATE_H -#define STATE_H - - -#include "types/message.h" -#include "types/ring.h" -#include "types/output.h" - -#include - -#include -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * The name of the process - */ -extern char* restrict argv0; - -/** - * The real pathname of the process's binary, - * `NULL` if `argv0` is satisfactory - */ -extern char* restrict argv0_real; - -/** - * Array of all outputs - */ -extern struct output* restrict outputs; - -/** - * The nubmer of elements in `outputs` - */ -extern size_t outputs_n; - -/** - * The server socket's file descriptor - */ -extern int socketfd; - -/** - * Has the process receive a signal - * telling it to re-execute? - */ -extern volatile sig_atomic_t reexec; - -/** - * Has the process receive a signal - * telling it to terminate? - */ -extern volatile sig_atomic_t terminate; - -/** - * Has the process receive a to - * disconnect from or reconnect to - * the site? 1 if disconnect, 2 if - * reconnect, 0 otherwise. - */ -extern volatile sig_atomic_t connection; - -/** - * List of all client's file descriptors - * - * Unused slots, with index less than `connections_used`, - * should have the value -1 (negative) - */ -extern int* restrict connections; - -/** - * The number of elements allocated for `connections` - */ -extern size_t connections_alloc; - -/** - * The index of the first unused slot in `connections` - */ -extern size_t connections_ptr; - -/** - * The index of the last used slot in `connections`, plus 1 - */ -extern size_t connections_used; - -/** - * The clients' connections' inbound-message buffers - */ -extern struct message* restrict inbound; - -/** - * The clients' connections' outbound-message buffers - */ -extern struct ring* restrict outbound; - -/** - * Is the server connect to the display? - * - * Set to true before the initial connection - */ -extern int connected; - -/** - * The adjustment method, -1 for automatic - */ -extern int method; - -/** - * The site's name, may be `NULL` - */ -extern char* restrict sitename; - -/** - * The libgamma site state - */ -extern libgamma_site_state_t site; - -/** - * The libgamma partition states - */ -extern libgamma_partition_state_t* restrict partitions; - -/** - * The libgamma CRTC states - */ -extern libgamma_crtc_state_t* restrict crtcs; - -/** - * Preserve gamma ramps at priority 0? - */ -extern int preserve; - - - -/** - * Dump the state to stderr - */ -void state_dump(void); - -/** - * Destroy the state - */ -void state_destroy(void); - -/** - * Marshal the state - * - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how many bytes - * this buffer needs - * @return The number of marshalled bytes - */ -size_t state_marshal(void* restrict buf); - -/** - * Unmarshal the state - * - * @param buf Buffer for the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t state_unmarshal(const void* restrict buf); - - -#endif - diff --git a/src/types/filter.c b/src/types/filter.c deleted file mode 100644 index e6facc9..0000000 --- a/src/types/filter.c +++ /dev/null @@ -1,144 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "filter.h" -#include "../util.h" - -#include -#include - - - -/** - * Free all resources allocated to a filter. - * The allocation of `filter` itself is not freed. - * - * @param this The filter - */ -void filter_destroy(struct filter* restrict this) -{ - free(this->class); - free(this->ramps); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal a filter - * - * @param this The filter - * @param buf Output buffer for the marshalled filter, - * `NULL` just measure how large the buffers - * needs to be - * @param ramps_size The byte-size of `this->ramps` - * @return The number of marshalled byte - */ -size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size) -{ - size_t off = 0, n; - char nonnulls = 0; - char* restrict bs = buf; - - if (bs != NULL) - { - if (this->class != NULL) nonnulls |= 1; - if (this->ramps != NULL) nonnulls |= 2; - *(bs + off) = nonnulls; - } - off += 1; - - if (bs != NULL) - *(int64_t*)(bs + off) = this->priority; - off += sizeof(int64_t); - - if (bs != NULL) - *(enum lifespan*)(bs + off) = this->lifespan; - off += sizeof(enum lifespan); - - if (this->class != NULL) - { - n = strlen(this->class) + 1; - if (bs != NULL) - memcpy(bs + off, this->class, n); - off += n; - } - - if (this->ramps != NULL) - { - if (bs != NULL) - memcpy(bs + off, this->ramps, ramps_size); - off += ramps_size; - } - - return off; -} - - -/** - * Unmarshal a filter - * - * @param this Output for the filter - * @param buf Buffer with the marshalled filter - * @param ramps_size The byte-size of `this->ramps` - * @return The number of unmarshalled bytes, 0 on error - */ -size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size) -{ - size_t off = 0, n; - char nonnulls = 0; - const char* restrict bs = buf; - - nonnulls = *(bs + off); - off += 1; - - this->class = NULL; - this->ramps = NULL; - - this->priority = *(const int64_t*)(bs + off); - off += sizeof(int64_t); - - this->lifespan = *(const enum lifespan*)(bs + off); - off += sizeof(enum lifespan); - - if (nonnulls & 1) - { - n = strlen(bs + off) + 1; - if (!(this->class = memdup(bs + off, n))) - goto fail; - off += n; - } - - if (nonnulls & 2) - { - if (!(this->ramps = memdup(bs + off, ramps_size))) - goto fail; - off += ramps_size; - } - - return off; - - fail: - free(this->class); - free(this->ramps); - return 0; -} - diff --git a/src/types/filter.h b/src/types/filter.h deleted file mode 100644 index c91ccb2..0000000 --- a/src/types/filter.h +++ /dev/null @@ -1,138 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TYPES_FILTER_H -#define TYPES_FILTER_H - - -#include -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * The lifespan of a filter - */ -enum lifespan -{ - /** - * The filter should be removed now - */ - LIFESPAN_REMOVE = 0, - - /** - * The filter should be applied - * until it is explicitly removed - */ - LIFESPAN_UNTIL_REMOVAL = 1, - - /** - * The filter should be applied - * until the client exists - */ - LIFESPAN_UNTIL_DEATH = 2 - -}; - - -/** - * Information about a filter - */ -struct filter -{ - /** - * The client that applied it. This need not be - * set unless `.lifespan == LIFESPAN_UNTIL_DEATH` - * and unless the process itself added this. - * This is the file descriptor of the client's - * connection. - */ - int client; - - /** - * The lifespan of the filter - */ - enum lifespan lifespan; - - /** - * The priority of the filter - */ - int64_t priority; - - /** - * Identifier for the filter - */ - char* class; - - /** - * The gamma ramp adjustments for the filter. - * This is raw binary data. `NULL` iff - * `lifespan == LIFESPAN_REMOVE`. - */ - void* ramps; - -}; - - - -/** - * Free all resources allocated to a filter. - * The allocation of `filter` itself is not freed. - * - * @param this The filter - */ -GCC_ONLY(__attribute__((nonnull))) -void filter_destroy(struct filter* restrict this); - -/** - * Marshal a filter - * - * @param this The filter - * @param buf Output buffer for the marshalled filter, - * `NULL` just measure how large the buffers - * needs to be - * @param ramps_size The byte-size of `filter->ramps` - * @return The number of marshalled byte - */ -GCC_ONLY(__attribute__((nonnull(1)))) -size_t filter_marshal(const struct filter* restrict this, void* restrict buf, size_t ramps_size); - -/** - * Unmarshal a filter - * - * @param this Output for the filter, `.red_size`, `.green_size`, - * and `.blue_size` must already be set - * @param buf Buffer with the marshalled filter - * @param ramps_size The byte-size of `filter->ramps` - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t filter_unmarshal(struct filter* restrict this, const void* restrict buf, size_t ramps_size); - - -#endif - diff --git a/src/types/message.c b/src/types/message.c deleted file mode 100644 index b0a6469..0000000 --- a/src/types/message.c +++ /dev/null @@ -1,572 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "message.h" -#include "../util.h" - -#include -#include -#include -#include -#include - - - -/** - * Initialise a message slot so that it can - * be used by to read messages - * - * @param this Memory slot in which to store the new message - * @return Non-zero on error, `errno` will be set accordingly - */ -int message_initialise(struct message* restrict this) -{ - this->headers = NULL; - this->header_count = 0; - this->payload = NULL; - this->payload_size = 0; - this->payload_ptr = 0; - this->buffer_size = 128; - this->buffer_ptr = 0; - this->stage = 0; - this->buffer = malloc(this->buffer_size); - if (this->buffer == NULL) - return -1; - return 0; -} - - -/** - * Release all resources in a message, should - * be done even if initialisation fails - * - * @param this The message - */ -void message_destroy(struct message* restrict this) -{ - size_t i; - if (this->headers != NULL) - for (i = 0; i < this->header_count; i++) - free(this->headers[i]); - - free(this->headers); - free(this->payload); - free(this->buffer); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal a message for state serialisation - * - * @param this The message - * @param buf Output buffer for the marshalled data, - * `NULL` just measure how large the buffers - * needs to be - * @return The number of marshalled byte - */ -size_t message_marshal(const struct message* restrict this, void* restrict buf) -{ - size_t i, n, off = 0; - char* bs = buf; - - if (bs != NULL) - *(size_t*)(bs + off) = this->header_count; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->payload_size; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->payload_ptr; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->buffer_ptr; - off += sizeof(size_t); - - if (bs != NULL) - *(int*)(bs + off) = this->stage; - off += sizeof(int); - - for (i = 0; i < this->header_count; i++) - { - n = strlen(this->headers[i]) + 1; - if (bs != NULL) - memcpy(bs + off, this->headers[i], n); - off += n; - } - - if (bs != NULL) - memcpy(bs + off, this->payload, this->payload_ptr); - off += this->payload_ptr; - - if (bs != NULL) - memcpy(bs + off, this->buffer, this->buffer_ptr); - off += this->buffer_ptr; - - return off; -} - - -/** - * Unmarshal a message for state deserialisation - * - * @param this Memory slot in which to store the new message - * @param buf In buffer with the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -size_t message_unmarshal(struct message* restrict this, const void* restrict buf) -{ - size_t i, n, off = 0, header_count; - const char* bs = buf; - - this->header_count = 0; - - header_count = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->payload_size = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->payload_ptr = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->buffer_size = this->buffer_ptr = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->stage = *(const int*)(bs + off); - off += sizeof(int); - - /* Make sure that the pointers are NULL so that they are - not freed without being allocated when the message is - destroyed if this function fails. */ - this->headers = NULL; - this->payload = NULL; - this->buffer = NULL; - - /* To 2-power-multiple of 128 bytes. */ - this->buffer_size >>= 7; - if (this->buffer_size == 0) - this->buffer_size = 1; - else - { - this->buffer_size -= 1; - this->buffer_size |= this->buffer_size >> 1; - this->buffer_size |= this->buffer_size >> 2; - this->buffer_size |= this->buffer_size >> 4; - this->buffer_size |= this->buffer_size >> 8; - this->buffer_size |= this->buffer_size >> 16; -#if SIZE_MAX == UINT64_MAX - this->buffer_size |= this->buffer_size >> 32; -#endif - this->buffer_size += 1; - } - this->buffer_size <<= 7; - - /* Allocate header list, payload and read buffer. */ - - if (header_count > 0) - if (!(this->headers = malloc(header_count * sizeof(char*)))) - goto fail; - - if (this->payload_size > 0) - if (!(this->payload = malloc(this->payload_size))) - goto fail; - - if (!(this->buffer = malloc(this->buffer_size))) - goto fail; - - /* Fill the header list, payload and read buffer. */ - - for (i = 0; i < header_count; i++) - { - n = strlen(bs + off) + 1; - this->headers[i] = memdup(bs + off, n); - if (this->headers[i] == NULL) - goto fail; - off += n; - this->header_count++; - } - - memcpy(this->payload, bs + off, this->payload_ptr); - off += this->payload_ptr; - - memcpy(this->buffer, bs + off, this->buffer_ptr); - off += this->buffer_ptr; - - return off; - - fail: - return 0; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - - - -/** - * Extend the header list's allocation - * - * @param this The message - * @param extent The number of additional entries - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int extend_headers(struct message* restrict this, size_t extent) -{ - char** new; - if (!(new = realloc(this->headers, (this->header_count + extent) * sizeof(char*)))) - return -1; - this->headers = new; - return 0; -} - - -/** - * Extend the read buffer by way of doubling - * - * @param this The message - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -static int extend_buffer(struct message* restrict this) -{ - char* restrict new; - if (!(new = realloc(this->buffer, (this->buffer_size << 1) * sizeof(char)))) - return -1; - this->buffer = new; - this->buffer_size <<= 1; - return 0; -} - - -/** - * Reset the header list and the payload - * - * @param this The message - */ -GCC_ONLY(__attribute__((nonnull))) -static void reset_message(struct message* restrict this) -{ - size_t i; - if (this->headers != NULL) - for (i = 0; i < this->header_count; i++) - free(this->headers[i]); - free(this->headers); - this->headers = NULL; - this->header_count = 0; - - free(this->payload); - this->payload = NULL; - this->payload_size = 0; - this->payload_ptr = 0; -} - - -/** - * Read the headers the message and determine, and store, its payload's length - * - * @param this The message - * @return Zero on success, negative on error (malformated message: unrecoverable state) - */ -GCC_ONLY(__attribute__((pure, nonnull))) -static int get_payload_length(struct message* restrict this) -{ - char* header; - size_t i; - - for (i = 0; i < this->header_count; i++) - if (strstr(this->headers[i], "Length: ") == this->headers[i]) - { - /* Store the message length. */ - header = this->headers[i] + strlen("Length: "); - this->payload_size = (size_t)atol(header); - - /* Do not except a length that is not correctly formated. */ - for (; *header; header++) - if ((*header < '0') || ('9' < *header)) - return -2; /* Malformated value, enters unrecoverable state. */ - - /* Stop searching for the ‘Length’ header, we have found and parsed it. */ - break; - } - - return 0; -} - - -/** - * Verify that a header is correctly formatted - * - * @param header The header, must be NUL-terminated - * @param length The length of the header - * @return Zero if valid, negative if invalid (malformated message: unrecoverable state) - */ -GCC_ONLY(__attribute__((pure, nonnull))) -static int validate_header(const char* restrict header, size_t length) -{ - char* restrict p = memchr(header, ':', length * sizeof(char)); - - if (verify_utf8(header) < 0) - /* Either the string is not UTF-8, or your are under an UTF-8 attack, - let's just call this unrecoverable because the client will not correct. */ - return -2; - - if ((p == NULL) || /* Buck you, rawmemchr should not segfault the program. */ - (p[1] != ' ')) /* Also an invalid format. ' ' is mandated after the ':'. */ - return -2; - - return 0; -} - - -/** - * Remove the beginning of the read buffer - * - * @param this The message - * @param length The number of characters to remove - * @param update_ptr Whether to update the buffer pointer - */ -GCC_ONLY(__attribute__((nonnull))) -static void unbuffer_beginning(struct message* restrict this, size_t length, int update_ptr) -{ - memmove(this->buffer, this->buffer + length, (this->buffer_ptr - length) * sizeof(char)); - if (update_ptr) - this->buffer_ptr -= length; -} - - -/** - * Remove the header–payload delimiter from the buffer, - * get the payload's size and allocate the payload - * - * @param this The message - * @return The return value follows the rules of `message_read` - */ -GCC_ONLY(__attribute__((nonnull))) -static int initialise_payload(struct message* restrict this) -{ - /* Remove the \n (end of empty line) we found from the buffer. */ - unbuffer_beginning(this, 1, 1); - - /* Get the length of the payload. */ - if (get_payload_length(this) < 0) - return -2; /* Malformated value, enters unrecoverable state. */ - - /* Allocate the payload buffer. */ - if (this->payload_size > 0) - if (!(this->payload = malloc(this->payload_size))) - return -1; - - return 0; -} - - -/** - * Create a header from the buffer and store it - * - * @param this The message - * @param length The length of the header, including LF-termination - * @return The return value follows the rules of `message_read` - */ -GCC_ONLY(__attribute__((nonnull))) -static int store_header(struct message* restrict this, size_t length) -{ - char* restrict header; - - /* Allocate the header. */ - if (!(header = malloc(length))) /* Last char is a LF, which is substituted with NUL. */ - return -1; - /* Copy the header data into the allocated header, */ - memcpy(header, this->buffer, length * sizeof(char)); - /* and NUL-terminate it. */ - header[length - 1] = '\0'; - - /* Remove the header data from the read buffer. */ - unbuffer_beginning(this, length, 1); - - /* Make sure the the header syntax is correct so that - the program does not need to care about it. */ - if (validate_header(header, length)) - { - free(header); - return -2; - } - - /* Store the header in the header list. */ - this->headers[this->header_count++] = header; - - return 0; -} - - -/** - * Continue reading from the socket into the buffer - * - * @param this The message - * @param fd The file descriptor of the socket - * @return The return value follows the rules of `message_read` - */ -GCC_ONLY(__attribute__((nonnull))) -static int continue_read(struct message* restrict this, int fd) -{ - size_t n; - ssize_t got; - int r; - - /* Figure out how much space we have left in the read buffer. */ - n = this->buffer_size - this->buffer_ptr; - - /* If we do not have too much left, */ - if (n < 128) - { - /* grow the buffer, */ - if ((r = extend_buffer(this)) < 0) - return r; - - /* and recalculate how much space we have left. */ - n = this->buffer_size - this->buffer_ptr; - } - - /* Then read from the socket. */ - errno = 0; - got = recv(fd, this->buffer + this->buffer_ptr, n, 0); - this->buffer_ptr += (size_t)(got < 0 ? 0 : got); - if (errno) - return -1; - if (got == 0) - { - errno = ECONNRESET; - return -1; - } - - return 0; -} - - -/** - * Read the next message from a file descriptor - * - * @param this Memory slot in which to store the new message - * @param fd The file descriptor - * @return 0: At least one message is available - * -1: Exceptional connection: - * EINTR: System call interrupted - * EAGAIN: No message is available - * EWOULDBLOCK: No message is available - * ECONNRESET: Connection closed - * Other: Failure - * -2: Corrupt message (unrecoverable) - */ -GCC_ONLY(__attribute__((nonnull))) -int message_read(struct message* restrict this, int fd) -{ - size_t header_commit_buffer = 0; - int r; - - /* If we are at stage 2, we are done and it is time to start over. - This is important because the function could have been interrupted. */ - if (this->stage == 2) - { - reset_message(this); - this->stage = 0; - } - - /* Read from file descriptor until we have a full message. */ - for (;;) - { - char* p; - size_t length; - - /* Stage 0: headers. */ - /* Read all headers that we have stored into the read buffer. */ - while ((this->stage == 0) && - ((p = memchr(this->buffer, '\n', this->buffer_ptr * sizeof(char))) != NULL)) - if ((length = (size_t)(p - this->buffer))) - { - /* We have found a header. */ - - /* On every eighth header found with this function call, - we prepare the header list for eight more headers so - that it does not need to be reallocated again and again. */ - if (header_commit_buffer == 0) - if ((r = extend_headers(this, header_commit_buffer = 8)) < 0) - return r; - - /* Create and store header. */ - if ((r = store_header(this, length + 1)) < 0) - return r; - header_commit_buffer -= 1; - } - else - { - /* We have found an empty line, i.e. the end of the headers. */ - - /* Remove the header–payload delimiter from the buffer, - get the payload's size and allocate the payload. */ - if ((r = initialise_payload(this)) < 0) - return r; - - /* Mark end of stage, next stage is getting the payload. */ - this->stage = 1; - } - - - /* Stage 1: payload. */ - if ((this->stage == 1) && (this->payload_size > 0)) - { - /* How much of the payload that has not yet been filled. */ - size_t need = this->payload_size - this->payload_ptr; - /* How much we have of that what is needed. */ - size_t move = this->buffer_ptr < need ? this->buffer_ptr : need; - - /* Copy what we have, and remove it from the the read buffer. */ - memcpy(this->payload + this->payload_ptr, this->buffer, move * sizeof(char)); - unbuffer_beginning(this, move, 1); - - /* Keep track of how much we have read. */ - this->payload_ptr += move; - } - if ((this->stage == 1) && (this->payload_ptr == this->payload_size)) - { - /* If we have filled the payload (or there was no payload), - mark the end of this stage, i.e. that the message is - complete, and return with success. */ - this->stage = 2; - return 0; - } - - - /* If stage 1 was not completed. */ - - /* Continue reading from the socket into the buffer. */ - if ((r = continue_read(this, fd)) < 0) - return r; - } -} - diff --git a/src/types/message.h b/src/types/message.h deleted file mode 100644 index 0f1ade2..0000000 --- a/src/types/message.h +++ /dev/null @@ -1,160 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TYPES_MESSAGE_H -#define TYPES_MESSAGE_H - - -#include -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Message passed between a server and a client - */ -struct message -{ - /** - * The headers in the message, each element in this list - * as an unparsed header, it consists of both the header - * name and its associated value, joined by ": ". A header - * cannot be `NULL` (unless its memory allocation failed,) - * but `headers` itself is `NULL` if there are no headers. - * The "Length" header should be included in this list. - */ - char** restrict headers; - - /** - * The number of headers in the message - */ - size_t header_count; - - /** - * The payload of the message, `NULL` if none (of zero-length) - */ - char* restrict payload; - - /** - * The size of the payload - */ - size_t payload_size; - - /** - * How much of the payload that has been stored (internal data) - */ - size_t payload_ptr; - - /** - * Internal buffer for the reading function (internal data) - */ - char* restrict buffer; - - /** - * The size allocated to `buffer` (internal data) - */ - size_t buffer_size; - - /** - * The number of bytes used in `buffer` (internal data) - */ - size_t buffer_ptr; - - /** - * 0 while reading headers, 1 while reading payload, and 2 when done (internal data) - */ - int stage; - -#if INT_MAX != LONG_MAX - int padding__; -#endif - -}; - - - -/** - * Initialise a message slot so that it can - * be used by to read messages - * - * @param this Memory slot in which to store the new message - * @return Non-zero on error, `errno` will be set accordingly - */ -GCC_ONLY(__attribute__((nonnull))) -int message_initialise(struct message* restrict this); - -/** - * Release all resources in a message, should - * be done even if initialisation fails - * - * @param this The message - */ -GCC_ONLY(__attribute__((nonnull))) -void message_destroy(struct message* restrict this); - -/** - * Marshal a message for state serialisation - * - * @param this The message - * @param buf Output buffer for the marshalled data, - * `NULL` just measure how large the buffers - * needs to be - * @return The number of marshalled byte - */ -GCC_ONLY(__attribute__((nonnull(1)))) -size_t message_marshal(const struct message* restrict this, void* restrict buf); - -/** - * Unmarshal a message for state deserialisation - * - * @param this Memory slot in which to store the new message - * @param buf In buffer with the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t message_unmarshal(struct message* restrict this, const void* restrict buf); - -/** - * Read the next message from a file descriptor - * - * @param this Memory slot in which to store the new message - * @param fd The file descriptor - * @return 0: At least one message is available - * -1: Exceptional connection: - * EINTR: System call interrupted - * EAGAIN: No message is available - * EWOULDBLOCK: No message is available - * ECONNRESET: Connection closed - * Other: Failure - * -2: Corrupt message (unrecoverable) - */ -GCC_ONLY(__attribute__((nonnull))) -int message_read(struct message* restrict this, int fd); - - -#endif - diff --git a/src/types/output.c b/src/types/output.c deleted file mode 100644 index b94d090..0000000 --- a/src/types/output.c +++ /dev/null @@ -1,335 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "output.h" -#include "../util.h" - -#include -#include - - - -/** - * Free all resources allocated to an output. - * The allocation of `output` itself is not freed, - * nor is its the libgamma destroyed. - * - * @param this The output - */ -void output_destroy(struct output* restrict this) -{ - size_t i; - - if (this->supported != LIBGAMMA_NO) - switch (this->depth) - { - case 8: - libgamma_gamma_ramps8_destroy(&(this->saved_ramps.u8)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_ramps8_destroy(&(this->table_sums[i].u8)); - break; - case 16: - libgamma_gamma_ramps16_destroy(&(this->saved_ramps.u16)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_ramps16_destroy(&(this->table_sums[i].u16)); - break; - case 32: - libgamma_gamma_ramps32_destroy(&(this->saved_ramps.u32)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_ramps32_destroy(&(this->table_sums[i].u32)); - break; - case 64: - libgamma_gamma_ramps64_destroy(&(this->saved_ramps.u64)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_ramps64_destroy(&(this->table_sums[i].u64)); - break; - case -1: - libgamma_gamma_rampsf_destroy(&(this->saved_ramps.f)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_rampsf_destroy(&(this->table_sums[i].f)); - break; - case -2: - libgamma_gamma_rampsd_destroy(&(this->saved_ramps.d)); - for (i = 0; i < this->table_size; i++) - libgamma_gamma_rampsd_destroy(&(this->table_sums[i].d)); - break; - default: - break; /* impossible */ - } - - for (i = 0; i < this->table_size; i++) - filter_destroy(this->table_filters + i); - - free(this->table_filters); - free(this->table_sums); - free(this->name); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal an output - * - * @param this The output - * @param buf Output buffer for the marshalled output, - * `NULL` just measure how large the buffers - * needs to be - * @return The number of marshalled byte - */ -size_t output_marshal(const struct output* restrict this, void* restrict buf) -{ - size_t off = 0, i, n; - char* bs = buf; - - if (bs != NULL) - *(signed*)(bs + off) = this->depth; - off += sizeof(signed); - - if (bs != NULL) - *(size_t*)(bs + off) = this->red_size; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->green_size; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->blue_size; - off += sizeof(size_t); - - if (bs != NULL) - *(size_t*)(bs + off) = this->ramps_size; - off += sizeof(size_t); - - if (bs != NULL) - *(enum libgamma_decision*)(bs + off) = this->supported; - off += sizeof(enum libgamma_decision); - - if (bs != NULL) - *(enum colourspace*)(bs + off) = this->colourspace; - off += sizeof(enum colourspace); - - if (bs != NULL) - *(int*)(bs + off) = this->name_is_edid; - off += sizeof(int); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->red_x; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->red_y; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->green_x; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->green_y; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->blue_x; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->blue_y; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->white_x; - off += sizeof(unsigned); - - if (bs != NULL) - *(unsigned*)(bs + off) = this->white_y; - off += sizeof(unsigned); - - n = strlen(this->name) + 1; - if (bs != NULL) - memcpy(bs + off, this->name, n); - off += n; - - off += gamma_ramps_marshal(&(this->saved_ramps), bs ? bs + off : NULL, this->ramps_size); - - if (bs != NULL) - *(size_t*)(bs + off) = this->table_size; - off += sizeof(size_t); - - for (i = 0; i < this->table_size; i++) - { - off += filter_marshal(this->table_filters + i, bs ? bs + off : NULL, this->ramps_size); - off += gamma_ramps_marshal(this->table_sums + i, bs ? bs + off : NULL, this->ramps_size); - } - - return off; -} - - -/** - * Unmarshal an output - * - * @param this Output for the output - * @param buf Buffer with the marshalled output - * @return The number of unmarshalled bytes, 0 on error - */ -size_t output_unmarshal(struct output* restrict this, const void* restrict buf) -{ - size_t off = 0, i, n; - const char* bs = buf; - - this->crtc = NULL; - this->name = NULL; - - this->depth = *(const signed*)(bs + off); - off += sizeof(signed); - - this->red_size = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->green_size = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->blue_size = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->ramps_size = *(const size_t*)(bs + off); - off += sizeof(size_t); - - this->supported = *(const enum libgamma_decision*)(bs + off); - off += sizeof(enum libgamma_decision); - - this->colourspace = *(const enum colourspace*)(bs + off); - off += sizeof(enum colourspace); - - this->name_is_edid = *(const int*)(bs + off); - off += sizeof(int); - - this->red_x = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->red_y = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->green_x = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->green_y = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->blue_x = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->blue_y = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->white_x = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - this->white_y = *(const unsigned*)(bs + off); - off += sizeof(unsigned); - - n = strlen(bs + off) + 1; - this->name = memdup(bs + off, n); - if (this->name == NULL) - return 0; - off += n; - - COPY_RAMP_SIZES(&(this->saved_ramps.u8), this); - off += n = gamma_ramps_unmarshal(&(this->saved_ramps), bs + off, this->ramps_size); - if (n == 0) - return 0; - - this->table_size = this->table_alloc = *(const size_t*)(bs + off); - off += sizeof(size_t); - if (this->table_size > 0) - { - this->table_filters = calloc(this->table_size, sizeof(*(this->table_filters))); - if (this->table_filters == NULL) - return 0; - this->table_sums = calloc(this->table_size, sizeof(*(this->table_sums))); - if (this->table_sums == NULL) - return 0; - } - - for (i = 0; i < this->table_size; i++) - { - off += n = filter_unmarshal(this->table_filters + i, bs + off, this->ramps_size); - if (n == 0) - return 0; - COPY_RAMP_SIZES(&(this->table_sums[i].u8), this); - off += n = gamma_ramps_unmarshal(this->table_sums + i, bs + off, this->ramps_size); - if (n == 0) - return 0; - } - - return off; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - - - -/** - * Compare to outputs by the names of their respective CRTC:s - * - * @param a Return -1 if this one is lower - * @param b Return +1 if this one is higher - * @return See description of `a` and `b`, - * 0 if returned if they are the same - */ -int output_cmp_by_name(const void* restrict a, const void* restrict b) -{ - const char* an = ((const struct output*)a)->name; - const char* bn = ((const struct output*)b)->name; - return strcmp(an, bn); -} - - -/** - * Find an output by its name - * - * @param key The name of the output - * @param base The array of outputs - * @param n The number of elements in `base` - * @return Output find in `base`, `NULL` if not found - */ -struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n) -{ - struct output k; - -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-qual" -#endif - k.name = (char*)key; -#if defined(__GNUC__) -# pragma GCC diagnostic pop -#endif - - return bsearch(&k, base, n, sizeof(*base), output_cmp_by_name); -} - diff --git a/src/types/output.h b/src/types/output.h deleted file mode 100644 index 750ec41..0000000 --- a/src/types/output.h +++ /dev/null @@ -1,308 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TYPES_OUTPUT_H -#define TYPES_OUTPUT_H - - -#include - -#include - -#include "ramps.h" -#include "filter.h" - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Copy the ramp sizes - * - * This macro supports both `struct output` - * and `struct gamma_ramps` - * - * @param dest The destination - * @param src The source - */ -#define COPY_RAMP_SIZES(dest, src) \ - ((dest)->red_size = (src)->red_size, \ - (dest)->green_size = (src)->green_size, \ - (dest)->blue_size = (src)->blue_size) - - - -/** - * Colour spaces - */ -enum colourspace -{ - - /** - * Unknown - */ - COLOURSPACE_UNKNOWN = 0, - - /** - * sRGB with explicit gamut - */ - COLOURSPACE_SRGB = 1, - - /** - * sRGB without explicit gamut - */ - COLOURSPACE_SRGB_SANS_GAMUT = 2, - - /** - * RGB (but not sRGB) with known gamut - */ - COLOURSPACE_RGB = 3, - - /** - * RGB (but not sRGB) without known gamut - */ - COLOURSPACE_RGB_SANS_GAMUT = 4, - - /** - * Non-RGB multicolour - */ - COLOURSPACE_NON_RGB = 5, - - /** - * Greyscale or monochrome - */ - COLOURSPACE_GREY = 6 -}; - - - -/** - * Information about an output - */ -struct output -{ - /** - * -2: double - * -1: float - * 8: uint8_t - * 16: uint16_t - * 32: uint32_t - * 64: uint64_t - */ - signed depth; - - /** - * Whether gamma ramps are supported - */ - enum libgamma_decision supported; - - /** - * Whether the name is the EDID - */ - int name_is_edid; - - /** - * The monitor's colour space - */ - enum colourspace colourspace; - - /** - * The x-value (CIE xyY) of the monitor's - * red colour, multiplied by 1024 - */ - unsigned red_x; - - /** - * The y-value (CIE xyY) of the monitor's - * red colour, multiplied by 1024 - */ - unsigned red_y; - - /** - * The x-value (CIE xyY) of the monitor's - * green colour, multiplied by 1024 - */ - unsigned green_x; - - /** - * The y-value (CIE xyY) of the monitor's - * green colour, multiplied by 1024 - */ - unsigned green_y; - - /** - * The x-value (CIE xyY) of the monitor's - * blue colour, multiplied by 1024 - */ - unsigned blue_x; - - /** - * The y-value (CIE xyY) of the monitor's - * blue colour, multiplied by 1024 - */ - unsigned blue_y; - - /** - * The x-value (CIE xyY) of the monitor's - * default white point, multiplied by 1024 - */ - unsigned white_x; - - /** - * The y-value (CIE xyY) of the monitor's - * default white point, multiplied by 1024 - */ - unsigned white_y; - - /** - * The number of stops in the red gamma ramp - */ - size_t red_size; - - /** - * The number of stops in the green gamma ramp - */ - size_t green_size; - - /** - * The number of stops in the blue gamma ramp - */ - size_t blue_size; - - /** - * `.red_size + .green_size + .blue_size` - * multiplied by the byte-size of each stop - */ - size_t ramps_size; - - /** - * The name of the output, will be its EDID - * if available, otherwise it will be the - * index of the partition, followed by a dot - * and the index of the CRTC within the - * partition, or if a name for the connector - * is available: the index of the partition - * followed by a dot and the name of the - * connector - */ - char* restrict name; - - /** - * The libgamma state for the output - */ - libgamma_crtc_state_t* restrict crtc; - - /** - * Saved gamma ramps - */ - union gamma_ramps saved_ramps; - - /** - * The table of all applied filters - */ - struct filter* restrict table_filters; - - /** - * `.table_sums[i]` is the resulting - * adjustment made when all filter - * from `.table_filters[0]` up to and - * including `.table_filters[i]` has - * been applied - */ - union gamma_ramps* restrict table_sums; - - /** - * The number of elements allocated - * for `.table_filters` and for `.table_sums` - */ - size_t table_alloc; - - /** - * The number of elements stored in - * `.table_filters` and in `.table_sums` - */ - size_t table_size; - -}; - - - -/** - * Free all resources allocated to an output. - * The allocation of `output` itself is not freed, - * nor is its the libgamma destroyed. - * - * @param this The output - */ -GCC_ONLY(__attribute__((nonnull))) -void output_destroy(struct output* restrict this); - -/** - * Marshal an output - * - * @param this The output - * @param buf Output buffer for the marshalled output, - * `NULL` just measure how large the buffers - * needs to be - * @return The number of marshalled byte - */ -GCC_ONLY(__attribute__((nonnull(1)))) -size_t output_marshal(const struct output* restrict this, void* restrict buf); - -/** - * Unmarshal an output - * - * @param this Output for the output - * @param buf Buffer with the marshalled output - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t output_unmarshal(struct output* restrict this, const void* restrict buf); - -/** - * Compare to outputs by the names of their respective CRTC:s - * - * @param a Return -1 if this one is lower - * @param b Return +1 if this one is higher - * @return See description of `a` and `b`, - * 0 if returned if they are the same - */ -GCC_ONLY(__attribute__((pure, nonnull))) -int output_cmp_by_name(const void* restrict a, const void* restrict b); - -/** - * Find an output by its name - * - * @param key The name of the output - * @param base The array of outputs - * @param n The number of elements in `base` - * @return Output find in `base`, `NULL` if not found - */ -GCC_ONLY(__attribute__((pure, nonnull))) -struct output* output_find_by_name(const char* restrict key, struct output* restrict base, size_t n); - - -#endif - diff --git a/src/types/ramps.c b/src/types/ramps.c deleted file mode 100644 index 30bed3e..0000000 --- a/src/types/ramps.c +++ /dev/null @@ -1,98 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "ramps.h" - -#include - -#include -#include -#include - - - -/** - * The name of the process - */ -extern char* restrict argv0; - - - -/** - * Marshal a ramp trio - * - * @param this The ramps - * @param buf Output buffer for the marshalled ramps, - * `NULL` just measure how large the buffers - * needs to be - * @param ramps_size The byte-size of ramps - * @return The number of marshalled byte - */ -size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size) -{ - if (buf != NULL) - memcpy(buf, this->u8.red, ramps_size); - return ramps_size; -} - - -/** - * Unmarshal a ramp trio - * - * @param this Output for the ramps, `.red_size`, `.green_size`, - * and `.blue_size` must already be set - * @param buf Buffer with the marshalled ramps - * @param ramps_size The byte-size of ramps - * @return The number of unmarshalled bytes, 0 on error - */ -size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size) -{ - size_t depth = ramps_size / (this->u8.red_size + this->u8.green_size + this->u8.blue_size); - int r = 0; - switch (depth) - { - case 1: - r = libgamma_gamma_ramps8_initialise(&(this->u8)); - break; - case 2: - r = libgamma_gamma_ramps16_initialise(&(this->u16)); - break; - case 4: - r = libgamma_gamma_ramps32_initialise(&(this->u32)); - break; - case 8: - r = libgamma_gamma_ramps64_initialise(&(this->u64)); - break; - default: - if (depth == sizeof(float)) - r = libgamma_gamma_rampsf_initialise(&(this->f)); - else if (depth == sizeof(double)) - r = libgamma_gamma_rampsd_initialise(&(this->d)); - else - abort(); - break; - } - if (r) - { - libgamma_perror(argv0, r); - errno = 0; - return 0; - } - memcpy(this->u8.red, buf, ramps_size); - return ramps_size; -} - diff --git a/src/types/ramps.h b/src/types/ramps.h deleted file mode 100644 index 001d504..0000000 --- a/src/types/ramps.h +++ /dev/null @@ -1,102 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TYPES_RAMPS_H -#define TYPES_RAMPS_H - - -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Gamma ramps union for all - * lbigamma gamma ramps types - */ -union gamma_ramps -{ - /** - * Ramps with 8-bit value - */ - libgamma_gamma_ramps8_t u8; - - /** - * Ramps with 16-bit value - */ - libgamma_gamma_ramps16_t u16; - - /** - * Ramps with 32-bit value - */ - libgamma_gamma_ramps32_t u32; - - /** - * Ramps with 64-bit value - */ - libgamma_gamma_ramps64_t u64; - - /** - * Ramps with `float` value - */ - libgamma_gamma_rampsf_t f; - - /** - * Ramps with `double` value - */ - libgamma_gamma_rampsd_t d; - -}; - - - -/** - * Marshal a ramp trio - * - * @param this The ramps - * @param buf Output buffer for the marshalled ramps, - * `NULL` just measure how large the buffers - * needs to be - * @param ramps_size The byte-size of ramps - * @return The number of marshalled byte - */ -GCC_ONLY(__attribute__((nonnull(1)))) -size_t gamma_ramps_marshal(const union gamma_ramps* restrict this, void* restrict buf, size_t ramps_size); - -/** - * Unmarshal a ramp trio - * - * @param this Output for the ramps - * @param buf Buffer with the marshalled ramps - * @param ramps_size The byte-size of ramps - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t gamma_ramps_unmarshal(union gamma_ramps* restrict this, const void* restrict buf, size_t ramps_size); - - -#endif - diff --git a/src/types/ring.c b/src/types/ring.c deleted file mode 100644 index 13cf8c9..0000000 --- a/src/types/ring.c +++ /dev/null @@ -1,224 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "ring.h" - -#include -#include - - - -/** - * Initialise a ring buffer - * - * @param this The ring buffer - */ -void ring_initialise(struct ring* restrict this) -{ - this->start = 0; - this->end = 0; - this->size = 0; - this->buffer = NULL; -} - - -/** - * Release resource allocated to a ring buffer - * - * @param this The ring buffer - */ -void ring_destroy(struct ring* restrict this) -{ - free(this->buffer); -} - - - -#if defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif - - -/** - * Marshal a ring buffer - * - * @param this The ring buffer - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how large buffer - * is needed - * @return The number of marshalled bytes - */ -size_t ring_marshal(const struct ring* restrict this, void* restrict buf) -{ - size_t off = 0, n = this->end - this->start; - char* bs = buf; - - if (bs != NULL) - *(size_t*)(bs + off) = n; - off += sizeof(size_t); - - if (bs != NULL) - memcpy(bs + off, this->buffer + this->start, n); - off += n; - - return off; -} - - -/** - * Unmarshal a ring buffer - * - * @param this Output parameter for the ring buffer - * @param buf Buffer with the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf) -{ - size_t off = 0; - const char* bs = buf; - - ring_initialise(this); - - this->size = this->end = *(const size_t*)(bs + off); - off += sizeof(size_t); - - if (this->end > 0) - { - if (!(this->buffer = malloc(this->end))) - return 0; - - memcpy(this->buffer, bs + off, this->end); - off += this->end; - } - - return off; -} - - -#if defined(__clang__) -# pragma GCC diagnostic pop -#endif - - - -/** - * Append data to a ring buffer - * - * @param this The ring buffer - * @param data The new data - * @param n The number of bytes in `data` - * @return Zero on success, -1 on error - */ -int ring_push(struct ring* restrict this, void* restrict data, size_t n) -{ - size_t used = 0; - - if (this->start == this->end) - { - if (this->buffer != NULL) - used = this->size; - } - else if (this->start > this->end) - used = this->size - this->start + this->end; - else - used = this->start - this->end; - - if (used + n > this->size) - { - char* restrict new = malloc(used + n); - if (new == NULL) - return -1; - if (this->buffer) - { - if (this->start < this->end) - memcpy(new, this->buffer + this->start, this->end - this->start); - else - { - memcpy(new, this->buffer + this->start, this->size - this->start); - memcpy(new + this->size - this->start, this->buffer, this->end); - } - } - memcpy(new + used, data, n); - this->buffer = new; - this->start = 0; - this->end = used + n; - this->size = used + n; - } - else if ((this->start >= this->end) || (this->end + n <= this->size)) - { - memcpy(this->buffer + this->end, data, n); - this->end += n; - } - else - { - memcpy(this->buffer + this->end, data, this->size - this->end); - data = (char*)data + (this->size - this->end); - n -= this->size - this->end; - memcpy(this->buffer, data, n); - this->end = n; - } - - return 0; -} - - -/** - * Get queued data from a ring buffer - * - * It can take up to two calls (with `ring_pop` between) - * to get all queued data - * - * @param this The ring buffer - * @param n Output parameter for the length - * of the returned segment - * @return The beginning of the queued data, - * `NULL` if there is nothing more - */ -void* ring_peek(struct ring* restrict this, size_t* restrict n) -{ - if (this->buffer == NULL) - return *n = 0, NULL; - - if (this->start < this->end) - *n = this->end - this->start; - else - *n = this->size - this->start; - return this->buffer + this->start; -} - - -/** - * Dequeue data from a ring bubber - * - * @param this The ring buffer - * @param n The number of bytes to dequeue - */ -void ring_pop(struct ring* restrict this, size_t n) -{ - this->start += n; - this->start %= this->size; - if (this->start == this->end) - { - free(this->buffer); - this->start = 0; - this->end = 0; - this->size = 0; - this->buffer = NULL; - } -} - diff --git a/src/types/ring.h b/src/types/ring.h deleted file mode 100644 index 0474f39..0000000 --- a/src/types/ring.h +++ /dev/null @@ -1,152 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef TYPES_RING_H -#define TYPES_RING_H - - -#include - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Ring buffer - */ -struct ring -{ - /** - * Buffer for the data - */ - char* restrict buffer; - - /** - * The first set byte in `.buffer` - */ - size_t start; - - /** - * The last set byte in `.buffer`, plus 1 - */ - size_t end; - - /** - * The size of `.buffer` - */ - size_t size; -}; - - - -/** - * Initialise a ring buffer - * - * @param this The ring buffer - */ -GCC_ONLY(__attribute__((nonnull))) -void ring_initialise(struct ring* restrict this); - -/** - * Release resource allocated to a ring buffer - * - * @param this The ring buffer - */ -GCC_ONLY(__attribute__((nonnull))) -void ring_destroy(struct ring* restrict this); - -/** - * Marshal a ring buffer - * - * @param this The ring buffer - * @param buf Output buffer for the marshalled data, - * `NULL` to only measure how large buffer - * is needed - * @return The number of marshalled bytes - */ -GCC_ONLY(__attribute__((nonnull(1)))) -size_t ring_marshal(const struct ring* restrict this, void* restrict buf); - -/** - * Unmarshal a ring buffer - * - * @param this Output parameter for the ring buffer - * @param buf Buffer with the marshalled data - * @return The number of unmarshalled bytes, 0 on error - */ -GCC_ONLY(__attribute__((nonnull))) -size_t ring_unmarshal(struct ring* restrict this, const void* restrict buf); - -/** - * Append data to a ring buffer - * - * @param this The ring buffer - * @param data The new data - * @param n The number of bytes in `data` - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull(1)))) -int ring_push(struct ring* restrict this, void* restrict data, size_t n); - -/** - * Get queued data from a ring buffer - * - * It can take up to two calls (with `ring_pop` between) - * to get all queued data - * - * @param this The ring buffer - * @param n Output parameter for the length - * of the returned segment - * @return The beginning of the queued data, - * `NULL` if there is nothing more - */ -GCC_ONLY(__attribute__((nonnull))) -void* ring_peek(struct ring* restrict this, size_t* restrict n); - -/** - * Dequeue data from a ring bubber - * - * @param this The ring buffer - * @param n The number of bytes to dequeue - */ -GCC_ONLY(__attribute__((nonnull))) -void ring_pop(struct ring* restrict this, size_t n); - -/** - * Check whether there is more data waiting - * in a ring buffer - * - * @param this The ring buffer - * @return 1 if there is more data, 0 otherwise - */ -GCC_ONLY(__attribute__((nonnull))) -static inline int ring_have_more(struct ring* restrict this) -{ - return this->buffer != NULL; -} - - -#endif - diff --git a/src/util.c b/src/util.c deleted file mode 100644 index 2fdbeae..0000000 --- a/src/util.c +++ /dev/null @@ -1,316 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "util.h" - -#include - -#include -#include -#include -#include -#include -#include - - - -/** - * Duplicate a memory segment - * - * @param src The memory segment, must not be `NULL` - * @param n The size of the memory segment, must not be zero - * @return The duplicate of the memory segment, - * `NULL` on error - */ -void* memdup(const void* restrict src, size_t n) -{ - void* dest = malloc(n); - if (dest == NULL) - return NULL; - memcpy(dest, src, n); - return dest; -} - - -/** - * Read an entire file - * - * @param fd The file descriptor - * @param n Output for the size of the file - * @return The read content, plus a NUL byte at - * the end (not counted in `*n`) - */ -void* nread(int fd, size_t* restrict n) -{ - size_t size = 32; - ssize_t got; - struct stat st; - char* buf = NULL; - char* new; - int saved_errno; - - *n = 0; - - if (!fstat(fd, &st)) - size = st.st_size <= 0 ? 32 : (size_t)(st.st_size); - - buf = malloc(size + 1); - if (buf == NULL) - return NULL; - - for (;;) - { - if (*n == size) - { - new = realloc(buf, (size <<= 1) + 1); - if (new == NULL) - goto fail; - buf = new; - } - - got = read(fd, buf + *n, size - *n); - if (got < 0) - { - if (errno == EINTR) - continue; - goto fail; - } - if (got == 0) - break; - *n += (size_t)got; - } - - buf[*n] = '\0'; - return buf; - fail: - saved_errno = errno; - free(buf); - errno = saved_errno; - return NULL; -} - - -/** - * Write an entire buffer to a file - * - * Not cancelled by `EINTR` - * - * @param fd The file descriptor - * @param buf The buffer which shall be written to the fail - * @param n The size of the buffer - * @return The number of written bytes, less than `n` - * on error, cannot exceed `n` - */ -size_t nwrite(int fd, const void* restrict buf, size_t n) -{ - const char* restrict bs = buf; - ssize_t wrote; - size_t ptr = 0; - - while (ptr < n) - { - wrote = write(fd, bs + ptr, n - ptr); - if (wrote <= 0) - { - if ((wrote < 0) && (errno == EINTR)) - continue; - return ptr; - } - ptr += (size_t)wrote; - } - - return ptr; -} - - -/** - * Duplicate a file descriptor an make sure - * the new file descriptor's index as a - * specified minimum value - * - * @param fd The file descriptor - * @param atleast The least acceptable new file descriptor - * @return The new file descriptor, -1 on error - */ -int dup2atleast(int fd, int atleast) -{ - int* stack = malloc((size_t)(atleast + 1) * sizeof(int)); - size_t stack_ptr = 0; - int new = -1, saved_errno; - - if (stack == NULL) - goto fail; - - for (;;) - { - new = dup(fd); - if (new < 0) - goto fail; - if (new >= atleast) - break; - } - - fail: - saved_errno = errno; - while (stack_ptr--) - close(stack[stack_ptr]); - free(stack); - errno = saved_errno; - return new; -} - - -/** - * Perform a timed suspention of the process. - * The process resumes when the timer expires, - * or when it is interrupted. - * - * @param ms The number of milliseconds to sleep, - * must be less than 1000 - */ -void msleep(unsigned ms) -{ - struct timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = (long)ms * 1000000L; - if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL) == ENOTSUP) - nanosleep(&ts, NULL); -} - - -/** - * Check whether a NUL-terminated string is encoded in UTF-8 - * - * @param string The string - * @return Zero if good, -1 on encoding error - */ -int verify_utf8(const char* restrict string) -{ - static long BYTES_TO_MIN_BITS[] = {0, 0, 8, 12, 17, 22, 37}; - static long BYTES_TO_MAX_BITS[] = {0, 7, 11, 16, 21, 26, 31}; - long bytes = 0, read_bytes = 0, bits = 0, c, character = 0; - - /* min bits max bits - 0....... 0 7 - 110..... 10...... 8 11 - 1110.... 10...... 10...... 12 16 - 11110... 10...... 10...... 10...... 17 21 - 111110.. 10...... 10...... 10...... 10...... 22 26 - 1111110. 10...... 10...... 10...... 10...... 10...... 27 31 - */ - - while ((c = (long)(*string++))) - if (read_bytes == 0) - { - /* First byte of the character. */ - - if ((c & 0x80) == 0x00) - /* Single-byte character. */ - continue; - - if ((c & 0xC0) == 0x80) - /* Single-byte character marked as multibyte, or - a non-first byte in a multibyte character. */ - return -1; - - /* Multibyte character. */ - while ((c & 0x80)) - bytes++, c <<= 1; - read_bytes = 1; - character = c & 0x7F; - if (bytes > 6) - /* 31-bit characters can be encoded with 6-bytes, - and UTF-8 does not cover higher code points. */ - return -1; - } - else - { - /* Not first byte of the character. */ - - if ((c & 0xC0) != 0x80) - /* Beginning of new character before a - multibyte character has ended. */ - return -1; - - character = (character << 6) | (c & 0x7F); - - if (++read_bytes < bytes) - /* Not at last byte yet. */ - continue; - - /* Check that the character is not unnecessarily long. */ - while (character) - character >>= 1, bits++; - if ((bits < BYTES_TO_MIN_BITS[bytes]) || (BYTES_TO_MAX_BITS[bytes] < bits)) - return -1; - - read_bytes = bytes = bits = 0; - } - - /* Make sure we did not stop at the middle of a multibyte character. */ - return read_bytes == 0 ? 0 : -1; -} - - -/** - * Make identity mapping ramps - * - * @param ramps Output parameter for the ramps - * @param output The output for which the ramps shall be configured - * @return Zero on success, -1 on error - */ -int make_plain_ramps(union gamma_ramps* restrict ramps, struct output* restrict output) -{ - COPY_RAMP_SIZES(&(ramps->u8), output); - switch (output->depth) - { - case 8: - if (libgamma_gamma_ramps8_initialise(&(ramps->u8))) - return -1; - libclut_start_over(&(ramps->u8), UINT8_MAX, uint8_t, 1, 1, 1); - break; - case 16: - if (libgamma_gamma_ramps16_initialise(&(ramps->u16))) - return -1; - libclut_start_over(&(ramps->u16), UINT16_MAX, uint16_t, 1, 1, 1); - break; - case 32: - if (libgamma_gamma_ramps32_initialise(&(ramps->u32))) - return -1; - libclut_start_over(&(ramps->u32), UINT32_MAX, uint32_t, 1, 1, 1); - break; - case 64: - if (libgamma_gamma_ramps64_initialise(&(ramps->u64))) - return -1; - libclut_start_over(&(ramps->u64), UINT64_MAX, uint64_t, 1, 1, 1); - break; - case -1: - if (libgamma_gamma_rampsf_initialise(&(ramps->f))) - return -1; - libclut_start_over(&(ramps->f), 1.0f, float, 1, 1, 1); - break; - case -2: - if (libgamma_gamma_rampsd_initialise(&(ramps->d))) - return -1; - libclut_start_over(&(ramps->d), (double)1, double, 1, 1, 1); - break; - default: - abort(); - } - return 0; -} - diff --git a/src/util.h b/src/util.h deleted file mode 100644 index eb98285..0000000 --- a/src/util.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * coopgammad -- Cooperative gamma server - * Copyright (C) 2016 Mattias Andrée (maandree@kth.se) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef UTIL_H -#define UTIL_H - - -#include "types/output.h" - - - -#ifndef GCC_ONLY -# if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ -# else -# define GCC_ONLY(...) /* nothing */ -# endif -#endif - - - -/** - * Duplicate a memory segment - * - * @param src The memory segment, must not be `NULL` - * @param n The size of the memory segment, must not be zero - * @return The duplicate of the memory segment, - * `NULL` on error - */ -GCC_ONLY(__attribute__((malloc, nonnull))) -void* memdup(const void* restrict src, size_t n); - -/** - * Read an entire file - * - * Not cancelled by `EINTR` - * - * @param fd The file descriptor - * @param n Output for the size of the file - * @return The read content, plus a NUL byte at - * the end (not counted in `*n`) - */ -GCC_ONLY(__attribute__((malloc))) -void* nread(int fd, size_t* restrict n); - -/** - * Write an entire buffer to a file - * - * Not cancelled by `EINTR` - * - * @param fd The file descriptor - * @param buf The buffer which shall be written to the fail - * @param n The size of the buffer - * @return The number of written bytes, less than `n` - * on error, cannot exceed `n` - */ -size_t nwrite(int fd, const void* restrict buf, size_t n); - -/** - * Duplicate a file descriptor an make sure - * the new file descriptor's index as a - * specified minimum value - * - * @param fd The file descriptor - * @param atleast The least acceptable new file descriptor - * @return The new file descriptor, -1 on error - */ -int dup2atleast(int fd, int atleast); - -/** - * Perform a timed suspention of the process. - * The process resumes when the timer expires, - * or when it is interrupted. - * - * @param ms The number of milliseconds to sleep, - * must be less than 1000 - */ -void msleep(unsigned ms); - -/** - * Check whether a NUL-terminated string is encoded in UTF-8 - * - * @param string The string - * @return Zero if good, -1 on encoding error - */ -GCC_ONLY(__attribute__((pure, nonnull))) -int verify_utf8(const char* restrict string); - -/** - * Make identity mapping ramps - * - * @param ramps Output parameter for the ramps - * @param output The output for which the ramps shall be configured - * @return Zero on success, -1 on error - */ -GCC_ONLY(__attribute__((nonnull))) -int make_plain_ramps(union gamma_ramps* restrict ramps, struct output* restrict output); - - -#endif - diff --git a/state.c b/state.c new file mode 100644 index 0000000..5aabf0e --- /dev/null +++ b/state.c @@ -0,0 +1,612 @@ +/* See LICENSE file for copyright and license details. */ +#include "state.h" +#include "util.h" + +#include +#include +#include +#include + + +/** + * The name of the process + */ +char *restrict argv0; /* do not marshal */ + +/** + * The real pathname of the process's binary, + * `NULL` if `argv0` is satisfactory + */ +char *restrict argv0_real = NULL; + +/** + * Array of all outputs + */ +struct output *restrict outputs = NULL; + +/** + * The nubmer of elements in `outputs` + */ +size_t outputs_n = 0; + +/** + * The server socket's file descriptor + */ +int socketfd = -1; + +/** + * Has the process receive a signal + * telling it to re-execute? + */ +volatile sig_atomic_t reexec = 0; /* do not marshal */ + +/** + * Has the process receive a signal + * telling it to terminate? + */ +volatile sig_atomic_t terminate = 0; /* do not marshal */ + +/** + * Has the process receive a to + * disconnect from or reconnect to + * the site? 1 if disconnect, 2 if + * reconnect, 0 otherwise. + */ +volatile sig_atomic_t connection = 0; + +/** + * List of all client's file descriptors + * + * Unused slots, with index less than `connections_used`, + * should have the value -1 (negative) + */ +int *restrict connections = NULL; + +/** + * The number of elements allocated for `connections` + */ +size_t connections_alloc = 0; + +/** + * The index of the first unused slot in `connections` + */ +size_t connections_ptr = 0; + +/** + * The index of the last used slot in `connections`, plus 1 + */ +size_t connections_used = 0; + +/** + * The clients' connections' inbound-message buffers + */ +struct message *restrict inbound = NULL; + +/** + * The clients' connections' outbound-message buffers + */ +struct ring *restrict outbound = NULL; + +/** + * Is the server connect to the display? + * + * Set to true before the initial connection + */ +int connected = 1; + +/** + * The adjustment method, -1 for automatic + */ +int method = -1; + +/** + * The site's name, may be `NULL` + */ +char *restrict sitename = NULL; + +/** + * The libgamma site state + */ +libgamma_site_state_t site; /* do not marshal */ + +/** + * The libgamma partition states + */ +libgamma_partition_state_t *restrict partitions = NULL; /* do not marshal */ + +/** + * The libgamma CRTC states + */ +libgamma_crtc_state_t *restrict crtcs = NULL; /* do not marshal */ + +/** + * Preserve gamma ramps at priority 0? + */ +int preserve = 0; + + +/** + * As part of a state dump, dump one or two gamma ramp-trios + * + * @param left The left ramps + * @param right The right ramps + * @param depth The gamma ramp type/depth + * @param have_right Print right ramps? + * @param indent Print indent + */ +static void +ramps_dump(union gamma_ramps *left, union gamma_ramps *right, signed depth, int have_right, const char *indent) +{ +#define STRINGISE(SIDE, CH, N, BUF)\ + do {\ + if (!SIDE || !SIDE->u8.CH) {\ + strcpy(BUF, "null");\ + } else if (i < N) {\ + switch (depth) {\ + case -2: snprintf(BUF, sizeof(BUF), "%lf", SIDE->d.CH[i]); break;\ + case -1: snprintf(BUF, sizeof(BUF), "%f", (double)(SIDE->f.CH[i])); break;\ + case 8: snprintf(BUF, sizeof(BUF), "%02" PRIx8, SIDE->u8.CH[i]); break;\ + case 16: snprintf(BUF, sizeof(BUF), "%04" PRIx16, SIDE->u16.CH[i]); break;\ + case 32: snprintf(BUF, sizeof(BUF), "%08" PRIx32, SIDE->u32.CH[i]); break;\ + case 64: snprintf(BUF, sizeof(BUF), "%16" PRIx64, SIDE->u64.CH[i]); break;\ + default:\ + strcpy(BUF, "corrupt state");\ + break;\ + }\ + }\ + } while (0) + + char lr[320], lg[320], lb[320], rr[320], rg[320], rb[320]; + size_t rn = left ? left->u8.red_size : right ? right->u8.red_size : 0; + size_t gn = left ? left->u8.green_size : right ? right->u8.green_size : 0; + size_t bn = left ? left->u8.blue_size : right ? right->u8.blue_size : 0; + size_t i, n = rn > gn ? rn : gn; + n = n > bn ? n : bn; + + for (i = 0; i < n; i++) { + *lr = *lg = *lb = *rr = *rg = *rb = '\0'; + + STRINGISE(left, red, rn, lr); + STRINGISE(left, green, gn, lg); + STRINGISE(left, blue, bn, lb); + + if (have_right) { + STRINGISE(right, red, rn, rr); + STRINGISE(right, green, gn, rg); + STRINGISE(right, blue, bn, rb); + } + + if (have_right) + fprintf(stderr, "%s%zu: %s, %s, %s :: %s, %s, %s\n", indent, i, lr, lg, lb, rr, rg, rb); + else + fprintf(stderr, "%s%zu: %s, %s, %s\n", indent, i, lr, lg, lb); + } +} + + +/** + * Dump the state to stderr + */ +void +state_dump(void) +{ + size_t i, j; + struct output *restrict out; + const char *str; + struct filter *restrict filter; + union gamma_ramps left; + size_t depth; + + fprintf(stderr, "argv0: %s\n", argv0 ? argv0 : "(null)"); + fprintf(stderr, "Realpath of argv0: %s\n", argv0_real ? argv0_real : "(null)"); + fprintf(stderr, "Calibrations preserved: %s\n", preserve ? "yes" : "no"); + fprintf(stderr, "Connected: %s\n", connected ? "yes" : "no"); + fprintf(stderr, "Socket FD: %i\n", socketfd); + fprintf(stderr, "Re-execution pending: %s\n", reexec ? "yes" : "no"); + fprintf(stderr, "Termination pending: %s\n", terminate ? "yes" : "no"); + if (0 <= connection && connection <= 2) + fprintf(stderr, "Pending connection change: %s\n", + connection == 0 ? "none" : connection == 1 ? "disconnect" : "reconnect"); + else + fprintf(stderr, "Pending connection change: %i (CORRUPT STATE)\n", connection); + fprintf(stderr, "Adjustment method: %i\n", method); + fprintf(stderr, "Site name: %s\n", sitename ? sitename : "(automatic)"); + fprintf(stderr, "Clients:\n"); + fprintf(stderr, " Next empty slot: %zu\n", connections_ptr); + fprintf(stderr, " Initialised slots: %zu\n", connections_used); + fprintf(stderr, " Allocated slots: %zu\n", connections_alloc); + if (!connections) { + fprintf(stderr, " File descriptor array is null\n"); + } else { + for (i = 0; i < connections_used; i++) { + if (connections[i] < 0) { + fprintf(stderr, " Slot %zu: empty\n", i); + continue; + } + fprintf(stderr, " Slot %zu:\n", i); + fprintf(stderr, " File descriptor: %i\n", connections[i]); + if (!inbound) { + fprintf(stderr, " Inbound message array is null\n"); + } else { + fprintf(stderr, " Inbound message:\n"); + fprintf(stderr, " Header array: %s\n", inbound[i].headers ? "non-null" : "null"); + fprintf(stderr, " Headers: %zu\n", inbound[i].header_count); + fprintf(stderr, " Payload buffer: %s\n", inbound[i].payload ? "non-null" : "null"); + fprintf(stderr, " Payload size: %zu\n", inbound[i].payload_size); + fprintf(stderr, " Payload write pointer: %zu\n", inbound[i].payload_ptr); + fprintf(stderr, " Message buffer: %s\n", inbound[i].buffer ? "non-null" : "null"); + fprintf(stderr, " Message buffer size: %zu\n", inbound[i].buffer_size); + fprintf(stderr, " Message buffer write pointer: %zu\n", inbound[i].buffer_ptr); + fprintf(stderr, " Read stage: %i\n", inbound[i].stage); + } + if (!outbound) { + fprintf(stderr, " Outbound message array is null\n"); + } else { + fprintf(stderr, " Ring buffer: %s\n", outbound[i].buffer ? "non-null" : "null"); + fprintf(stderr, " Head: %zu\n", outbound[i].end); + fprintf(stderr, " Tail: %zu\n", outbound[i].start); + fprintf(stderr, " Size: %zu\n", outbound[i].size); + } + } + } + fprintf(stderr, "Partition array: %s\n", partitions ? "non-null" : "null"); + fprintf(stderr, "CRTC array: %s\n", crtcs ? "non-null" : "null"); + fprintf(stderr, "Output:\n"); + fprintf(stderr, " Output count: %zu\n", outputs_n); + if (!outputs) { + fprintf(stderr, " Output array is null\n"); + } else { + for (i = 0; i < outputs_n; i++) { + out = outputs + i; + fprintf(stderr, " Output %zu:\n", i); + fprintf(stderr, " Depth: %i (%s)\n", out->depth, + out->depth == -1 ? "float" : + out->depth == -2 ? "double" : + out->depth == 8 ? "uint8_t" : + out->depth == 16 ? "uint16_t" : + out->depth == 32 ? "uint32_t" : + out->depth == 64 ? "uint64_t" : "CORRUPT STATE"); + fprintf(stderr, " Gamma supported: %s (%u)\n", + out->supported == LIBGAMMA_YES ? "yes" : + out->supported == LIBGAMMA_NO ? "no" : + out->supported == LIBGAMMA_MAYBE ? "maybe" : + "CORRUPT STATE", out->supported); + fprintf(stderr, " Name is EDID: %s\n", out->name_is_edid ? "yes" : "no"); + switch (out->colourspace) { + case COLOURSPACE_UNKNOWN: str = "unknown"; break; + case COLOURSPACE_SRGB: str = "sRGB with explicit gamut"; break; + case COLOURSPACE_SRGB_SANS_GAMUT: str = "sRGB with implicit gamut (actually illegal)"; break; + case COLOURSPACE_RGB: str = "RGB other than sRGB, with unknown gamut"; break; + case COLOURSPACE_RGB_SANS_GAMUT: str = "RGB other than sRGB, with listed gamut"; break; + case COLOURSPACE_NON_RGB: str = "Non-RGB multicolour"; break; + case COLOURSPACE_GREY: str = "Monochrome or singlecolour scale"; break; + default: str = "CORRUPT STATE"; break; + } + fprintf(stderr, " Colourspace: %s (%u)\n", str, out->colourspace); + if (out->colourspace == COLOURSPACE_SRGB || out->colourspace == COLOURSPACE_RGB) { + fprintf(stderr, " Red (x, y): (%u / 1024, %u / 1024)\n", out->red_x, out->red_y); + fprintf(stderr, " Green (x, y): (%u / 1024, %u / 1024)\n", out->green_x, out->green_y); + fprintf(stderr, " Blue (x, y): (%u / 1024, %u / 1024)\n", out->blue_x, out->blue_y); + fprintf(stderr, " White (x, y): (%u / 1024, %u / 1024)\n", out->white_x, out->white_y); + if (out->colourspace == COLOURSPACE_SRGB) { + fprintf(stderr, " Expected red (x, y): (655 / 1024, 338 / 1024)\n"); + fprintf(stderr, " Expected green (x, y): (307 / 1024, 614 / 1024)\n"); + fprintf(stderr, " Expected blue (x, y): (154 / 1024, 61 / 1024)\n"); + fprintf(stderr, " Expected white (x, y): (320 / 1024, 337 / 1024)\n"); + } + } + if (out->supported) { + fprintf(stderr, " Gamma ramp size:\n"); + fprintf(stderr, " Red: %zu stops\n", out->red_size); + fprintf(stderr, " Green: %zu stops\n", out->green_size); + fprintf(stderr, " Blue: %zu stops\n", out->blue_size); + fprintf(stderr, " Total: %zu bytes\n", out->ramps_size); + fprintf(stderr, " Name: %s\n", out->name ? out->name : "(null)"); + fprintf(stderr, " CRTC state: %s\n", out->crtc ? "non-null" : "null"); + fprintf(stderr, " Saved gamma ramps (stop: red, green, blue):\n"); + ramps_dump(&out->saved_ramps, NULL, out->depth, 0, " "); + fprintf(stderr, " Filter table:\n"); + fprintf(stderr, " Filter count: %zu\n", out->table_size); + fprintf(stderr, " Slots allocated: %zu\n", out->table_alloc); + if (out->table_size > 0) { + if (!out->table_filters) + fprintf(stderr, " Filter table is null\n"); + if (!out->table_sums) + fprintf(stderr, " Result table is null\n"); + } + for (j = 0; j < out->table_size; j++) { + filter = out->table_filters ? out->table_filters + j : NULL; + fprintf(stderr, " Filter %zu:\n", j); + if (filter) { + if (filter->lifespan == LIFESPAN_UNTIL_DEATH) + fprintf(stderr, " Client FD: %i\n", filter->client); + switch (filter->lifespan) { + case LIFESPAN_REMOVE: str = "remove (ILLEGAL STATE)"; break; + case LIFESPAN_UNTIL_REMOVAL: str = "until-removal"; break; + case LIFESPAN_UNTIL_DEATH: str = "until-death"; break; + default: str = "CORRUPT STATE"; break; + } + fprintf(stderr, " Lifespan: %s (%u)\n", str, filter->lifespan); + fprintf(stderr, " Priority: %"PRIi64"\n", filter->priority); + fprintf(stderr, " Class: %s\n", filter->class ? filter->class : "(null)"); + str = "yes"; + if (!filter->class) + str = "no, is NULL"; + else if (strchr(filter->class, '\n')) + str = "no, contains LF"; + else if (!strstr(filter->class, "::")) + str = "no, does not contain \"::\""; + else if (!strstr(strstr(filter->class, "::") + 2, "::")) + str = "no, contains only one \"::\""; + else if (verify_utf8(filter->class) < 0) + str = "no, not UTF-8"; + fprintf(stderr, " Class legal: %s\n", str); + if (!filter->ramps && filter->lifespan != LIFESPAN_REMOVE) + fprintf(stderr, " Ramps are NULL\n"); + } + if (filter ? filter->lifespan != LIFESPAN_REMOVE : !!out->table_sums) { + switch (out->depth) { + case -2: + depth = sizeof(double); + break; + case -1: + depth = sizeof(float); + break; + case 8: case 16: case 32: case 64: + depth = (size_t)(out->depth) / 8; + break; + default: + goto corrupt_depth; + } + if (filter && filter->ramps) { + left.u8.red_size = out->red_size; + left.u8.green_size = out->green_size; + left.u8.blue_size = out->blue_size; + left.u8.red = filter->ramps; + left.u8.green = left.u8.red + out->red_size * depth; + left.u8.blue = left.u8.green + out->green_size * depth; + } + fprintf(stderr, " Ramps (stop: filter red, green, blue :: " + "composite red, geen, blue):\n"); + ramps_dump((filter && filter->ramps) ? &left : NULL, + out->table_sums ? out->table_sums + j : NULL, + out->depth, 1, " "); + corrupt_depth:; + } + } + } + } + } +} + + +/** + * Destroy the state + */ +void +state_destroy(void) +{ + size_t i; + + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + message_destroy(inbound + i); + ring_destroy(outbound + i); + } + } + free(inbound); + free(outbound); + free(connections); + + if (outputs) + for (i = 0; i < outputs_n; i++) + output_destroy(outputs + i); + free(outputs); + + if (crtcs) + for (i = 0; i < outputs_n; i++) + libgamma_crtc_destroy(crtcs + i); + free(crtcs); + + if (partitions) + for (i = 0; i < site.partitions_available; i++) + libgamma_partition_destroy(partitions + i); + free(partitions); + + libgamma_site_destroy(&site); + free(sitename); +} + + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal the state + * + * @param buf Output buffer for the marshalled data, + * `NULL` to only measure how many bytes + * this buffer needs + * @return The number of marshalled bytes + */ +size_t +state_marshal(void *restrict buf) +{ + size_t off = 0, i, n; + char *restrict bs = buf; + + if (!argv0_real) { + if (bs) + bs[off] = '\0'; + off += 1; + } else { + n = strlen(argv0_real) + 1; + if (bs) + memcpy(&bs[off], argv0_real, n); + off += n; + } + + if (bs) + *(size_t *)&bs[off] = outputs_n; + off += sizeof(size_t); + + for (i = 0; i < outputs_n; i++) + off += output_marshal(outputs + i, bs ? &bs[off] : NULL); + + if (bs) + *(int *)&bs[off] = socketfd; + off += sizeof(int); + + if (bs) + *(sig_atomic_t *)&bs[off] = connection; + off += sizeof(sig_atomic_t); + + if (bs) + *(int *)&bs[off] = connected; + off += sizeof(int); + + if (bs) + *(size_t *)&bs[off] = connections_ptr; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = connections_used; + off += sizeof(size_t); + + if (bs) + memcpy(&bs[off], connections, connections_used * sizeof(*connections)); + off += connections_used * sizeof(*connections); + + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + off += message_marshal(&inbound[i], bs ? &bs[off] : NULL); + off += ring_marshal(&outbound[i], bs ? &bs[off] : NULL); + } + } + + if (bs) + *(int *)&bs[off] = method; + off += sizeof(int); + + if (bs) + *(int *)&bs[off] = sitename != NULL; + off += sizeof(int); + if (sitename) { + n = strlen(sitename) + 1; + if (bs) + memcpy(&bs[off], sitename, n); + off += n; + } + + if (bs) + *(int *)&bs[off] = preserve; + off += sizeof(int); + + return off; +} + + +/** + * Unmarshal the state + * + * @param buf Buffer for the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +state_unmarshal(const void *restrict buf) +{ + size_t off = 0, i, n; + const char *restrict bs = buf; + + connections = NULL; + inbound = NULL; + + if (bs[off]) { + n = strlen(&bs[off]) + 1; + if (!(argv0_real = memdup(&bs[off], n))) + return 0; + off += n; + } else { + off += 1; + } + + outputs_n = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + outputs = calloc(outputs_n, sizeof(*outputs)); + if (!outputs) + return 0; + + for (i = 0; i < outputs_n; i++) { + off += n = output_unmarshal(outputs + i, &bs[off]); + if (!n) + return 0; + } + + socketfd = *(const int *)&bs[off]; + off += sizeof(int); + + connection = *(const sig_atomic_t *)&bs[off]; + off += sizeof(sig_atomic_t); + + connected = *(const int *)&bs[off]; + off += sizeof(int); + + connections_ptr = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + connections_alloc = connections_used = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + if (connections_used > 0) { + connections = memdup(&bs[off], connections_used * sizeof(*connections)); + if (!connections) + return 0; + off += connections_used * sizeof(*connections); + + inbound = malloc(connections_used * sizeof(*inbound)); + if (!inbound) + return 0; + + outbound = malloc(connections_used * sizeof(*outbound)); + if (!outbound) + return 0; + } + + for (i = 0; i < connections_used; i++) { + if (connections[i] >= 0) { + off += n = message_unmarshal(&inbound[i], &bs[off]); + if (!n) + return 0; + off += n = ring_unmarshal(&outbound[i], &bs[off]); + if (!n) + return 0; + } + } + + method = *(const int *)&bs[off]; + off += sizeof(int); + + if (*(const int *)&bs[off]) { + off += sizeof(int); + n = strlen(&bs[off]) + 1; + if (!(sitename = memdup(&bs[off], n))) + return 0; + off += n; + } else { + off += sizeof(int); + } + + preserve = *(const int *)&bs[off]; + off += sizeof(int); + + return off; +} + + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif diff --git a/state.h b/state.h new file mode 100644 index 0000000..f707c81 --- /dev/null +++ b/state.h @@ -0,0 +1,167 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef STATE_H +#define STATE_H + +#include "types-message.h" +#include "types-ring.h" +#include "types-output.h" + +#include + +#include +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * The name of the process + */ +extern char *restrict argv0; + +/** + * The real pathname of the process's binary, + * `NULL` if `argv0` is satisfactory + */ +extern char *restrict argv0_real; + +/** + * Array of all outputs + */ +extern struct output *restrict outputs; + +/** + * The nubmer of elements in `outputs` + */ +extern size_t outputs_n; + +/** + * The server socket's file descriptor + */ +extern int socketfd; + +/** + * Has the process receive a signal + * telling it to re-execute? + */ +extern volatile sig_atomic_t reexec; + +/** + * Has the process receive a signal + * telling it to terminate? + */ +extern volatile sig_atomic_t terminate; + +/** + * Has the process receive a to + * disconnect from or reconnect to + * the site? 1 if disconnect, 2 if + * reconnect, 0 otherwise. + */ +extern volatile sig_atomic_t connection; + +/** + * List of all client's file descriptors + * + * Unused slots, with index less than `connections_used`, + * should have the value -1 (negative) + */ +extern int *restrict connections; + +/** + * The number of elements allocated for `connections` + */ +extern size_t connections_alloc; + +/** + * The index of the first unused slot in `connections` + */ +extern size_t connections_ptr; + +/** + * The index of the last used slot in `connections`, plus 1 + */ +extern size_t connections_used; + +/** + * The clients' connections' inbound-message buffers + */ +extern struct message *restrict inbound; + +/** + * The clients' connections' outbound-message buffers + */ +extern struct ring *restrict outbound; + +/** + * Is the server connect to the display? + * + * Set to true before the initial connection + */ +extern int connected; + +/** + * The adjustment method, -1 for automatic + */ +extern int method; + +/** + * The site's name, may be `NULL` + */ +extern char *restrict sitename; + +/** + * The libgamma site state + */ +extern libgamma_site_state_t site; + +/** + * The libgamma partition states + */ +extern libgamma_partition_state_t *restrict partitions; + +/** + * The libgamma CRTC states + */ +extern libgamma_crtc_state_t *restrict crtcs; + +/** + * Preserve gamma ramps at priority 0? + */ +extern int preserve; + +/** + * Dump the state to stderr + */ +void state_dump(void); + +/** + * Destroy the state + */ +void state_destroy(void); + +/** + * Marshal the state + * + * @param buf Output buffer for the marshalled data, + * `NULL` to only measure how many bytes + * this buffer needs + * @return The number of marshalled bytes + */ +size_t state_marshal(void *restrict buf); + +/** + * Unmarshal the state + * + * @param buf Buffer for the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t state_unmarshal(const void *restrict buf); + +#endif diff --git a/types-filter.c b/types-filter.c new file mode 100644 index 0000000..7fbebbd --- /dev/null +++ b/types-filter.c @@ -0,0 +1,125 @@ +/* See LICENSE file for copyright and license details. */ +#include "types-filter.h" +#include "util.h" + +#include +#include + + +/** + * Free all resources allocated to a filter. + * The allocation of `filter` itself is not freed. + * + * @param this The filter + */ +void +filter_destroy(struct filter *restrict this) +{ + free(this->class); + free(this->ramps); +} + + +#if defined(__clang__) +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal a filter + * + * @param this The filter + * @param buf Output buffer for the marshalled filter, + * `NULL` just measure how large the buffers + * needs to be + * @param ramps_size The byte-size of `this->ramps` + * @return The number of marshalled byte + */ +size_t +filter_marshal(const struct filter *restrict this, void *restrict buf, size_t ramps_size) +{ + size_t off = 0, n; + char nonnulls = 0; + char *restrict bs = buf; + + if (bs) { + if (this->class) + nonnulls |= 1; + if (this->ramps) + nonnulls |= 2; + bs[off] = nonnulls; + } + off += 1; + + if (bs) + *(int64_t *)&bs[off] = this->priority; + off += sizeof(int64_t); + + if (bs) + *(enum lifespan *)&bs[off] = this->lifespan; + off += sizeof(enum lifespan); + + if (this->class) { + n = strlen(this->class) + 1; + if (bs) + memcpy(&bs[off], this->class, n); + off += n; + } + + if (this->ramps) { + if (bs) + memcpy(&bs[off], this->ramps, ramps_size); + off += ramps_size; + } + + return off; +} + + +/** + * Unmarshal a filter + * + * @param this Output for the filter + * @param buf Buffer with the marshalled filter + * @param ramps_size The byte-size of `this->ramps` + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +filter_unmarshal(struct filter *restrict this, const void *restrict buf, size_t ramps_size) +{ + size_t off = 0, n; + char nonnulls = 0; + const char *restrict bs = buf; + + nonnulls = bs[off]; + off += 1; + + this->class = NULL; + this->ramps = NULL; + + this->priority = *(const int64_t *)&bs[off]; + off += sizeof(int64_t); + + this->lifespan = *(const enum lifespan *)&bs[off]; + off += sizeof(enum lifespan); + + if (nonnulls & 1) { + n = strlen(&bs[off]) + 1; + if (!(this->class = memdup(&bs[off], n))) + goto fail; + off += n; + } + + if (nonnulls & 2) { + if (!(this->ramps = memdup(&bs[off], ramps_size))) + goto fail; + off += ramps_size; + } + + return off; + +fail: + free(this->class); + free(this->ramps); + return 0; +} diff --git a/types-filter.h b/types-filter.h new file mode 100644 index 0000000..22f4783 --- /dev/null +++ b/types-filter.h @@ -0,0 +1,108 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef TYPES_FILTER_H +#define TYPES_FILTER_H + +#include +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * The lifespan of a filter + */ +enum lifespan { + /** + * The filter should be removed now + */ + LIFESPAN_REMOVE = 0, + + /** + * The filter should be applied + * until it is explicitly removed + */ + LIFESPAN_UNTIL_REMOVAL = 1, + + /** + * The filter should be applied + * until the client exists + */ + LIFESPAN_UNTIL_DEATH = 2 +}; + +/** + * Information about a filter + */ +struct filter { + /** + * The client that applied it. This need not be + * set unless `.lifespan == LIFESPAN_UNTIL_DEATH` + * and unless the process itself added this. + * This is the file descriptor of the client's + * connection. + */ + int client; + + /** + * The lifespan of the filter + */ + enum lifespan lifespan; + + /** + * The priority of the filter + */ + int64_t priority; + + /** + * Identifier for the filter + */ + char *class; + + /** + * The gamma ramp adjustments for the filter. + * This is raw binary data. `NULL` iff + * `lifespan == LIFESPAN_REMOVE`. + */ + void *ramps; +}; + +/** + * Free all resources allocated to a filter. + * The allocation of `filter` itself is not freed. + * + * @param this The filter + */ +GCC_ONLY(__attribute__((__nonnull__))) +void filter_destroy(struct filter *restrict this); + +/** + * Marshal a filter + * + * @param this The filter + * @param buf Output buffer for the marshalled filter, + * `NULL` just measure how large the buffers + * needs to be + * @param ramps_size The byte-size of `filter->ramps` + * @return The number of marshalled byte + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +size_t filter_marshal(const struct filter *restrict this, void *restrict buf, size_t ramps_size); + +/** + * Unmarshal a filter + * + * @param this Output for the filter, `.red_size`, `.green_size`, + * and `.blue_size` must already be set + * @param buf Buffer with the marshalled filter + * @param ramps_size The byte-size of `filter->ramps` + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t filter_unmarshal(struct filter *restrict this, const void *restrict buf, size_t ramps_size); + +#endif diff --git a/types-message.c b/types-message.c new file mode 100644 index 0000000..de5644d --- /dev/null +++ b/types-message.c @@ -0,0 +1,561 @@ +/* See LICENSE file for copyright and license details. */ +#include "types-message.h" +#include "util.h" + +#include +#include +#include +#include +#include + + +/** + * Initialise a message slot so that it can + * be used by to read messages + * + * @param this Memory slot in which to store the new message + * @return Non-zero on error, `errno` will be set accordingly + */ +int +message_initialise(struct message *restrict this) +{ + this->headers = NULL; + this->header_count = 0; + this->payload = NULL; + this->payload_size = 0; + this->payload_ptr = 0; + this->buffer_size = 128; + this->buffer_ptr = 0; + this->stage = 0; + this->buffer = malloc(this->buffer_size); + if (!this->buffer) + return -1; + return 0; +} + + +/** + * Release all resources in a message, should + * be done even if initialisation fails + * + * @param this The message + */ +void +message_destroy(struct message *restrict this) +{ + size_t i; + + if (this->headers) { + for (i = 0; i < this->header_count; i++) + free(this->headers[i]); + free(this->headers); + } + free(this->payload); + free(this->buffer); +} + + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal a message for state serialisation + * + * @param this The message + * @param buf Output buffer for the marshalled data, + * `NULL` just measure how large the buffers + * needs to be + * @return The number of marshalled byte + */ +size_t +message_marshal(const struct message *restrict this, void *restrict buf) +{ + size_t i, n, off = 0; + char *bs = buf; + + if (bs) + *(size_t *)&bs[off] = this->header_count; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->payload_size; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->payload_ptr; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->buffer_ptr; + off += sizeof(size_t); + + if (bs) + *(int *)&bs[off] = this->stage; + off += sizeof(int); + + for (i = 0; i < this->header_count; i++) { + n = strlen(this->headers[i]) + 1; + if (bs) + memcpy(&bs[off], this->headers[i], n); + off += n; + } + + if (bs) + memcpy(&bs[off], this->payload, this->payload_ptr); + off += this->payload_ptr; + + if (bs) + memcpy(&bs[off], this->buffer, this->buffer_ptr); + off += this->buffer_ptr; + + return off; +} + + +/** + * Unmarshal a message for state deserialisation + * + * @param this Memory slot in which to store the new message + * @param buf In buffer with the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +message_unmarshal(struct message *restrict this, const void *restrict buf) +{ + size_t i, n, off = 0, header_count; + const char *bs = buf; + + this->header_count = 0; + + header_count = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->payload_size = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->payload_ptr = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->buffer_size = this->buffer_ptr = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->stage = *(const int *)&bs[off]; + off += sizeof(int); + + /* Make sure that the pointers are NULL so that they are + not freed without being allocated when the message is + destroyed if this function fails. */ + this->headers = NULL; + this->payload = NULL; + this->buffer = NULL; + + /* To 2-power-multiple of 128 bytes. */ + this->buffer_size >>= 7; + if (!this->buffer_size) { + this->buffer_size = 1; + } else { + this->buffer_size -= 1; + this->buffer_size |= this->buffer_size >> 1; + this->buffer_size |= this->buffer_size >> 2; + this->buffer_size |= this->buffer_size >> 4; + this->buffer_size |= this->buffer_size >> 8; + this->buffer_size |= this->buffer_size >> 16; +#if SIZE_MAX == UINT64_MAX + this->buffer_size |= this->buffer_size >> 32; +#endif + this->buffer_size += 1; + } + this->buffer_size <<= 7; + + /* Allocate header list, payload and read buffer. */ + + if (header_count > 0) + if (!(this->headers = malloc(header_count * sizeof(char*)))) + goto fail; + + if (this->payload_size > 0) + if (!(this->payload = malloc(this->payload_size))) + goto fail; + + if (!(this->buffer = malloc(this->buffer_size))) + goto fail; + + /* Fill the header list, payload and read buffer. */ + + for (i = 0; i < header_count; i++) { + n = strlen(&bs[off]) + 1; + this->headers[i] = memdup(&bs[off], n); + if (!this->headers[i]) + goto fail; + off += n; + this->header_count++; + } + + memcpy(this->payload, &bs[off], this->payload_ptr); + off += this->payload_ptr; + + memcpy(this->buffer, &bs[off], this->buffer_ptr); + off += this->buffer_ptr; + + return off; + +fail: + return 0; +} + + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + + + +/** + * Extend the header list's allocation + * + * @param this The message + * @param extent The number of additional entries + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +extend_headers(struct message *restrict this, size_t extent) +{ + char **new = realloc(this->headers, (this->header_count + extent) * sizeof(char *)); + if (!new) + return -1; + this->headers = new; + return 0; +} + + +/** + * Extend the read buffer by way of doubling + * + * @param this The message + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +extend_buffer(struct message *restrict this) +{ + char *restrict new = realloc(this->buffer, (this->buffer_size << 1) * sizeof(char)); + if (!new) + return -1; + this->buffer = new; + this->buffer_size <<= 1; + return 0; +} + + +/** + * Reset the header list and the payload + * + * @param this The message + */ +GCC_ONLY(__attribute__((__nonnull__))) +static void +reset_message(struct message *restrict this) +{ + size_t i; + if (this->headers) { + for (i = 0; i < this->header_count; i++) + free(this->headers[i]); + free(this->headers); + this->headers = NULL; + } + this->header_count = 0; + + free(this->payload); + this->payload = NULL; + this->payload_size = 0; + this->payload_ptr = 0; +} + + +/** + * Read the headers the message and determine, and store, its payload's length + * + * @param this The message + * @return Zero on success, negative on error (malformated message: unrecoverable state) + */ +GCC_ONLY(__attribute__((__pure__, __nonnull__))) +static int +get_payload_length(struct message *restrict this) +{ + char *header; + size_t i; + + for (i = 0; i < this->header_count; i++) { + if (strstr(this->headers[i], "Length: ") == this->headers[i]) { + /* Store the message length. */ + header = this->headers[i] + strlen("Length: "); + this->payload_size = (size_t)atol(header); + + /* Do not except a length that is not correctly formated. */ + for (; *header; header++) + if (*header < '0' || '9' < *header) + return -2; /* Malformated value, enters unrecoverable state. */ + + /* Stop searching for the ‘Length’ header, we have found and parsed it. */ + break; + } + } + + return 0; +} + + +/** + * Verify that a header is correctly formatted + * + * @param header The header, must be NUL-terminated + * @param length The length of the header + * @return Zero if valid, negative if invalid (malformated message: unrecoverable state) + */ +GCC_ONLY(__attribute__((__pure__, __nonnull__))) +static int +validate_header(const char *restrict header, size_t length) +{ + char *restrict p = memchr(header, ':', length * sizeof(char)); + + if (verify_utf8(header) < 0) { + /* Either the string is not UTF-8, or your are under an UTF-8 attack, + let's just call this unrecoverable because the client will not correct. */ + return -2; + } + + if (!p || /* Buck you, rawmemchr should not segfault the program. */ + p[1] != ' ') /* Also an invalid format. ' ' is mandated after the ':'. */ + return -2; + + return 0; +} + + +/** + * Remove the beginning of the read buffer + * + * @param this The message + * @param length The number of characters to remove + * @param update_ptr Whether to update the buffer pointer + */ +GCC_ONLY(__attribute__((__nonnull__))) +static void +unbuffer_beginning(struct message *restrict this, size_t length, int update_ptr) +{ + memmove(this->buffer, &this->buffer[length], (this->buffer_ptr - length) * sizeof(char)); + if (update_ptr) + this->buffer_ptr -= length; +} + + +/** + * Remove the header–payload delimiter from the buffer, + * get the payload's size and allocate the payload + * + * @param this The message + * @return The return value follows the rules of `message_read` + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +initialise_payload(struct message *restrict this) +{ + /* Remove the \n (end of empty line) we found from the buffer. */ + unbuffer_beginning(this, 1, 1); + + /* Get the length of the payload. */ + if (get_payload_length(this) < 0) + return -2; /* Malformated value, enters unrecoverable state. */ + + /* Allocate the payload buffer. */ + if (this->payload_size > 0) { + this->payload = malloc(this->payload_size); + if (!this->payload) + return -1; + } + + return 0; +} + + +/** + * Create a header from the buffer and store it + * + * @param this The message + * @param length The length of the header, including LF-termination + * @return The return value follows the rules of `message_read` + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +store_header(struct message *restrict this, size_t length) +{ + char *restrict header; + + /* Allocate the header. */ + header = malloc(length); /* Last char is a LF, which is substituted with NUL. */ + if (!header) + return -1; + /* Copy the header data into the allocated header, */ + memcpy(header, this->buffer, length * sizeof(char)); + /* and NUL-terminate it. */ + header[length - 1] = '\0'; + + /* Remove the header data from the read buffer. */ + unbuffer_beginning(this, length, 1); + + /* Make sure the the header syntax is correct so that + the program does not need to care about it. */ + if (validate_header(header, length)) { + free(header); + return -2; + } + + /* Store the header in the header list. */ + this->headers[this->header_count++] = header; + + return 0; +} + + +/** + * Continue reading from the socket into the buffer + * + * @param this The message + * @param fd The file descriptor of the socket + * @return The return value follows the rules of `message_read` + */ +GCC_ONLY(__attribute__((__nonnull__))) +static int +continue_read(struct message *restrict this, int fd) +{ + size_t n; + ssize_t got; + int r; + + /* Figure out how much space we have left in the read buffer. */ + n = this->buffer_size - this->buffer_ptr; + + /* If we do not have too much left, */ + if (n < 128) { + /* grow the buffer, */ + if ((r = extend_buffer(this)) < 0) + return r; + + /* and recalculate how much space we have left. */ + n = this->buffer_size - this->buffer_ptr; + } + + /* Then read from the socket. */ + errno = 0; + got = recv(fd, this->buffer + this->buffer_ptr, n, 0); + this->buffer_ptr += (size_t)(got < 0 ? 0 : got); + if (errno) + return -1; + if (got == 0) { + errno = ECONNRESET; + return -1; + } + + return 0; +} + + +/** + * Read the next message from a file descriptor + * + * @param this Memory slot in which to store the new message + * @param fd The file descriptor + * @return 0: At least one message is available + * -1: Exceptional connection: + * EINTR: System call interrupted + * EAGAIN: No message is available + * EWOULDBLOCK: No message is available + * ECONNRESET: Connection closed + * Other: Failure + * -2: Corrupt message (unrecoverable) + */ +GCC_ONLY(__attribute__((__nonnull__))) +int +message_read(struct message *restrict this, int fd) +{ + size_t header_commit_buffer = 0; + size_t length, need, move; + int r; + char *p; + + /* If we are at stage 2, we are done and it is time to start over. + This is important because the function could have been interrupted. */ + if (this->stage == 2) { + reset_message(this); + this->stage = 0; + } + + /* Read from file descriptor until we have a full message. */ + for (;;) { + /* Stage 0: headers. */ + /* Read all headers that we have stored into the read buffer. */ + while (this->stage == 0 && + ((p = memchr(this->buffer, '\n', this->buffer_ptr * sizeof(char))))) { + length = (size_t)(p - this->buffer); + if (length) { + /* We have found a header. */ + + /* On every eighth header found with this function call, + we prepare the header list for eight more headers so + that it does not need to be reallocated again and again. */ + if (!header_commit_buffer) + if ((r = extend_headers(this, header_commit_buffer = 8)) < 0) + return r; + + /* Create and store header. */ + if ((r = store_header(this, length + 1)) < 0) + return r; + header_commit_buffer -= 1; + } else { + /* We have found an empty line, i.e. the end of the headers. */ + + /* Remove the header–payload delimiter from the buffer, + get the payload's size and allocate the payload. */ + if ((r = initialise_payload(this)) < 0) + return r; + + /* Mark end of stage, next stage is getting the payload. */ + this->stage = 1; + } + } + + + /* Stage 1: payload. */ + if ((this->stage == 1) && (this->payload_size > 0)) { + /* How much of the payload that has not yet been filled. */ + need = this->payload_size - this->payload_ptr; + /* How much we have of that what is needed. */ + move = this->buffer_ptr < need ? this->buffer_ptr : need; + + /* Copy what we have, and remove it from the the read buffer. */ + memcpy(&this->payload[this->payload_ptr], this->buffer, move * sizeof(char)); + unbuffer_beginning(this, move, 1); + + /* Keep track of how much we have read. */ + this->payload_ptr += move; + } + if (this->stage == 1 && this->payload_ptr == this->payload_size) { + /* If we have filled the payload (or there was no payload), + mark the end of this stage, i.e. that the message is + complete, and return with success. */ + this->stage = 2; + return 0; + } + + + /* If stage 1 was not completed. */ + + /* Continue reading from the socket into the buffer. */ + if ((r = continue_read(this, fd)) < 0) + return r; + } +} diff --git a/types-message.h b/types-message.h new file mode 100644 index 0000000..5d479b7 --- /dev/null +++ b/types-message.h @@ -0,0 +1,133 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef TYPES_MESSAGE_H +#define TYPES_MESSAGE_H + +#include +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Message passed between a server and a client + */ +struct message { + /** + * The headers in the message, each element in this list + * as an unparsed header, it consists of both the header + * name and its associated value, joined by ": ". A header + * cannot be `NULL` (unless its memory allocation failed,) + * but `headers` itself is `NULL` if there are no headers. + * The "Length" header should be included in this list. + */ + char **restrict headers; + + /** + * The number of headers in the message + */ + size_t header_count; + + /** + * The payload of the message, `NULL` if none (of zero-length) + */ + char *restrict payload; + + /** + * The size of the payload + */ + size_t payload_size; + + /** + * How much of the payload that has been stored (internal data) + */ + size_t payload_ptr; + + /** + * Internal buffer for the reading function (internal data) + */ + char *restrict buffer; + + /** + * The size allocated to `buffer` (internal data) + */ + size_t buffer_size; + + /** + * The number of bytes used in `buffer` (internal data) + */ + size_t buffer_ptr; + + /** + * 0 while reading headers, 1 while reading payload, and 2 when done (internal data) + */ + int stage; + +#if INT_MAX != LONG_MAX + int padding__; +#endif +}; + +/** + * Initialise a message slot so that it can + * be used by to read messages + * + * @param this Memory slot in which to store the new message + * @return Non-zero on error, `errno` will be set accordingly + */ +GCC_ONLY(__attribute__((__nonnull__))) +int message_initialise(struct message *restrict this); + +/** + * Release all resources in a message, should + * be done even if initialisation fails + * + * @param this The message + */ +GCC_ONLY(__attribute__((__nonnull__))) +void message_destroy(struct message *restrict this); + +/** + * Marshal a message for state serialisation + * + * @param this The message + * @param buf Output buffer for the marshalled data, + * `NULL` just measure how large the buffers + * needs to be + * @return The number of marshalled byte + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +size_t message_marshal(const struct message *restrict this, void *restrict buf); + +/** + * Unmarshal a message for state deserialisation + * + * @param this Memory slot in which to store the new message + * @param buf In buffer with the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t message_unmarshal(struct message *restrict this, const void *restrict buf); + +/** + * Read the next message from a file descriptor + * + * @param this Memory slot in which to store the new message + * @param fd The file descriptor + * @return 0: At least one message is available + * -1: Exceptional connection: + * EINTR: System call interrupted + * EAGAIN: No message is available + * EWOULDBLOCK: No message is available + * ECONNRESET: Connection closed + * Other: Failure + * -2: Corrupt message (unrecoverable) + */ +GCC_ONLY(__attribute__((__nonnull__))) +int message_read(struct message *restrict this, int fd); + +#endif diff --git a/types-output.c b/types-output.c new file mode 100644 index 0000000..e3296cc --- /dev/null +++ b/types-output.c @@ -0,0 +1,322 @@ +/* See LICENSE file for copyright and license details. */ +#include "types-output.h" +#include "util.h" + +#include +#include + + +/** + * Free all resources allocated to an output. + * The allocation of `output` itself is not freed, + * nor is its the libgamma destroyed. + * + * @param this The output + */ +void +output_destroy(struct output *restrict this) +{ + size_t i; + + if (this->supported != LIBGAMMA_NO) { + switch (this->depth) { + case 8: + libgamma_gamma_ramps8_destroy(&this->saved_ramps.u8); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_ramps8_destroy(&this->table_sums[i].u8); + break; + + case 16: + libgamma_gamma_ramps16_destroy(&this->saved_ramps.u16); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_ramps16_destroy(&this->table_sums[i].u16); + break; + + case 32: + libgamma_gamma_ramps32_destroy(&this->saved_ramps.u32); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_ramps32_destroy(&this->table_sums[i].u32); + break; + + case 64: + libgamma_gamma_ramps64_destroy(&this->saved_ramps.u64); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_ramps64_destroy(&this->table_sums[i].u64); + break; + + case -1: + libgamma_gamma_rampsf_destroy(&this->saved_ramps.f); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_rampsf_destroy(&this->table_sums[i].f); + break; + + case -2: + libgamma_gamma_rampsd_destroy(&this->saved_ramps.d); + for (i = 0; i < this->table_size; i++) + libgamma_gamma_rampsd_destroy(&this->table_sums[i].d); + break; + + default: + break; /* impossible */ + } + } + + for (i = 0; i < this->table_size; i++) + filter_destroy(&this->table_filters[i]); + + free(this->table_filters); + free(this->table_sums); + free(this->name); +} + + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal an output + * + * @param this The output + * @param buf Output buffer for the marshalled output, + * `NULL` just measure how large the buffers + * needs to be + * @return The number of marshalled byte + */ +size_t +output_marshal(const struct output *restrict this, void *restrict buf) +{ + size_t off = 0, i, n; + char *bs = buf; + + if (bs) + *(signed *)&bs[off] = this->depth; + off += sizeof(signed); + + if (bs) + *(size_t *)&bs[off] = this->red_size; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->green_size; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->blue_size; + off += sizeof(size_t); + + if (bs) + *(size_t *)&bs[off] = this->ramps_size; + off += sizeof(size_t); + + if (bs) + *(enum libgamma_decision *)&bs[off] = this->supported; + off += sizeof(enum libgamma_decision); + + if (bs) + *(enum colourspace *)&bs[off] = this->colourspace; + off += sizeof(enum colourspace); + + if (bs) + *(int *)&bs[off] = this->name_is_edid; + off += sizeof(int); + + if (bs) + *(unsigned *)&bs[off] = this->red_x; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->red_y; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->green_x; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->green_y; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->blue_x; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->blue_y; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->white_x; + off += sizeof(unsigned); + + if (bs) + *(unsigned *)&bs[off] = this->white_y; + off += sizeof(unsigned); + + n = strlen(this->name) + 1; + if (bs) + memcpy(&bs[off], this->name, n); + off += n; + + off += gamma_ramps_marshal(&(this->saved_ramps), bs ? &bs[off] : NULL, this->ramps_size); + + if (bs) + *(size_t *)&bs[off] = this->table_size; + off += sizeof(size_t); + + for (i = 0; i < this->table_size; i++) { + off += filter_marshal(this->table_filters + i, bs ? &bs[off] : NULL, this->ramps_size); + off += gamma_ramps_marshal(this->table_sums + i, bs ? &bs[off] : NULL, this->ramps_size); + } + + return off; +} + + +/** + * Unmarshal an output + * + * @param this Output for the output + * @param buf Buffer with the marshalled output + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +output_unmarshal(struct output *restrict this, const void *restrict buf) +{ + size_t off = 0, i, n; + const char *bs = buf; + + this->crtc = NULL; + this->name = NULL; + + this->depth = *(const signed *)&bs[off]; + off += sizeof(signed); + + this->red_size = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->green_size = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->blue_size = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->ramps_size = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + this->supported = *(const enum libgamma_decision *)&bs[off]; + off += sizeof(enum libgamma_decision); + + this->colourspace = *(const enum colourspace *)&bs[off]; + off += sizeof(enum colourspace); + + this->name_is_edid = *(const int *)&bs[off]; + off += sizeof(int); + + this->red_x = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->red_y = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->green_x = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->green_y = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->blue_x = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->blue_y = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->white_x = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + this->white_y = *(const unsigned *)&bs[off]; + off += sizeof(unsigned); + + n = strlen(&bs[off]) + 1; + this->name = memdup(&bs[off], n); + if (this->name == NULL) + return 0; + off += n; + + COPY_RAMP_SIZES(&this->saved_ramps.u8, this); + off += n = gamma_ramps_unmarshal(&this->saved_ramps, &bs[off], this->ramps_size); + if (n == 0) + return 0; + + this->table_size = this->table_alloc = *(const size_t*)&bs[off]; + off += sizeof(size_t); + if (this->table_size > 0) { + this->table_filters = calloc(this->table_size, sizeof(*this->table_filters)); + if (!this->table_filters) + return 0; + this->table_sums = calloc(this->table_size, sizeof(*this->table_sums)); + if (!this->table_sums) + return 0; + } + + for (i = 0; i < this->table_size; i++) { + off += n = filter_unmarshal(&this->table_filters[i], &bs[off], this->ramps_size); + if (!n) + return 0; + COPY_RAMP_SIZES(&this->table_sums[i].u8, this); + off += n = gamma_ramps_unmarshal(&this->table_sums[i], &bs[off], this->ramps_size); + if (!n) + return 0; + } + + return off; +} + + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + + +/** + * Compare to outputs by the names of their respective CRTC:s + * + * @param a Return -1 if this one is lower + * @param b Return +1 if this one is higher + * @return See description of `a` and `b`, + * 0 if returned if they are the same + */ +int +output_cmp_by_name(const void *restrict a, const void *restrict b) +{ + const struct output *x = a, *y = b; + return strcmp(x->name, y->name); +} + + +/** + * Find an output by its name + * + * @param key The name of the output + * @param base The array of outputs + * @param n The number of elements in `base` + * @return Output find in `base`, `NULL` if not found + */ +struct output * +output_find_by_name(const char *restrict key, struct output *restrict base, size_t n) +{ + struct output k; + +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-qual" +#endif + k.name = (char *)key; +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + return bsearch(&k, base, n, sizeof(*base), output_cmp_by_name); +} diff --git a/types-output.h b/types-output.h new file mode 100644 index 0000000..ca5bf61 --- /dev/null +++ b/types-output.h @@ -0,0 +1,275 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef TYPES_OUTPUT_H +#define TYPES_OUTPUT_H + +#include + +#include + +#include "types-ramps.h" +#include "types-filter.h" + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Copy the ramp sizes + * + * This macro supports both `struct output` + * and `struct gamma_ramps` + * + * @param dest The destination + * @param src The source + */ +#define COPY_RAMP_SIZES(dest, src) \ + ((dest)->red_size = (src)->red_size, \ + (dest)->green_size = (src)->green_size, \ + (dest)->blue_size = (src)->blue_size) + +/** + * Colour spaces + */ +enum colourspace { + /** + * Unknown + */ + COLOURSPACE_UNKNOWN = 0, + + /** + * sRGB with explicit gamut + */ + COLOURSPACE_SRGB = 1, + + /** + * sRGB without explicit gamut + */ + COLOURSPACE_SRGB_SANS_GAMUT = 2, + + /** + * RGB (but not sRGB) with known gamut + */ + COLOURSPACE_RGB = 3, + + /** + * RGB (but not sRGB) without known gamut + */ + COLOURSPACE_RGB_SANS_GAMUT = 4, + + /** + * Non-RGB multicolour + */ + COLOURSPACE_NON_RGB = 5, + + /** + * Greyscale or monochrome + */ + COLOURSPACE_GREY = 6 +}; + +/** + * Information about an output + */ +struct output { + /** + * -2: double + * -1: float + * 8: uint8_t + * 16: uint16_t + * 32: uint32_t + * 64: uint64_t + */ + signed depth; + + /** + * Whether gamma ramps are supported + */ + enum libgamma_decision supported; + + /** + * Whether the name is the EDID + */ + int name_is_edid; + + /** + * The monitor's colour space + */ + enum colourspace colourspace; + + /** + * The x-value (CIE xyY) of the monitor's + * red colour, multiplied by 1024 + */ + unsigned red_x; + + /** + * The y-value (CIE xyY) of the monitor's + * red colour, multiplied by 1024 + */ + unsigned red_y; + + /** + * The x-value (CIE xyY) of the monitor's + * green colour, multiplied by 1024 + */ + unsigned green_x; + + /** + * The y-value (CIE xyY) of the monitor's + * green colour, multiplied by 1024 + */ + unsigned green_y; + + /** + * The x-value (CIE xyY) of the monitor's + * blue colour, multiplied by 1024 + */ + unsigned blue_x; + + /** + * The y-value (CIE xyY) of the monitor's + * blue colour, multiplied by 1024 + */ + unsigned blue_y; + + /** + * The x-value (CIE xyY) of the monitor's + * default white point, multiplied by 1024 + */ + unsigned white_x; + + /** + * The y-value (CIE xyY) of the monitor's + * default white point, multiplied by 1024 + */ + unsigned white_y; + + /** + * The number of stops in the red gamma ramp + */ + size_t red_size; + + /** + * The number of stops in the green gamma ramp + */ + size_t green_size; + + /** + * The number of stops in the blue gamma ramp + */ + size_t blue_size; + + /** + * `.red_size + .green_size + .blue_size` + * multiplied by the byte-size of each stop + */ + size_t ramps_size; + + /** + * The name of the output, will be its EDID + * if available, otherwise it will be the + * index of the partition, followed by a dot + * and the index of the CRTC within the + * partition, or if a name for the connector + * is available: the index of the partition + * followed by a dot and the name of the + * connector + */ + char *restrict name; + + /** + * The libgamma state for the output + */ + libgamma_crtc_state_t *restrict crtc; + + /** + * Saved gamma ramps + */ + union gamma_ramps saved_ramps; + + /** + * The table of all applied filters + */ + struct filter *restrict table_filters; + + /** + * `.table_sums[i]` is the resulting + * adjustment made when all filter + * from `.table_filters[0]` up to and + * including `.table_filters[i]` has + * been applied + */ + union gamma_ramps *restrict table_sums; + + /** + * The number of elements allocated + * for `.table_filters` and for `.table_sums` + */ + size_t table_alloc; + + /** + * The number of elements stored in + * `.table_filters` and in `.table_sums` + */ + size_t table_size; +}; + +/** + * Free all resources allocated to an output. + * The allocation of `output` itself is not freed, + * nor is its the libgamma destroyed. + * + * @param this The output + */ +GCC_ONLY(__attribute__((__nonnull__))) +void output_destroy(struct output *restrict this); + +/** + * Marshal an output + * + * @param this The output + * @param buf Output buffer for the marshalled output, + * `NULL` just measure how large the buffers + * needs to be + * @return The number of marshalled byte + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +size_t output_marshal(const struct output *restrict this, void *restrict buf); + +/** + * Unmarshal an output + * + * @param this Output for the output + * @param buf Buffer with the marshalled output + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t output_unmarshal(struct output *restrict this, const void *restrict buf); + +/** + * Compare to outputs by the names of their respective CRTC:s + * + * @param a Return -1 if this one is lower + * @param b Return +1 if this one is higher + * @return See description of `a` and `b`, + * 0 if returned if they are the same + */ +GCC_ONLY(__attribute__((pure, __nonnull__))) +int output_cmp_by_name(const void *restrict a, const void *restrict b); + +/** + * Find an output by its name + * + * @param key The name of the output + * @param base The array of outputs + * @param n The number of elements in `base` + * @return Output find in `base`, `NULL` if not found + */ +GCC_ONLY(__attribute__((pure, __nonnull__))) +struct output *output_find_by_name(const char *restrict key, struct output *restrict base, size_t n); + +#endif diff --git a/types-ramps.c b/types-ramps.c new file mode 100644 index 0000000..a80f66b --- /dev/null +++ b/types-ramps.c @@ -0,0 +1,86 @@ +/* See LICENSE file for copyright and license details. */ +#include "types-ramps.h" + +#include + +#include +#include +#include + + +/** + * The name of the process + */ +extern char *restrict argv0; + + +/** + * Marshal a ramp trio + * + * @param this The ramps + * @param buf Output buffer for the marshalled ramps, + * `NULL` just measure how large the buffers + * needs to be + * @param ramps_size The byte-size of ramps + * @return The number of marshalled byte + */ +size_t +gamma_ramps_marshal(const union gamma_ramps *restrict this, void *restrict buf, size_t ramps_size) +{ + if (buf) + memcpy(buf, this->u8.red, ramps_size); + return ramps_size; +} + + +/** + * Unmarshal a ramp trio + * + * @param this Output for the ramps, `.red_size`, `.green_size`, + * and `.blue_size` must already be set + * @param buf Buffer with the marshalled ramps + * @param ramps_size The byte-size of ramps + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +gamma_ramps_unmarshal(union gamma_ramps *restrict this, const void *restrict buf, size_t ramps_size) +{ + size_t depth = ramps_size / (this->u8.red_size + this->u8.green_size + this->u8.blue_size); + int r = 0; + + switch (depth) { + case 1: + r = libgamma_gamma_ramps8_initialise(&this->u8); + break; + + case 2: + r = libgamma_gamma_ramps16_initialise(&this->u16); + break; + + case 4: + r = libgamma_gamma_ramps32_initialise(&this->u32); + break; + + case 8: + r = libgamma_gamma_ramps64_initialise(&this->u64); + break; + + default: + if (depth == sizeof(float)) + r = libgamma_gamma_rampsf_initialise(&this->f); + else if (depth == sizeof(double)) + r = libgamma_gamma_rampsd_initialise(&this->d); + else + abort(); + break; + } + + if (r) { + libgamma_perror(argv0, r); + errno = 0; + return 0; + } + + memcpy(this->u8.red, buf, ramps_size); + return ramps_size; +} diff --git a/types-ramps.h b/types-ramps.h new file mode 100644 index 0000000..e8e6956 --- /dev/null +++ b/types-ramps.h @@ -0,0 +1,75 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef TYPES_RAMPS_H +#define TYPES_RAMPS_H + +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Gamma ramps union for all + * lbigamma gamma ramps types + */ +union gamma_ramps { + /** + * Ramps with 8-bit value + */ + libgamma_gamma_ramps8_t u8; + + /** + * Ramps with 16-bit value + */ + libgamma_gamma_ramps16_t u16; + + /** + * Ramps with 32-bit value + */ + libgamma_gamma_ramps32_t u32; + + /** + * Ramps with 64-bit value + */ + libgamma_gamma_ramps64_t u64; + + /** + * Ramps with `float` value + */ + libgamma_gamma_rampsf_t f; + + /** + * Ramps with `double` value + */ + libgamma_gamma_rampsd_t d; +}; + +/** + * Marshal a ramp trio + * + * @param this The ramps + * @param buf Output buffer for the marshalled ramps, + * `NULL` just measure how large the buffers + * needs to be + * @param ramps_size The byte-size of ramps + * @return The number of marshalled byte + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +size_t gamma_ramps_marshal(const union gamma_ramps *restrict this, void *restrict buf, size_t ramps_size); + +/** + * Unmarshal a ramp trio + * + * @param this Output for the ramps + * @param buf Buffer with the marshalled ramps + * @param ramps_size The byte-size of ramps + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t gamma_ramps_unmarshal(union gamma_ramps *restrict this, const void *restrict buf, size_t ramps_size); + +#endif diff --git a/types-ring.c b/types-ring.c new file mode 100644 index 0000000..6cceea1 --- /dev/null +++ b/types-ring.c @@ -0,0 +1,193 @@ +/* See LICENSE file for copyright and license details. */ +#include "types-ring.h" + +#include +#include + + +/** + * Initialise a ring buffer + * + * @param this The ring buffer + */ +void +ring_initialise(struct ring *restrict this) +{ + this->start = 0; + this->end = 0; + this->size = 0; + this->buffer = NULL; +} + + +#if defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-align" +#endif + + +/** + * Marshal a ring buffer + * + * @param this The ring buffer + * @param buf Output buffer for the marshalled data, + * `NULL` to only measure how large buffer + * is needed + * @return The number of marshalled bytes + */ +size_t +ring_marshal(const struct ring *restrict this, void *restrict buf) +{ + size_t off = 0, n = this->end - this->start; + char *bs = buf; + + if (bs) + *(size_t *)&bs[off] = n; + off += sizeof(size_t); + + if (bs) + memcpy(&bs[off], this->buffer + this->start, n); + off += n; + + return off; +} + + +/** + * Unmarshal a ring buffer + * + * @param this Output parameter for the ring buffer + * @param buf Buffer with the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +size_t +ring_unmarshal(struct ring *restrict this, const void *restrict buf) +{ + size_t off = 0; + const char *bs = buf; + + ring_initialise(this); + + this->size = this->end = *(const size_t *)&bs[off]; + off += sizeof(size_t); + + if (this->end > 0) { + this->buffer = malloc(this->end); + if (!this->buffer) + return 0; + + memcpy(this->buffer, bs + off, this->end); + off += this->end; + } + + return off; +} + + +#if defined(__clang__) +# pragma GCC diagnostic pop +#endif + + +/** + * Append data to a ring buffer + * + * @param this The ring buffer + * @param data The new data + * @param n The number of bytes in `data` + * @return Zero on success, -1 on error + */ +int +ring_push(struct ring *restrict this, void *restrict data, size_t n) +{ + size_t used = 0; + char *restrict new; + + if (this->start == this->end) { + if (this->buffer) + used = this->size; + } else if (this->start > this->end) { + used = this->size - this->start + this->end; + } else { + used = this->start - this->end; + } + + if (used + n > this->size) { + new = malloc(used + n); + if (!new) + return -1; + if (this->buffer) { + if (this->start < this->end) { + memcpy(new, this->buffer + this->start, this->end - this->start); + } else { + memcpy(new, this->buffer + this->start, this->size - this->start); + memcpy(new + this->size - this->start, this->buffer, this->end); + } + } + memcpy(new + used, data, n); + this->buffer = new; + this->start = 0; + this->end = used + n; + this->size = used + n; + } else if (this->start >= this->end || this->end + n <= this->size) { + memcpy(&this->buffer[this->end], data, n); + this->end += n; + } else { + memcpy(&this->buffer[this->end], data, this->size - this->end); + data = &((char *)data)[this->size - this->end]; + n -= this->size - this->end; + memcpy(this->buffer, data, n); + this->end = n; + } + + return 0; +} + + +/** + * Get queued data from a ring buffer + * + * It can take up to two calls (with `ring_pop` between) + * to get all queued data + * + * @param this The ring buffer + * @param n Output parameter for the length + * of the returned segment + * @return The beginning of the queued data, + * `NULL` if there is nothing more + */ +void * +ring_peek(struct ring *restrict this, size_t *restrict n) +{ + if (!this->buffer) { + *n = 0; + return NULL; + } + + if (this->start < this->end) + *n = this->end - this->start; + else + *n = this->size - this->start; + return this->buffer + this->start; +} + + +/** + * Dequeue data from a ring bubber + * + * @param this The ring buffer + * @param n The number of bytes to dequeue + */ +void +ring_pop(struct ring *restrict this, size_t n) +{ + this->start += n; + this->start %= this->size; + if (this->start == this->end) { + free(this->buffer); + this->start = 0; + this->end = 0; + this->size = 0; + this->buffer = NULL; + } +} diff --git a/types-ring.h b/types-ring.h new file mode 100644 index 0000000..c8b5f77 --- /dev/null +++ b/types-ring.h @@ -0,0 +1,131 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef TYPES_RING_H +#define TYPES_RING_H + +#include + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Ring buffer + */ +struct ring { + /** + * Buffer for the data + */ + char *restrict buffer; + + /** + * The first set byte in `.buffer` + */ + size_t start; + + /** + * The last set byte in `.buffer`, plus 1 + */ + size_t end; + + /** + * The size of `.buffer` + */ + size_t size; +}; + +/** + * Initialise a ring buffer + * + * @param this The ring buffer + */ +GCC_ONLY(__attribute__((__nonnull__))) +void ring_initialise(struct ring *restrict this); + +/** + * Marshal a ring buffer + * + * @param this The ring buffer + * @param buf Output buffer for the marshalled data, + * `NULL` to only measure how large buffer + * is needed + * @return The number of marshalled bytes + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +size_t ring_marshal(const struct ring *restrict this, void *restrict buf); + +/** + * Unmarshal a ring buffer + * + * @param this Output parameter for the ring buffer + * @param buf Buffer with the marshalled data + * @return The number of unmarshalled bytes, 0 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +size_t ring_unmarshal(struct ring *restrict this, const void *restrict buf); + +/** + * Append data to a ring buffer + * + * @param this The ring buffer + * @param data The new data + * @param n The number of bytes in `data` + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__(1)))) +int ring_push(struct ring *restrict this, void *restrict data, size_t n); + +/** + * Get queued data from a ring buffer + * + * It can take up to two calls (with `ring_pop` between) + * to get all queued data + * + * @param this The ring buffer + * @param n Output parameter for the length + * of the returned segment + * @return The beginning of the queued data, + * `NULL` if there is nothing more + */ +GCC_ONLY(__attribute__((__nonnull__))) +void *ring_peek(struct ring *restrict this, size_t *restrict n); + +/** + * Dequeue data from a ring bubber + * + * @param this The ring buffer + * @param n The number of bytes to dequeue + */ +GCC_ONLY(__attribute__((__nonnull__))) +void ring_pop(struct ring *restrict this, size_t n); + +/** + * Release resource allocated to a ring buffer + * + * @param this The ring buffer + */ +GCC_ONLY(__attribute__((__nonnull__))) +static inline void +ring_destroy(struct ring *restrict this) +{ + free(this->buffer); +} + +/** + * Check whether there is more data waiting + * in a ring buffer + * + * @param this The ring buffer + * @return 1 if there is more data, 0 otherwise + */ +GCC_ONLY(__attribute__((__nonnull__))) +static inline int +ring_have_more(struct ring *restrict this) +{ + return !!this->buffer; +} + +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..a6a97a9 --- /dev/null +++ b/util.c @@ -0,0 +1,274 @@ +/* See LICENSE file for copyright and license details. */ +#include "util.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + + +/** + * Duplicate a memory segment + * + * @param src The memory segment, must not be `NULL` + * @param n The size of the memory segment, must not be zero + * @return The duplicate of the memory segment, + * `NULL` on error + */ +void * +memdup(const void *restrict src, size_t n) +{ + void *dest = malloc(n); + if (!dest) + return NULL; + memcpy(dest, src, n); + return dest; +} + + +/** + * Read an entire file + * + * Not cancelled by `EINTR` + * + * @param fd The file descriptor + * @param n Output for the size of the file + * @return The read content, plus a NUL byte at + * the end (not counted in `*n`) + */ +void * +nread(int fd, size_t *restrict n) +{ + size_t size = 32; + ssize_t got; + struct stat st; + char *buf, *new; + + *n = 0; + + if (!fstat(fd, &st)) + size = st.st_size <= 0 ? 32 : (size_t)(st.st_size); + + buf = malloc(size + 1); + if (!buf) + return NULL; + + for (;;) { + if (*n == size) { + new = realloc(buf, (size <<= 1) + 1); + if (!new) + goto fail; + buf = new; + } + + got = read(fd, buf + *n, size - *n); + if (got <= 0) { + if (!got) + break; + if (errno == EINTR) + continue; + goto fail; + } + *n += (size_t)got; + } + + buf[*n] = '\0'; + return buf; + +fail: + free(buf); + return NULL; +} + + +/** + * Write an entire buffer to a file + * + * Not cancelled by `EINTR` + * + * @param fd The file descriptor + * @param buf The buffer which shall be written to the fail + * @param n The size of the buffer + * @return The number of written bytes, less than `n` + * on error, cannot exceed `n` + */ +size_t +nwrite(int fd, const void *restrict buf, size_t n) +{ + const char *restrict bs = buf; + ssize_t wrote; + size_t ptr = 0; + + while (ptr < n) { + wrote = write(fd, bs + ptr, n - ptr); + if (wrote <= 0) { + if (wrote < 0 && errno == EINTR) + continue; + return ptr; + } + ptr += (size_t)wrote; + } + + return ptr; +} + + +/** + * Perform a timed suspention of the process. + * The process resumes when the timer expires, + * or when it is interrupted. + * + * @param ms The number of milliseconds to sleep, + * must be less than 1000 + */ +void +msleep(unsigned ms) +{ + struct timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = (long)ms * 1000000L; + if (clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL) == ENOTSUP) + nanosleep(&ts, NULL); +} + + +/** + * Check whether a NUL-terminated string is encoded in UTF-8 + * + * @param string The string + * @return Zero if good, -1 on encoding error + */ +int +verify_utf8(const char *restrict string) +{ + static long BYTES_TO_MIN_BITS[] = {0, 0, 8, 12, 17, 22, 37}; + static long BYTES_TO_MAX_BITS[] = {0, 7, 11, 16, 21, 26, 31}; + long int bytes = 0, read_bytes = 0, bits = 0, c, character = 0; + + /* min bits max bits + 0....... 0 7 + 110..... 10...... 8 11 + 1110.... 10...... 10...... 12 16 + 11110... 10...... 10...... 10...... 17 21 + 111110.. 10...... 10...... 10...... 10...... 22 26 + 1111110. 10...... 10...... 10...... 10...... 10...... 27 31 + */ + + while ((c = (long)(*string++))) { + if (!read_bytes) { + /* First byte of the character. */ + + if ((c & 0x80) == 0x00) { + /* Single-byte character. */ + continue; + } + + if ((c & 0xC0) == 0x80) { + /* Single-byte character marked as multibyte, or + a non-first byte in a multibyte character. */ + return -1; + } + + /* Multibyte character. */ + while ((c & 0x80)) { + bytes++; + c <<= 1; + } + read_bytes = 1; + character = c & 0x7F; + if (bytes > 6) { + /* 31-bit characters can be encoded with 6-bytes, + and UTF-8 does not cover higher code points. */ + return -1; + } + } else { + /* Not first byte of the character. */ + + if ((c & 0xC0) != 0x80) { + /* Beginning of new character before a + multibyte character has ended. */ + return -1; + } + + character = (character << 6) | (c & 0x7F); + + if (++read_bytes < bytes) { + /* Not at last byte yet. */ + continue; + } + + /* Check that the character is not unnecessarily long. */ + while (character) { + character >>= 1, bits++; + } + if ((bits < BYTES_TO_MIN_BITS[bytes]) || (BYTES_TO_MAX_BITS[bytes] < bits)) { + return -1; + } + + read_bytes = bytes = bits = 0; + } + } + + /* Make sure we did not stop at the middle of a multibyte character. */ + return !read_bytes ? 0 : -1; +} + + +/** + * Make identity mapping ramps + * + * @param ramps Output parameter for the ramps + * @param output The output for which the ramps shall be configured + * @return Zero on success, -1 on error + */ +int +make_plain_ramps(union gamma_ramps *restrict ramps, struct output *restrict output) +{ + COPY_RAMP_SIZES(&ramps->u8, output); + switch (output->depth) { + case 8: + if (libgamma_gamma_ramps8_initialise(&(ramps->u8))) + return -1; + libclut_start_over(&ramps->u8, UINT8_MAX, uint8_t, 1, 1, 1); + break; + + case 16: + if (libgamma_gamma_ramps16_initialise(&(ramps->u16))) + return -1; + libclut_start_over(&ramps->u16, UINT16_MAX, uint16_t, 1, 1, 1); + break; + + case 32: + if (libgamma_gamma_ramps32_initialise(&(ramps->u32))) + return -1; + libclut_start_over(&ramps->u32, UINT32_MAX, uint32_t, 1, 1, 1); + break; + + case 64: + if (libgamma_gamma_ramps64_initialise(&(ramps->u64))) + return -1; + libclut_start_over(&ramps->u64, UINT64_MAX, uint64_t, 1, 1, 1); + break; + + case -1: + if (libgamma_gamma_rampsf_initialise(&(ramps->f))) + return -1; + libclut_start_over(&ramps->f, 1.0f, float, 1, 1, 1); + break; + + case -2: + if (libgamma_gamma_rampsd_initialise(&(ramps->d))) + return -1; + libclut_start_over(&ramps->d, (double)1, double, 1, 1, 1); + break; + + default: + abort(); + } + return 0; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..0901449 --- /dev/null +++ b/util.h @@ -0,0 +1,81 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef UTIL_H +#define UTIL_H + +#include "types-output.h" + +#ifndef GCC_ONLY +# if defined(__GNUC__) && !defined(__clang__) +# define GCC_ONLY(...) __VA_ARGS__ +# else +# define GCC_ONLY(...) /* nothing */ +# endif +#endif + +/** + * Duplicate a memory segment + * + * @param src The memory segment, must not be `NULL` + * @param n The size of the memory segment, must not be zero + * @return The duplicate of the memory segment, + * `NULL` on error + */ +GCC_ONLY(__attribute__((__malloc__, __nonnull__))) +void *memdup(const void *restrict src, size_t n); + +/** + * Read an entire file + * + * Not cancelled by `EINTR` + * + * @param fd The file descriptor + * @param n Output for the size of the file + * @return The read content, plus a NUL byte at + * the end (not counted in `*n`) + */ +GCC_ONLY(__attribute__((__malloc__))) +void *nread(int fd, size_t *restrict n); + +/** + * Write an entire buffer to a file + * + * Not cancelled by `EINTR` + * + * @param fd The file descriptor + * @param buf The buffer which shall be written to the fail + * @param n The size of the buffer + * @return The number of written bytes, less than `n` + * on error, cannot exceed `n` + */ +size_t nwrite(int fd, const void *restrict buf, size_t n); + +/** + * Perform a timed suspention of the process. + * The process resumes when the timer expires, + * or when it is interrupted. + * + * @param ms The number of milliseconds to sleep, + * must be less than 1000 + */ +void msleep(unsigned ms); + +/** + * Check whether a NUL-terminated string is encoded in UTF-8 + * + * @param string The string + * @return Zero if good, -1 on encoding error + */ +GCC_ONLY(__attribute__((__pure__, __nonnull__))) +int verify_utf8(const char *restrict string); + +/** + * Make identity mapping ramps + * + * @param ramps Output parameter for the ramps + * @param output The output for which the ramps shall be configured + * @return Zero on success, -1 on error + */ +GCC_ONLY(__attribute__((__nonnull__))) +int make_plain_ramps(union gamma_ramps *restrict ramps, struct output *restrict output); + +#endif -- cgit v1.2.3-70-g09d2