diff options
author | Mattias Andrée <maandree@kth.se> | 2019-10-22 15:04:45 +0200 |
---|---|---|
committer | Mattias Andrée <maandree@kth.se> | 2019-10-22 15:04:45 +0200 |
commit | b13efce73e506b0feb4bb7c275c273a54ae6e716 (patch) | |
tree | 79f93e69b01d236e96037aa60332d214696e048b | |
parent | Fix NULL-pointer bug in get_pathname when running with -mdrm (diff) | |
download | coopgammad-1.3.tar.gz coopgammad-1.3.tar.bz2 coopgammad-1.3.tar.xz |
Change license and style, reorganise file, make makefile portable, and fix bugs1.3
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | COPYING | 15 | ||||
-rw-r--r-- | DEPENDENCIES | 1 | ||||
-rw-r--r-- | LICENSE | 683 | ||||
-rw-r--r-- | Makefile | 139 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | arg.h (renamed from src/arg.h) | 0 | ||||
-rw-r--r-- | communication.c | 159 | ||||
-rw-r--r-- | communication.h (renamed from src/communication.h) | 73 | ||||
-rw-r--r-- | config.mk | 12 | ||||
-rw-r--r-- | coopgammad.1 (renamed from doc/coopgammad.1) | 1 | ||||
-rw-r--r-- | coopgammad.c | 875 | ||||
-rw-r--r-- | servers-coopgamma.c | 524 | ||||
-rw-r--r-- | servers-coopgamma.h (renamed from src/servers/coopgamma.h) | 51 | ||||
-rw-r--r-- | servers-crtc.c | 312 | ||||
-rw-r--r-- | servers-crtc.h (renamed from src/servers/crtc.h) | 43 | ||||
-rw-r--r-- | servers-gamma.c | 381 | ||||
-rw-r--r-- | servers-gamma.h | 60 | ||||
-rw-r--r-- | servers-kernel.c | 363 | ||||
-rw-r--r-- | servers-kernel.h | 64 | ||||
-rw-r--r-- | servers-master.c | 370 | ||||
-rw-r--r-- | servers-master.h | 17 | ||||
-rw-r--r-- | src/communication.c | 175 | ||||
-rw-r--r-- | src/coopgammad.c | 895 | ||||
-rw-r--r-- | src/servers/coopgamma.c | 558 | ||||
-rw-r--r-- | src/servers/crtc.c | 325 | ||||
-rw-r--r-- | src/servers/gamma.c | 398 | ||||
-rw-r--r-- | src/servers/gamma.h | 85 | ||||
-rw-r--r-- | src/servers/kernel.c | 384 | ||||
-rw-r--r-- | src/servers/kernel.h | 85 | ||||
-rw-r--r-- | src/servers/master.c | 394 | ||||
-rw-r--r-- | src/servers/master.h | 36 | ||||
-rw-r--r-- | src/state.c | 638 | ||||
-rw-r--r-- | src/types/filter.c | 144 | ||||
-rw-r--r-- | src/types/filter.h | 138 | ||||
-rw-r--r-- | src/types/message.c | 572 | ||||
-rw-r--r-- | src/types/message.h | 160 | ||||
-rw-r--r-- | src/types/output.c | 335 | ||||
-rw-r--r-- | src/types/output.h | 308 | ||||
-rw-r--r-- | src/types/ramps.c | 98 | ||||
-rw-r--r-- | src/types/ramps.h | 102 | ||||
-rw-r--r-- | src/types/ring.c | 224 | ||||
-rw-r--r-- | src/types/ring.h | 152 | ||||
-rw-r--r-- | src/util.c | 316 | ||||
-rw-r--r-- | src/util.h | 115 | ||||
-rw-r--r-- | state.c | 612 | ||||
-rw-r--r-- | state.h (renamed from src/state.h) | 61 | ||||
-rw-r--r-- | types-filter.c | 125 | ||||
-rw-r--r-- | types-filter.h | 108 | ||||
-rw-r--r-- | types-message.c | 561 | ||||
-rw-r--r-- | types-message.h | 133 | ||||
-rw-r--r-- | types-output.c | 322 | ||||
-rw-r--r-- | types-output.h | 275 | ||||
-rw-r--r-- | types-ramps.c | 86 | ||||
-rw-r--r-- | types-ramps.h | 75 | ||||
-rw-r--r-- | types-ring.c | 193 | ||||
-rw-r--r-- | types-ring.h | 131 | ||||
-rw-r--r-- | util.c | 274 | ||||
-rw-r--r-- | util.h | 81 |
59 files changed, 6229 insertions, 7593 deletions
@@ -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 <http://www.gnu.org/licenses/>. diff --git a/DEPENDENCIES b/DEPENDENCIES index 7e4084b..c449ab4 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -18,4 +18,3 @@ INSTALL DEPENDENCIES: make coreutils - @@ -1,674 +1,15 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +ISC License - Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +© 2016, 2019 Mattias Andrée <maandree@kth.se> - 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. - - <one line to give the program's name and a brief idea of what it does.> - Copyright (C) <year> <name of author> - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - <program> Copyright (C) <year> <name of author> - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -<http://www.gnu.org/licenses/>. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -<http://www.gnu.org/philosophy/why-not-lgpl.html>. +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. @@ -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 @@ -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/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 <sys/socket.h> +#include <errno.h> +#include <string.h> + + +/** + * 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/src/communication.h b/communication.h index 244ba9d..b40022b 100644 --- a/src/communication.h +++ b/communication.h @@ -1,39 +1,18 @@ -/** - * 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 <http://www.gnu.org/licenses/>. - */ +/* See LICENSE file for copyright and license details. */ #ifndef COMMUNICATION_H #define COMMUNICATION_H - #include <stdio.h> #include <stdlib.h> - - #ifndef GCC_ONLY # if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ +# define GCC_ONLY(...) __VA_ARGS__ # else -# define GCC_ONLY(...) /* nothing */ +# define GCC_ONLY(...) /* nothing */ # endif #endif - - /** * Construct a message * @@ -43,18 +22,16 @@ * @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) +#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 @@ -64,7 +41,7 @@ * 0: Success (possibily delayed) * -1: An error occurred */ -#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__)) +#define send_error(...) ((send_error)(conn, message_id, __VA_ARGS__)) /** * Send a standard error @@ -74,9 +51,7 @@ * 0: Success (possibily delayed) * -1: An error occurred */ -#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__)) - - +#define send_errno(...) ((send_errno)(conn, message_id, __VA_ARGS__)) /** * Send a message @@ -89,7 +64,7 @@ * as success (ECONNRESET cause 1 to be returned), * and are handled appropriately. */ -int send_message(size_t conn, char* restrict buf, size_t n); +int send_message(size_t conn, char *restrict buf, size_t n); /** * Send a custom error without an error number @@ -101,8 +76,8 @@ int send_message(size_t conn, char* restrict buf, size_t n); * 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); +GCC_ONLY(__attribute__((__nonnull__))) +int (send_error)(size_t conn, const char *restrict message_id, const char *restrict desc); /** * Send a standard error @@ -114,8 +89,8 @@ int (send_error)(size_t conn, const char* restrict message_id, const char* restr * 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); +GCC_ONLY(__attribute__((__nonnull__))) +int (send_errno)(size_t conn, const char *restrict message_id, int number); /** * Continue sending the queued messages @@ -126,12 +101,10 @@ int (send_errno)(size_t conn, const char* restrict message_id, int number); * as success (ECONNRESET cause 1 to be returned), * and are handled appropriately. */ -static inline int continue_send(size_t conn) +static inline int +continue_send(size_t conn) { - return send_message(conn, NULL, 0); + 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/doc/coopgammad.1 b/coopgammad.1 index 58c8ae2..b8166b5 100644 --- a/doc/coopgammad.1 +++ b/coopgammad.1 @@ -117,4 +117,5 @@ implementation-wise. .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 <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +/** + * 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/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 <libclut.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/** + * 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/src/servers/coopgamma.h b/servers-coopgamma.h index 5a9f7d9..584e760 100644 --- a/src/servers/coopgamma.h +++ b/servers-coopgamma.h @@ -1,40 +1,19 @@ -/** - * 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 <http://www.gnu.org/licenses/>. - */ +/* See LICENSE file for copyright and license details. */ #ifndef SERVERS_COOPGAMMA_H #define SERVERS_COOPGAMMA_H - -#include "../types/output.h" +#include "types-output.h" #include <stddef.h> - - #ifndef GCC_ONLY # if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ +# define GCC_ONLY(...) __VA_ARGS__ # else -# define GCC_ONLY(...) /* nothing */ +# define GCC_ONLY(...) /* nothing */ # endif #endif - - /** * Handle a closed connection * @@ -55,10 +34,10 @@ int connection_closed(int client); * @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); +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 @@ -72,10 +51,9 @@ int handle_get_gamma(size_t conn, const char* restrict message_id, const char* r * @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); - +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 @@ -85,9 +63,8 @@ int handle_set_gamma(size_t conn, const char* restrict message_id, const char* r * @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); - +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 @@ -96,6 +73,4 @@ int flush_filters(struct output* restrict output, size_t first_updated); */ 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 <errno.h> +#include <string.h> + + +/** + * 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/src/servers/crtc.h b/servers-crtc.h index 68239d4..08e4b02 100644 --- a/src/servers/crtc.h +++ b/servers-crtc.h @@ -1,40 +1,19 @@ -/** - * 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 <http://www.gnu.org/licenses/>. - */ +/* See LICENSE file for copyright and license details. */ #ifndef SERVERS_CRTC_H #define SERVERS_CRTC_H - -#include "../types/output.h" +#include "types-output.h" #include <libgamma.h> - - #ifndef GCC_ONLY # if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ +# define GCC_ONLY(...) __VA_ARGS__ # else -# define GCC_ONLY(...) /* nothing */ +# define GCC_ONLY(...) /* nothing */ # endif #endif - - /** * Handle a ‘Command: enumerate-crtcs’ message * @@ -43,8 +22,8 @@ * @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); +GCC_ONLY(__attribute__((__nonnull__))) +int handle_enumerate_crtcs(size_t conn, const char *restrict message_id); /** * Get the name of a CRTC @@ -53,9 +32,8 @@ int handle_enumerate_crtcs(size_t conn, const char* restrict message_id); * @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); +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 @@ -78,8 +56,7 @@ int initialise_crtcs(void); * @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); - +int merge_state(struct output *restrict old_outputs, size_t old_outputs_n); /** * Disconnect from the site @@ -95,6 +72,4 @@ int disconnect(void); */ 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 <errno.h> +#include <string.h> + + +#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 <stddef.h> + +#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 <libgamma.h> + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +/** + * 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 <sys/socket.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +/** + * 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/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 <http://www.gnu.org/licenses/>. - */ -#include "communication.h" -#include "state.h" -#include "servers/coopgamma.h" - -#include <sys/socket.h> -#include <errno.h> -#include <string.h> - - - -/** - * 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/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 <http://www.gnu.org/licenses/>. - */ -#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 <sys/resource.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#include "coopgamma.h" -#include "gamma.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" -#include "../types/output.h" - -#include <libclut.h> - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - - - -/** - * 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/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 <http://www.gnu.org/licenses/>. - */ -#include "crtc.h" -#include "gamma.h" -#include "coopgamma.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" - -#include <errno.h> -#include <string.h> - - - -/** - * 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/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 <http://www.gnu.org/licenses/>. - */ -#include "gamma.h" -#include "crtc.h" -#include "../state.h" -#include "../communication.h" -#include "../util.h" - -#include <errno.h> -#include <string.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#ifndef SERVERS_GAMMA_H -#define SERVERS_GAMMA_H - - -#include "../types/output.h" - -#include <stddef.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#include "kernel.h" -#include "../state.h" -#include "../util.h" - -#include <libgamma.h> - -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/un.h> -#include <errno.h> -#include <fcntl.h> -#include <pwd.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#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 <http://www.gnu.org/licenses/>. - */ -#include "master.h" -#include "crtc.h" -#include "gamma.h" -#include "coopgamma.h" -#include "../util.h" -#include "../communication.h" -#include "../state.h" - -#include <sys/socket.h> -#include <errno.h> -#include <fcntl.h> -#include <poll.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#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 <http://www.gnu.org/licenses/>. - */ -#include "state.h" -#include "util.h" - -#include <inttypes.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - - - -/** - * 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/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 <http://www.gnu.org/licenses/>. - */ -#include "filter.h" -#include "../util.h" - -#include <stdlib.h> -#include <string.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#ifndef TYPES_FILTER_H -#define TYPES_FILTER_H - - -#include <stddef.h> -#include <stdint.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#include "message.h" -#include "../util.h" - -#include <sys/socket.h> -#include <errno.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#ifndef TYPES_MESSAGE_H -#define TYPES_MESSAGE_H - - -#include <stddef.h> -#include <limits.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#include "output.h" -#include "../util.h" - -#include <stdlib.h> -#include <string.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#ifndef TYPES_OUTPUT_H -#define TYPES_OUTPUT_H - - -#include <stddef.h> - -#include <libgamma.h> - -#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 <http://www.gnu.org/licenses/>. - */ -#include "ramps.h" - -#include <libclut.h> - -#include <errno.h> -#include <stdlib.h> -#include <string.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#ifndef TYPES_RAMPS_H -#define TYPES_RAMPS_H - - -#include <libgamma.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#include "ring.h" - -#include <stdlib.h> -#include <string.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#ifndef TYPES_RING_H -#define TYPES_RING_H - - -#include <stddef.h> - - - -#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 <http://www.gnu.org/licenses/>. - */ -#include "util.h" - -#include <libclut.h> - -#include <sys/stat.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <unistd.h> - - - -/** - * 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 <http://www.gnu.org/licenses/>. - */ -#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 - @@ -0,0 +1,612 @@ +/* See LICENSE file for copyright and license details. */ +#include "state.h" +#include "util.h" + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +/** + * 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 @@ -1,60 +1,39 @@ -/** - * 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 <http://www.gnu.org/licenses/>. - */ +/* 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 "types-message.h" +#include "types-ring.h" +#include "types-output.h" #include <libgamma.h> #include <stddef.h> #include <signal.h> - - #ifndef GCC_ONLY # if defined(__GNUC__) && !defined(__clang__) -# define GCC_ONLY(...) __VA_ARGS__ +# define GCC_ONLY(...) __VA_ARGS__ # else -# define GCC_ONLY(...) /* nothing */ +# define GCC_ONLY(...) /* nothing */ # endif #endif - - /** * The name of the process */ -extern char* restrict argv0; +extern char *restrict argv0; /** * The real pathname of the process's binary, * `NULL` if `argv0` is satisfactory */ -extern char* restrict argv0_real; +extern char *restrict argv0_real; /** * Array of all outputs */ -extern struct output* restrict outputs; +extern struct output *restrict outputs; /** * The nubmer of elements in `outputs` @@ -92,7 +71,7 @@ extern volatile sig_atomic_t connection; * Unused slots, with index less than `connections_used`, * should have the value -1 (negative) */ -extern int* restrict connections; +extern int *restrict connections; /** * The number of elements allocated for `connections` @@ -112,12 +91,12 @@ extern size_t connections_used; /** * The clients' connections' inbound-message buffers */ -extern struct message* restrict inbound; +extern struct message *restrict inbound; /** * The clients' connections' outbound-message buffers */ -extern struct ring* restrict outbound; +extern struct ring *restrict outbound; /** * Is the server connect to the display? @@ -134,7 +113,7 @@ extern int method; /** * The site's name, may be `NULL` */ -extern char* restrict sitename; +extern char *restrict sitename; /** * The libgamma site state @@ -144,20 +123,18 @@ extern libgamma_site_state_t site; /** * The libgamma partition states */ -extern libgamma_partition_state_t* restrict partitions; +extern libgamma_partition_state_t *restrict partitions; /** * The libgamma CRTC states */ -extern libgamma_crtc_state_t* restrict crtcs; +extern libgamma_crtc_state_t *restrict crtcs; /** * Preserve gamma ramps at priority 0? */ extern int preserve; - - /** * Dump the state to stderr */ @@ -176,7 +153,7 @@ void state_destroy(void); * this buffer needs * @return The number of marshalled bytes */ -size_t state_marshal(void* restrict buf); +size_t state_marshal(void *restrict buf); /** * Unmarshal the state @@ -184,9 +161,7 @@ size_t state_marshal(void* restrict buf); * @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); - +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 <stdlib.h> +#include <string.h> + + +/** + * 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 <stddef.h> +#include <stdint.h> + +#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 <sys/socket.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + + +/** + * 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 <stddef.h> +#include <limits.h> + +#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 <stdlib.h> +#include <string.h> + + +/** + * 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 <stddef.h> + +#include <libgamma.h> + +#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 <libclut.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + + +/** + * 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 <libgamma.h> + +#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 <stdlib.h> +#include <string.h> + + +/** + * 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 <stdlib.h> + +#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 @@ -0,0 +1,274 @@ +/* See LICENSE file for copyright and license details. */ +#include "util.h" + +#include <libclut.h> + +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + + +/** + * 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; +} @@ -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 |