diff --git a/CMakeLists.txt b/CMakeLists.txt index a73a56e10..2840cab39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories("${CMAKE_CURRENT_SOURCE_DIR}/duct/include") add_subdirectory(libc) add_subdirectory(libm) +add_subdirectory(libinfo) add_subdirectory(libmalloc) add_subdirectory(libsystem) add_subdirectory(keymgr) diff --git a/libinfo/CMakeLists.txt b/libinfo/CMakeLists.txt new file mode 100644 index 000000000..ecedd3927 --- /dev/null +++ b/libinfo/CMakeLists.txt @@ -0,0 +1,29 @@ +project(libinfo) + +cmake_minimum_required(VERSION 2.4.0) + +if(CMAKE_SIZEOF_VOID_P EQUAL 4 OR CMAKE_INSTALL_LIBDIR STREQUAL "lib32") + set(BITS 32) + add_definitions(-DTARGET_CPU_X86=1) +else(CMAKE_SIZEOF_VOID_P EQUAL 4 OR CMAKE_INSTALL_LIBDIR STREQUAL "lib32") + set(BITS 64) + add_definitions(-DTARGET_CPU_X86_64=1) +endif(CMAKE_SIZEOF_VOID_P EQUAL 4 OR CMAKE_INSTALL_LIBDIR STREQUAL "lib32") + +add_definitions(-DTARGET_OS_MAC=1) +add_definitions(-DHAVE_STDINT_H=1) +add_definitions(-D__APPLE__ -D__DYNAMIC__) +add_definitions(-D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=1080) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostdinc -D__DARWIN_UNIX03 -fPIC -w") +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -nostdlib -Wl,--unresolved-symbols=ignore-all") + +add_subdirectory(util.subproj) +add_subdirectory(dns.subproj) +#add_subdirectory(lookup.subproj) + +add_library(system_info SHARED $ + $ + #$ # lookup doesn't build yet +) +target_link_libraries(system_info system_c) diff --git a/libinfo/dns.subproj/CMakeLists.txt b/libinfo/dns.subproj/CMakeLists.txt new file mode 100644 index 000000000..d2a184109 --- /dev/null +++ b/libinfo/dns.subproj/CMakeLists.txt @@ -0,0 +1,18 @@ +project(libinfo-dns) + +cmake_minimum_required(VERSION 2.4.0) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale/FreeBSD) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/pthreads) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/stdtime/FreeBSD) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../external/libdispatch/) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lookup.subproj) + +set(info-dns_sources + herror.c + res_comp.c + res_query.c +) + +add_library(info-dns OBJECT ${info-dns_sources}) diff --git a/libinfo/lookup.subproj/CMakeLists.txt b/libinfo/lookup.subproj/CMakeLists.txt new file mode 100644 index 000000000..782b2a117 --- /dev/null +++ b/libinfo/lookup.subproj/CMakeLists.txt @@ -0,0 +1,30 @@ +project(libinfo-lookup) + +cmake_minimum_required(VERSION 2.4.0) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale/FreeBSD) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/pthreads) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/stdtime/FreeBSD) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../external/libdispatch/) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fblocks") + +set(info-lookup_sources + cache_module.c + ds_module.c + file_module.c + getnameinfo_link.c + ils.c + kvbuf.c + libinfo.c + mdns_module.c + search_module.c + si_data.c + si_getaddrinfo.c + si_module.c + thread_data.c +) + +add_library(info-lookup OBJECT ${info-lookup_sources}) diff --git a/libinfo/util.subproj/CMakeLists.txt b/libinfo/util.subproj/CMakeLists.txt new file mode 100644 index 000000000..b1c9f1e3a --- /dev/null +++ b/libinfo/util.subproj/CMakeLists.txt @@ -0,0 +1,15 @@ +project(libinfo-util) + +cmake_minimum_required(VERSION 2.4.0) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/locale/FreeBSD) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/pthreads) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../libc/stdtime/FreeBSD) + +set(info-util_sources + hton.c + rcmd.c +) + +add_library(info-util OBJECT ${info-util_sources}) diff --git a/libnotify/APPLE_LICENSE b/libnotify/APPLE_LICENSE new file mode 100644 index 000000000..84687a44b --- /dev/null +++ b/libnotify/APPLE_LICENSE @@ -0,0 +1,372 @@ +APPLE PUBLIC SOURCE LICENSE +Version 1.1 - April 19,1999 + +Please read this License carefully before downloading this software. +By downloading and using this software, you are agreeing to be bound +by the terms of this License. If you do not or cannot agree to the +terms of this License, please do not download or use the software. + +1. General; Definitions. This License applies to any program or other +work which Apple Computer, Inc. ("Apple") publicly announces as +subject to this Apple Public Source License and which contains a +notice placed by Apple identifying such program or work as "Original +Code" and stating that it is subject to the terms of this Apple Public +Source License version 1.1 (or subsequent version thereof), as it may +be revised from time to time by Apple ("License"). As used in this +License: + +1.1 "Affected Original Code" means only those specific portions of +Original Code that allegedly infringe upon any party's intellectual +property rights or are otherwise the subject of a claim of +infringement. + +1.2 "Applicable Patent Rights" mean: (a) in the case where Apple is +the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to Apple and (ii) that cover subject +matter contained in the Original Code, but only to the extent +necessary to use, reproduce and/or distribute the Original Code +without infringement; and (b) in the case where You are the grantor of +rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to You and (ii) that cover subject matter in Your +Modifications, taken alone or in combination with Original Code. + +1.3 "Covered Code" means the Original Code, Modifications, the +combination of Original Code and any Modifications, and/or any +respective portions thereof. + +1.4 "Deploy" means to use, sublicense or distribute Covered Code other +than for Your internal research and development (R&D), and includes +without limitation, any and all internal use or distribution of +Covered Code within Your business or organization except for R&D use, +as well as direct or indirect sublicensing or distribution of Covered +Code by You to any third party in any form or manner. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change +to, the substance and/or structure of Covered Code. When code is +released as a series of files, a Modification is: (a) any addition to +or deletion from the contents of a file containing Covered Code; +and/or (b) any new file or other representation of computer program +statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other +work as originally made available by Apple under this License, +including the Source Code of any updates or upgrades to such programs +or works made available by Apple under this License, and that has been +expressly identified by Apple as such in the header file(s) of such +work; and (b) the object code compiled from such Source Code and +originally made available by Apple under this License. + +1.8 "Source Code" means the human readable form of a program or other +work that is suitable for making modifications to it, including all +modules it contains, plus any associated interface definition files, +scripts used to control compilation and installation of an executable +(object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising +rights under this License. For legal entities, "You" or "Your" +includes any entity which controls, is controlled by, or is under +common control with, You, where "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of fifty percent +(50%) or more of the outstanding shares or beneficial ownership of +such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms +and conditions of this License, Apple hereby grants You, effective on +the date You accept this License and download the Original Code, a +world-wide, royalty-free, non- exclusive license, to the extent of +Apple's Applicable Patent Rights and copyrights covering the Original +Code, to do the following: + +2.1 You may use, copy, modify and distribute Original Code, with or +without Modifications, solely for Your internal research and +development, provided that You must in each instance: + +(a) retain and reproduce in all copies of Original Code the copyright +and other proprietary notices and disclaimers of Apple as they appear +in the Original Code, and keep intact all notices in the Original Code +that refer to this License; + +(b) include a copy of this License with every copy of Source Code of +Covered Code and documentation You distribute, and You may not offer +or impose any terms on such Source Code that alter or restrict this +License or the recipients' rights hereunder, except as permitted under +Section 6; and + +(c) completely and accurately document all Modifications that you have +made and the date of each such Modification, designate the version of +the Original Code you used, prominently include a file carrying such +information with the Modifications, and duplicate the notice in +Exhibit A in each file of the Source Code of all such Modifications. + +2.2 You may Deploy Covered Code, provided that You must in each + instance: + +(a) satisfy all the conditions of Section 2.1 with respect to the +Source Code of the Covered Code; + +(b) make all Your Deployed Modifications publicly available in Source +Code form via electronic distribution (e.g. download from a web site) +under the terms of this License and subject to the license grants set +forth in Section 3 below, and any additional terms You may choose to +offer under Section 6. You must continue to make the Source Code of +Your Deployed Modifications available for as long as you Deploy the +Covered Code or twelve (12) months from the date of initial +Deployment, whichever is longer; + +(c) if You Deploy Covered Code containing Modifications made by You, +inform others of how to obtain those Modifications by filling out and +submitting the information found at +http://www.apple.com/publicsource/modifications.html, if available; +and + +(d) if You Deploy Covered Code in object code, executable form only, +include a prominent notice, in the code itself as well as in related +documentation, stating that Source Code of the Covered Code is +available under the terms of this License with information on how and +where to obtain such Source Code. + +3. Your Grants. In consideration of, and as a condition to, the +licenses granted to You under this License: + +(a) You hereby grant to Apple and all third parties a non-exclusive, +royalty-free license, under Your Applicable Patent Rights and other +intellectual property rights owned or controlled by You, to use, +reproduce, modify, distribute and Deploy Your Modifications of the +same scope and extent as Apple's licenses under Sections 2.1 and 2.2; +and + +(b) You hereby grant to Apple and its subsidiaries a non-exclusive, +worldwide, royalty-free, perpetual and irrevocable license, under Your +Applicable Patent Rights and other intellectual property rights owned +or controlled by You, to use, reproduce, execute, compile, display, +perform, modify or have modified (for Apple and/or its subsidiaries), +sublicense and distribute Your Modifications, in any form, through +multiple tiers of distribution. + +4. Larger Works. You may create a Larger Work by combining Covered +Code with other code not governed by the terms of this License and +distribute the Larger Work as a single product. In each such +instance, You must make sure the requirements of this License are +fulfilled for the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in +Section 2, no other patent rights, express or implied, are granted by +Apple herein. Modifications and/or Larger Works may require +additional patent licenses from Apple which Apple may grant in its +sole discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee +for, warranty, support, indemnity or liability obligations and/or +other rights consistent with the scope of the license granted herein +("Additional Terms") to one or more recipients of Covered +Code. However, You may do so only on Your own behalf and as Your sole +responsibility, and not on behalf of Apple. You must obtain the +recipient's agreement that any such Additional Terms are offered by +You alone, and You hereby agree to indemnify, defend and hold Apple +harmless for any liability incurred by or claims asserted against +Apple by reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new +versions of this License from time to time. Each version will be +given a distinguishing version number. Once Original Code has been +published under a particular version of this License, You may continue +to use it under the terms of that version. You may also choose to use +such Original Code under the terms of any subsequent version of this +License published by Apple. No one other than Apple has the right to +modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Original Code may contain in whole or +in part pre-release, untested, or not fully tested works. The +Original Code may contain errors that could cause failures or loss of +data, and may be incomplete or contain inaccuracies. You expressly +acknowledge and agree that use of the Original Code, or any portion +thereof, is at Your sole and entire risk. THE ORIGINAL CODE IS +PROVIDED "AS IS" AND WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND +AND APPLE AND APPLE'S LICENSOR(S) (FOR THE PURPOSES OF SECTIONS 8 AND +9, APPLE AND APPLE'S LICENSOR(S) ARE COLLECTIVELY REFERRED TO AS +"APPLE") EXPRESSLY DISCLAIM ALL WARRANTIES AND/OR CONDITIONS, EXPRESS +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +AND/OR CONDITIONS OF MERCHANTABILITY OR SATISFACTORY QUALITY AND +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY +RIGHTS. APPLE DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE +ORIGINAL CODE WILL MEET YOUR REQUIREMENTS, OR THAT THE OPERATION OF +THE ORIGINAL CODE WILL BE UNINTERRUPTED OR ERROR- FREE, OR THAT +DEFECTS IN THE ORIGINAL CODE WILL BE CORRECTED. NO ORAL OR WRITTEN +INFORMATION OR ADVICE GIVEN BY APPLE OR AN APPLE AUTHORIZED +REPRESENTATIVE SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE +SCOPE OF THIS WARRANTY. You acknowledge that the Original Code is not +intended for use in the operation of nuclear facilities, aircraft +navigation, communication systems, or air traffic control machines in +which case the failure of the Original Code could lead to death, +personal injury, or severe physical or environmental damage. + +9. Liability. + +9.1 Infringement. If any portion of, or functionality implemented by, +the Original Code becomes the subject of a claim of infringement, +Apple may, at its option: (a) attempt to procure the rights necessary +for Apple and You to continue using the Affected Original Code; (b) +modify the Affected Original Code so that it is no longer infringing; +or (c) suspend Your rights to use, reproduce, modify, sublicense and +distribute the Affected Original Code until a final determination of +the claim is made by a court or governmental administrative agency of +competent jurisdiction and Apple lifts the suspension as set forth +below. Such suspension of rights will be effective immediately upon +Apple's posting of a notice to such effect on the Apple web site that +is used for implementation of this License. Upon such final +determination being made, if Apple is legally able, without the +payment of a fee or royalty, to resume use, reproduction, +modification, sublicensing and distribution of the Affected Original +Code, Apple will lift the suspension of rights to the Affected +Original Code by posting a notice to such effect on the Apple web site +that is used for implementation of this License. If Apple suspends +Your rights to Affected Original Code, nothing in this License shall +be construed to restrict You, at Your option and subject to applicable +law, from replacing the Affected Original Code with non-infringing +code or independently negotiating for necessary rights from such third +party. + +9.2 LIMITATION OF LIABILITY. UNDER NO CIRCUMSTANCES SHALL APPLE BE +LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES +ARISING OUT OF OR RELATING TO THIS LICENSE OR YOUR USE OR INABILITY TO +USE THE ORIGINAL CODE, OR ANY PORTION THEREOF, WHETHER UNDER A THEORY +OF CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY +OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF +ANY REMEDY. In no event shall Apple's total liability to You for all +damages under this License exceed the amount of fifty dollars +($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Apple Computer", "Mac OS X", "Mac +OS X Server" or any other trademarks or trade names belonging to Apple +(collectively "Apple Marks") and no Apple Marks may be used to endorse +or promote products derived from the Original Code other than as +permitted by and in strict compliance at all times with Apple's third +party trademark usage guidelines which are posted at +http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Apple retains all rights, title and interest in and to +the Original Code and any Modifications made by or on behalf of Apple +("Apple Modifications"), and such Apple Modifications will not be +automatically subject to this License. Apple may, at its sole +discretion, choose to license such Apple Modifications under this +License, or on different terms from those contained in this License or +may choose not to license them at all. Apple's development, use, +reproduction, modification, sublicensing and distribution of Covered +Code will not be subject to this License. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will + terminate: + +(a) automatically without notice from Apple if You fail to comply with +any term(s) of this License and fail to cure such breach within 30 +days of becoming aware of such breach; (b) immediately in the event of +the circumstances described in Section 13.5(b); or (c) automatically +without notice from Apple if You, at any time during the term of this +License, commence an action for patent infringement against Apple. + +12.2 Effect of Termination. Upon termination, You agree to +immediately stop any further use, reproduction, modification, +sublicensing and distribution of the Covered Code and to destroy all +copies of the Covered Code that are in your possession or control. +All sublicenses to the Covered Code which have been properly granted +prior to termination shall survive any termination of this License. +Provisions which, by their nature, should remain in effect beyond the +termination of this License shall survive, including but not limited +to Sections 3, 5, 8, 9, 10, 11, 12.2 and 13. Neither party will be +liable to the other for compensation, indemnity or damages of any sort +solely as a result of terminating this License in accordance with its +terms, and termination of this License will be without prejudice to +any other right or remedy of either party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights +in the Covered Code include only those rights customarily provided to +the public as defined in this License. This customary commercial +license in technical data and software is provided in accordance with +FAR 12.211 (Technical Data) and 12.212 (Computer Software) and, for +Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- +Commercial Items) and 227.7202-3 (Rights in Commercial Computer +Software or Computer Software Documentation). Accordingly, all U.S. +Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of +legal association between You and Apple, and You will not represent to +the contrary, whether expressly, by implication, appearance or +otherwise. + +13.3 Independent Development. Nothing in this License will impair +Apple's right to acquire, license, develop, have others develop for +it, market and/or distribute technology or products that perform the +same or similar functions as, or otherwise compete with, +Modifications, Larger Works, technology or products that You may +develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple to enforce any provision +of this License will not be deemed a waiver of future enforcement of +that or any other provision. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent +jurisdiction finds any provision of this License, or portion thereof, +to be unenforceable, that provision of the License will be enforced to +the maximum extent permissible so as to effect the economic benefits +and intent of the parties, and the remainder of this License will +continue in full force and effect. (b) Notwithstanding the foregoing, +if applicable law prohibits or restricts You from fully and/or +specifically complying with Sections 2 and/or 3 or prevents the +enforceability of either of those Sections, this License will +immediately terminate and You must immediately discontinue any use of +the Covered Code and destroy all copies of it that are in your +possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution +between You and Apple relating to this License shall take place in the +Northern District of California, and You and Apple hereby consent to +the personal jurisdiction of, and venue in, the state and federal +courts within that District with respect to this License. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the +entire agreement between the parties with respect to the subject +matter hereof. This License shall be governed by the laws of the +United States and the State of California, except that body of +California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following +clause applies: The parties hereby confirm that they have requested +that this License and all related documents be drafted in English. Les +parties ont exige que le present contrat et tous les documents +connexes soient rediges en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999 Apple Computer, Inc. All Rights +Reserved. This file contains Original Code and/or Modifications of +Original Code as defined in and that are subject to the Apple Public +Source License Version 1.1 (the "License"). You may not use this file +except in compliance with the License. Please obtain a copy of the +License at http://www.apple.com/publicsource and read it before using +this file. + +The Original Code and all software distributed under the License are +distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the +License for the specific language governing rights and limitations +under the License." diff --git a/libnotify/Libnotify.xcodeproj/project.pbxproj b/libnotify/Libnotify.xcodeproj/project.pbxproj new file mode 100644 index 000000000..ae4470aed --- /dev/null +++ b/libnotify/Libnotify.xcodeproj/project.pbxproj @@ -0,0 +1,698 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXAggregateTarget section */ + 3FA21AC7148AA93000099D2F /* cli_apps */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 3FA21AC8148AA93000099D2F /* Build configuration list for PBXAggregateTarget "cli_apps" */; + buildPhases = ( + 3F947784191C32DC00A93E8E /* No Simulator Man Pages */, + ); + dependencies = ( + 3FA21ACB148AA94A00099D2F /* PBXTargetDependency */, + 3FA21ACD148AA94A00099D2F /* PBXTargetDependency */, + ); + name = cli_apps; + productName = cli_apps; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 2D312B76102CA2E300F90022 /* libnotify.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D312B73102CA2E300F90022 /* libnotify.c */; }; + 2D312B77102CA2E300F90022 /* notify_client.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D312B74102CA2E300F90022 /* notify_client.c */; }; + 2D312B78102CA2E300F90022 /* table.c in Sources */ = {isa = PBXBuildFile; fileRef = 2D312B75102CA2E300F90022 /* table.c */; }; + 2D312B7A102CA30200F90022 /* notify_ipc.defs in Sources */ = {isa = PBXBuildFile; fileRef = 2D312B79102CA30200F90022 /* notify_ipc.defs */; settings = {ATTRIBUTES = (Client, ); }; }; + 2D312B7E102CA32500F90022 /* notify_keys.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D312B7C102CA32500F90022 /* notify_keys.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D312B7F102CA32500F90022 /* notify.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D312B7D102CA32500F90022 /* notify.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2D312B82102CA34D00F90022 /* libnotify.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D312B81102CA34D00F90022 /* libnotify.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 2D312B87102CA36C00F90022 /* table.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D312B85102CA36C00F90022 /* table.h */; }; + 2D38AA0A102CD88300D3D622 /* notify.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B92102CA38F00F90022 /* notify.3 */; }; + 2D38AA0B102CD89B00D3D622 /* notify_cancel.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B88102CA38F00F90022 /* notify_cancel.3 */; }; + 2D38AA0C102CD89B00D3D622 /* notify_check.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B89102CA38F00F90022 /* notify_check.3 */; }; + 2D38AA0D102CD8B800D3D622 /* notify_get_state.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8A102CA38F00F90022 /* notify_get_state.3 */; }; + 2D38AA0E102CD8B800D3D622 /* notify_is_valid_token.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D6D820D18DA602A0034E7B4 /* notify_is_valid_token.3 */; }; + 2D38AA0F102CD8B800D3D622 /* notify_post.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8B102CA38F00F90022 /* notify_post.3 */; }; + 2D38AA10102CD8B800D3D622 /* notify_register_check.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8C102CA38F00F90022 /* notify_register_check.3 */; }; + 2D38AA11102CD8B800D3D622 /* notify_register_dispatch.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8D102CA38F00F90022 /* notify_register_dispatch.3 */; }; + 2D38AA12102CD8B800D3D622 /* notify_register_file_descriptor.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8E102CA38F00F90022 /* notify_register_file_descriptor.3 */; }; + 2D38AA13102CD8B800D3D622 /* notify_register_mach_port.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B8F102CA38F00F90022 /* notify_register_mach_port.3 */; }; + 2D38AA14102CD8B800D3D622 /* notify_register_signal.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B90102CA38F00F90022 /* notify_register_signal.3 */; }; + 2D38AA15102CD8B800D3D622 /* notify_set_state.3 in Copy man3 Files */ = {isa = PBXBuildFile; fileRef = 2D312B91102CA38F00F90022 /* notify_set_state.3 */; }; + 2DCB287210D99ADA00DF3A8D /* notify_private.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DCB287110D99ADA00DF3A8D /* notify_private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 3FA21ACF148AAA5000099D2F /* notify_proc.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21A9C148AA7FA00099D2F /* notify_proc.c */; }; + 3FA21AD0148AAA5000099D2F /* notifyd.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21A9E148AA7FA00099D2F /* notifyd.c */; }; + 3FA21AD1148AAA5000099D2F /* pathwatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21AA0148AA7FA00099D2F /* pathwatch.c */; }; + 3FA21AD2148AAA5000099D2F /* service.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21AA2148AA7FA00099D2F /* service.c */; }; + 3FA21AD3148AAA5000099D2F /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21AA5148AA7FA00099D2F /* timer.c */; }; + 3FA21AD4148AAA5D00099D2F /* notifyd.8 in Install man page */ = {isa = PBXBuildFile; fileRef = 3FA21A9D148AA7FA00099D2F /* notifyd.8 */; }; + 3FA21AD5148AAA6E00099D2F /* notifyutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 3FA21AA9148AA82700099D2F /* notifyutil.c */; }; + 3FA21AD6148AAA7500099D2F /* notifyutil.1 in Install man page */ = {isa = PBXBuildFile; fileRef = 3FA21AA8148AA82700099D2F /* notifyutil.1 */; }; + 3FA21AD8148AAABE00099D2F /* com.apple.notifyd.plist in Install launchd.plist */ = {isa = PBXBuildFile; fileRef = 3FA21A99148AA7FA00099D2F /* com.apple.notifyd.plist */; }; + 3FA21AE6148AAEAC00099D2F /* notify_ipc.defs in Sources */ = {isa = PBXBuildFile; fileRef = 2D312B79102CA30200F90022 /* notify_ipc.defs */; settings = {ATTRIBUTES = (Client, Server, ); }; }; + 3FD0DBAD148AB12000C50811 /* libbsm.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FD0DBAC148AB12000C50811 /* libbsm.dylib */; }; + FC7B7A53155781930064D203 /* notify_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = FC7B7A52155781930064D203 /* notify_internal.h */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3FA21ACA148AA94A00099D2F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3FA21AAF148AA8E300099D2F; + remoteInfo = notifyd; + }; + 3FA21ACC148AA94A00099D2F /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3FA21ABD148AA8F000099D2F; + remoteInfo = notifyutil; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 2D38AA09102CD87C00D3D622 /* Copy man3 Files */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = "$(INSTALL_PATH_PREFIX)/usr/share/man/man3"; + dstSubfolderSpec = 0; + files = ( + 2D38AA0A102CD88300D3D622 /* notify.3 in Copy man3 Files */, + 2D38AA0B102CD89B00D3D622 /* notify_cancel.3 in Copy man3 Files */, + 2D38AA0C102CD89B00D3D622 /* notify_check.3 in Copy man3 Files */, + 2D38AA0D102CD8B800D3D622 /* notify_get_state.3 in Copy man3 Files */, + 2D38AA0E102CD8B800D3D622 /* notify_is_valid_token.3 in Copy man3 Files */, + 2D38AA0F102CD8B800D3D622 /* notify_post.3 in Copy man3 Files */, + 2D38AA10102CD8B800D3D622 /* notify_register_check.3 in Copy man3 Files */, + 2D38AA11102CD8B800D3D622 /* notify_register_dispatch.3 in Copy man3 Files */, + 2D38AA12102CD8B800D3D622 /* notify_register_file_descriptor.3 in Copy man3 Files */, + 2D38AA13102CD8B800D3D622 /* notify_register_mach_port.3 in Copy man3 Files */, + 2D38AA14102CD8B800D3D622 /* notify_register_signal.3 in Copy man3 Files */, + 2D38AA15102CD8B800D3D622 /* notify_set_state.3 in Copy man3 Files */, + ); + name = "Copy man3 Files"; + runOnlyForDeploymentPostprocessing = 1; + }; + 3FA21AAE148AA8E300099D2F /* Install man page */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(INSTALL_PATH_PREFIX)/usr/share/man/man8"; + dstSubfolderSpec = 0; + files = ( + 3FA21AD4148AAA5D00099D2F /* notifyd.8 in Install man page */, + ); + name = "Install man page"; + runOnlyForDeploymentPostprocessing = 1; + }; + 3FA21ABC148AA8F000099D2F /* Install man page */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(INSTALL_PATH_PREFIX)/usr/share/man/man1"; + dstSubfolderSpec = 0; + files = ( + 3FA21AD6148AAA7500099D2F /* notifyutil.1 in Install man page */, + ); + name = "Install man page"; + runOnlyForDeploymentPostprocessing = 1; + }; + 3FA21AD7148AAAA600099D2F /* Install launchd.plist */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = "$(INSTALL_PATH_PREFIX)/System/Library/LaunchDaemons"; + dstSubfolderSpec = 0; + files = ( + 3FA21AD8148AAABE00099D2F /* com.apple.notifyd.plist in Install launchd.plist */, + ); + name = "Install launchd.plist"; + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2D312B73102CA2E300F90022 /* libnotify.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = libnotify.c; sourceTree = ""; }; + 2D312B74102CA2E300F90022 /* notify_client.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = notify_client.c; sourceTree = ""; }; + 2D312B75102CA2E300F90022 /* table.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = table.c; sourceTree = ""; }; + 2D312B79102CA30200F90022 /* notify_ipc.defs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.mig; path = notify_ipc.defs; sourceTree = ""; }; + 2D312B7C102CA32500F90022 /* notify_keys.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; path = notify_keys.h; sourceTree = ""; }; + 2D312B7D102CA32500F90022 /* notify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notify.h; sourceTree = ""; }; + 2D312B81102CA34D00F90022 /* libnotify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libnotify.h; sourceTree = ""; }; + 2D312B84102CA36C00F90022 /* notify_ipc_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notify_ipc_types.h; sourceTree = ""; }; + 2D312B85102CA36C00F90022 /* table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = table.h; sourceTree = ""; }; + 2D312B88102CA38F00F90022 /* notify_cancel.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_cancel.3; sourceTree = ""; }; + 2D312B89102CA38F00F90022 /* notify_check.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_check.3; sourceTree = ""; }; + 2D312B8A102CA38F00F90022 /* notify_get_state.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_get_state.3; sourceTree = ""; }; + 2D312B8B102CA38F00F90022 /* notify_post.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_post.3; sourceTree = ""; }; + 2D312B8C102CA38F00F90022 /* notify_register_check.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_register_check.3; sourceTree = ""; }; + 2D312B8D102CA38F00F90022 /* notify_register_dispatch.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_register_dispatch.3; sourceTree = ""; }; + 2D312B8E102CA38F00F90022 /* notify_register_file_descriptor.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_register_file_descriptor.3; sourceTree = ""; }; + 2D312B8F102CA38F00F90022 /* notify_register_mach_port.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_register_mach_port.3; sourceTree = ""; }; + 2D312B90102CA38F00F90022 /* notify_register_signal.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_register_signal.3; sourceTree = ""; }; + 2D312B91102CA38F00F90022 /* notify_set_state.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_set_state.3; sourceTree = ""; }; + 2D312B92102CA38F00F90022 /* notify.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify.3; sourceTree = ""; }; + 2D6D820D18DA602A0034E7B4 /* notify_is_valid_token.3 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notify_is_valid_token.3; sourceTree = ""; }; + 2DCB287110D99ADA00DF3A8D /* notify_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notify_private.h; sourceTree = ""; }; + 2DF9EA0B102CF33400DE9E8D /* APPLE_LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = APPLE_LICENSE; sourceTree = ""; }; + 3F82235D12B18551005DD509 /* libnotify.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = libnotify.xcconfig; sourceTree = ""; }; + 3F8223B412B18877005DD509 /* libsystem_notify.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libsystem_notify.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + 3F947780191C322100A93E8E /* no-sim-man.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "no-sim-man.sh"; sourceTree = ""; }; + 3F947781191C322100A93E8E /* sim-compat-symlink.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "sim-compat-symlink.sh"; sourceTree = ""; }; + 3F999961185C474E00EAD3A0 /* base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = base.xcconfig; sourceTree = ""; }; + 3F999963185C474E00EAD3A0 /* notifyd.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = notifyd.xcconfig; sourceTree = ""; }; + 3F999964185C474E00EAD3A0 /* notifyutil.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = notifyutil.xcconfig; sourceTree = ""; }; + 3FA21A99148AA7FA00099D2F /* com.apple.notifyd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.notifyd.plist; sourceTree = ""; }; + 3FA21A9A148AA7FA00099D2F /* notify.conf */ = {isa = PBXFileReference; lastKnownFileType = text; path = notify.conf; sourceTree = ""; }; + 3FA21A9B148AA7FA00099D2F /* notify.conf.iPhone */ = {isa = PBXFileReference; lastKnownFileType = text; name = notify.conf.iPhone; path = ../notify.conf.iPhone; sourceTree = ""; }; + 3FA21A9C148AA7FA00099D2F /* notify_proc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = notify_proc.c; sourceTree = ""; }; + 3FA21A9D148AA7FA00099D2F /* notifyd.8 */ = {isa = PBXFileReference; lastKnownFileType = text; path = notifyd.8; sourceTree = ""; }; + 3FA21A9E148AA7FA00099D2F /* notifyd.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = notifyd.c; sourceTree = ""; }; + 3FA21A9F148AA7FA00099D2F /* notifyd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = notifyd.h; sourceTree = ""; }; + 3FA21AA0148AA7FA00099D2F /* pathwatch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pathwatch.c; sourceTree = ""; }; + 3FA21AA1148AA7FA00099D2F /* pathwatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = pathwatch.h; sourceTree = ""; }; + 3FA21AA2148AA7FA00099D2F /* service.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = service.c; sourceTree = ""; }; + 3FA21AA3148AA7FA00099D2F /* service.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = service.h; sourceTree = ""; }; + 3FA21AA4148AA7FA00099D2F /* table.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = table.h; sourceTree = ""; }; + 3FA21AA5148AA7FA00099D2F /* timer.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = timer.c; sourceTree = ""; }; + 3FA21AA6148AA7FA00099D2F /* timer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = timer.h; sourceTree = ""; }; + 3FA21AA8148AA82700099D2F /* notifyutil.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = notifyutil.1; sourceTree = ""; }; + 3FA21AA9148AA82700099D2F /* notifyutil.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = notifyutil.c; sourceTree = ""; }; + 3FA21AB0148AA8E300099D2F /* notifyd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = notifyd; sourceTree = BUILT_PRODUCTS_DIR; }; + 3FA21ABE148AA8F000099D2F /* notifyutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = notifyutil; sourceTree = BUILT_PRODUCTS_DIR; }; + 3FA21ADD148AABA900099D2F /* mk_notify_conf.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = mk_notify_conf.sh; sourceTree = ""; }; + 3FA21ADF148AACA000099D2F /* notify.conf.MacOSX */ = {isa = PBXFileReference; lastKnownFileType = text; name = notify.conf.MacOSX; path = notifyd/notify.conf.MacOSX; sourceTree = SOURCE_ROOT; }; + 3FD0DBAC148AB12000C50811 /* libbsm.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbsm.dylib; path = /usr/lib/libbsm.dylib; sourceTree = ""; }; + FC7B7A52155781930064D203 /* notify_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = notify_internal.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3FA21AAD148AA8E300099D2F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3FD0DBAD148AB12000C50811 /* libbsm.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3FA21ABB148AA8F000099D2F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D289987405E68DCB004EDB86 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* Libnotify */ = { + isa = PBXGroup; + children = ( + 3F94777F191C322100A93E8E /* xcodescripts */, + 3F999960185C474E00EAD3A0 /* xcodeconfig */, + 2D312B79102CA30200F90022 /* notify_ipc.defs */, + 3FA21A97148AA7CD00099D2F /* libsystem_notify */, + 3FA21AA7148AA82700099D2F /* notifyutil */, + 3FA21A98148AA7FA00099D2F /* notifyd */, + 2DF9EA0A102CF31700DE9E8D /* Additional Files */, + 3FA21ACE148AAA0D00099D2F /* Products */, + ); + name = Libnotify; + sourceTree = ""; + }; + 2D312B72102CA2C400F90022 /* Source */ = { + isa = PBXGroup; + children = ( + 2D312B73102CA2E300F90022 /* libnotify.c */, + 2D312B74102CA2E300F90022 /* notify_client.c */, + 2D312B75102CA2E300F90022 /* table.c */, + ); + name = Source; + sourceTree = ""; + }; + 2D312B7B102CA30600F90022 /* Public Headers */ = { + isa = PBXGroup; + children = ( + 2D312B7C102CA32500F90022 /* notify_keys.h */, + 2D312B7D102CA32500F90022 /* notify.h */, + ); + name = "Public Headers"; + sourceTree = ""; + }; + 2D312B80102CA33600F90022 /* Private Headers */ = { + isa = PBXGroup; + children = ( + 2DCB287110D99ADA00DF3A8D /* notify_private.h */, + 2D312B81102CA34D00F90022 /* libnotify.h */, + ); + name = "Private Headers"; + sourceTree = ""; + }; + 2D312B83102CA35300F90022 /* Project Headers */ = { + isa = PBXGroup; + children = ( + FC7B7A52155781930064D203 /* notify_internal.h */, + 2D312B84102CA36C00F90022 /* notify_ipc_types.h */, + 2D312B85102CA36C00F90022 /* table.h */, + ); + name = "Project Headers"; + sourceTree = ""; + }; + 2DF9EA0A102CF31700DE9E8D /* Additional Files */ = { + isa = PBXGroup; + children = ( + 2DF9EA0B102CF33400DE9E8D /* APPLE_LICENSE */, + ); + name = "Additional Files"; + sourceTree = ""; + }; + 3F94777F191C322100A93E8E /* xcodescripts */ = { + isa = PBXGroup; + children = ( + 3F947780191C322100A93E8E /* no-sim-man.sh */, + 3F947781191C322100A93E8E /* sim-compat-symlink.sh */, + ); + path = xcodescripts; + sourceTree = ""; + }; + 3F999960185C474E00EAD3A0 /* xcodeconfig */ = { + isa = PBXGroup; + children = ( + 3F999961185C474E00EAD3A0 /* base.xcconfig */, + 3F82235D12B18551005DD509 /* libnotify.xcconfig */, + 3F999963185C474E00EAD3A0 /* notifyd.xcconfig */, + 3F999964185C474E00EAD3A0 /* notifyutil.xcconfig */, + ); + path = xcodeconfig; + sourceTree = ""; + }; + 3FA21A97148AA7CD00099D2F /* libsystem_notify */ = { + isa = PBXGroup; + children = ( + 2D312B83102CA35300F90022 /* Project Headers */, + 2D312B80102CA33600F90022 /* Private Headers */, + 2D312B7B102CA30600F90022 /* Public Headers */, + 2D312B72102CA2C400F90022 /* Source */, + C6A0FF2B0290797F04C91782 /* Documentation */, + ); + name = libsystem_notify; + sourceTree = ""; + }; + 3FA21A98148AA7FA00099D2F /* notifyd */ = { + isa = PBXGroup; + children = ( + 3FA21ADC148AABA900099D2F /* Build Support */, + 3FA21A99148AA7FA00099D2F /* com.apple.notifyd.plist */, + 3FA21A9A148AA7FA00099D2F /* notify.conf */, + 3FA21AAA148AA85300099D2F /* Source */, + 3FA21A9D148AA7FA00099D2F /* notifyd.8 */, + 3FA21AAB148AA86600099D2F /* Private Headers */, + ); + path = notifyd; + sourceTree = ""; + }; + 3FA21AA7148AA82700099D2F /* notifyutil */ = { + isa = PBXGroup; + children = ( + 3FA21AA8148AA82700099D2F /* notifyutil.1 */, + 3FA21AA9148AA82700099D2F /* notifyutil.c */, + ); + path = notifyutil; + sourceTree = ""; + }; + 3FA21AAA148AA85300099D2F /* Source */ = { + isa = PBXGroup; + children = ( + 3FA21A9C148AA7FA00099D2F /* notify_proc.c */, + 3FA21A9E148AA7FA00099D2F /* notifyd.c */, + 3FA21AA0148AA7FA00099D2F /* pathwatch.c */, + 3FA21AA2148AA7FA00099D2F /* service.c */, + 3FA21AA5148AA7FA00099D2F /* timer.c */, + ); + name = Source; + sourceTree = ""; + }; + 3FA21AAB148AA86600099D2F /* Private Headers */ = { + isa = PBXGroup; + children = ( + 3FA21A9F148AA7FA00099D2F /* notifyd.h */, + 3FA21AA1148AA7FA00099D2F /* pathwatch.h */, + 3FA21AA3148AA7FA00099D2F /* service.h */, + 3FA21AA4148AA7FA00099D2F /* table.h */, + 3FA21AA6148AA7FA00099D2F /* timer.h */, + ); + name = "Private Headers"; + sourceTree = ""; + }; + 3FA21ACE148AAA0D00099D2F /* Products */ = { + isa = PBXGroup; + children = ( + 3F8223B412B18877005DD509 /* libsystem_notify.dylib */, + 3FA21AB0148AA8E300099D2F /* notifyd */, + 3FA21ABE148AA8F000099D2F /* notifyutil */, + ); + name = Products; + sourceTree = ""; + }; + 3FA21ADC148AABA900099D2F /* Build Support */ = { + isa = PBXGroup; + children = ( + 3FD0DBAC148AB12000C50811 /* libbsm.dylib */, + 3FA21ADF148AACA000099D2F /* notify.conf.MacOSX */, + 3FA21A9B148AA7FA00099D2F /* notify.conf.iPhone */, + 3FA21ADD148AABA900099D2F /* mk_notify_conf.sh */, + ); + name = "Build Support"; + path = xcodescripts; + sourceTree = ""; + }; + C6A0FF2B0290797F04C91782 /* Documentation */ = { + isa = PBXGroup; + children = ( + 2D312B88102CA38F00F90022 /* notify_cancel.3 */, + 2D312B89102CA38F00F90022 /* notify_check.3 */, + 2D312B8A102CA38F00F90022 /* notify_get_state.3 */, + 2D6D820D18DA602A0034E7B4 /* notify_is_valid_token.3 */, + 2D312B8B102CA38F00F90022 /* notify_post.3 */, + 2D312B8C102CA38F00F90022 /* notify_register_check.3 */, + 2D312B8D102CA38F00F90022 /* notify_register_dispatch.3 */, + 2D312B8E102CA38F00F90022 /* notify_register_file_descriptor.3 */, + 2D312B8F102CA38F00F90022 /* notify_register_mach_port.3 */, + 2D312B90102CA38F00F90022 /* notify_register_signal.3 */, + 2D312B91102CA38F00F90022 /* notify_set_state.3 */, + 2D312B92102CA38F00F90022 /* notify.3 */, + ); + name = Documentation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC043055464E500DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D312B7E102CA32500F90022 /* notify_keys.h in Headers */, + 2D312B7F102CA32500F90022 /* notify.h in Headers */, + 2D312B82102CA34D00F90022 /* libnotify.h in Headers */, + 2D312B87102CA36C00F90022 /* table.h in Headers */, + 2DCB287210D99ADA00DF3A8D /* notify_private.h in Headers */, + FC7B7A53155781930064D203 /* notify_internal.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 3FA21AAF148AA8E300099D2F /* notifyd */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3FA21AB8148AA8E300099D2F /* Build configuration list for PBXNativeTarget "notifyd" */; + buildPhases = ( + 3FA21AAC148AA8E300099D2F /* Sources */, + 3FA21AAD148AA8E300099D2F /* Frameworks */, + 3FA21AAE148AA8E300099D2F /* Install man page */, + 3FA21AD7148AAAA600099D2F /* Install launchd.plist */, + 3FA21ADB148AAB1C00099D2F /* Install notify.conf */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = notifyd; + productName = notifyd; + productReference = 3FA21AB0148AA8E300099D2F /* notifyd */; + productType = "com.apple.product-type.tool"; + }; + 3FA21ABD148AA8F000099D2F /* notifyutil */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3FA21AC5148AA8F000099D2F /* Build configuration list for PBXNativeTarget "notifyutil" */; + buildPhases = ( + 3FA21ABA148AA8F000099D2F /* Sources */, + 3FA21ABB148AA8F000099D2F /* Frameworks */, + 3FA21ABC148AA8F000099D2F /* Install man page */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = notifyutil; + productName = notifyutil; + productReference = 3FA21ABE148AA8F000099D2F /* notifyutil */; + productType = "com.apple.product-type.tool"; + }; + D2AAC045055464E500DB518D /* libnotify */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB91EB08733DB70010E9CD /* Build configuration list for PBXNativeTarget "libnotify" */; + buildPhases = ( + D2AAC043055464E500DB518D /* Headers */, + D2AAC044055464E500DB518D /* Sources */, + D289987405E68DCB004EDB86 /* Frameworks */, + 2D38AA09102CD87C00D3D622 /* Copy man3 Files */, + 3F947782191C324900A93E8E /* No Simulator Man Pages */, + 3F947783191C327700A93E8E /* Sim compat symlink */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libnotify; + productName = Libnotify; + productReference = 3F8223B412B18877005DD509 /* libsystem_notify.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + }; + buildConfigurationList = 1DEB91EF08733DB70010E9CD /* Build configuration list for PBXProject "Libnotify" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 08FB7794FE84155DC02AAC07 /* Libnotify */; + productRefGroup = 08FB7794FE84155DC02AAC07 /* Libnotify */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC045055464E500DB518D /* libnotify */, + 3FA21AAF148AA8E300099D2F /* notifyd */, + 3FA21ABD148AA8F000099D2F /* notifyutil */, + 3FA21AC7148AA93000099D2F /* cli_apps */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3F947782191C324900A93E8E /* No Simulator Man Pages */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/xcodescripts/no-sim-man.sh", + ); + name = "No Simulator Man Pages"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = "/bin/bash -e -x"; + shellScript = "exec \"${SCRIPT_INPUT_FILE_0}\""; + }; + 3F947783191C327700A93E8E /* Sim compat symlink */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/xcodescripts/sim-compat-symlink.sh", + ); + name = "Sim compat symlink"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = "/bin/bash -e -x"; + shellScript = "exec \"${SCRIPT_INPUT_FILE_0}\""; + }; + 3F947784191C32DC00A93E8E /* No Simulator Man Pages */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/xcodescripts/no-sim-man.sh", + ); + name = "No Simulator Man Pages"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = "/bin/bash -e -x"; + shellScript = "exec \"${SCRIPT_INPUT_FILE_0}\""; + }; + 3FA21ADB148AAB1C00099D2F /* Install notify.conf */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 8; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/notifyd/xcodescripts/mk_notify_conf.sh", + ); + name = "Install notify.conf"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 1; + shellPath = "/bin/bash -e -x"; + shellScript = "exec \"${SCRIPT_INPUT_FILE_0}\""; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3FA21AAC148AA8E300099D2F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3FA21AE6148AAEAC00099D2F /* notify_ipc.defs in Sources */, + 3FA21ACF148AAA5000099D2F /* notify_proc.c in Sources */, + 3FA21AD0148AAA5000099D2F /* notifyd.c in Sources */, + 3FA21AD1148AAA5000099D2F /* pathwatch.c in Sources */, + 3FA21AD2148AAA5000099D2F /* service.c in Sources */, + 3FA21AD3148AAA5000099D2F /* timer.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3FA21ABA148AA8F000099D2F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3FA21AD5148AAA6E00099D2F /* notifyutil.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC044055464E500DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2D312B7A102CA30200F90022 /* notify_ipc.defs in Sources */, + 2D312B76102CA2E300F90022 /* libnotify.c in Sources */, + 2D312B77102CA2E300F90022 /* notify_client.c in Sources */, + 2D312B78102CA2E300F90022 /* table.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3FA21ACB148AA94A00099D2F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3FA21AAF148AA8E300099D2F /* notifyd */; + targetProxy = 3FA21ACA148AA94A00099D2F /* PBXContainerItemProxy */; + }; + 3FA21ACD148AA94A00099D2F /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3FA21ABD148AA8F000099D2F /* notifyutil */; + targetProxy = 3FA21ACC148AA94A00099D2F /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DEB91ED08733DB70010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F82235D12B18551005DD509 /* libnotify.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 1DEB91F108733DB70010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLY_RULES_IN_COPY_FILES = YES; + }; + name = Release; + }; + 3FA21AB9148AA8E300099D2F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F999963185C474E00EAD3A0 /* notifyd.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 3FA21AC6148AA8F000099D2F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F999964185C474E00EAD3A0 /* notifyutil.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 3FA21AC9148AA93000099D2F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3F999961185C474E00EAD3A0 /* base.xcconfig */; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB91EB08733DB70010E9CD /* Build configuration list for PBXNativeTarget "libnotify" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91ED08733DB70010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB91EF08733DB70010E9CD /* Build configuration list for PBXProject "Libnotify" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91F108733DB70010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3FA21AB8148AA8E300099D2F /* Build configuration list for PBXNativeTarget "notifyd" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3FA21AB9148AA8E300099D2F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3FA21AC5148AA8F000099D2F /* Build configuration list for PBXNativeTarget "notifyutil" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3FA21AC6148AA8F000099D2F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3FA21AC8148AA93000099D2F /* Build configuration list for PBXAggregateTarget "cli_apps" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3FA21AC9148AA93000099D2F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/libnotify/libnotify.c b/libnotify/libnotify.c new file mode 100644 index 000000000..1bfe45356 --- /dev/null +++ b/libnotify/libnotify.c @@ -0,0 +1,1544 @@ +/* + * Copyright (c) 2003-2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnotify.h" +#include "notify.h" +#include "notify_internal.h" + +#define USER_PROTECTED_UID_PREFIX "user.uid." +#define USER_PROTECTED_UID_PREFIX_LEN 9 + +uint64_t +make_client_id(pid_t pid, int token) +{ + uint64_t cid; + + cid = pid; + cid <<= 32; + cid |= token; + + return cid; +} + +notify_state_t * +_notify_lib_notify_state_new(uint32_t flags, uint32_t table_size) +{ + notify_state_t *ns; + + ns = (notify_state_t *)calloc(1, sizeof(notify_state_t)); + if (ns == NULL) return NULL; + + ns->flags = flags; + ns->sock = -1; + + if (ns->flags & NOTIFY_STATE_USE_LOCKS) + { + ns->lock = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); + if (ns->lock == NULL) + { + free(ns); + return NULL; + } + + pthread_mutex_init(ns->lock, NULL); + } + + ns->name_table = _nc_table_new(table_size); + ns->name_id_table = _nc_table_new(table_size); + ns->client_table = _nc_table_new(table_size); + ns->port_table = _nc_table_new(table_size); + ns->proc_table = _nc_table_new(table_size); + + if ((ns->name_table == NULL) || (ns->name_id_table == NULL) || (ns->client_table == NULL) || (ns->port_table == NULL) || (ns->proc_table == NULL)) + { + free(ns->lock); + _nc_table_free(ns->name_table); + _nc_table_free(ns->name_id_table); + _nc_table_free(ns->client_table); + _nc_table_free(ns->port_table); + _nc_table_free(ns->proc_table); + free(ns); + return NULL; + } + + return ns; +} + +void +_notify_lib_notify_state_free(notify_state_t *ns) +{ + if (ns == NULL) return; + + _nc_table_free(ns->name_table); + _nc_table_free(ns->name_id_table); + _nc_table_free(ns->client_table); + _nc_table_free(ns->port_table); + _nc_table_free(ns->proc_table); + + if (ns->lock != NULL) + { + pthread_mutex_destroy(ns->lock); + free(ns->lock); + } + + if (ns->sock != -1) + { + shutdown(ns->sock, 2); + close(ns->sock); + } + + if (ns->controlled_name != NULL) free(ns->controlled_name); +} + +static client_t * +_internal_client_new(notify_state_t *ns, pid_t pid, int token) +{ + client_t *c; + uint64_t cid = make_client_id(pid, token); + + if (ns == NULL) return NULL; + + /* detect duplicates - should never happen, but it would be bad */ + c = _nc_table_find_64(ns->client_table, cid); + if (c != NULL) return NULL; + + c = calloc(1, sizeof(client_t)); + if (c == NULL) return NULL; + + ns->stat_client_alloc++; + + c->client_id = cid; + c->pid = pid; + c->send_val = token; + + _nc_table_insert_64(ns->client_table, cid, c); + + return c; +} + +static void +_internal_client_release(notify_state_t *ns, client_t *c) +{ + uint64_t cid; + + if (ns == NULL) return; + if (c == NULL) return; + + cid = c->client_id; + _nc_table_delete_64(ns->client_table, cid); + + switch (c->notify_type) + { + case NOTIFY_TYPE_SIGNAL: + { + break; + } + case NOTIFY_TYPE_FILE: + { + if (c->fd > 0) close(c->fd); + c->fd = -1; + break; + } + case NOTIFY_TYPE_PORT: + { + if (c->port != MACH_PORT_NULL) + { + /* release my send right to the port */ + mach_port_deallocate(mach_task_self(), c->port); + } + break; + } + default: + { + break; + } + } + + free(c); + ns->stat_client_free++; +} + +static name_info_t * +_internal_new_name(notify_state_t *ns, const char *name) +{ + name_info_t *n; + size_t namelen; + + if (ns == NULL) return NULL; + if (name == NULL) return NULL; + + namelen = strlen(name) + 1; + + n = (name_info_t *)calloc(1, sizeof(name_info_t) + namelen); + if (n == NULL) return NULL; + + ns->stat_name_alloc++; + + n->name = (char *)n + sizeof(name_info_t); + memcpy(n->name, name, namelen); + + notify_globals_t globals = _notify_globals(); + n->name_id = globals->name_id++; + + n->access = NOTIFY_ACCESS_DEFAULT; + n->slot = (uint32_t)-1; + n->val = 1; + + _nc_table_insert_no_copy(ns->name_table, n->name, n); + _nc_table_insert_64(ns->name_id_table, n->name_id, n); + + return n; +} + +static void +_internal_insert_controlled_name(notify_state_t *ns, name_info_t *n) +{ + int i, j; + + if (ns == NULL) return; + if (n == NULL) return; + + if (ns->controlled_name == NULL) ns->controlled_name_count = 0; + + for (i = 0; i < ns->controlled_name_count; i++) + { + if (ns->controlled_name[i] == n) return; + } + + ns->controlled_name = (name_info_t **)reallocf(ns->controlled_name, (ns->controlled_name_count + 1) * sizeof(name_info_t *)); + + /* + * Insert name in reverse sorted order (longer names preceed shorter names). + * this means that in _internal_check_access, we check subspaces from the bottom up + * i.e. we check access for the "deepest" controlled subspace. + */ + + for (i = 0; i < ns->controlled_name_count; i++) + { + if (strcmp(n->name, ns->controlled_name[i]->name) > 0) break; + } + + for (j = ns->controlled_name_count; j > i; j--) + { + ns->controlled_name[j] = ns->controlled_name[j-1]; + } + + ns->controlled_name[i] = n; + ns->controlled_name_count++; +} + +static void +_internal_remove_controlled_name(notify_state_t *ns, name_info_t *n) +{ + uint32_t i, j; + + for (i = 0; i < ns->controlled_name_count; i++) + { + if (ns->controlled_name[i] == n) + { + for (j = i + 1; j < ns->controlled_name_count; j++) + { + ns->controlled_name[j-1] = ns->controlled_name[j]; + } + + ns->controlled_name_count--; + if (ns->controlled_name_count == 0) + { + free(ns->controlled_name); + ns->controlled_name = NULL; + } + else + { + ns->controlled_name = (name_info_t **)reallocf(ns->controlled_name, ns->controlled_name_count * sizeof(name_info_t *)); + } + + return; + } + } +} + +static uint32_t +_internal_check_access(notify_state_t *ns, const char *name, uid_t uid, gid_t gid, int req) +{ + uint32_t i, len, plen; + name_info_t *p; + char str[64]; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + /* root may do anything */ + if (uid == 0) return NOTIFY_STATUS_OK; + + /* if name has "user.uid." as a prefix, it is a user-protected namespace */ + if (!strncmp(name, USER_PROTECTED_UID_PREFIX, USER_PROTECTED_UID_PREFIX_LEN)) + { + snprintf(str, sizeof(str) - 1, "%s%d", USER_PROTECTED_UID_PREFIX, uid); + len = strlen(str); + + /* user may access user.uid. or a subtree name */ + if ((!strncmp(name, str, len)) && ((name[len] == '\0') || (name[len] == '.'))) return NOTIFY_STATUS_OK; + return NOTIFY_STATUS_NOT_AUTHORIZED; + } + + len = strlen(name); + + if (ns->controlled_name == NULL) ns->controlled_name_count = 0; + for (i = 0; i < ns->controlled_name_count; i++) + { + p = ns->controlled_name[i]; + if (p == NULL) break; + if (p->name == NULL) continue; + + plen = strlen(p->name); + if (plen > len) continue; + if (strncmp(p->name, name, plen)) continue; + + /* Found a match or a prefix, check if restrictions apply to this uid/gid */ + if ((p->uid == uid) && (p->access & (req << NOTIFY_ACCESS_USER_SHIFT))) break; + if ((p->gid == gid) && (p->access & (req << NOTIFY_ACCESS_GROUP_SHIFT))) break; + if (p->access & (req << NOTIFY_ACCESS_OTHER_SHIFT)) break; + + return NOTIFY_STATUS_NOT_AUTHORIZED; + } + + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_check_controlled_access(notify_state_t *ns, char *name, uid_t uid, gid_t gid, int req) +{ + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + status = _internal_check_access(ns, name, uid, gid, req); + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + + return status; +} + +uint32_t +_notify_lib_port_proc_new(notify_state_t *ns, mach_port_t port, pid_t proc, uint32_t state, dispatch_source_t src) +{ + portproc_data_t *pdata; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if ((proc == 0) && (port == MACH_PORT_NULL)) return NOTIFY_STATUS_FAILED; + + pdata = (portproc_data_t *)calloc(1, sizeof(portproc_data_t)); + if (pdata == NULL) return NOTIFY_STATUS_FAILED; + + ns->stat_portproc_alloc++; + + pdata->refcount = 1; + pdata->flags = state; + pdata->src = src; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + if (proc == 0) _nc_table_insert_n(ns->port_table, port, pdata); + else _nc_table_insert_n(ns->proc_table, proc, pdata); + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + + return NOTIFY_STATUS_OK; +} + +portproc_data_t * +_notify_lib_port_proc_find(notify_state_t *ns, mach_port_t port, pid_t proc) +{ + portproc_data_t *pdata = NULL; + + if (ns == NULL) return NULL; + if ((proc == 0) && (port == MACH_PORT_NULL)) return NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + if (proc == 0) pdata = _nc_table_find_n(ns->port_table, port); + else pdata = _nc_table_find_n(ns->proc_table, proc); + + if (pdata != NULL) pdata->refcount++; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + + return pdata; +} + +void +_notify_lib_port_proc_release(notify_state_t *ns, mach_port_t port, pid_t proc) +{ + portproc_data_t *pdata = NULL; + + if (ns == NULL) return; + if ((proc == 0) && (port == MACH_PORT_NULL)) return; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + if (proc == 0) pdata = _nc_table_find_n(ns->port_table, port); + else pdata = _nc_table_find_n(ns->proc_table, proc); + + if (pdata != NULL) + { + if (pdata->refcount > 0) pdata->refcount--; + if (pdata->refcount == 0) + { + if (proc == 0) _nc_table_delete_n(ns->port_table, port); + else _nc_table_delete_n(ns->proc_table, proc); + + dispatch_source_cancel(pdata->src); + dispatch_release(pdata->src); + + free(pdata); + ns->stat_portproc_free++; + } + } + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +/* + * Send notification to a subscriber + */ +static uint32_t +_internal_send(notify_state_t *ns, client_t *c) +{ + uint32_t send; + portproc_data_t *pdata; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (c == NULL) return NOTIFY_STATUS_FAILED; + + if (c->state & NOTIFY_CLIENT_STATE_SUSPENDED) + { + c->state |= NOTIFY_CLIENT_STATE_PENDING; + return NOTIFY_STATUS_OK; + } + + pdata = _nc_table_find_n(ns->proc_table, c->pid); + if ((pdata != NULL) && (pdata->flags & NOTIFY_PORT_PROC_STATE_SUSPENDED)) + { + c->suspend_count++; + c->state |= NOTIFY_CLIENT_STATE_SUSPENDED; + c->state |= NOTIFY_CLIENT_STATE_PENDING; + return NOTIFY_STATUS_OK; + } + + send = c->send_val; + + switch (c->notify_type) + { + case NOTIFY_TYPE_SIGNAL: + { + int rc = 0; + + if (c->pid == NOTIFY_CLIENT_SELF) rc = kill(getpid(), c->sig); + else rc = kill(c->pid, c->sig); + + if (rc != 0) return NOTIFY_STATUS_FAILED; + + c->state &= ~NOTIFY_CLIENT_STATE_PENDING; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + return NOTIFY_STATUS_OK; + } + + case NOTIFY_TYPE_FILE: + { + ssize_t len; + + if (c->fd >= 0) + { + send = htonl(send); + len = write(c->fd, &send, sizeof(uint32_t)); + if (len != sizeof(uint32_t)) + { + close(c->fd); + c->fd = -1; + return NOTIFY_STATUS_FAILED; + } + } + + c->state &= ~NOTIFY_CLIENT_STATE_PENDING; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + return NOTIFY_STATUS_OK; + } + + case NOTIFY_TYPE_PORT: + { + kern_return_t kstatus; + mach_msg_empty_send_t msg; + mach_msg_option_t opts = MACH_SEND_MSG | MACH_SEND_TIMEOUT; + + pdata = _nc_table_find_n(ns->port_table, c->port); + if ((pdata != NULL) && (pdata->flags & NOTIFY_PORT_PROC_STATE_SUSPENDED)) + { + c->suspend_count++; + c->state |= NOTIFY_CLIENT_STATE_SUSPENDED; + c->state |= NOTIFY_CLIENT_STATE_PENDING; + return NOTIFY_STATUS_OK; + } + + if (ns->flags & NOTIFY_STATE_ENABLE_RESEND) opts |= MACH_SEND_NOTIFY; + + memset(&msg, 0, sizeof(mach_msg_empty_send_t)); + msg.header.msgh_size = sizeof(mach_msg_empty_send_t); + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO); + msg.header.msgh_local_port = MACH_PORT_NULL; + msg.header.msgh_remote_port = c->port; + msg.header.msgh_id = (mach_msg_id_t)send; + + kstatus = mach_msg(&msg.header, opts, msg.header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + if (kstatus == MACH_SEND_TIMED_OUT) + { + /* deallocate port rights obtained via pseudo-receive after failed mach_msg() send */ + mach_msg_destroy(&msg.header); + if (ns->flags & NOTIFY_STATE_ENABLE_RESEND) + { + /* + * Suspend on timeout. + * notifyd will get a MACH_NOTIFY_SEND_POSSIBLE and trigger a retry. + * c->suspend_count must be zero, or we would not be trying to send. + */ + c->suspend_count++; + c->state |= NOTIFY_CLIENT_STATE_SUSPENDED; + c->state |= NOTIFY_CLIENT_STATE_PENDING; + c->state |= NOTIFY_CLIENT_STATE_TIMEOUT; + + return NOTIFY_STATUS_OK; + } + + return NOTIFY_STATUS_FAILED; + } + else if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + + c->state &= ~NOTIFY_CLIENT_STATE_PENDING; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + return NOTIFY_STATUS_OK; + } + + default: + { + break; + } + } + + c->state &= ~NOTIFY_CLIENT_STATE_PENDING; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_post_client(notify_state_t *ns, client_t *c) +{ + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (c == NULL) return NOTIFY_STATUS_FAILED; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + status = _internal_send(ns, c); + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + + return status; +} + +static uint32_t +_internal_post_name(notify_state_t *ns, name_info_t *n, uid_t uid, gid_t gid) +{ + int auth; + list_t *l; + client_t *c; + + if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; + + auth = _internal_check_access(ns, n->name, uid, gid, NOTIFY_ACCESS_WRITE); + if (auth != 0) return NOTIFY_STATUS_NOT_AUTHORIZED; + + n->val++; + + for (l = n->subscriptions; l != NULL; l = _nc_list_next(l)) + { + c = _nc_list_data(l); + if (c != NULL) _internal_send(ns, c); + } + + return NOTIFY_STATUS_OK; +} + +/* + * Notify subscribers of this name. + */ +uint32_t +_notify_lib_post(notify_state_t *ns, const char *name, uid_t uid, gid_t gid) +{ + name_info_t *n; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_NAME; + } + + status = _internal_post_name(ns, n, uid, gid); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; +} + +uint32_t +_notify_lib_post_nid(notify_state_t *ns, uint64_t nid, uid_t uid, gid_t gid) +{ + name_info_t *n; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find_64(ns->name_id_table, nid); + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_NAME; + } + + status = _internal_post_name(ns, n, uid, gid); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; +} + +static void +_internal_release_name_info(notify_state_t *ns, name_info_t *n) +{ + if (ns == NULL) return; + if (n == NULL) return; + + if (n->refcount > 0) n->refcount--; + if (n->refcount == 0) + { + _internal_remove_controlled_name(ns, n); + _nc_table_delete(ns->name_table, n->name); + _nc_table_delete_64(ns->name_id_table, n->name_id); + _nc_list_release_list(n->subscriptions); + free(n); + ns->stat_name_free++; + } +} + +/* + * Cancel (delete) a client + */ +static void +_internal_cancel(notify_state_t *ns, uint64_t cid) +{ + client_t *c; + name_info_t *n; + + if (ns == NULL) return; + + c = NULL; + n = NULL; + + c = _nc_table_find_64(ns->client_table, cid); + if (c == NULL) return; + + n = c->name_info; + if (n == NULL) return; + + n->subscriptions =_nc_list_find_release(n->subscriptions, c); + _internal_client_release(ns, c); + _internal_release_name_info(ns, n); +} + +void +_notify_lib_cancel(notify_state_t *ns, pid_t pid, int token) +{ + uint64_t cid; + + if (ns == NULL) return; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + _internal_cancel(ns, cid); + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +void +_notify_lib_suspend(notify_state_t *ns, pid_t pid, int token) +{ + client_t *c; + uint64_t cid; + + if (ns == NULL) return; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + c = _nc_table_find_64(ns->client_table, cid); + if (c != NULL) + { + c->state |= NOTIFY_CLIENT_STATE_SUSPENDED; + if (c->suspend_count < UINT32_MAX) c->suspend_count++; + } + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +uint32_t +_notify_lib_resume(notify_state_t *ns, pid_t pid, int token) +{ + client_t *c; + uint64_t cid; + uint32_t status = NOTIFY_STATUS_OK; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + c = _nc_table_find_64(ns->client_table, cid); + if (c != NULL) + { + if (c->suspend_count > 0) c->suspend_count--; + if (c->suspend_count == 0) + { + c->state &= ~NOTIFY_CLIENT_STATE_SUSPENDED; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + if (c->state & NOTIFY_CLIENT_STATE_PENDING) + { + status = _internal_send(ns, c); + } + } + } + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + + return status; +} + +void +_notify_lib_suspend_proc(notify_state_t *ns, pid_t pid) +{ + portproc_data_t *pdata; + + if (ns == NULL) return; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + pdata = _nc_table_find_n(ns->proc_table, pid); + if (pdata != NULL) pdata->flags |= NOTIFY_PORT_PROC_STATE_SUSPENDED; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +void +_notify_lib_resume_proc(notify_state_t *ns, pid_t pid) +{ + client_t *c; + void *tt; + portproc_data_t *pdata; + + if (ns == NULL) return; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + /* Resume all subscriptions for this process */ + pdata = _nc_table_find_n(ns->proc_table, pid); + if (pdata != NULL) pdata->flags &= ~NOTIFY_PORT_PROC_STATE_SUSPENDED; + + tt = _nc_table_traverse_start(ns->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(ns->client_table, tt); + if (c == NULL) break; + + if (c->pid == pid) + { + if (c->suspend_count > 0) c->suspend_count--; + if (c->suspend_count == 0) + { + c->state &= ~NOTIFY_CLIENT_STATE_SUSPENDED; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + if (c->state & NOTIFY_CLIENT_STATE_PENDING) + { + _internal_send(ns, c); + } + } + } + } + _nc_table_traverse_end(ns->client_table, tt); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +void +_notify_lib_suspend_port(notify_state_t *ns, mach_port_t port) +{ + portproc_data_t *pdata; + + if (ns == NULL) return; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + pdata = _nc_table_find_n(ns->port_table, port); + if (pdata != NULL) pdata->flags |= NOTIFY_PORT_PROC_STATE_SUSPENDED; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +void +_notify_lib_resume_port(notify_state_t *ns, mach_port_t port) +{ + client_t *c; + void *tt; + portproc_data_t *pdata; + + if (ns == NULL) return; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + /* Resume all subscriptions with this port */ + pdata = _nc_table_find_n(ns->port_table, port); + if (pdata != NULL) pdata->flags &= ~NOTIFY_PORT_PROC_STATE_SUSPENDED; + + tt = _nc_table_traverse_start(ns->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(ns->client_table, tt); + if (c == NULL) break; + + if (c->port == port) + { + if (c->suspend_count > 0) c->suspend_count--; + if (c->suspend_count == 0) + { + c->state &= ~NOTIFY_CLIENT_STATE_SUSPENDED; + c->state &= ~NOTIFY_CLIENT_STATE_TIMEOUT; + + if (c->state & NOTIFY_CLIENT_STATE_PENDING) + { + _internal_send(ns, c); + } + } + } + } + _nc_table_traverse_end(ns->client_table, tt); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +/* + * Delete all clients for a process + * N.B. notifyd does not use this routine. + */ +void +_notify_lib_cancel_proc(notify_state_t *ns, pid_t pid) +{ + client_t *c; + void *tt; + list_t *l, *x; + + if (ns == NULL) return; + + x = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + tt = _nc_table_traverse_start(ns->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(ns->client_table, tt); + if (c == NULL) break; + + if (c->pid == pid) x = _nc_list_prepend(x, _nc_list_new(c)); + } + _nc_table_traverse_end(ns->client_table, tt); + + for (l = x; l != NULL; l = _nc_list_next(l)) + { + c = _nc_list_data(l); + _internal_cancel(ns, c->client_id); + } + + _nc_list_release_list(x); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +/* + * Delete all clients for a port + * N.B. notifyd does not use this routine. + */ +void +_notify_lib_cancel_port(notify_state_t *ns, mach_port_t port) +{ + client_t *c; + void *tt; + list_t *l, *x; + + if (ns == NULL) return; + + x = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + tt = _nc_table_traverse_start(ns->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(ns->client_table, tt); + if (c == NULL) break; + + if (c->port == port) x = _nc_list_prepend(x, _nc_list_new(c)); + } + _nc_table_traverse_end(ns->client_table, tt); + + for (l = x; l != NULL; l = _nc_list_next(l)) + { + c = _nc_list_data(l); + _internal_cancel(ns, c->client_id); + } + + _nc_list_release_list(x); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); +} + +/* + * Check if a name has changed since the last time this client checked. + * Returns true, false, or error. + */ +uint32_t +_notify_lib_check(notify_state_t *ns, pid_t pid, int token, int *check) +{ + client_t *c; + uint64_t cid; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (check == NULL) return NOTIFY_STATUS_FAILED; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + c = _nc_table_find_64(ns->client_table, cid); + + if (c == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (c->name_info == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (c->name_info->val == c->lastval) + { + *check = 0; + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + c->lastval = c->name_info->val; + *check = 1; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +/* + * SPI: get value for a name. + */ +uint32_t +_notify_lib_peek(notify_state_t *ns, pid_t pid, int token, int *val) +{ + client_t *c; + uint64_t cid; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (val == NULL) return NOTIFY_STATUS_FAILED; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + c = _nc_table_find_64(ns->client_table, cid); + + if (c == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (c->name_info == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + *val = c->name_info->val; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +int * +_notify_lib_check_addr(notify_state_t *ns, pid_t pid, int token) +{ + client_t *c; + int *addr; + uint64_t cid; + + if (ns == NULL) return NULL; + + cid = make_client_id(pid, token); + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + c = _nc_table_find_64(ns->client_table, cid); + + if (c == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NULL; + } + + if (c->name_info == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NULL; + } + + addr = (int *)&(c->name_info->val); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return addr; +} + +/* + * Get state value for a name. + */ +uint32_t +_notify_lib_get_state(notify_state_t *ns, uint64_t nid, uint64_t *state, uid_t uid, gid_t gid) +{ + name_info_t *n; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (state == NULL) return NOTIFY_STATUS_FAILED; + + *state = 0; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find_64(ns->name_id_table, nid); + + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_NAME; + } + +#ifdef GET_STATE_AUTH_CHECK + int auth = _internal_check_access(ns, n->name, uid, gid, NOTIFY_ACCESS_READ); + if (auth != 0) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_NOT_AUTHORIZED; + } +#endif + + *state = n->state; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +/* + * Set state value for a name. + */ +uint32_t +_notify_lib_set_state(notify_state_t *ns, uint64_t nid, uint64_t state, uid_t uid, gid_t gid) +{ + name_info_t *n; + int auth; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find_64(ns->name_id_table, nid); + + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_NAME; + } + + auth = _internal_check_access(ns, n->name, uid, gid, NOTIFY_ACCESS_WRITE); + if (auth != 0) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_NOT_AUTHORIZED; + } + + n->state = state; + n->state_time = mach_absolute_time(); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +static uint32_t +_internal_register_common(notify_state_t *ns, const char *name, pid_t pid, int token, uid_t uid, gid_t gid, client_t **outc) +{ + client_t *c; + name_info_t *n; + int is_new_name; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (outc == NULL) return NOTIFY_STATUS_OK; + + status = _internal_check_access(ns, name, uid, gid, NOTIFY_ACCESS_READ); + if (status != NOTIFY_STATUS_OK) return NOTIFY_STATUS_NOT_AUTHORIZED; + + *outc = NULL; + is_new_name = 0; + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n == NULL) + { + is_new_name = 1; + + n = _internal_new_name(ns, name); + if (n == NULL) return NOTIFY_STATUS_FAILED; + } + + c = _internal_client_new(ns, pid, token); + if (c == NULL) + { + if (is_new_name == 1) + { + _nc_table_delete(ns->name_table, n->name); + _nc_list_release_list(n->subscriptions); + free(n); + ns->stat_name_free++; + } + + return NOTIFY_STATUS_FAILED; + } + + n->refcount++; + + c->name_info = n; + n->subscriptions = _nc_list_prepend(n->subscriptions, _nc_list_new(c)); + + *outc = c; + + return NOTIFY_STATUS_OK; +} + +/* + * Register for signal. + * Returns the client_id; + */ +uint32_t +_notify_lib_register_signal(notify_state_t *ns, const char *name, pid_t pid, int token, uint32_t sig, uid_t uid, gid_t gid, uint64_t *out_nid) +{ + client_t *c; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + c = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + status = _internal_register_common(ns, name, pid, token, uid, gid, &c); + if (status != NOTIFY_STATUS_OK) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; + } + + c->notify_type = NOTIFY_TYPE_SIGNAL; + c->pid = pid; + c->sig = sig; + *out_nid = c->name_info->name_id; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +/* + * Register for notification on a file descriptor. + * Returns the client_id; + */ +uint32_t +_notify_lib_register_file_descriptor(notify_state_t *ns, const char *name, pid_t pid, int token, int fd, uid_t uid, gid_t gid, uint64_t *out_nid) +{ + client_t *c; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + c = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + status = _internal_register_common(ns, name, pid, token, uid, gid, &c); + if (status != NOTIFY_STATUS_OK) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; + } + + c->notify_type = NOTIFY_TYPE_FILE; + c->fd = fd; + *out_nid = c->name_info->name_id; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +/* + * Register for notification on a mach port. + * Returns the client_id; + */ +uint32_t +_notify_lib_register_mach_port(notify_state_t *ns, const char *name, pid_t pid, int token, mach_port_t port, uid_t uid, gid_t gid, uint64_t *out_nid) +{ + client_t *c; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + c = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + status = _internal_register_common(ns, name, pid, token, uid, gid, &c); + if (status != NOTIFY_STATUS_OK) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; + } + + c->notify_type = NOTIFY_TYPE_PORT; + c->port = port; + *out_nid = c->name_info->name_id; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +/* + * Plain registration - only for notify_check() + * Returns the client_id. + */ +uint32_t +_notify_lib_register_plain(notify_state_t *ns, const char *name, pid_t pid, int token, uint32_t slot, uint32_t uid, uint32_t gid, uint64_t *out_nid) +{ + client_t *c; + uint32_t status; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + c = NULL; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + status = _internal_register_common(ns, name, pid, token, uid, gid, &c); + if (status != NOTIFY_STATUS_OK) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return status; + } + + if (slot == SLOT_NONE) + { + c->notify_type = NOTIFY_TYPE_PLAIN; + } + else + { + c->notify_type = NOTIFY_TYPE_MEMORY; + c->name_info->slot = slot; + } + + *out_nid = c->name_info->name_id; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_set_owner(notify_state_t *ns, const char *name, uid_t uid, gid_t gid) +{ + name_info_t *n; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n == NULL) + { + /* create new name */ + n = _internal_new_name(ns, name); + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_FAILED; + } + + /* + * Setting the refcount here allows the namespace to be "pre-populated" + * with controlled names. notifyd does this for reserved names in + * its configuration file. + */ + n->refcount++; + } + + n->uid = uid; + n->gid = gid; + + _internal_insert_controlled_name(ns, n); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_get_owner(notify_state_t *ns, const char *name, uint32_t *uid, uint32_t *gid) +{ + name_info_t *n; + int i, nlen, len; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n != NULL) + { + *uid = n->uid; + *gid = n->gid; + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + len = strlen(name); + + for (i = 0; i < ns->controlled_name_count; i++) + { + n = ns->controlled_name[i]; + if (n == NULL) break; + + nlen = strlen(n->name); + + if (!strcmp(n->name, name)) + { + *uid = n->uid; + *gid = n->gid; + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + /* check if this key is a prefix */ + if (nlen >= len) continue; + if (strncmp(n->name, name, nlen)) continue; + + *uid = n->uid; + *gid = n->gid; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + *uid = 0; + *gid = 0; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_set_access(notify_state_t *ns, const char *name, uint32_t mode) +{ + name_info_t *n; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n == NULL) + { + /* create new name */ + n = _internal_new_name(ns, name); + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_FAILED; + } + + /* + * Setting the refcount here allows the namespace to be "pre-populated" + * with controlled names. notifyd does this for reserved names in + * its configuration file. + */ + n->refcount++; + } + + n->access = mode; + + _internal_insert_controlled_name(ns, n); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_get_access(notify_state_t *ns, const char *name, uint32_t *mode) +{ + name_info_t *n; + int i, nlen, len; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n != NULL) + { + *mode = n->access; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + len = strlen(name); + + for (i = 0; i < ns->controlled_name_count; i++) + { + n = ns->controlled_name[i]; + if (n == NULL) break; + if (n->name == NULL) continue; + + nlen = strlen(n->name); + + if (!strcmp(n->name, name)) + { + *mode = n->access; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + /* check if this key is a prefix */ + if (nlen >= len) continue; + if (strncmp(n->name, name, nlen)) continue; + + *mode = n->access; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; + } + + *mode = NOTIFY_ACCESS_DEFAULT; + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} + +uint32_t +_notify_lib_release_name(notify_state_t *ns, const char *name, uid_t uid, gid_t gid) +{ + name_info_t *n; + + if (ns == NULL) return NOTIFY_STATUS_FAILED; + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (ns->lock != NULL) pthread_mutex_lock(ns->lock); + + n = (name_info_t *)_nc_table_find(ns->name_table, name); + if (n == NULL) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_INVALID_NAME; + } + + /* Owner and root may release */ + if ((n->uid != uid) && (uid != 0)) + { + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_NOT_AUTHORIZED; + } + + _internal_release_name_info(ns, n); + + if (ns->lock != NULL) pthread_mutex_unlock(ns->lock); + return NOTIFY_STATUS_OK; +} diff --git a/libnotify/libnotify.h b/libnotify/libnotify.h new file mode 100644 index 000000000..e9eca4e4a --- /dev/null +++ b/libnotify/libnotify.h @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2003-2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _LIBNOTIFY_H_ +#define _LIBNOTIFY_H_ + +#include +#include +#include +#include "table.h" + +#include + +#if TARGET_IPHONE_SIMULATOR +extern const char *_notify_shm_id(); +#define SHM_ID _notify_shm_id() +#else +#define SHM_ID "apple.shm.notification_center" +#endif + +#define NOTIFY_IPC_VERSION_NAME "com.apple.system.notify.ipc_version" +#define NOTIFY_IPC_VERSION_NAME_LEN 35 +#define NOTIFY_SERVICE_NAME "com.apple.system.notification_center" +#define NOTIFY_SERVICE_NAME_LEN 36 + +#define COMMON_PORT_KEY "com.apple.system.notify.common" + +/* Notification types */ +#define NOTIFY_TYPE_NONE 0x00000000 +#define NOTIFY_TYPE_MEMORY 0x00000001 +#define NOTIFY_TYPE_PLAIN 0x00000002 +#define NOTIFY_TYPE_PORT 0x00000004 +#define NOTIFY_TYPE_FILE 0x00000008 +#define NOTIFY_TYPE_SIGNAL 0x00000010 +#define NOTIFY_TYPE_MASK 0x000000ff +#define NOTIFY_FLAG_SELF 0x80000000 +#define NOTIFY_FLAG_REGEN 0x40000000 +#define NOTIFY_FLAG_RELEASE_SEND 0x20000000 + +/* Return values for notify_check() */ +#define NOTIFY_CHECK_FALSE 0 +#define NOTIFY_CHECK_TRUE 1 +#define NOTIFY_CHECK_ERROR 2 + +/* Access control */ +#define NOTIFY_ACCESS_READ 1 +#define NOTIFY_ACCESS_WRITE 2 + +#define NOTIFY_ACCESS_OTHER_SHIFT 8 +#define NOTIFY_ACCESS_GROUP_SHIFT 4 +#define NOTIFY_ACCESS_USER_SHIFT 0 + +#define NOTIFY_ACCESS_DEFAULT 0x00000333 +#define NOTIFY_ACCESS_USER_RW 0x00000003 + +/* Filesystem Services */ +#define NOTIFY_SERVICE_FILE_STATUS_QUO 0x00 +#define NOTIFY_SERVICE_FILE_ADD 0x01 +#define NOTIFY_SERVICE_FILE_DELETE 0x02 +#define NOTIFY_SERVICE_FILE_MODIFY 0x04 +#define NOTIFY_SERVICE_FILE_ATTR 0x08 + +#define NOTIFY_SERVICE_DIR_FILE_ADD 0x10 +#define NOTIFY_SERVICE_DIR_FILE_DELETE 0x20 + +#define NOTIFY_CLIENT_STATE_SUSPENDED 0x00000001 +#define NOTIFY_CLIENT_STATE_PENDING 0x00000002 +#define NOTIFY_CLIENT_STATE_TIMEOUT 0x00000004 + +#define NOTIFY_PORT_PROC_TYPE_PORT 0x00000010 +#define NOTIFY_PORT_PROC_TYPE_PROC 0x00000020 +#define NOTIFY_PORT_PROC_TYPE_MASK 0x000000f0 +#define NOTIFY_PORT_PROC_STATE_INVALID 0x00000001 +#define NOTIFY_PORT_PROC_STATE_SUSPENDED 0x00000002 +#define NOTIFY_PORT_PROC_STATE_MASK 0x0000000f + +/* notify state flags */ +#define NOTIFY_STATE_USE_LOCKS 0x00000001 +#define NOTIFY_STATE_ENABLE_RESEND 0x00000002 + +#define NOTIFY_CLIENT_SELF 0 +#define SIGNAL_NONE -1 +#define FD_NONE -1 +#define SLOT_NONE -1 + +#define _notify_lib_port_new(A,B,C,D) _notify_lib_port_proc_new(A,B,0,C,D) +#define _notify_lib_proc_new(A,B,C,D) _notify_lib_port_proc_new(A,MACH_PORT_NULL,B,C,D) +#define _notify_lib_port_find(A,B) _notify_lib_port_proc_find(A,B,0) +#define _notify_lib_proc_find(A,B) _notify_lib_port_proc_find(A,MACH_PORT_NULL,B) +#define _notify_lib_port_release(A,B) _notify_lib_port_proc_release(A,B,0) +#define _notify_lib_proc_release(A,B) _notify_lib_port_proc_release(A,MACH_PORT_NULL,B) + +typedef struct +{ + char *name; + uint64_t name_id; + uint32_t uid; + uint32_t gid; + uint32_t access; + uint32_t slot; + uint32_t refcount; + uint32_t val; + uint64_t state; + uint64_t state_time; + void *private; + list_t *subscriptions; +} name_info_t; + +typedef struct +{ + uint64_t client_id; + uint32_t state; + name_info_t *name_info; + uint32_t suspend_count; + uint32_t notify_type; + uint32_t lastval; + mach_port_t port; + int fd; + uint32_t send_val; + uint32_t pid; + uint32_t sig; + void *private; +} client_t; + +typedef struct +{ + uint32_t refcount; + uint32_t flags; + dispatch_source_t src; +} portproc_data_t; + +typedef struct +{ + uint32_t flags; + table_t *name_table; + table_t *name_id_table; + table_t *client_table; + table_t *port_table; + table_t *proc_table; + name_info_t **controlled_name; + uint32_t controlled_name_count; + pthread_mutex_t *lock; + int sock; + uint32_t stat_name_alloc; + uint32_t stat_name_free; + uint32_t stat_client_alloc; + uint32_t stat_client_free; + uint32_t stat_portproc_alloc; + uint32_t stat_portproc_free; +} notify_state_t; + +notify_state_t *_notify_lib_notify_state_new(uint32_t flags, uint32_t table_size); +void _notify_lib_notify_state_free(notify_state_t *ns); + +uint32_t _notify_lib_post(notify_state_t *ns, const char *name, uint32_t uid, uint32_t gid); +uint32_t _notify_lib_post_nid(notify_state_t *ns, uint64_t nid, uid_t uid, gid_t gid); +uint32_t _notify_lib_post_client(notify_state_t *ns, client_t *c); + +uint32_t _notify_lib_check(notify_state_t *ns, pid_t pid, int token, int *check); +uint32_t _notify_lib_get_state(notify_state_t *ns, uint64_t nid, uint64_t *state, uint32_t uid, uint32_t gid); +uint32_t _notify_lib_set_state(notify_state_t *ns, uint64_t nid, uint64_t state, uint32_t uid, uint32_t gid); + +uint32_t _notify_lib_register_plain(notify_state_t *ns, const char *name, pid_t pid, int token, uint32_t slot, uint32_t uid, uint32_t gid, uint64_t *out_nid); +uint32_t _notify_lib_register_signal(notify_state_t *ns, const char *name, pid_t pid, int token, uint32_t sig, uint32_t uid, uint32_t gid, uint64_t *out_nid); +uint32_t _notify_lib_register_mach_port(notify_state_t *ns, const char *name, pid_t pid, int token, mach_port_t port, uint32_t uid, uint32_t gid, uint64_t *out_nid); +uint32_t _notify_lib_register_file_descriptor(notify_state_t *ns, const char *name, pid_t pid, int token, int fd, uint32_t uid, uint32_t gid, uint64_t *out_nid); + +uint32_t _notify_lib_get_owner(notify_state_t *ns, const char *name, uint32_t *uid, uint32_t *gid); +uint32_t _notify_lib_get_access(notify_state_t *ns, const char *name, uint32_t *access); + +uint32_t _notify_lib_set_owner(notify_state_t *ns, const char *name, uint32_t uid, uint32_t gid); +uint32_t _notify_lib_set_access(notify_state_t *ns, const char *name, uint32_t access); + +uint32_t _notify_lib_release_name(notify_state_t *ns, const char *name, uint32_t uid, uint32_t gid); + +void _notify_lib_cancel(notify_state_t *ns, pid_t pid, int token); +void _notify_lib_suspend(notify_state_t *ns, pid_t pid, int token); +uint32_t _notify_lib_resume(notify_state_t *ns, pid_t pid, int token); + +void _notify_lib_cancel_proc(notify_state_t *ns, pid_t pid); +void _notify_lib_suspend_proc(notify_state_t *ns, pid_t pid); +void _notify_lib_resume_proc(notify_state_t *ns, pid_t pid); + +void _notify_lib_suspend_port(notify_state_t *ns, mach_port_t port); +void _notify_lib_resume_port(notify_state_t *ns, mach_port_t port); + +uint32_t _notify_lib_check_controlled_access(notify_state_t *ns, char *name, uid_t uid, gid_t gid, int req); + +uint64_t make_client_id(pid_t pid, int token); + +uint32_t _notify_lib_port_proc_new(notify_state_t *ns, mach_port_t port, pid_t proc, uint32_t state, dispatch_source_t src); +portproc_data_t *_notify_lib_port_proc_find(notify_state_t *ns, mach_port_t port, pid_t proc); +void _notify_lib_port_proc_release(notify_state_t *ns, mach_port_t port, pid_t proc); + + +#endif /* _LIBNOTIFY_H_ */ diff --git a/libnotify/notify.3 b/libnotify/notify.3 new file mode 100644 index 000000000..af4b88e0a --- /dev/null +++ b/libnotify/notify.3 @@ -0,0 +1,419 @@ +.\" Copyright (c) 2003-2014 Apple Inc. All rights reserved. +.\" +.\" @APPLE_LICENSE_HEADER_START@ +.\" +.\" Portions Copyright (c) 2003-2010 Apple Inc. All Rights Reserved. +.\" +.\" This file contains Original Code and/or Modifications of Original Code +.\" as defined in and that are subject to the Apple Public Source License +.\" Version 2.0 (the 'License'). You may not use this file except in +.\" compliance with the License. Please obtain a copy of the License at +.\" http://www.opensource.apple.com/apsl/ and read it before using this +.\" file. +.\" +.\" The Original Code and all software distributed under the License are +.\" distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +.\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +.\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +.\" Please see the License for the specific language governing rights and +.\" limitations under the License. +.\" +.\" @APPLE_LICENSE_HEADER_END@ +.\" +.\" +.Dd September 3, 2008 +.Dt notify 3 +.Os "Mac OS X" +.Sh NAME +.Nm notify_post , +.Nm notify_register_check , +.Nm notify_register_dispatch , +.Nm notify_register_signal , +.Nm notify_register_mach_port , +.Nm notify_register_file_descriptor , +.Nm notify_check , +.Nm notify_get_state , +.Nm notify_set_state , +.Nm notify_suspend , +.Nm notify_resume , +.Nm notify_cancel , +.Nm notify_is_valid_token +.Nd event distribution functions +.Sh SYNOPSIS +.Fd #include +.Ft uint32_t +.Fn notify_post "const char *name" +.Ft uint32_t +.Fn notify_register_check "const char *name, int *out_token" +.Ft uint32_t +.Fn notify_register_dispatch "const char *name, int *out_token" "dispatch_queue_t queue" "notify_handler_t handler" +.Ft uint32_t +.Fn notify_register_signal "const char *name, int sig, int *out_token" +.Ft uint32_t +.Fn notify_register_mach_port "const char *name, mach_port_t *notify_port, int flags, int *out_token" +.Ft uint32_t +.Fn notify_register_file_descriptor "const char *name, int *notify_fd, int flags, int *out_token" +.Ft uint32_t +.Fn notify_check "int token, int *check" +.Ft uint32_t +.Fn notify_set_state "int token, uint64_t state" +.Ft uint32_t +.Fn notify_get_state "int token, uint64_t *state" +.Ft uint32_t +.Fn notify_suspend "int token" +.Ft uint32_t +.Fn notify_resume "int token" +.Ft uint32_t +.Fn notify_cancel "int token" +.Ft bool +.Fn notify_is_valid_token "int val" +.Sh DESCRIPTION +These routines allow processes to exchange stateless notification events. +Processes post notifications to a single system-wide notification server, +which then distributes notifications to client processes that have +registered to receive those notifications, including processes run by +other users. +.Pp +Notifications are associated with names in a namespace shared by all +clients of the system. +Clients may post notifications for names, and +may monitor names for posted notifications. +Clients may request +notification delivery by a number of different methods. +.Pp +Clients desiring to monitor names in the notification system must +register with the system, providing a name and other information +required for the desired notification delivery method. +Clients are +given an integer token representing the registration. +Token values are zero or positive integers. +.Pp +The kernel provides limited queues for mach message and file descriptor messages. +It is important to make sure that clients read mach ports and file descriptors frequently +to prevent messages from being lost due to resource limitations. +Clients that use signal-based notification should be aware that signals +are not delivered to a process while it is running in a signal handler. +This may affect the delivery of signals in close succession. +.Pp +Notifications may be coalesced in some cases. +Multiple events posted +for a name in rapid succession may result in a single notification sent +to clients registered for notification for that name. +Clients checking +for changes using the notify_check() routine cannot determine if +more than one event has been posted since a previous call to +notify_check() for that name. +.Pp +"False positives" may occur in notify_check() when used with a token +generated by notify_register_check() due to implementation constraints. +This behavior may vary in future releases. +.Ss notify_post +This routine causes the system to send a notification for the given +name to all clients that have registered for notifications of this name. +This is the only API required for an application that only produces +notifications. +.Ss notify_register_check +Registers for passive notification for the given name. +The routine generates +a token that may be used with the +.Fn notify_check +routine to check if any notifications have been posted for the name. +The check is implemented using a shared memory scheme, making the check +very fast and efficient. +The implementation has a limited amount +of shared memory, so developers are encouraged to use this mechanism +sparingly. +It is also important to release the resources consumed +by a registration with +.Fn notify_cancel +when they are no longer required by the application. +.Ss notify_register_dispatch +registers a callback handler in the form of a block which will be +dispatched to the queue when a notification for the given name is +received. This is a convenient way to register callbacks without any +management of file descriptors, mach ports, or signals on the part of +the application. The given queue is retained by the system for the +lifetime of the notification. Use +.Fn notify_cancel +to release the notification and its reference to the queue. +.Ss notify_register_signal +registers a client for notification delivery via a signal. +This fits +well with the design of many UNIX daemons that use a signal such as SIGHUP +to reinitialize of reset internal state information. +Clients may use the +registration token generated by this routine to check for notifications using +.Fn notify_check . +This allows the application to determine if a signal was received as the +result of a notification, or if the signal was generated by some other source. +It also permits the application that registers for signal notification for +multiple names to determine which name was associated with the notification. +.Ss notify_register_mach_port +registers a client for notification delivery via mach messaging. +Notifications are delivered by an empty message sent to a mach port. +By default, a new port is created by a call to this routine. +A mach port +previously created by a call to this routine may be used for notifications +if a pointer to that port is passed in to the routine and NOTIFY_REUSE is +set in the flags parameter. +The notification service must be able to extract +send rights to the port. +.Pp +Note that the kernel limits the size of the message queue for any port. +If it is important that notifications should not be lost due to queue +overflow, clients should service messages quickly, and be cautious in +using the same port for notifications for more than one name. +.Pp +A notification message has an empty message body. +The msgh_id field +in the mach message header will have the value of the notification +token. +If a port is reused for multiple notification registrations, +the msgh_id value may be used to determine which name generated +the notification. +.Ss notify_register_file_descriptor +Register for notification by a write to a file descriptor. +.Pp +By default, a new file descriptor is created and a pointer to it +is returned as the value of the "notify_fd" parameter. +A file descriptor +created by a previous call to this routine may be used for notifications +if a pointer to that file descriptor is passed in to the routine and +NOTIFY_REUSE is set in the flags parameter. +.Pp +Note that the kernel limits the buffer space for queued writes on a +file descriptor. +If it is important that notifications should not be +lost due to queue overflow, clients should service messages quickly, +and be cautious in using the same file descriptor for notifications +for more than one name. +.Pp +Notifications are delivered by an integer value written to the +file descriptor. +The value is sent in network byte order. +When converted to host byte order, for example by using +.Fn ntohl , +it will match the notification token +for which the notification was generated. +.Ss notify_check +Checks if any notifications have been posted for a name. +The output +parameter "check" is set to 0 for false, 1 for true. +A true indication is +returned the first time notify_check is called for a token. +Subsequent calls +give a true indication when notifications have been posted for the name +associated with the notification token. +.Pp +.Fn notify_check +may be used with any notification token produced by any of the notification +registration routines. +A fast check based on a shared memory implementation +is used when the token was generated by +.Fn notify_register_check . +Other tokens are checked by a call to the notification server. +.Ss notify_set_state +Set a 64-bit unsigned integer variable associated with a token. +.Pp +Each registered notification key has an associated 64-bit integer variable, +which may be set using this routine and examined using the +.Fn notify_get_state +routine. +The state variable is free to be used by clients of the notification API. +It may be used to synchronize state information between cooperating processes or threads. +(Available in Mac OS X 10.5 or later.) +.Ss notify_get_state +Get the 64-bit unsigned integer value associated with a token. +The default value of a state variable is zero. +(Available in Mac OS X 10.5 or later.) +.Ss notify_suspend +Suspends delivery of notifications for a notification token. +Any notifications corresponding to a token that are posted while it is suspended +will be coalesced, and pended until notifications are resumed using +.Fn notify_resume . +.Pp +Calls to +.Fn notify_suspend +may be nested. +Notifications will resume only when a matching number of calls are made to +.Fn notify_resume . +.Ss notify_resume +Removes one level of suspension for a token previously suspended by a call to +.Fn notify_suspend . +When resumed, notifications will be delivered normally. +A single notification will be generated if any notifications were pended while the token was suspended. +.Ss notify_cancel +Cancel notification and free resources associated with a notification +token. +Mach ports and file descriptor associated with a token are released +(deallocated or closed) when all registration tokens associated with +the port or file descriptor have been cancelled. +.Pp +.Ss notify_is_valid_token +Determines if an integer value is valid for a current registration. +Negative integers are never valid. +A positive or zero value is valid if the current process has a registration associated with the given value. +.Sh NAMESPACE CONVENTIONS +Names in the namespace must be NULL-terminated. +Names should be encoded as UTF-8 strings. +.Pp +The namespace supported by the system is unstructured, but users of +this API are highly encouraged to follow the reverse-ICANN domain +name convention used for Java package names and for System Preferences +on Mac OS X. +For example, "com.mydomain.example.event". +.Pp +Apple reserves the portion +of the namespace prefixed by "com.apple.". +This policy is not +enforced in the current implementation, but may be in the future. +.Pp +Names in the space "user.uid.UID", where UID is a numeric user ID number +are reserved for processes with that UID. +Names in this protected space may only be accessed or modified by processes +with the effective UID specified as the UID in the name. +The name "user.uid.UID" is protected for the given UID, as are any +names of the form "user.uid.UID.". +In the latter case, the name must have a dot character following the UID. +.Pp +Third party developers are encouraged to choose a prefix for names +that will avoid conflicts in the shared namespace. +.Pp +The portion of the namespace prefixed by the string "self." is set aside +for private use by applications. +That is, each client may use that part +of the namespace for intra-process notifications. +These notifications +are private to each individual process and are not propagated between +processes. +.Sh USAGE EXAMPLES +A notification producer. +.Pp + #include + ... +.Pp + notify_post("com.eg.random.event"); +.Pp +A client using notify_check() to determine when to invalidate a cache. +.Pp + #include + #include + #include +.Pp + int + main(int argc, char *argv[]) + { + uint32_t status; + int token, check; +.Pp + status = notify_register_check("com.eg.update", &token); + if (status != NOTIFY_STATUS_OK) + { + fprintf(stderr, "registration failed (%u)\\n", status); + exit(status); + } +.Pp + build_my_cache(); +.Pp + ... +.Pp + status = notify_check(token, &check); + if ((status == NOTIFY_STATUS_OK) && (check != 0)) + { + /* An update has occurred - invalidate the cache */ + reset_my_cache(); + } +.Pp + ... +.Pp +A client using file descriptor notifications. +.Pp + #include + #include + #include + #include + #include + #include + #include + #include +.Pp + int + main(int argc, char *argv[]) + { + uint32_t status; + int nf, rtoken, qtoken, t, ret; + fd_set readfds; +.Pp + status = notify_register_file_descriptor("com.eg.random.event", + &nf, 0, &rtoken); + if (status != NOTIFY_STATUS_OK) + { + fprintf(stderr, "registration failed (%u)\\n", status); + exit(status); + } +.Pp + status = notify_register_file_descriptor("com.eg.random.quit", + &nf, NOTIFY_REUSE, &qtoken); + if (status != NOTIFY_STATUS_OK) + { + fprintf(stderr, "registration failed (%u)\\n", status); + exit(status); + } +.Pp + FD_ZERO(&readfds); + FD_SET(nf, &readfds); +.Pp + for (;;) + { + ret = select(nf+1, &readfds, NULL, NULL, NULL); + if (ret <= 0) continue; + if (!FD_ISSET(nf, &readfds)) continue; +.Pp + status = read(nf, &t, sizeof(int)); + if (status < 0) + { + perror("read"); + break; + } +.Pp + t = ntohl(t); +.Pp + if (t == rtoken) printf("random event\\n"); + else if (t == qtoken) break; + } +.Pp + printf("shutting down\\n"); + notify_cancel(rtoken); + notify_cancel(qtoken); + exit(0); + } +.Pp +A client using dispatch notifications. +.Pp + #include + #include + #include + #include +.Pp + int + main(void) + { + uint32_t status; + int token; +.Pp + status = notify_register_dispatch("com.eg.random.event", &token, + dispatch_get_main_queue(), ^(int t) { + printf("com.eg.random.event received!\\n"); }); +.Pp + dispatch_main(); + exit(0); + } +.Sh HISTORY +These functions first appeared in +Mac OS X 10.3. +.Sh SEE ALSO +.Xr ntohl 3 , +.Xr read 2 , +.Xr select 2 , +.Xr signal 3 diff --git a/libnotify/notify.h b/libnotify/notify.h new file mode 100644 index 000000000..2d1a219c1 --- /dev/null +++ b/libnotify/notify.h @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Portions Copyright (c) 2003-2010 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef __NOTIFICATION_H__ +#define __NOTIFICATION_H__ + +#include +#include +#include +#include +#ifdef __BLOCKS__ +#include +#endif /* __BLOCKS__ */ + +/*! @header + * These routines allow processes to exchange stateless notification events. + * Processes post notifications to a single system-wide notification server, + * which then distributes notifications to client processes that have + * registered to receive those notifications, including processes run by + * other users. + * + * Notifications are associated with names in a namespace shared by all + * clients of the system. Clients may post notifications for names, and + * may monitor names for posted notifications. Clients may request + * notification delivery by a number of different methods. + * + * Clients desiring to monitor names in the notification system must + * register with the system, providing a name and other information + * required for the desired notification delivery method. Clients are + * given an integer token representing the registration. + * + * Note that the kernel provides limited queues for mach message and file + * descriptor messages. It is important to make sure that clients read + * mach ports and file descriptors frequently to prevent messages from + * being lost due to resource limitations. Clients that use signal-based + * notification should be aware that signals are not delivered to + * a process while it is running in a signal handler. This may affect + * the delivery of signals in close succession. + * + * Notifications may be coalesced in some cases. Multiple events posted + * for a name in rapid succession may result in a single notification sent + * to clients registered for notification for that name. Clients checking + * for changes using the notify_check() routine cannot determine if + * more than one event pas been posted since a previous call to + * notify_check() for that name. + * + * "False positives" may occur in notify_check() when used with a token + * generated by notify_register_check() due to implementation constraints. + * This behavior may vary in future releases. + * + * Synchronization between two processes may be achieved using the + * notify_set_state() and notify_get_state() routines. + */ + +/*! @defineblock Status Codes + * Status codes returned by the API. + */ +#define NOTIFY_STATUS_OK 0 +#define NOTIFY_STATUS_INVALID_NAME 1 +#define NOTIFY_STATUS_INVALID_TOKEN 2 +#define NOTIFY_STATUS_INVALID_PORT 3 +#define NOTIFY_STATUS_INVALID_FILE 4 +#define NOTIFY_STATUS_INVALID_SIGNAL 5 +#define NOTIFY_STATUS_INVALID_REQUEST 6 +#define NOTIFY_STATUS_NOT_AUTHORIZED 7 +#define NOTIFY_STATUS_FAILED 1000000 +/*! @/defineblock */ + +/*! + * Flag bits used for registration. + */ +#define NOTIFY_REUSE 0x00000001 + + +/*! + * Token values are zero or positive integers. + * NOTIFY_TOKEN_INVALID is useful as an initial value for + * a token value passed as an in/out parameter to one of + * the registration routines below. + */ +#define NOTIFY_TOKEN_INVALID -1 + +__BEGIN_DECLS + +/*! + * Post a notification for a name. + * + * This is the only call that is required for a notification producer. + * Returns status. + */ +uint32_t notify_post(const char *name); + + +#ifdef __BLOCKS__ +typedef void (^notify_handler_t)(int token); + +/*! + * @function notify_register + * @abstract Request notification delivery to a dispatch queue. + * @discussion When notifications are received by the process, the notify + * subsystem will deliver the registered Block to the target + * dispatch queue. Notification blocks are not re-entrant, + * and subsequent notification Blocks will not be delivered + * for the same registration until the previous Block has + * returned. + * @param name (input) The notification name. + * @param out_token (output) The registration token. + * @param queue (input) The dispatch queue to which the Block is submitted. + * The dispatch queue is retained by the notify subsystem while + * the notification is registered, and will be released when + * notification is canceled. + * @param block (input) The Block to invoke on the dispatch queue in response + * to a notification. The notification token is passed to the + * Block as an argument so that the callee can modify the state + * of the notification or cancel the registration. + * @result Returns status. + */ +uint32_t notify_register_dispatch(const char *name, int *out_token, dispatch_queue_t queue, notify_handler_t handler) +__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_2); +#endif /* __BLOCKS__ */ + +/*! + * Creates a registration token be used with notify_check(), + * but no active notifications will be delivered. + * + * @param name + * (input) notification name + * @param out_token + * (output) registration token + * @result Returns status. + */ +uint32_t notify_register_check(const char *name, int *out_token); + +/*! + * Request notification delivery by UNIX signal. + * + * A client may request signal notification for multiple names. After a signal + * is delivered, the notify_check() routine may be called with each notification + * token to determine which name (if any) generated the signal notification. + * + * @param name (input) notification name + * @param sig (input) signal number (see signal(3)) + * @param out_token (output) notification token + * @result Returns status. + */ +uint32_t notify_register_signal(const char *name, int sig, int *out_token); + +/*! + * Request notification by mach message. + * + * Notifications are delivered by an empty message sent to a mach port. + * By default, a new port is allocated and a pointer to it is returned + * as the value of "notify_port". A mach port previously returned by a + * call to this routine may be used for notifications if a pointer to that + * port is passed in to the routine and NOTIFY_REUSE is set in the flags + * parameter. The notification service must be able to extract send + * rights to the port. + * + * Note that the kernel limits the size of the message queue for any port. + * If it is important that notifications should not be lost due to queue + * overflow, clients should service messages quickly, and be careful about + * using the same port for notifications for more than one name. + * + * A notification message has an empty message body. The msgh_id field + * in the mach message header will have the value of the notification + * token. If a port is reused for multiple notification registrations, + * the msgh_id value may be used to determine which name generated + * the notification. + * + * @param name + * (input) notification name + * @param out_token + * (output) notification token + * @param notify_port + * (input/output) pointer to a mach port + * @result Returns status. + */ +uint32_t notify_register_mach_port(const char *name, mach_port_t *notify_port, int flags, int *out_token); + +/* + * Request notification by a write to a file descriptor. + * + * Notifications are delivered by a write to a file descriptor. + * By default, a new file descriptor is created and a pointer to it + * is returned as the value of "notify_fd". A file descriptor created + * by a previous call to this routine may be used for notifications if + * a pointer to that file descriptor is passed in to the routine and + * NOTIFY_REUSE is set in the flags parameter. + * + * Note that the kernel limits the buffer space for queued writes on a + * file descriptor. If it is important that notifications should not be + * lost due to queue overflow, clients should service messages quickly, + * and be careful about using the same file descriptor for notifications + * for more than one name. + * + * Notifications are delivered by an integer value written to the + * file descriptor. The value will match the notification token + * for which the notification was generated. + * + * @param name + * (input) notification name + * @param out_token + * (output) notification token + * @param notify_fd + * (input/output) pointer to a file descriptor + * @result Returns status. + */ +uint32_t notify_register_file_descriptor(const char *name, int *notify_fd, int flags, int *out_token); + +/*! + * Check if any notifications have been posted. + * + * Output parameter check is set to 0 for false, 1 for true. Returns status. + * check is set to true the first time notify_check is called for a token. + * Subsequent calls set check to true when notifications have been posted for + * the name associated with the notification token. This routine is independent + * of notify_post(). That is, check will be true if an application calls + * notify_post() for a name and then calls notify_check() for a token associated + * with that name. + * + * @param token + * (input)notification token + * @param check + * (output) true/false indication + * @result Returns status. + */ +uint32_t notify_check(int token, int *check); + +/*! + * Cancel notification and free resources associated with a notification + * token. Mach ports and file descriptor associated with a token are released + * (deallocated or closed) when all registration tokens associated with + * the port or file descriptor have been cancelled. + * + * @param token + * (input) notification token + * @result Returns status. + */ +uint32_t notify_cancel(int token); + +/*! + * Suspend delivery of notifications for a token. Notifications for this token will be + * pended and coalesced, then delivered following a matching call to notify_resume. + * Calls to notify_suspend may be nested. Notifications remain suspended until + * an equal number of calls have been made to notify_resume. + * + * @param token + * (input) notification token + * @result Returns status. + */ +uint32_t notify_suspend(int token) +__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0); + +/*! + * Removes one level of suspension for a token previously suspended + * by a call to notify_suspend. Notifications will resume when a matching + * call to notify_resume is made for each previous call to notify_suspend. + * Notifications posted while a token is suspended are coalesced into + * a single notification sent following a resumption. + * + * @param token + * (input) notification token + * @result Returns status. + */ +uint32_t notify_resume(int token) +__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0); + +/*! + * Set or get a state value associated with a notification token. + * Each key in the notification namespace has an associated integer value available + * for use by clients as for application-specific purposes. A common usage is to + * allow two processes or threads to synchronize their activities. For example, a + * server process may need send a notification when a resource becomes available. + * A client process can register for the notification, but when it starts up it will + * not know whether the resource is available. The server can set the state value, + * and the client can check the value at startup time to synchronize with the server. + * + * Set the 64-bit integer state value. + * + * @param token + * (input) notification token + * @param state64 + * (input) 64-bit unsigned integer value + * @result Returns status. + */ +uint32_t notify_set_state(int token, uint64_t state64) +__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0); + +/*! + * Get the 64-bit integer state value. + * + * @param token + * (input) notification token + * @param state64 + * (output) 64-bit unsigned integer value + * @result Returns status. + */ +uint32_t notify_get_state(int token, uint64_t *state64) +__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0); + +/*! + * Determine if a token is valid (currently registered). + * Negative integer values are always invalid. Positive or + * zero values are valid only if they are associated with an + * existing registratiom. + * + * @param val + * (input) integer value + * @result Returns true if the value is a valid token, false otherwise. + */ +bool notify_is_valid_token(int val) +__OSX_AVAILABLE_STARTING(__MAC_10_10,__IPHONE_8_0); + +__END_DECLS + +#endif /* __NOTIFICATION_H__ */ diff --git a/libnotify/notify_cancel.3 b/libnotify/notify_cancel.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_cancel.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_check.3 b/libnotify/notify_check.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_check.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_client.c b/libnotify/notify_client.c new file mode 100644 index 000000000..290be04e8 --- /dev/null +++ b/libnotify/notify_client.c @@ -0,0 +1,2465 @@ +/* + * Copyright (c) 2003-2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <_simple.h> + +#include "libnotify.h" + +#include "notify.h" +#include "notify_internal.h" +#include "notify_ipc.h" +#include "notify_private.h" + +#define INITIAL_TOKEN_ID 0 + +// +WEAK_IMPORT_ATTRIBUTE bool _dispatch_is_multithreaded(void); + +#define EVENT_INIT 0 +#define EVENT_REGEN 1 + +#define SELF_PREFIX "self." +#define SELF_PREFIX_LEN 5 + +#define COMMON_SELF_PORT_KEY "self.com.apple.system.notify.common" + +#define MULTIPLE_REGISTRATION_WARNING_TRIGGER 20 + +extern uint32_t _notify_lib_peek(notify_state_t *ns, pid_t pid, int token, int *val); +extern int *_notify_lib_check_addr(notify_state_t *ns, pid_t pid, int token); + +#define CLIENT_TOKEN_TABLE_SIZE 256 + +#define NID_UNSET 0xffffffffffffffffL +#define NID_CALLED_ONCE 0xfffffffffffffffeL + +#define NO_LOCK 1 + +typedef struct +{ + uint32_t refcount; + uint64_t name_id; +} name_table_node_t; + +typedef struct +{ + uint32_t refcount; + const char *name; + size_t namelen; + name_table_node_t *name_node; + uint32_t token; + uint32_t slot; + uint32_t val; + uint32_t flags; + int fd; + int signal; + mach_port_t mp; + uint32_t client_id; + uint64_t set_state_time; + uint64_t set_state_val; + char * path; + int path_flags; + dispatch_queue_t queue; + notify_handler_t block; +} token_table_node_t; + +/* FORWARD */ +static void _notify_lib_regenerate(int src); +static void notify_retain_mach_port(mach_port_t mp, int mine); +static void _notify_dispatch_handle(mach_port_t port); +static notify_state_t *_notify_lib_self_state(); + +#if TARGET_IPHONE_SIMULATOR +const char * +_notify_shm_id() +{ + static dispatch_once_t once; + static char *shm_id; + + dispatch_once(&once, ^{ + // According to documentation, our shm_id must be no more than 31 characters long + // but in practice, even 31 characters is too long (), + // so we jump through some hoops to make a smaller string based on our UDID. + const char *udid = getenv("SIMULATOR_UDID"); + if (udid && strlen(udid) == 36) { + char scratch[34]; // 32 characters, 2 NUL + + // 01234567890123456789012345678901234567890 + // UUUUUUUU-UUUU-UUUU-LLLL-LLLLLLLLLLLL + memcpy(scratch, udid, 8); + memcpy(scratch+8, udid+9, 4); + memcpy(scratch+12, udid+14, 4); + scratch[16] = '\0'; + + memcpy(scratch+17, udid+19, 4); + memcpy(scratch+21, udid+24, 12); + scratch[33] = '\0'; + + // If the input is invalid, these will end up being undefined + // values, but they'll still be values we can use. + uint64_t upper = strtoull(scratch, NULL, 16); + uint64_t lower = strtoull(scratch + 17, NULL, 16); + + const char *c64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + scratch[0] = c64[(upper >> 57) & 0xf]; + scratch[1] = c64[(upper >> 50) & 0xf]; + scratch[2] = c64[(upper >> 43) & 0xf]; + scratch[3] = c64[(upper >> 36) & 0xf]; + scratch[4] = c64[(upper >> 29) & 0xf]; + scratch[5] = c64[(upper >> 22) & 0xf]; + scratch[6] = c64[(upper >> 15) & 0xf]; + scratch[7] = c64[(upper >> 8) & 0xf]; + scratch[8] = c64[(upper >> 1) & 0xf]; + // Drop a bit on the floor, but that probably doesn't matter. It does not need to be reversible + + scratch[10] = c64[(lower >> 57) & 0xf]; + scratch[11] = c64[(lower >> 50) & 0xf]; + scratch[12] = c64[(lower >> 43) & 0xf]; + scratch[13] = c64[(lower >> 36) & 0xf]; + scratch[14] = c64[(lower >> 29) & 0xf]; + scratch[15] = c64[(lower >> 22) & 0xf]; + scratch[16] = c64[(lower >> 15) & 0xf]; + scratch[17] = c64[(lower >> 8) & 0xf]; + scratch[18] = c64[(lower >> 1) & 0xf]; + // Drop a bit on the floor, but that probably doesn't matter. It does not need to be reversible + + scratch[19] = '\0'; + + asprintf(&shm_id, "sim.not.%s", scratch); + assert(shm_id); + } + + if (!shm_id) { + shm_id = "apple.shm.notification_center"; + } + }); + + return shm_id; +} +#endif + +static int +shm_attach(uint32_t size) +{ + int32_t shmfd; + notify_globals_t globals = _notify_globals(); + + shmfd = shm_open(SHM_ID, O_RDONLY, 0); + if (shmfd == -1) return -1; + + globals->shm_base = mmap(NULL, size, PROT_READ, MAP_SHARED, shmfd, 0); + close(shmfd); + + if (globals->shm_base == (uint32_t *)-1) globals->shm_base = NULL; + if (globals->shm_base == NULL) return -1; + + return 0; +} + +#ifdef NOTDEF +static void +shm_detach(void) +{ + if (shm_base != NULL) + { + shmdt(shm_base); + shm_base = NULL; + } +} +#endif + +/* + * Initialization of global variables. Called once per process. + */ +void +_notify_init_globals(void * /* notify_globals_t */ _globals) +{ + notify_globals_t globals = _globals; + + pthread_mutex_init(&globals->notify_lock, NULL); + globals->token_id = INITIAL_TOKEN_ID; + globals->notify_common_token = -1; +} + +#if !_NOTIFY_HAS_ALLOC_ONCE +notify_globals_t +_notify_globals_impl(void) +{ + static dispatch_once_t once; + static notify_globals_t globals; + dispatch_once(&once, ^{ + globals = calloc(1, sizeof(struct notify_globals_s)); + _notify_init_globals(globals); + }); + return globals; +} +#endif + +/* + * _notify_lib_init is called for each new registration (event = EVENT_INIT). + * It is also called to re-initialize when the library has detected that + * notifyd has restarted (event = EVENT_REGEN). + */ +static uint32_t +_notify_lib_init(uint32_t event) +{ + __block kern_return_t kstatus; + __block bool first = false; + int status, cid; + uint64_t state; + + notify_globals_t globals = _notify_globals(); + + /* notifyd sets NOTIFY_OPT_DISABLE to avoid re-entrancy issues */ + if (globals->client_opts & NOTIFY_OPT_DISABLE) return NOTIFY_STATUS_FAILED; + + /* Look up the notifyd server port just once. */ + kstatus = KERN_SUCCESS; + dispatch_once(&globals->notify_server_port_once, ^{ + first = true; + kstatus = bootstrap_look_up2(bootstrap_port, NOTIFY_SERVICE_NAME, &globals->notify_server_port, 0, BOOTSTRAP_PRIVILEGED_SERVER); + }); + + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + + pthread_mutex_lock(&globals->notify_lock); + + /* + * _dispatch_is_multithreaded() tells us if it is safe to use dispatch queues for + * a shared port for all registratios, and to watch for notifyd exiting / restarting. + * + * Note that _dispatch_is_multithreaded is weak imported, + */ + if (_dispatch_is_multithreaded) + { + if (_dispatch_is_multithreaded()) globals->client_opts |= (NOTIFY_OPT_DEMUX | NOTIFY_OPT_REGEN); + } + + /* + * Look up the server's PID and supported IPC version on the first call, + * and on a regeneration event (when the server has restarted). + */ + if (first || (event == EVENT_REGEN)) + { + pid_t last_pid = globals->notify_server_pid; + + globals->notify_ipc_version = 0; + globals->notify_server_pid = 0; + + kstatus = _notify_server_register_plain(globals->notify_server_port, NOTIFY_IPC_VERSION_NAME, NOTIFY_IPC_VERSION_NAME_LEN, &cid, &status); + if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) + { + kstatus = _notify_server_get_state(globals->notify_server_port, cid, &state, &status); + if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) + { + globals->notify_ipc_version = state; + state >>= 32; + globals->notify_server_pid = state; + } + + _notify_server_cancel(globals->notify_server_port, cid, &status); + + if ((last_pid == globals->notify_server_pid) && (event == EVENT_REGEN)) + { + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_INVALID_REQUEST; + } + } + + if (globals->server_proc_source != NULL) + { + dispatch_source_cancel(globals->server_proc_source); + dispatch_release(globals->server_proc_source); + globals->server_proc_source = NULL; + } + } + + if (globals->notify_ipc_version < 2) + { + /* regen is not supported below version 2 */ + globals->client_opts &= ~NOTIFY_OPT_REGEN; + } + + /* + * Create a source (DISPATCH_SOURCE_TYPE_PROC) to invoke _notify_lib_regenerate if notifyd restarts. + * Available in IPC version 2. + */ + if ((globals->server_proc_source == NULL) && (globals->client_opts & NOTIFY_OPT_REGEN) && (globals->notify_server_pid != 0)) + { + globals->server_proc_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)globals->notify_server_pid, DISPATCH_PROC_EXIT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + dispatch_source_set_event_handler(globals->server_proc_source, ^{ _notify_lib_regenerate(1); }); + dispatch_resume(globals->server_proc_source); + } + + /* + * Create the shared multiplex ports if NOTIFY_OPT_DEMUX is set. + */ + if ((globals->client_opts & NOTIFY_OPT_DEMUX) && (globals->notify_common_port == MACH_PORT_NULL)) + { + kern_return_t kr; + task_t task = mach_task_self(); + + kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &globals->notify_common_port); + if (kr == KERN_SUCCESS) + { + globals->notify_dispatch_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, globals->notify_common_port, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + dispatch_source_set_event_handler(globals->notify_dispatch_source, ^{ + notify_globals_t globals = _notify_globals(); + _notify_dispatch_handle(globals->notify_common_port); + }); + dispatch_source_set_cancel_handler(globals->notify_dispatch_source, ^{ + task_t task = mach_task_self(); + notify_globals_t globals = _notify_globals(); + mach_port_mod_refs(task, globals->notify_common_port, MACH_PORT_RIGHT_RECEIVE, -1); + }); + dispatch_resume(globals->notify_dispatch_source); + } + } + + pthread_mutex_unlock(&globals->notify_lock); + + if (globals->notify_common_port != MACH_PORT_NULL && (first || event == EVENT_REGEN)) + { + /* register the common port with notifyd */ + status = notify_register_mach_port(COMMON_PORT_KEY, &globals->notify_common_port, NOTIFY_REUSE, &globals->notify_common_token); + } + + return NOTIFY_STATUS_OK; +} + +/* Reset all internal state at fork */ +void +_notify_fork_child(void) +{ + notify_globals_t globals = _notify_globals(); + + _notify_init_globals(globals); + + /* + * Expressly disable notify in the child side of a fork if it had + * been initialized in the parent. Using notify in the child process + * can lead to deadlock (see ). + * + * Also disable notify in the forked child of a multi-threaded parent that + * used dispatch, since notify will use dispatch, and that will blow up. + * It's OK to make that check here by calling _dispatch_is_multithreaded(), + * since we will actually be looking at the parent's state. + */ + if (globals->notify_server_port != MACH_PORT_NULL) globals->client_opts = NOTIFY_OPT_DISABLE; + if (_dispatch_is_multithreaded) // weak imported symbol + { + if (_dispatch_is_multithreaded()) globals->client_opts = NOTIFY_OPT_DISABLE; + } + + globals->self_state = NULL; + globals->notify_server_port = MACH_PORT_NULL; + globals->notify_ipc_version = 0; + globals->notify_server_pid = 0; + + globals->token_table = NULL; + globals->token_name_table = NULL; + + globals->fd_count = 0; + globals->fd_clnt = NULL; + globals->fd_srv = NULL; + globals->fd_refcount = NULL; + + globals->mp_count = 0; + globals->mp_list = NULL; + globals->mp_refcount = NULL; + globals->mp_mine = NULL; + + globals->shm_base = NULL; +} + +static uint32_t +token_table_add(const char *name, size_t namelen, uint64_t nid, uint32_t token, uint32_t cid, uint32_t slot, uint32_t flags, int sig, int fd, mach_port_t mp, int lock) +{ + token_table_node_t *t; + name_table_node_t *n; + uint32_t warn_count = 0; + notify_globals_t globals = _notify_globals(); + + dispatch_once(&globals->token_table_once, ^{ + globals->token_table = _nc_table_new(CLIENT_TOKEN_TABLE_SIZE); + globals->token_name_table = _nc_table_new(CLIENT_TOKEN_TABLE_SIZE); + }); + + if (globals->token_table == NULL) return -1; + if (globals->token_name_table == NULL) return -1; + if (name == NULL) return -1; + + t = (token_table_node_t *)calloc(1, sizeof(token_table_node_t)); + if (t == NULL) return -1; + + t->refcount = 1; + + /* we will get t->name from the token_name_table */ + t->name = NULL; + + t->namelen = namelen; + t->token = token; + t->slot = slot; + t->val = 0; + t->flags = flags; + t->fd = fd; + t->mp = mp; + t->client_id = cid; + + if (lock != NO_LOCK) pthread_mutex_lock(&globals->notify_lock); + _nc_table_insert_n(globals->token_table, t->token, t); + + /* check if we have this name in the name table */ + n = _nc_table_find_get_key(globals->token_name_table, name, &(t->name)); + if (n == NULL) + { + char *copy_name = strdup(name); + if (copy_name == NULL) + { + free(t); + if (lock != NO_LOCK) pthread_mutex_unlock(&globals->notify_lock); + return -1; + } + + t->name = (const char *)copy_name; + + /* create a new name table node */ + n = (name_table_node_t *)calloc(1, sizeof(name_table_node_t)); + if (n != NULL) + { + n->refcount = 1; + n->name_id = nid; + + /* the name table node "owns" the name */ + _nc_table_insert_pass(globals->token_name_table, copy_name, n); + t->name_node = n; + } + } + else + { + /* this token retains the name table node */ + t->name_node = n; + n->refcount++; + + if ((n->refcount % MULTIPLE_REGISTRATION_WARNING_TRIGGER) == 0) + { + warn_count = n->refcount; + } + } + + if (lock != NO_LOCK) pthread_mutex_unlock(&globals->notify_lock); + + if (warn_count > 0) + { + char *msg; + asprintf(&msg, "notify name \"%s\" has been registered %d times - this may be a leak", name, warn_count); + if (msg) + _simple_asl_log(ASL_LEVEL_WARNING, "com.apple.notify", msg); + free(msg); + } + + return 0; +} + +static token_table_node_t * +token_table_find_retain(uint32_t token) +{ + token_table_node_t *t; + notify_globals_t globals = _notify_globals(); + + pthread_mutex_lock(&globals->notify_lock); + + t = (token_table_node_t *)_nc_table_find_n(globals->token_table, token); + if (t != NULL) t->refcount++; + + pthread_mutex_unlock(&globals->notify_lock); + + return t; +} + +static token_table_node_t * +token_table_find_no_lock(uint32_t token) +{ + notify_globals_t globals = _notify_globals(); + return (token_table_node_t *)_nc_table_find_n(globals->token_table, token); +} + +static name_table_node_t * +name_table_find_retain_no_lock(const char *name) +{ + name_table_node_t *n; + notify_globals_t globals = _notify_globals(); + + n = (name_table_node_t *)_nc_table_find(globals->token_name_table, name); + if (n != NULL) n->refcount++; + + return n; +} + +static void +name_table_release_no_lock(const char *name) +{ + name_table_node_t *n; + notify_globals_t globals = _notify_globals(); + + n = (name_table_node_t *)_nc_table_find(globals->token_name_table, name); + if (n != NULL) + { + if (n->refcount > 0) n->refcount--; + if (n->refcount == 0) + { + _nc_table_delete(globals->token_name_table, name); + free(n); + } + } +} + +static void +name_table_set_nid(const char *name, uint64_t nid) +{ + name_table_node_t *n; + notify_globals_t globals = _notify_globals(); + + pthread_mutex_lock(&globals->notify_lock); + + n = (name_table_node_t *)_nc_table_find(globals->token_name_table, name); + if (n != NULL) n->name_id = nid; + + pthread_mutex_unlock(&globals->notify_lock); +} + +static void +_notify_lib_regenerate_token(token_table_node_t *t) +{ + uint32_t type; + int status, new_slot; + kern_return_t kstatus; + mach_port_t port; + uint64_t new_nid; + size_t pathlen; + + if (t == NULL) return; + if (t->name == NULL) return; + if (t->flags & NOTIFY_FLAG_SELF) return; + if ((t->flags & NOTIFY_FLAG_REGEN) == 0) return; + if (!strcmp(t->name, COMMON_PORT_KEY)) return; + + notify_globals_t globals = _notify_globals(); + + port = MACH_PORT_NULL; + if (t->flags & NOTIFY_TYPE_PORT) + { + port = globals->notify_common_port; + } + + pathlen = 0; + if (t->path != NULL) pathlen = strlen(t->path); + type = t->flags & 0x000000ff; + + kstatus = _notify_server_regenerate(globals->notify_server_port, (caddr_t)t->name, t->namelen, t->token, type, port, t->signal, t->slot, t->set_state_val, t->set_state_time, t->path, pathlen, t->path_flags, &new_slot, &new_nid, &status); + + if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_FAILED; + if (status != NOTIFY_STATUS_OK) return; + + t->slot = new_slot; + + /* reset the name_id in the name table node */ + if (t->name_node != NULL) t->name_node->name_id = new_nid; +} + +/* + * Invoked when server has died. + * Regenerates all registrations and state. + */ +static void +_notify_lib_regenerate(int src) +{ + void *tt; + token_table_node_t *t; + notify_globals_t globals = _notify_globals(); + + if ((globals->client_opts & NOTIFY_OPT_REGEN) == 0) return; + + /* _notify_lib_init returns an error if regeneration is unnecessary */ + if (_notify_lib_init(EVENT_REGEN) == NOTIFY_STATUS_OK) + { + pthread_mutex_lock(&globals->notify_lock); + + tt = _nc_table_traverse_start(globals->token_table); + while (tt != NULL) + { + t = _nc_table_traverse(globals->token_table, tt); + if (t == NULL) break; + _notify_lib_regenerate_token(t); + } + + _nc_table_traverse_end(globals->token_table, tt); + + pthread_mutex_unlock(&globals->notify_lock); + } +} + +/* + * Regenerate if the server PID (shared memory slot 0) has changed. + */ +static inline void +regenerate_check() +{ + notify_globals_t globals = _notify_globals(); + + if ((globals->client_opts & NOTIFY_OPT_REGEN) == 0) return; + + if ((globals->shm_base != NULL) && (globals->shm_base[0] != globals->notify_server_pid)) _notify_lib_regenerate(0); +} + +/* notify_lock is required in notify_retain_file_descriptor */ +static void +notify_retain_file_descriptor(int clnt, int srv) +{ + int x, i; + notify_globals_t globals = _notify_globals(); + + if (clnt < 0) return; + if (srv < 0) return; + + pthread_mutex_lock(&globals->notify_lock); + + x = -1; + for (i = 0; (i < globals->fd_count) && (x < 0); i++) + { + if (globals->fd_clnt[i] == clnt) x = i; + } + + if (x >= 0) + { + globals->fd_refcount[x]++; + pthread_mutex_unlock(&globals->notify_lock); + return; + } + + x = globals->fd_count; + globals->fd_count++; + + if (x == 0) + { + globals->fd_clnt = (int *)calloc(1, sizeof(int)); + globals->fd_srv = (int *)calloc(1, sizeof(int)); + globals->fd_refcount = (int *)calloc(1, sizeof(int)); + } + else + { + globals->fd_clnt = (int *)reallocf(globals->fd_clnt, globals->fd_count * sizeof(int)); + globals->fd_srv = (int *)reallocf(globals->fd_srv, globals->fd_count * sizeof(int)); + globals->fd_refcount = (int *)reallocf(globals->fd_refcount, globals->fd_count * sizeof(int)); + } + + if ((globals->fd_clnt == NULL) || (globals->fd_srv == NULL) || (globals->fd_refcount == NULL)) + { + free(globals->fd_clnt); + free(globals->fd_srv); + free(globals->fd_refcount); + globals->fd_count = 0; + } + else + { + globals->fd_clnt[x] = clnt; + globals->fd_srv[x] = srv; + globals->fd_refcount[x] = 1; + } + + pthread_mutex_unlock(&globals->notify_lock); +} + +/* notify_lock is NOT required in notify_release_file_descriptor */ +static void +notify_release_file_descriptor(int fd) +{ + int x, i, j; + notify_globals_t globals = _notify_globals(); + + if (fd < 0) return; + + x = -1; + for (i = 0; (i < globals->fd_count) && (x < 0); i++) + { + if (globals->fd_clnt[i] == fd) x = i; + } + + if (x < 0) return; + + if (globals->fd_refcount[x] > 0) globals->fd_refcount[x]--; + if (globals->fd_refcount[x] > 0) return; + + close(globals->fd_clnt[x]); + close(globals->fd_srv[x]); + + if (globals->fd_count == 1) + { + free(globals->fd_clnt); + free(globals->fd_srv); + free(globals->fd_refcount); + globals->fd_count = 0; + return; + } + + for (i = x + 1, j = x; i < globals->fd_count; i++, j++) + { + globals->fd_clnt[j] = globals->fd_clnt[i]; + globals->fd_srv[j] = globals->fd_srv[i]; + globals->fd_refcount[j] = globals->fd_refcount[i]; + } + + globals->fd_count--; + + globals->fd_clnt = (int *)reallocf(globals->fd_clnt, globals->fd_count * sizeof(int)); + globals->fd_srv = (int *)reallocf(globals->fd_srv, globals->fd_count * sizeof(int)); + globals->fd_refcount = (int *)reallocf(globals->fd_refcount, globals->fd_count * sizeof(int)); + + if ((globals->fd_clnt == NULL) || (globals->fd_srv == NULL) || (globals->fd_refcount == NULL)) + { + free(globals->fd_clnt); + free(globals->fd_srv); + free(globals->fd_refcount); + globals->fd_count = 0; + } +} + +/* notify_lock is required in notify_retain_mach_port */ +static void +notify_retain_mach_port(mach_port_t mp, int mine) +{ + int x, i; + notify_globals_t globals = _notify_globals(); + + if (mp == MACH_PORT_NULL) return; + + pthread_mutex_lock(&globals->notify_lock); + + x = -1; + for (i = 0; (i < globals->mp_count) && (x < 0); i++) + { + if (globals->mp_list[i] == mp) x = i; + } + + if (x >= 0) + { + globals->mp_refcount[x]++; + pthread_mutex_unlock(&globals->notify_lock); + return; + } + + x = globals->mp_count; + globals->mp_count++; + + if (x == 0) + { + globals->mp_list = (mach_port_t *)calloc(1, sizeof(mach_port_t)); + globals->mp_refcount = (int *)calloc(1, sizeof(int)); + globals->mp_mine = (int *)calloc(1, sizeof(int)); + } + else + { + globals->mp_list = (mach_port_t *)reallocf(globals->mp_list, globals->mp_count * sizeof(mach_port_t)); + globals->mp_refcount = (int *)reallocf(globals->mp_refcount, globals->mp_count * sizeof(int)); + globals->mp_mine = (int *)reallocf(globals->mp_mine, globals->mp_count * sizeof(int)); + } + + if ((globals->mp_list == NULL) || (globals->mp_refcount == NULL) || (globals->mp_mine == NULL)) + { + if (globals->mp_list != NULL) free(globals->mp_list); + if (globals->mp_refcount != NULL) free(globals->mp_refcount); + if (globals->mp_mine != NULL) free(globals->mp_mine); + globals->mp_count = 0; + } + else + { + globals->mp_list[x] = mp; + globals->mp_refcount[x] = 1; + globals->mp_mine[x] = mine; + } + + pthread_mutex_unlock(&globals->notify_lock); +} + +/* notify_lock is NOT required in notify_release_mach_port */ +static void +notify_release_mach_port(mach_port_t mp, uint32_t flags) +{ + int x, i; + notify_globals_t globals = _notify_globals(); + + if (mp == MACH_PORT_NULL) return; + + x = -1; + for (i = 0; (i < globals->mp_count) && (x < 0); i++) + { + if (globals->mp_list[i] == mp) x = i; + } + + if (x < 0) return; + + if (globals->mp_refcount[x] > 0) globals->mp_refcount[x]--; + if (globals->mp_refcount[x] > 0) return; + + if (globals->mp_mine[x] == 1) + { + mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_RECEIVE, -1); + + /* release send right if this is a self notification */ + if (flags & NOTIFY_FLAG_SELF) mach_port_deallocate(mach_task_self(), mp); + } + + if (flags & NOTIFY_FLAG_RELEASE_SEND) + { + /* multiplexed registration holds a send right in Libnotify */ + mach_port_deallocate(mach_task_self(), mp); + } + + if (globals->mp_count == 1) + { + if (globals->mp_list != NULL) free(globals->mp_list); + if (globals->mp_refcount != NULL) free(globals->mp_refcount); + if (globals->mp_mine != NULL) free(globals->mp_mine); + globals->mp_count = 0; + return; + } + + for (i = x + 1; i < globals->mp_count; i++) + { + globals->mp_list[i - 1] = globals->mp_list[i]; + globals->mp_refcount[i - 1] = globals->mp_refcount[i]; + globals->mp_mine[i - 1] = globals->mp_mine[i]; + } + + globals->mp_count--; + + globals->mp_list = (mach_port_t *)reallocf(globals->mp_list, globals->mp_count * sizeof(mach_port_t)); + globals->mp_refcount = (int *)reallocf(globals->mp_refcount, globals->mp_count * sizeof(int)); + globals->mp_mine = (int *)reallocf(globals->mp_mine, globals->mp_count * sizeof(int)); + + if ((globals->mp_list == NULL) || (globals->mp_refcount == NULL) || (globals->mp_mine == NULL)) + { + if (globals->mp_list != NULL) free(globals->mp_list); + if (globals->mp_refcount != NULL) free(globals->mp_refcount); + if (globals->mp_mine != NULL) free(globals->mp_mine); + globals->mp_count = 0; + } +} + +static void +token_table_release_no_lock(token_table_node_t *t) +{ + notify_globals_t globals = _notify_globals(); + + if (t == NULL) return; + + if (t->refcount > 0) t->refcount--; + if (t->refcount > 0) return; + + notify_release_file_descriptor(t->fd); + notify_release_mach_port(t->mp, t->flags); + + if (t->block != NULL) + { + dispatch_async_f(t->queue, t->block, (dispatch_function_t)_Block_release); + } + + t->block = NULL; + + if (t->queue != NULL) dispatch_release(t->queue); + t->queue = NULL; + + _nc_table_delete_n(globals->token_table, t->token); + name_table_release_no_lock(t->name); + + free(t->path); + free(t); +} + +static void +token_table_release(token_table_node_t *t) +{ + notify_globals_t globals = _notify_globals(); + + pthread_mutex_lock(&globals->notify_lock); + token_table_release_no_lock(t); + pthread_mutex_unlock(&globals->notify_lock); +} + +static notify_state_t * +_notify_lib_self_state() +{ + notify_globals_t globals = _notify_globals(); + + dispatch_once(&globals->self_state_once, ^{ + globals->self_state = _notify_lib_notify_state_new(NOTIFY_STATE_USE_LOCKS, 0); + }); + + return globals->self_state; +} + +/* SPI */ +void +notify_set_options(uint32_t opts) +{ + notify_globals_t globals = _notify_globals(); + + /* NOTIFY_OPT_DISABLE can be unset with NOTIFY_OPT_ENABLE */ + if (globals->client_opts & NOTIFY_OPT_DISABLE) + { + if ((opts & NOTIFY_OPT_ENABLE) == 0) return; + + /* re-enable by swapping in the saved server port and saved opts*/ + pthread_mutex_lock(&globals->notify_lock); + + globals->client_opts = globals->saved_opts; + globals->notify_server_port = globals->saved_server_port; + + pthread_mutex_unlock(&globals->notify_lock); + return; + } + + /* + * A client can disable the library even if the server port has already been fetched. + * Note that this could race with another thread making a Libnotify call. + */ + if (opts & NOTIFY_OPT_DISABLE) + { + pthread_mutex_lock(&globals->notify_lock); + + globals->saved_opts = globals->client_opts; + globals->client_opts = NOTIFY_OPT_DISABLE; + + globals->saved_server_port = globals->notify_server_port; + globals->notify_server_port = MACH_PORT_NULL; + + pthread_mutex_unlock(&globals->notify_lock); + return; + } + + globals->client_opts = opts; + + /* call _notify_lib_init to create ports / dispatch sources as required */ + _notify_lib_init(EVENT_INIT); +} + +/* + * PUBLIC API + */ + +/* + * notify_post is a very simple API, but the implementation is + * more complex to try to optimize the time it takes. + * + * The server - notifyd - keeps a unique ID number for each key + * in the namespace. Although it's reasonably fast to call + * _notify_server_post_4 (a MIG simpleroutine), the MIG call + * allocates VM and copies the name string. It's much faster to + * call using the ID number. The problem is mapping from name to + * ID number. The token table keeps track of all registered names + * (in the client), but the registration calls are simpleroutines, + * except for notify_register_check. notify_register_check saves + * the name ID in the token table, but the other routines set it + * to NID_UNSET. + * + * In notify_post, we check if the name is known. If it is not, + * then the client is doing a "cold call". There may be no + * clients for this name anywhere on the system. In this case + * we simply send the name. We take the allocate/copy cost, but + * the latency is still not too bad since we use a simpleroutine. + * + * If the name in registered and the ID number is known, we send + * the ID using a simpleroutine. This is very fast. + * + * If the name is registered but the ID number is NID_UNSET, we + * send the name (as in a "cold call". It *might* just be that + * this client process just posts once, and we don't want to incur + * any addition cost. The ID number is reset to NID_CALLED_ONCE. + * + * If the client posts the same name again (the ID number is + * NID_CALLED_ONCE, we do a synchronous call to notifyd, sending + * the name string and getting back the name ID, whcih we save + * in the token table. This is simply a zero/one/many heuristic: + * If the client posts the same name more than once, we make the + * guess that it's going to do it more frequently, and it's worth + * the time it takes to fetch the ID from notifyd. + */ +uint32_t +notify_post(const char *name) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + size_t namelen = 0; + name_table_node_t *n; + uint64_t nid = UINT64_MAX; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + _notify_lib_post(ns_self, name, 0, 0); + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + if (globals->notify_ipc_version == 0) + { + namelen = strlen(name); + kstatus = _notify_server_post(globals->notify_server_port, (caddr_t)name, namelen, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; + } + + namelen = strlen(name); + + /* Lock to prevent a race with notify cancel over the use of name IDs */ + pthread_mutex_lock(&globals->notify_lock); + + /* See if we have a name ID for this name. */ + n = name_table_find_retain_no_lock(name); + if (n != NULL) + { + if (n->name_id == NID_UNSET) + { + /* First post goes using the name string. */ + kstatus = _notify_server_post_4(globals->notify_server_port, (caddr_t)name, namelen); + if (kstatus != KERN_SUCCESS) + { + name_table_release_no_lock(name); + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_FAILED; + } + + n->name_id = NID_CALLED_ONCE; + name_table_release_no_lock(name); + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_OK; + } + else if (n->name_id == NID_CALLED_ONCE) + { + /* Post and fetch the name ID. Slow, but subsequent posts will be very fast. */ + kstatus = _notify_server_post_2(globals->notify_server_port, (caddr_t)name, namelen, &nid, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) + { + name_table_release_no_lock(name); + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_FAILED; + } + + if (status == NOTIFY_STATUS_OK) n->name_id = nid; + name_table_release_no_lock(name); + pthread_mutex_unlock(&globals->notify_lock); + return status; + } + else + { + /* We have the name ID. Do an async post using the name ID. Very fast. */ + kstatus = _notify_server_post_3(globals->notify_server_port, n->name_id); + name_table_release_no_lock(name); + pthread_mutex_unlock(&globals->notify_lock); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return NOTIFY_STATUS_OK; + } + } + + pthread_mutex_unlock(&globals->notify_lock); + + /* Do an async post using the name string. Fast (but not as fast as using name ID). */ + kstatus = _notify_server_post_4(globals->notify_server_port, (caddr_t)name, namelen); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_set_owner(const char *name, uint32_t uid, uint32_t gid) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + notify_globals_t globals = _notify_globals(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + status = _notify_lib_set_owner(ns_self, name, uid, gid); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + kstatus = _notify_server_set_owner(globals->notify_server_port, (caddr_t)name, strlen(name), uid, gid, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_get_owner(const char *name, uint32_t *uid, uint32_t *gid) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + notify_globals_t globals = _notify_globals(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + status = _notify_lib_get_owner(ns_self, name, uid, gid); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + kstatus = _notify_server_get_owner(globals->notify_server_port, (caddr_t)name, strlen(name), (int32_t *)uid, (int32_t *)gid, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_set_access(const char *name, uint32_t access) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + notify_globals_t globals = _notify_globals(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + status = _notify_lib_set_access(ns_self, name, access); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + kstatus = _notify_server_set_access(globals->notify_server_port, (caddr_t)name, strlen(name), access, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_get_access(const char *name, uint32_t *access) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + notify_globals_t globals = _notify_globals(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + status = _notify_lib_get_access(ns_self, name, access); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + kstatus = _notify_server_get_access(globals->notify_server_port, (caddr_t)name, strlen(name), (int32_t *)access, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +/* notifyd retains and releases a name when clients register and cancel. */ +uint32_t +notify_release_name(const char *name) +{ + return NOTIFY_STATUS_OK; +} + +static void +_notify_dispatch_handle(mach_port_t port) +{ + token_table_node_t *t; + int token; + mach_msg_empty_rcv_t msg; + kern_return_t status; + + if (port == MACH_PORT_NULL) return; + + memset(&msg, 0, sizeof(msg)); + + status = mach_msg(&msg.header, MACH_RCV_MSG, 0, sizeof(msg), port, 0, MACH_PORT_NULL); + if (status != KERN_SUCCESS) return; + + token = msg.header.msgh_id; + + t = token_table_find_retain(token); + + if (t != NULL) + { + if ((t->queue != NULL) && (t->block != NULL)) + { + /* + * Don't reference into the token table node after token_table_release(). + * If the block calls notify_cancel, the node can get trashed, so + * we keep anything we need from the block (properly retained and released) + * in local variables. Concurrent notify_cancel() calls in the block are safe. + */ + notify_handler_t theblock = Block_copy(t->block); + dispatch_queue_t thequeue = t->queue; + dispatch_retain(thequeue); + + dispatch_async(thequeue, ^{ + token_table_node_t *t = token_table_find_no_lock(token); + if (t != NULL) theblock(token); + }); + + _Block_release(theblock); + dispatch_release(thequeue); + } + + token_table_release(t); + } +} + +uint32_t +notify_register_dispatch(const char *name, int *out_token, dispatch_queue_t queue, notify_handler_t handler) +{ + __block uint32_t status; + token_table_node_t *t; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (queue == NULL) return NOTIFY_STATUS_FAILED; + if (handler == NULL) return NOTIFY_STATUS_FAILED; + + /* client is using dispatch: enable local demux and regeneration */ + notify_set_options(NOTIFY_OPT_DEMUX | NOTIFY_OPT_REGEN); + + status = notify_register_mach_port(name, &globals->notify_common_port, NOTIFY_REUSE, out_token); + if (status != NOTIFY_STATUS_OK) return status; + + t = token_table_find_retain(*out_token); + if (t == NULL) return NOTIFY_STATUS_FAILED; + + t->queue = queue; + dispatch_retain(t->queue); + t->block = Block_copy(handler); + token_table_release(t); + + return NOTIFY_STATUS_OK; +} + +/* note this does not get self names */ +static uint32_t +notify_register_mux_fd(const char *name, int *out_token, int rfd, int wfd) +{ + __block uint32_t status; + token_table_node_t *t; + int val; + notify_globals_t globals = _notify_globals(); + + status = NOTIFY_STATUS_OK; + + if (globals->notify_common_port == MACH_PORT_NULL) return NOTIFY_STATUS_FAILED; + + status = notify_register_mach_port(name, &globals->notify_common_port, NOTIFY_REUSE, out_token); + + t = token_table_find_retain(*out_token); + if (t == NULL) return NOTIFY_STATUS_FAILED; + + t->token = *out_token; + t->fd = rfd; + t->queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + dispatch_retain(t->queue); + val = htonl(t->token); + t->block = (notify_handler_t)Block_copy(^(int unused){ write(wfd, &val, sizeof(val)); }); + + token_table_release(t); + + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_register_check(const char *name, int *out_token) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status, token; + uint64_t nid; + int32_t slot, shmsize; + size_t namelen; + uint32_t cid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (out_token == NULL) return NOTIFY_STATUS_FAILED; + + *out_token = -1; + namelen = strlen(name); + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + status = _notify_lib_register_plain(ns_self, name, NOTIFY_CLIENT_SELF, token, SLOT_NONE, 0, 0, &nid); + if (status != NOTIFY_STATUS_OK) return status; + + cid = token; + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_FLAG_SELF | NOTIFY_TYPE_PLAIN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL, 0); + + *out_token = token; + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + kstatus = KERN_SUCCESS; + + if (globals->notify_ipc_version == 0) + { + nid = NID_UNSET; + kstatus = _notify_server_register_check(globals->notify_server_port, (caddr_t)name, namelen, &shmsize, &slot, (int32_t *)&cid, (int32_t *)&status); + } + else + { + cid = token; + kstatus = _notify_server_register_check_2(globals->notify_server_port, (caddr_t)name, namelen, token, &shmsize, &slot, &nid, (int32_t *)&status); + } + + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + if (status != NOTIFY_STATUS_OK) return status; + + if (shmsize != -1) + { + if (globals->shm_base == NULL) + { + if (shm_attach(shmsize) != 0) return NOTIFY_STATUS_FAILED; + if (globals->shm_base == NULL) return NOTIFY_STATUS_FAILED; + } + + token_table_add(name, namelen, nid, token, cid, slot, NOTIFY_TYPE_MEMORY | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL, 0); + } + else + { + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_TYPE_PLAIN | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL, 0); + } + + *out_token = token; + return status; +} + +uint32_t +notify_register_plain(const char *name, int *out_token) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + uint64_t nid; + size_t namelen; + int token; + uint32_t cid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + namelen = strlen(name); + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + status = _notify_lib_register_plain(ns_self, name, NOTIFY_CLIENT_SELF, token, SLOT_NONE, 0, 0, &nid); + if (status != NOTIFY_STATUS_OK) return status; + + cid = token; + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_FLAG_SELF | NOTIFY_TYPE_PLAIN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL, 0); + + *out_token = token; + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_register_plain(globals->notify_server_port, (caddr_t)name, namelen, (int32_t *)&cid, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + if (status != NOTIFY_STATUS_OK) return status; + } + else + { + cid = token; + kstatus = _notify_server_register_plain_2(globals->notify_server_port, (caddr_t)name, namelen, token); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + } + + token_table_add(name, namelen, NID_UNSET, token, cid, SLOT_NONE, NOTIFY_TYPE_PLAIN | NOTIFY_FLAG_REGEN, SIGNAL_NONE, FD_NONE, MACH_PORT_NULL, 0); + + *out_token = token; + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_register_signal(const char *name, int sig, int *out_token) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + uint64_t nid; + size_t namelen; + int token; + uint32_t cid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + + namelen = strlen(name); + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) return NOTIFY_STATUS_FAILED; + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + status = _notify_lib_register_signal(ns_self, name, NOTIFY_CLIENT_SELF, token, sig, 0, 0, &nid); + if (status != NOTIFY_STATUS_OK) return status; + + cid = token; + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_FLAG_SELF | NOTIFY_TYPE_SIGNAL, sig, FD_NONE, MACH_PORT_NULL, 0); + + *out_token = token; + return NOTIFY_STATUS_OK; + } + + if (globals->client_opts & NOTIFY_OPT_DEMUX) + { + return notify_register_dispatch(name, out_token, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int unused){ kill(getpid(), sig); }); + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_register_signal(globals->notify_server_port, (caddr_t)name, namelen, sig, (int32_t *)&cid, (int32_t *)&status); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + if (status != NOTIFY_STATUS_OK) return status; + } + else + { + cid = token; + kstatus = _notify_server_register_signal_2(globals->notify_server_port, (caddr_t)name, namelen, token, sig); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + } + + token_table_add(name, namelen, NID_UNSET, token, cid, SLOT_NONE, NOTIFY_TYPE_SIGNAL | NOTIFY_FLAG_REGEN, sig, FD_NONE, MACH_PORT_NULL, 0); + + *out_token = token; + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_register_mach_port(const char *name, mach_port_name_t *notify_port, int flags, int *out_token) +{ + notify_state_t *ns_self; + kern_return_t kstatus; + uint32_t status; + uint64_t nid; + task_t task; + int token, mine; + size_t namelen; + uint32_t cid, tflags; + token_table_node_t *t; + mach_port_name_t port; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (notify_port == NULL) return NOTIFY_STATUS_INVALID_PORT; + + mine = 0; + namelen = strlen(name); + + task = mach_task_self(); + + if ((flags & NOTIFY_REUSE) == 0) + { + mine = 1; + kstatus = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, notify_port); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + } + + kstatus = mach_port_insert_right(task, *notify_port, *notify_port, MACH_MSG_TYPE_MAKE_SEND); + if (kstatus != KERN_SUCCESS) + { + if (mine == 1) mach_port_mod_refs(task, *notify_port, MACH_PORT_RIGHT_RECEIVE, -1); + return NOTIFY_STATUS_FAILED; + } + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) + { + if (mine == 1) + { + mach_port_mod_refs(task, *notify_port, MACH_PORT_RIGHT_RECEIVE, -1); + } + + mach_port_deallocate(task, *notify_port); + return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + status = _notify_lib_register_mach_port(ns_self, name, NOTIFY_CLIENT_SELF, token, *notify_port, 0, 0, &nid); + if (status != NOTIFY_STATUS_OK) + { + if (mine == 1) + { + mach_port_mod_refs(task, *notify_port, MACH_PORT_RIGHT_RECEIVE, -1); + } + + mach_port_deallocate(task, *notify_port); + return status; + } + + cid = token; + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_FLAG_SELF | NOTIFY_TYPE_PORT, SIGNAL_NONE, FD_NONE, *notify_port, 0); + + *out_token = token; + notify_retain_mach_port(*notify_port, mine); + + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + if (mine == 1) + { + mach_port_mod_refs(task, *notify_port, MACH_PORT_RIGHT_RECEIVE, -1); + } + + mach_port_deallocate(task, *notify_port); + return NOTIFY_STATUS_FAILED; + } + } + + if ((globals->client_opts & NOTIFY_OPT_DEMUX) && (*notify_port != globals->notify_common_port)) + { + port = globals->notify_common_port; + kstatus = mach_port_insert_right(task, globals->notify_common_port, globals->notify_common_port, MACH_MSG_TYPE_MAKE_SEND); + } + else + { + port = *notify_port; + kstatus = KERN_SUCCESS; + } + + if (kstatus == KERN_SUCCESS) + { + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_register_mach_port(globals->notify_server_port, (caddr_t)name, namelen, port, token, (int32_t *)&cid, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + cid = token; + kstatus = _notify_server_register_mach_port_2(globals->notify_server_port, (caddr_t)name, namelen, token, port); + } + } + + if (kstatus != KERN_SUCCESS) + { + if (mine == 1) + { + mach_port_mod_refs(task, *notify_port, MACH_PORT_RIGHT_RECEIVE, -1); + } + + mach_port_deallocate(task, *notify_port); + return NOTIFY_STATUS_FAILED; + } + + tflags = NOTIFY_TYPE_PORT; + if (port == globals->notify_common_port) tflags |= NOTIFY_FLAG_REGEN; + token_table_add(name, namelen, NID_UNSET, token, cid, SLOT_NONE, tflags, SIGNAL_NONE, FD_NONE, *notify_port, 0); + + if ((globals->client_opts & NOTIFY_OPT_DEMUX) && (*notify_port != globals->notify_common_port)) + { + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_FAILED; + + /* remember to release the send right when this gets cancelled */ + t->flags |= NOTIFY_FLAG_RELEASE_SEND; + + port = *notify_port; + t->queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + dispatch_retain(t->queue); + t->block = (notify_handler_t)Block_copy(^(int unused){ + mach_msg_empty_send_t msg; + kern_return_t kstatus; + + /* send empty message to the port with msgh_id = token; */ + memset(&msg, 0, sizeof(msg)); + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO); + msg.header.msgh_remote_port = port; + msg.header.msgh_local_port = MACH_PORT_NULL; + msg.header.msgh_size = sizeof(mach_msg_empty_send_t); + msg.header.msgh_id = token; + + kstatus = mach_msg(&(msg.header), MACH_SEND_MSG | MACH_SEND_TIMEOUT, msg.header.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); + }); + + token_table_release(t); + } + + *out_token = token; + notify_retain_mach_port(*notify_port, mine); + + return NOTIFY_STATUS_OK; +} + +static char * +_notify_mk_tmp_path(int tid) +{ +#if TARGET_OS_EMBEDDED + int freetmp = 0; + char *path, *tmp = getenv("TMPDIR"); + + if (tmp == NULL) + { + asprintf(&tmp, "/tmp/com.apple.notify.%d", geteuid()); + mkdir(tmp, 0755); + freetmp = 1; + } + + if (tmp == NULL) return NULL; + + asprintf(&path, "%s/com.apple.notify.%d.%d", tmp, getpid(), tid); + if (freetmp) free(tmp); + return path; +#else + char tmp[PATH_MAX], *path; + + if (confstr(_CS_DARWIN_USER_TEMP_DIR, tmp, sizeof(tmp)) <= 0) return NULL; +#endif + + path = NULL; + asprintf(&path, "%s/com.apple.notify.%d.%d", tmp, getpid(), tid); + return path; +} + +uint32_t +notify_register_file_descriptor(const char *name, int *notify_fd, int flags, int *out_token) +{ + notify_state_t *ns_self; + uint32_t i, status; + uint64_t nid; + int token, mine, fdpair[2]; + size_t namelen; + fileport_t fileport; + kern_return_t kstatus; + uint32_t cid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + mine = 0; + + if (name == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (notify_fd == NULL) return NOTIFY_STATUS_INVALID_FILE; + + namelen = strlen(name); + + if ((flags & NOTIFY_REUSE) == 0) + { + if (pipe(fdpair) < 0) return NOTIFY_STATUS_FAILED; + + mine = 1; + *notify_fd = fdpair[0]; + } + else + { + /* check the file descriptor - it must be one of "ours" */ + for (i = 0; i < globals->fd_count; i++) + { + if (globals->fd_clnt[i] == *notify_fd) break; + } + + if (i >= globals->fd_count) return NOTIFY_STATUS_INVALID_FILE; + + fdpair[0] = globals->fd_clnt[i]; + fdpair[1] = globals->fd_srv[i]; + } + + if (!strncmp(name, SELF_PREFIX, SELF_PREFIX_LEN)) + { + ns_self = _notify_lib_self_state(); + if (ns_self == NULL) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + status = _notify_lib_register_file_descriptor(ns_self, name, NOTIFY_CLIENT_SELF, token, fdpair[1], 0, 0, &nid); + if (status != NOTIFY_STATUS_OK) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return status; + } + + cid = token; + token_table_add(name, namelen, nid, token, cid, SLOT_NONE, NOTIFY_FLAG_SELF | NOTIFY_TYPE_FILE, SIGNAL_NONE, *notify_fd, MACH_PORT_NULL, 0); + + *out_token = token; + notify_retain_file_descriptor(fdpair[0], fdpair[1]); + + return NOTIFY_STATUS_OK; + } + + if (globals->client_opts & NOTIFY_OPT_DEMUX) + { + /* + * Use dispatch to do a write() on fdpair[1] when notified. + */ + status = notify_register_mux_fd(name, out_token, fdpair[0], fdpair[1]); + if (status != NOTIFY_STATUS_OK) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return status; + } + + notify_retain_file_descriptor(fdpair[0], fdpair[1]); + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return NOTIFY_STATUS_FAILED; + } + } + + /* send fdpair[1] (the sender's fd) to notifyd using a fileport */ + fileport = MACH_PORT_NULL; + if (fileport_makeport(fdpair[1], (fileport_t *)&fileport) < 0) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return NOTIFY_STATUS_FAILED; + } + + token = OSAtomicIncrement32((int32_t *)&globals->token_id); + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_register_file_descriptor(globals->notify_server_port, (caddr_t)name, namelen, (mach_port_t)fileport, token, (int32_t *)&cid, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + kstatus = _notify_server_register_file_descriptor_2(globals->notify_server_port, (caddr_t)name, namelen, token, (mach_port_t)fileport); + } + + if (kstatus != KERN_SUCCESS) + { + if (mine == 1) + { + close(fdpair[0]); + close(fdpair[1]); + } + + return NOTIFY_STATUS_FAILED; + } + + token_table_add(name, namelen, NID_UNSET, token, cid, SLOT_NONE, NOTIFY_TYPE_FILE, SIGNAL_NONE, *notify_fd, MACH_PORT_NULL, 0); + + *out_token = token; + notify_retain_file_descriptor(fdpair[0], fdpair[1]); + + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_check(int token, int *check) +{ + kern_return_t kstatus; + uint32_t status, val; + token_table_node_t *t; + uint32_t tid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + pthread_mutex_lock(&globals->notify_lock); + + t = token_table_find_no_lock(token); + if (t == NULL) + { + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* _notify_lib_check returns NOTIFY_STATUS_FAILED if self_state is NULL */ + status = _notify_lib_check(globals->self_state, NOTIFY_CLIENT_SELF, token, check); + pthread_mutex_unlock(&globals->notify_lock); + return status; + } + + if (t->flags & NOTIFY_TYPE_MEMORY) + { + if (globals->shm_base == NULL) + { + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_FAILED; + } + + *check = 0; + val = globals->shm_base[t->slot]; + if (t->val != val) + { + *check = 1; + t->val = val; + } + + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_OK; + } + + tid = token; + if (globals->notify_ipc_version == 0) tid = t->client_id; + + pthread_mutex_unlock(&globals->notify_lock); + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) return NOTIFY_STATUS_FAILED; + } + + kstatus = _notify_server_check(globals->notify_server_port, tid, check, (int32_t *)&status); + + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_peek(int token, uint32_t *val) +{ + token_table_node_t *t; + uint32_t status; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* _notify_lib_peek returns NOTIFY_STATUS_FAILED if self_state is NULL */ + status = _notify_lib_peek(globals->self_state, NOTIFY_CLIENT_SELF, token, (int *)val); + token_table_release(t); + return status; + } + + if (t->flags & NOTIFY_TYPE_MEMORY) + { + if (globals->shm_base == NULL) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + + *val = globals->shm_base[t->slot]; + token_table_release(t); + return NOTIFY_STATUS_OK; + } + + token_table_release(t); + return NOTIFY_STATUS_INVALID_REQUEST; +} + +int * +notify_check_addr(int token) +{ + token_table_node_t *t; + uint32_t slot; + int *val; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NULL; + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* _notify_lib_check_addr returns NOTIFY_STATUS_FAILED if self_state is NULL */ + val = _notify_lib_check_addr(globals->self_state, NOTIFY_CLIENT_SELF, token); + token_table_release(t); + return val; + } + + if (t->flags & NOTIFY_TYPE_MEMORY) + { + slot = t->slot; + token_table_release(t); + + if (globals->shm_base == NULL) return NULL; + return (int *)&(globals->shm_base[slot]); + } + + token_table_release(t); + return NULL; +} + +uint32_t +notify_monitor_file(int token, char *path, int flags) +{ + kern_return_t kstatus; + uint32_t status, len; + token_table_node_t *t; + char *dup; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST; + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + + if (t->flags & NOTIFY_FLAG_SELF) + { + token_table_release(t); + return NOTIFY_STATUS_INVALID_REQUEST; + } + + /* can only monitor one path with a token */ + if (t->path != NULL) + { + token_table_release(t); + return NOTIFY_STATUS_INVALID_REQUEST; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + } + + len = strlen(path); + dup = strdup(path); + if (dup == NULL) return NOTIFY_STATUS_FAILED; + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_monitor_file(globals->notify_server_port, t->client_id, path, len, flags, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + kstatus = _notify_server_monitor_file_2(globals->notify_server_port, token, path, len, flags); + } + + t->path = dup; + t->path_flags = flags; + + token_table_release(t); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_get_event(int token, int *ev, char *buf, int *len) +{ + if (ev != NULL) *ev = 0; + if (len != NULL) *len = 0; + + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_get_state(int token, uint64_t *state) +{ + kern_return_t kstatus; + uint32_t status; + token_table_node_t *t; + uint64_t nid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + if (t->name_node == NULL) + { + token_table_release(t); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* _notify_lib_get_state returns NOTIFY_STATUS_FAILED if self_state is NULL */ + status = _notify_lib_get_state(globals->self_state, t->name_node->name_id, state, 0, 0); + token_table_release(t); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + } + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_get_state(globals->notify_server_port, t->client_id, state, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + if (t->name_node->name_id >= NID_CALLED_ONCE) + { + kstatus = _notify_server_get_state_3(globals->notify_server_port, t->token, state, (uint64_t *)&nid, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) name_table_set_nid(t->name, nid); + } + else + { + kstatus = _notify_server_get_state_2(globals->notify_server_port, t->name_node->name_id, state, (int32_t *)&status); + } + } + + token_table_release(t); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_set_state(int token, uint64_t state) +{ + kern_return_t kstatus; + uint32_t status; + token_table_node_t *t; + uint64_t nid; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + if (t->name_node == NULL) + { + token_table_release(t); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* _notify_lib_set_state returns NOTIFY_STATUS_FAILED if self_state is NULL */ + status = _notify_lib_set_state(globals->self_state, t->name_node->name_id, state, 0, 0); + token_table_release(t); + return status; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + } + + status = NOTIFY_STATUS_OK; + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_set_state(globals->notify_server_port, t->client_id, state, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + if (t->name_node->name_id >= NID_CALLED_ONCE) + { + kstatus = _notify_server_set_state_3(globals->notify_server_port, t->token, state, (uint64_t *)&nid, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) name_table_set_nid(t->name, nid); + } + else + { + status = NOTIFY_STATUS_OK; + kstatus = _notify_server_set_state_2(globals->notify_server_port, t->name_node->name_id, state); + } + } + + if ((kstatus == KERN_SUCCESS) && (status == NOTIFY_STATUS_OK)) + { + t->set_state_time = mach_absolute_time(); + t->set_state_val = state; + } + + token_table_release(t); + if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + return NOTIFY_STATUS_OK; +} + +uint32_t +notify_cancel(int token) +{ + token_table_node_t *t; + uint32_t status; + kern_return_t kstatus; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + /* + * Lock to prevent a race with notify_post, which uses the name ID. + * If we are cancelling the last registration for this name, then we need + * to block those routines from getting the name ID from the name table. + * Once notifyd gets the cancellation, the name may vanish, and the name ID + * held in the name table would go stale. + * + * Uses token_table_find_no_lock() which does not retain, and + * token_table_release_no_lock() which releases the token. + */ + pthread_mutex_lock(&globals->notify_lock); + + t = token_table_find_no_lock(token); + if (t == NULL) + { + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_INVALID_TOKEN; + } + + if (t->flags & NOTIFY_FLAG_SELF) + { + /* + * _notify_lib_cancel returns NOTIFY_STATUS_FAILED if self_state is NULL + * We let it fail quietly. + */ + _notify_lib_cancel(globals->self_state, NOTIFY_CLIENT_SELF, t->token); + + token_table_release_no_lock(t); + pthread_mutex_unlock(&globals->notify_lock); + return NOTIFY_STATUS_OK; + } + + if (globals->notify_ipc_version == 0) + { + kstatus = _notify_server_cancel(globals->notify_server_port, t->client_id, (int32_t *)&status); + if ((kstatus == KERN_SUCCESS) && (status != NOTIFY_STATUS_OK)) kstatus = KERN_FAILURE; + } + else + { + kstatus = _notify_server_cancel_2(globals->notify_server_port, token); + } + + token_table_release_no_lock(t); + pthread_mutex_unlock(&globals->notify_lock); + + if ((kstatus == MIG_SERVER_DIED) || (kstatus == MACH_SEND_INVALID_DEST)) return NOTIFY_STATUS_OK; + else if (kstatus != KERN_SUCCESS) return NOTIFY_STATUS_FAILED; + + return NOTIFY_STATUS_OK; +} + +bool +notify_is_valid_token(int val) +{ + token_table_node_t *t; + bool valid = false; + + if (val < 0) return false; + + notify_globals_t globals = _notify_globals(); + + pthread_mutex_lock(&globals->notify_lock); + + t = (token_table_node_t *)_nc_table_find_n(globals->token_table, val); + if (t != NULL) valid = true; + + pthread_mutex_unlock(&globals->notify_lock); + + return valid; +} + +uint32_t +notify_suspend(int token) +{ + token_table_node_t *t; + uint32_t status, tid; + kern_return_t kstatus; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + + if (t->flags & NOTIFY_FLAG_SELF) + { + _notify_lib_suspend(globals->self_state, NOTIFY_CLIENT_SELF, t->token); + token_table_release(t); + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + } + + tid = token; + if (globals->notify_ipc_version == 0) tid = t->client_id; + + kstatus = _notify_server_suspend(globals->notify_server_port, tid, (int32_t *)&status); + + token_table_release(t); + if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_resume(int token) +{ + token_table_node_t *t; + uint32_t status, tid; + kern_return_t kstatus; + notify_globals_t globals = _notify_globals(); + + regenerate_check(); + + t = token_table_find_retain(token); + if (t == NULL) return NOTIFY_STATUS_INVALID_TOKEN; + + if (t->flags & NOTIFY_FLAG_SELF) + { + _notify_lib_resume(globals->self_state, NOTIFY_CLIENT_SELF, t->token); + token_table_release(t); + return NOTIFY_STATUS_OK; + } + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + token_table_release(t); + return NOTIFY_STATUS_FAILED; + } + } + + tid = token; + if (globals->notify_ipc_version == 0) tid = t->client_id; + + kstatus = _notify_server_resume(globals->notify_server_port, tid, (int32_t *)&status); + + token_table_release(t); + if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_suspend_pid(pid_t pid) +{ + uint32_t status; + kern_return_t kstatus; + notify_globals_t globals = _notify_globals(); + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + return NOTIFY_STATUS_FAILED; + } + } + + kstatus = _notify_server_suspend_pid(globals->notify_server_port, pid, (int32_t *)&status); + + if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_FAILED; + return status; +} + +uint32_t +notify_resume_pid(pid_t pid) +{ + uint32_t status; + kern_return_t kstatus; + notify_globals_t globals = _notify_globals(); + + if (globals->notify_server_port == MACH_PORT_NULL) + { + status = _notify_lib_init(EVENT_INIT); + if (status != 0) + { + return NOTIFY_STATUS_FAILED; + } + } + + kstatus = _notify_server_resume_pid(globals->notify_server_port, pid, (int32_t *)&status); + + if (kstatus != KERN_SUCCESS) status = NOTIFY_STATUS_FAILED; + return status; +} + +/* Deprecated SPI */ +uint32_t +notify_simple_post(const char *name) +{ + return notify_post(name); +} diff --git a/libnotify/notify_get_state.3 b/libnotify/notify_get_state.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_get_state.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_internal.h b/libnotify/notify_internal.h new file mode 100644 index 000000000..a48d579d4 --- /dev/null +++ b/libnotify/notify_internal.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include + +struct notify_globals_s { + // Global lock. + pthread_mutex_t notify_lock; + + int32_t notify_ipc_version; + pid_t notify_server_pid; + + uint32_t client_opts; + uint32_t saved_opts; + + // Last allocated name id. + uint64_t name_id; + + dispatch_once_t self_state_once; + notify_state_t *self_state; + + dispatch_once_t notify_server_port_once; + mach_port_t notify_server_port; + mach_port_t saved_server_port; + + mach_port_t notify_common_port; + int notify_common_token; + dispatch_source_t notify_dispatch_source; + dispatch_source_t server_proc_source; + + dispatch_once_t token_table_once; + table_t *token_table; + table_t *token_name_table; + uint32_t token_id; + + // File descriptor list. + uint32_t fd_count; + int *fd_clnt; + int *fd_srv; + int *fd_refcount; + + // Mach port list. + uint32_t mp_count; + mach_port_t *mp_list; + int *mp_refcount; + int *mp_mine; + + // Shared memory base address. + uint32_t *shm_base; +}; +typedef struct notify_globals_s *notify_globals_t; + +#if __has_include() +#include +#if defined(OS_ALLOC_ONCE_KEY_LIBSYSTEM_NOTIFY) +#define _NOTIFY_HAS_ALLOC_ONCE 1 +#endif +#endif + +__attribute__((visibility("hidden"))) +void _notify_init_globals(void * /* notify_globals_t */ globals); + +__attribute__((visibility("hidden"))) +notify_globals_t _notify_globals_impl(void); + +__attribute__((__pure__)) +static inline notify_globals_t +_notify_globals(void) { +#if _NOTIFY_HAS_ALLOC_ONCE + return (notify_globals_t)os_alloc_once(OS_ALLOC_ONCE_KEY_LIBSYSTEM_NOTIFY, + sizeof(struct notify_globals_s), &_notify_init_globals); +#else + return _notify_globals_impl(); +#endif +} diff --git a/libnotify/notify_ipc.defs b/libnotify/notify_ipc.defs new file mode 100644 index 000000000..4dc42807b --- /dev/null +++ b/libnotify/notify_ipc.defs @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2003-2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include + +subsystem notify_ipc 78945668; +serverprefix _; + +import ; + +import "notify_ipc_types.h"; + +type notify_name = ^ array [] of MACH_MSG_TYPE_BYTE + ctype : caddr_t; + +routine _notify_server_post +( + server : mach_port_t; + name : notify_name; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_plain +( + server : mach_port_t; + name : notify_name; + out token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_check +( + server : mach_port_t; + name : notify_name; + out size : int; + out slot : int; + out token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_signal +( + server : mach_port_t; + name : notify_name; + sig: int; + out token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_file_descriptor +( + server : mach_port_t; + name : notify_name; + fileport : mach_port_move_send_t; + ntoken : int; + out token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_mach_port +( + server : mach_port_t; + name : notify_name; + port : mach_port_move_send_t; + ntoken : int; + out token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_set_owner +( + server : mach_port_t; + name : notify_name; + user : int; + group : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_get_owner +( + server : mach_port_t; + name : notify_name; + out user : int; + out group : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_set_access +( + server : mach_port_t; + name : notify_name; + mode : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_get_access +( + server : mach_port_t; + name : notify_name; + out mode : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_release_name +( + server : mach_port_t; + name : notify_name; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_cancel +( + server : mach_port_t; + token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_check +( + server : mach_port_t; + token : int; + out check : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_get_state +( + server : mach_port_t; + token : int; + out state : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_set_state +( + server : mach_port_t; + token : int; + state : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +skip; /* formerly _notify_server_get_val */ + +skip; /* formerly _notify_server_set_val */ + +routine _notify_server_monitor_file +( + server : mach_port_t; + token : int; + path : notify_name; + flags : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_suspend +( + server : mach_port_t; + token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_resume +( + server : mach_port_t; + token : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_suspend_pid +( + server : mach_port_t; + pid : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_resume_pid +( + server : mach_port_t; + pid : int; + out status : int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_simple_post +( + server : mach_port_t; + name : notify_name; + ServerAuditToken audit : audit_token_t +); + +/* Additions for version 2 - more async support */ + +routine _notify_server_post_2 +( + server : mach_port_t; + name : notify_name; + out name_id : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_post_3 +( + server : mach_port_t; + name_id : uint64_t; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_post_4 +( + server : mach_port_t; + name : notify_name; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_register_plain_2 +( + server : mach_port_t; + name : notify_name; + token : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_register_check_2 +( + server : mach_port_t; + name : notify_name; + token: int; + out size : int; + out slot : int; + out name_id : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_register_signal_2 +( + server : mach_port_t; + name : notify_name; + token : int; + sig: int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_register_file_descriptor_2 +( + server : mach_port_t; + name : notify_name; + token: int; + fileport : mach_port_move_send_t; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_register_mach_port_2 +( + server : mach_port_t; + name : notify_name; + token: int; + port : mach_port_move_send_t; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_cancel_2 +( + server : mach_port_t; + token : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_get_state_2 +( + server : mach_port_t; + name_id : uint64_t; + out state : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_get_state_3 +( + server : mach_port_t; + token : int; + out state : uint64_t; + out nid : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_set_state_2 +( + server : mach_port_t; + name_id : uint64_t; + state : uint64_t; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_set_state_3 +( + server : mach_port_t; + token : int; + state : uint64_t; + out nid : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); + +simpleroutine _notify_server_monitor_file_2 +( + server : mach_port_t; + token : int; + path : notify_name; + flags : int; + ServerAuditToken audit : audit_token_t +); + +routine _notify_server_regenerate +( + server : mach_port_t; + name : notify_name; + token : int; + reg_type : uint32_t; + port : mach_port_make_send_t; + sig: int; + prev_slot: int; + prev_state : uint64_t; + prev_time : uint64_t; + path : notify_name; + path_flags: int; + out new_slot : int; + out new_name_id : uint64_t; + out status : int; + ServerAuditToken audit : audit_token_t +); diff --git a/libnotify/notify_ipc_types.h b/libnotify/notify_ipc_types.h new file mode 100644 index 000000000..f1ae72270 --- /dev/null +++ b/libnotify/notify_ipc_types.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2003-2009 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Portions Copyright (c) 2003-2009 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NOTIFY_IPC_TYPES_H_ +#define _NOTIFY_IPC_TYPES_H_ + +#include +typedef char inline_data_t[2048]; + +#endif diff --git a/libnotify/notify_is_valid_token.3 b/libnotify/notify_is_valid_token.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_is_valid_token.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_keys.h b/libnotify/notify_keys.h new file mode 100644 index 000000000..528417f97 --- /dev/null +++ b/libnotify/notify_keys.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2007-2009 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Portions Copyright (c) 2007-2009 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * This file lists notification keys that are posted using the + * notify_post() API by various Mac OS X system services. + * The exact circumstances under which services post these + * notifications is controlled by those services, and may change + * in future software releases. + */ + +/* + * Directory Service notifications + * These are posted by the DirectoryService daemon to advise clients that + * cached data should be invalidated. + */ +#define kNotifyDSCacheInvalidation "com.apple.system.DirectoryService.InvalidateCache" +#define kNotifyDSCacheInvalidationGroup "com.apple.system.DirectoryService.InvalidateCache.group" +#define kNotifyDSCacheInvalidationHost "com.apple.system.DirectoryService.InvalidateCache.host" +#define kNotifyDSCacheInvalidationService "com.apple.system.DirectoryService.InvalidateCache.service" +#define kNotifyDSCacheInvalidationUser "com.apple.system.DirectoryService.InvalidateCache.user" + +/* + * File System notifications + * These advise clients of various filesystem events. + */ +#define kNotifyVFSMount "com.apple.system.kernel.mount" +#define kNotifyVFSUnmount "com.apple.system.kernel.unmount" +#define kNotifyVFSUpdate "com.apple.system.kernel.mountupdate" +#define kNotifyVFSLowDiskSpace "com.apple.system.lowdiskspace" +#define kNotifyVFSLowDiskSpaceRootFS "com.apple.system.lowdiskspace.system" +#define kNotifyVFSLowDiskSpaceOtherFS "com.apple.system.lowdiskspace.user" + +/* + * System Configuration notifications + * These advise clients of changes in the system configuration + * managed by the system configuration server (configd). + * Note that a much richer set of notifications are available to + * clients using the SCDynamicStore API. + */ +#define kNotifySCHostNameChange "com.apple.system.hostname" +#define kNotifySCNetworkChange "com.apple.system.config.network_change" + +/* + * ASL notifications + * Sent by syslogd to advise clients that new log messages have been + * added to the ASL database. + */ +#define kNotifyASLDBUpdate "com.apple.system.logger.message" + +/* + * Time Zone change notification + * Sent by notifyd when the system's timezone changes. + */ +#define kNotifyTimeZoneChange "com.apple.system.timezone" + +/* + * System clock change notification + * Sent when a process modifies the system clock using the settimeofday system call. + */ +#define kNotifyClockSet "com.apple.system.clock_set" diff --git a/libnotify/notify_post.3 b/libnotify/notify_post.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_post.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_private.h b/libnotify/notify_private.h new file mode 100644 index 000000000..aa475832d --- /dev/null +++ b/libnotify/notify_private.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef __NOTIFY_PRIVATE_H__ +#define __NOTIFY_PRIVATE_H__ + +#include +#include + +#define NOTIFY_OPT_DEMUX 0x00000001 +#define NOTIFY_OPT_REGEN 0x00000002 +#define NOTIFY_OPT_ENABLE 0x04000000 +#define NOTIFY_OPT_DISABLE 0x08000000 + +uint32_t notify_suspend_pid(pid_t pid) +__OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0); + +uint32_t notify_resume_pid(pid_t pid) +__OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_0); + +uint32_t notify_simple_post(const char *name) +__OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_3); + +void notify_set_options(uint32_t opts) +__OSX_AVAILABLE_STARTING(__MAC_10_8,__IPHONE_6_0); + +#endif /* __NOTIFY_PRIVATE_H__ */ diff --git a/libnotify/notify_register_check.3 b/libnotify/notify_register_check.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_register_check.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_register_dispatch.3 b/libnotify/notify_register_dispatch.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_register_dispatch.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_register_file_descriptor.3 b/libnotify/notify_register_file_descriptor.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_register_file_descriptor.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_register_mach_port.3 b/libnotify/notify_register_mach_port.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_register_mach_port.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_register_signal.3 b/libnotify/notify_register_signal.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_register_signal.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notify_set_state.3 b/libnotify/notify_set_state.3 new file mode 100644 index 000000000..2d3019baa --- /dev/null +++ b/libnotify/notify_set_state.3 @@ -0,0 +1 @@ +.so man3/notify.3 diff --git a/libnotify/notifyd/com.apple.notifyd.plist b/libnotify/notifyd/com.apple.notifyd.plist new file mode 100644 index 000000000..6750726f0 --- /dev/null +++ b/libnotify/notifyd/com.apple.notifyd.plist @@ -0,0 +1,31 @@ + + + + + EnableTransactions + + Label + com.apple.notifyd + EnvironmentVariables + + ASL_DISABLE + 1 + + MachServices + + com.apple.system.notification_center + + + ProgramArguments + + /usr/sbin/notifyd + + JetsamProperties + + JetsamPriority + -1000 + + POSIXSpawnType + Interactive + + diff --git a/libnotify/notifyd/notify.conf.MacOSX b/libnotify/notifyd/notify.conf.MacOSX new file mode 100644 index 000000000..fe9b78ac9 --- /dev/null +++ b/libnotify/notifyd/notify.conf.MacOSX @@ -0,0 +1,10 @@ +# +# Notification Center configuration file +# + +reserve com.apple.system. 0 0 rwr-r- +monitor com.apple.system.timezone /etc/localtime +monitor com.apple.system.info:/etc/hosts /etc/hosts +monitor com.apple.system.info:/etc/services /etc/services +monitor com.apple.system.info:/etc/protocols /etc/protocols + diff --git a/libnotify/notifyd/notify.conf.iPhone b/libnotify/notifyd/notify.conf.iPhone new file mode 100644 index 000000000..0303cb75b --- /dev/null +++ b/libnotify/notifyd/notify.conf.iPhone @@ -0,0 +1,7 @@ +# +# Notification Center configuration file +# + +reserve com.apple.system. 0 0 rwr-r- +reserve com.apple.system.clock_set 0 501 rwrwr- +monitor com.apple.system.timezone /var/db/timezone/localtime diff --git a/libnotify/notifyd/notify_proc.c b/libnotify/notifyd/notify_proc.c new file mode 100644 index 000000000..79344b211 --- /dev/null +++ b/libnotify/notifyd/notify_proc.c @@ -0,0 +1,1623 @@ +/* + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "notify.h" +#include "notifyd.h" +#include "service.h" +#include "notify_ipc.h" +#include "notify_ipcServer.h" + +static void +cancel_subscription(client_t *c) +{ + name_info_t *n; + int token; + + if (global.notify_state == NULL) return; + if (c == NULL) return; + + call_statistics.cleanup++; + + token = c->client_id; + + if (c->private != NULL) + { + service_close(c->private); + c->private = NULL; + } + + n = c->name_info; + if ((n != NULL) && (n->refcount == 1) && (n->private != NULL)) + { + service_close(n->private); + n->private = NULL; + } + + _notify_lib_port_proc_release(global.notify_state, MACH_PORT_NULL, c->pid); + + if (c->notify_type == NOTIFY_TYPE_MEMORY) + { + global.shared_memory_refcount[n->slot]--; + } + else if (c->notify_type == NOTIFY_TYPE_PORT) + { + _notify_lib_port_proc_release(global.notify_state, c->port, 0); + } + + _notify_lib_cancel(global.notify_state, c->pid, token); +} + +static void +cancel_proc(void *px) +{ + void *tt; + long lpid = (long)px; + pid_t pid; + client_t *c; + list_t *l, *x; + + if (global.notify_state == NULL) return; + + pid = lpid; + x = NULL; + + tt = _nc_table_traverse_start(global.notify_state->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(global.notify_state->client_table, tt); + if (c == NULL) break; + + if (c->pid == pid) x = _nc_list_prepend(x, _nc_list_new(c)); + } + _nc_table_traverse_end(global.notify_state->client_table, tt); + + for (l = x; l != NULL; l = _nc_list_next(l)) + { + c = _nc_list_data(l); + cancel_subscription(c); + } + + _nc_list_release_list(x); +} + +static void +cancel_port(mach_port_t port, dispatch_source_t src) +{ + void *tt; + client_t *c; + list_t *l, *x; + + if (global.notify_state == NULL) return; + + x = NULL; + + tt = _nc_table_traverse_start(global.notify_state->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(global.notify_state->client_table, tt); + if (c == NULL) break; + + if (c->port == port) x = _nc_list_prepend(x, _nc_list_new(c)); + } + _nc_table_traverse_end(global.notify_state->client_table, tt); + + for (l = x; l != NULL; l = _nc_list_next(l)) + { + c = _nc_list_data(l); + cancel_subscription(c); + } + + _nc_list_release_list(x); +} + +static void +port_event(void *px) +{ + long lport = (long)px; + mach_port_t port; + unsigned long data; + portproc_data_t *pp; + + if (global.notify_state == NULL) return; + + port = (mach_port_t)lport; + if (port == MACH_PORT_NULL) return; + + pp = _notify_lib_port_proc_find(global.notify_state, port, 0); + if (pp == NULL) + { + log_message(ASL_LEVEL_DEBUG, "can't find port source for %u\n", port); + return; + } + + data = dispatch_source_get_data(pp->src); + + if (data & DISPATCH_MACH_SEND_DEAD) + { + cancel_port(port, pp->src); + } + else if (data & DISPATCH_MACH_SEND_POSSIBLE) + { + _notify_lib_resume_port(global.notify_state, port); + } + else + { + log_message(ASL_LEVEL_DEBUG, "unknown data 0x%lx for %u\n", data, port); + } + + _notify_lib_port_proc_release(global.notify_state, port, 0); +} + +static void +port_dealloc(void *px) +{ + long lport = (long)px; + mach_port_t port = (mach_port_t)lport; + + if (port == MACH_PORT_NULL) return; + mach_port_deallocate(mach_task_self(), port); +} + +static void +port_registration_complete(void *px) +{ + long lport = (long)px; + mach_port_t port; + + if (global.notify_state == NULL) return; + + port = (mach_port_t)lport; + _notify_lib_resume_port(global.notify_state, port); +} + +static void +register_pid(pid_t pid) +{ + dispatch_source_t src; + long lpid; + portproc_data_t *pp; + + if (global.notify_state == NULL) return; + if (pid <= 0) return; + + /* + * Check if this pid has already registered. + * N.B. This call retains the portproc_data_t. We want that. + */ + pp = _notify_lib_port_proc_find(global.notify_state, MACH_PORT_NULL, pid); + if (pp != NULL) return; + + src = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, global.work_q); + dispatch_source_set_event_handler_f(src, (dispatch_function_t)cancel_proc); + + lpid = pid; + dispatch_set_context(src, (void *)lpid); + + _notify_lib_port_proc_new(global.notify_state, MACH_PORT_NULL, pid, 0, src); + + dispatch_resume(src); +} + +static void +register_port(client_t *c) +{ + dispatch_source_t src; + long lport; + portproc_data_t *pp; + + if (c == NULL) return; + + /* ignore MACH_PORT_DEAD */ + if (c->port == MACH_PORT_DEAD) return; + + /* + * Check if this port has already registered. + * N.B. This call retains the portproc_data_t. We want that. + */ + pp = _notify_lib_port_proc_find(global.notify_state, c->port, 0); + if (pp != NULL) return; + + src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_SEND, c->port, DISPATCH_MACH_SEND_DEAD | DISPATCH_MACH_SEND_POSSIBLE, global.work_q); + + dispatch_source_set_event_handler_f(src, (dispatch_function_t)port_event); + + /* retain send right for port - port_dealloc() will release when the source goes away */ + mach_port_mod_refs(mach_task_self(), c->port, MACH_PORT_RIGHT_SEND, +1); + dispatch_source_set_cancel_handler_f(src, (dispatch_function_t)port_dealloc); + + lport = c->port; + dispatch_set_context(src, (void *)lport); + + dispatch_source_set_registration_handler_f(src, (dispatch_function_t)port_registration_complete); + + _notify_lib_port_proc_new(global.notify_state, c->port, 0, NOTIFY_PORT_PROC_STATE_SUSPENDED, src); + + dispatch_resume(src); +} + +static uint32_t +server_preflight(caddr_t name, mach_msg_type_number_t nameCnt, audit_token_t audit, int token, uid_t *uid, gid_t *gid, pid_t *pid, uint64_t *cid) +{ + pid_t xpid; + + if ((name == NULL) && (nameCnt != 0)) return NOTIFY_STATUS_INVALID_NAME; + + if (global.notify_state == NULL) + { + if (name != NULL) vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return NOTIFY_STATUS_FAILED; + } + + if ((name != NULL) && (name[nameCnt] != '\0')) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return NOTIFY_STATUS_INVALID_NAME; + } + + audit_token_to_au32(audit, NULL, uid, gid, NULL, NULL, &xpid, NULL, NULL); + if (pid != NULL) *pid = xpid; + + if (token > 0) + { + client_t *c; + uint64_t xcid = make_client_id(xpid, token); + if (cid != NULL) *cid = xcid; + + c = _nc_table_find_64(global.notify_state->client_table, xcid); + if (c != NULL) + { + /* duplicate tokens can occur if a process exec()s */ + log_message(ASL_LEVEL_DEBUG, "duplicate token %d sent from PID %d\n", token, xpid); + cancel_subscription(c); + } + } + + return NOTIFY_STATUS_OK; +} + +kern_return_t __notify_server_post_3 +( + mach_port_t server, + uint64_t name_id, + audit_token_t audit +) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + + if (global.notify_state == NULL) return KERN_SUCCESS; + + call_statistics.post++; + call_statistics.post_by_id++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_post name_id %llu\n", name_id); + + audit_token_to_au32(audit, NULL, &uid, &gid, NULL, NULL, NULL, NULL, NULL); + + daemon_post_nid(name_id, uid, gid); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_post_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + uint64_t *name_id, + int *status, + audit_token_t audit +) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + name_info_t *n; + + *name_id = 0; + + *status = server_preflight(name, nameCnt, audit, -1, &uid, &gid, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.post++; + call_statistics.post_by_name_and_fetch_id++; + + *status = _notify_lib_check_controlled_access(global.notify_state, name, uid, gid, NOTIFY_ACCESS_WRITE); + if (*status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + n = NULL; + *status = daemon_post(name, uid, gid); + if (*status == NOTIFY_STATUS_OK) + { + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + } + + if (n == NULL) + { + *status = NOTIFY_STATUS_INVALID_NAME; + *name_id = UINT64_MAX; + call_statistics.post_no_op++; + } + else + { + *name_id = n->name_id; + } + + if (*name_id == UINT64_MAX) log_message(ASL_LEVEL_DEBUG, "__notify_server_post %s\n", name); + else log_message(ASL_LEVEL_DEBUG, "__notify_server_post %s [%llu]\n", name, *name_id); + + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_post_4 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + audit_token_t audit +) +{ + uint64_t ignored_name_id; + int ignored_status; + kern_return_t kstatus; + + kstatus = __notify_server_post_2(server, name, nameCnt, &ignored_name_id, &ignored_status, audit); + + call_statistics.post_by_name_and_fetch_id--; + call_statistics.post_by_name++; + + return kstatus; +} + +kern_return_t __notify_server_post +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *status, + audit_token_t audit +) +{ + uint64_t ignored_name_id; + kern_return_t kstatus; + + *status = NOTIFY_STATUS_OK; + + kstatus = __notify_server_post_2(server, name, nameCnt, &ignored_name_id, status, audit); + + call_statistics.post_by_name_and_fetch_id--; + call_statistics.post_by_name++; + + if (*status == NOTIFY_STATUS_INVALID_NAME) *status = NOTIFY_STATUS_OK; + + return kstatus; +} + +kern_return_t __notify_server_register_plain_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + audit_token_t audit +) +{ + client_t *c; + uint64_t nid, cid; + uint32_t status; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + call_statistics.reg++; + call_statistics.reg_plain++; + + status = server_preflight(name, nameCnt, audit, token, &uid, &gid, &pid, &cid); + if (status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_register_plain %s %d %d\n", name, pid, token); + + status = _notify_lib_register_plain(global.notify_state, name, pid, token, -1, uid, gid, &nid); + if (status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + c = _nc_table_find_64(global.notify_state->client_table, cid); + + if (!strncmp(name, SERVICE_PREFIX, SERVICE_PREFIX_LEN)) service_open(name, c, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + register_pid(pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_register_check_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + int *size, + int *slot, + uint64_t *name_id, + int *status, + audit_token_t audit +) +{ + name_info_t *n; + uint32_t i, j, x, new_slot; + uint64_t cid; + client_t *c; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + *size = 0; + *slot = 0; + *name_id = 0; + *status = NOTIFY_STATUS_OK; + + *status = server_preflight(name, nameCnt, audit, token, &uid, &gid, &pid, &cid); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.reg++; + call_statistics.reg_check++; + + if (global.nslots == 0) + { + *size = -1; + *slot = -1; + return __notify_server_register_plain_2(server, name, nameCnt, token, audit); + } + + x = (uint32_t)-1; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n != NULL) x = n->slot; + + new_slot = 0; + if (x == (uint32_t)-1) + { + /* find a slot */ + new_slot = 1; + + /* + * Check slots beginning at the current slot_id + 1, since it's likely that the + * next slot will be available. Keep looking until we have examined all the + * slots (skipping slot 0, which is reserved for notifyd). Stop if we find + * an unused (refcount == 0) slot. + */ + for (i = 1, j = global.slot_id + 1; i < global.nslots; i++, j++) + { + if (j >= global.nslots) j = 1; + if (global.shared_memory_refcount[j] == 0) + { + x = j; + break; + } + } + + if (x == (uint32_t)-1) + { + /* + * We did not find an unused slot. At this point, the shared + * memory table is full, so we start re-using slots, beginning at + * global.slot_id + 1. + */ + global.slot_id++; + + /* wrap around to slot 1 (slot 0 is reserved for notifyd) */ + if (global.slot_id >= global.nslots) global.slot_id = 1; + log_message(ASL_LEVEL_DEBUG, "reused shared memory slot %u\n", global.slot_id); + x = global.slot_id; + } + else + { + /* found a free slot */ + global.slot_id = x; + } + } + + if (new_slot == 1) global.shared_memory_base[x] = 1; + global.shared_memory_refcount[x]++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_register_check %s %d %d\n", name, pid, token); + + *size = global.nslots * sizeof(uint32_t); + *slot = x; + *status = _notify_lib_register_plain(global.notify_state, name, pid, token, x, uid, gid, name_id); + if (*status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + c = _nc_table_find_64(global.notify_state->client_table, cid); + + if (!strncmp(name, SERVICE_PREFIX, SERVICE_PREFIX_LEN)) service_open(name, c, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + register_pid(pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_register_signal_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + int sig, + audit_token_t audit +) +{ + client_t *c; + uint64_t name_id, cid; + uint32_t status; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + status = server_preflight(name, nameCnt, audit, token, &uid, &gid, &pid, &cid); + if (status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.reg++; + call_statistics.reg_signal++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_register_signal %s %d %d %d\n", name, pid, token, sig); + + status = _notify_lib_register_signal(global.notify_state, name, pid, token, sig, uid, gid, &name_id); + if (status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + c = _nc_table_find_64(global.notify_state->client_table, cid); + + if (!strncmp(name, SERVICE_PREFIX, SERVICE_PREFIX_LEN)) service_open(name, c, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + register_pid(pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_register_file_descriptor_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + fileport_t fileport, + audit_token_t audit +) +{ + client_t *c; + int fd, flags; + uint32_t status; + uint64_t name_id, cid; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + status = server_preflight(name, nameCnt, audit, token, &uid, &gid, &pid, &cid); + if (status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.reg++; + call_statistics.reg_file++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_register_file_descriptor %s %d %d\n", name, pid, token); + + fd = fileport_makefd(fileport); + mach_port_deallocate(mach_task_self(), fileport); + if (fd < 0) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + status = _notify_lib_register_file_descriptor(global.notify_state, name, pid, token, fd, uid, gid, &name_id); + if (status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + c = _nc_table_find_64(global.notify_state->client_table, cid); + + if (!strncmp(name, SERVICE_PREFIX, SERVICE_PREFIX_LEN)) service_open(name, c, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + register_pid(pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_register_mach_port_2 +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + mach_port_t port, + audit_token_t audit +) +{ + client_t *c; + uint64_t name_id, cid; + uint32_t status; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + if (port == MACH_PORT_DEAD) return KERN_SUCCESS; + + status = server_preflight(name, nameCnt, audit, token, &uid, &gid, &pid, &cid); + if (status != NOTIFY_STATUS_OK) + { + mach_port_deallocate(mach_task_self(), port); + return KERN_SUCCESS; + } + + call_statistics.reg++; + call_statistics.reg_port++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_register_mach_port %s %d %d\n", name, pid, token); + + status = _notify_lib_register_mach_port(global.notify_state, name, pid, token, port, uid, gid, &name_id); + if (status != NOTIFY_STATUS_OK) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + mach_port_deallocate(mach_task_self(), port); + return KERN_SUCCESS; + } + + c = _nc_table_find_64(global.notify_state->client_table,cid); + + if (!strncmp(name, SERVICE_PREFIX, SERVICE_PREFIX_LEN)) service_open(name, c, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + + register_pid(pid); + register_port(c); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_cancel +( + mach_port_t server, + int token, + int *status, + audit_token_t audit +) +{ + client_t *c; + uid_t uid = (uid_t)-1; + pid_t pid = (pid_t)-1; + + *status = server_preflight(NULL, 0, audit, -1, &uid, NULL, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.cancel++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_cancel %d %d\n", pid, token); + + *status = NOTIFY_STATUS_OK; + + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if (c == NULL) *status = NOTIFY_STATUS_FAILED; + else cancel_subscription(c); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_cancel_2 +( + mach_port_t server, + int token, + audit_token_t audit +) +{ + int ignored; + return __notify_server_cancel(server, token, &ignored, audit); +} + +kern_return_t __notify_server_suspend +( + mach_port_t server, + int token, + int *status, + audit_token_t audit +) +{ + pid_t pid = (pid_t)-1; + + *status = server_preflight(NULL, 0, audit, -1, NULL, NULL, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.suspend++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_suspend %d %d\n", pid, token); + + _notify_lib_suspend(global.notify_state, pid, token); + *status = NOTIFY_STATUS_OK; + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_resume +( + mach_port_t server, + int token, + int *status, + audit_token_t audit +) +{ + pid_t pid = (pid_t)-1; + + *status = server_preflight(NULL, 0, audit, -1, NULL, NULL, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.resume++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_resume %d %d\n", pid, token); + + _notify_lib_resume(global.notify_state, pid, token); + *status = NOTIFY_STATUS_OK; + + return KERN_SUCCESS; +} + +static uid_t +uid_for_pid(pid_t pid) +{ + int mib[4]; + struct kinfo_proc info; + size_t size = sizeof(struct kinfo_proc); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + sysctl(mib, 4, &info, &size, 0, 0); + + return (uid_t)info.kp_eproc.e_ucred.cr_uid; +} + +kern_return_t __notify_server_suspend_pid +( + mach_port_t server, + int pid, + int *status, + audit_token_t audit +) +{ + uid_t uid, target_uid; + + uid = (uid_t)-1; + + *status = server_preflight(NULL, 0, audit, -1, &uid, NULL, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.suspend_pid++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_suspend_pid %d\n", pid); + + target_uid = uid_for_pid(pid); + + if ((uid != 0) && (target_uid != uid)) + { + *status = NOTIFY_STATUS_NOT_AUTHORIZED; + return KERN_SUCCESS; + } + + *status = NOTIFY_STATUS_OK; + + _notify_lib_suspend_proc(global.notify_state, pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_resume_pid +( + mach_port_t server, + int pid, + int *status, + audit_token_t audit +) +{ + uid_t uid, target_uid; + + uid = (uid_t)-1; + + *status = server_preflight(NULL, 0, audit, -1, &uid, NULL, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.resume_pid++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_resume_pid %d\n", pid); + + target_uid = uid_for_pid(pid); + + if ((uid != 0) && (target_uid != uid)) + { + *status = NOTIFY_STATUS_NOT_AUTHORIZED; + return KERN_SUCCESS; + } + + *status = NOTIFY_STATUS_OK; + + _notify_lib_resume_proc(global.notify_state, pid); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_check +( + mach_port_t server, + int token, + int *check, + int *status, + audit_token_t audit +) +{ + pid_t pid = (gid_t)-1; + + *check = 0; + + call_statistics.check++; + + audit_token_to_au32(audit, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL); + + log_message(ASL_LEVEL_DEBUG, "__notify_server_check %d %d\n", pid, token); + + *status = _notify_lib_check(global.notify_state, pid, token, check); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_get_state +( + mach_port_t server, + int token, + uint64_t *state, + int *status, + audit_token_t audit +) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + client_t *c; + + *state = 0; + + *status = server_preflight(NULL, 0, audit, -1, &uid, &gid, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.get_state++; + call_statistics.get_state_by_client++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_get_state %d %d\n", pid, token); + + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if ((c == NULL) || (c->name_info == NULL)) + { + *status = NOTIFY_STATUS_FAILED; + } + else + { + *status = _notify_lib_get_state(global.notify_state, c->name_info->name_id, state, uid, gid); + } + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_get_state_2 +( + mach_port_t server, + uint64_t name_id, + uint64_t *state, + int *status, + audit_token_t audit +) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + + *state = 0; + + *status = server_preflight(NULL, 0, audit, -1, &uid, &gid, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_get_state_2 %llu\n", name_id); + + *status = _notify_lib_get_state(global.notify_state, name_id, state, uid, gid); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_get_state_3 +( + mach_port_t server, + int token, + uint64_t *state, + uint64_t *name_id, + int *status, + audit_token_t audit +) +{ + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + client_t *c; + + *state = 0; + *name_id = 0; + + *status = server_preflight(NULL, 0, audit, -1, &uid, &gid, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.get_state++; + call_statistics.get_state_by_client_and_fetch_id++; + + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if ((c == NULL) || (c->name_info == NULL)) + { + *status = NOTIFY_STATUS_FAILED; + *name_id = UINT64_MAX; + } + else + { + *status = _notify_lib_get_state(global.notify_state, c->name_info->name_id, state, uid, gid); + *name_id = c->name_info->name_id; + } + + if (*name_id == UINT64_MAX) log_message(ASL_LEVEL_DEBUG, "__notify_server_get_state_3 %d %d\n", pid, token); + else log_message(ASL_LEVEL_DEBUG, "__notify_server_get_state_3 %d %d [%llu]\n", pid, token, *name_id); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_set_state_3 +( + mach_port_t server, + int token, + uint64_t state, + uint64_t *name_id, + int *status, + audit_token_t audit +) +{ + client_t *c; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + + *name_id = 0; + + *status = server_preflight(NULL, 0, audit, -1, &uid, &gid, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.set_state++; + call_statistics.set_state_by_client_and_fetch_id++; + + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if ((c == NULL) || (c->name_info == NULL)) + { + *status = NOTIFY_STATUS_FAILED; + *name_id = UINT64_MAX; + } + else + { + *status = _notify_lib_set_state(global.notify_state, c->name_info->name_id, state, uid, gid); + *name_id = c->name_info->name_id; + } + + if (*name_id == UINT64_MAX) log_message(ASL_LEVEL_DEBUG, "__notify_server_set_state_3 %d %d %llu\n", pid, token, state); + else log_message(ASL_LEVEL_DEBUG, "__notify_server_set_state_3 %d %d %llu [%llu]\n", pid, token, state, *name_id); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_set_state +( + mach_port_t server, + int token, + uint64_t state, + int *status, + audit_token_t audit +) +{ + uint64_t ignored; + kern_return_t kstatus; + + *status = NOTIFY_STATUS_OK; + + kstatus = __notify_server_set_state_3(server, token, state, &ignored, status, audit); + + call_statistics.set_state_by_client_and_fetch_id--; + call_statistics.set_state_by_client++; + + return kstatus; +} + +kern_return_t __notify_server_set_state_2 +( + mach_port_t server, + uint64_t name_id, + uint64_t state, + audit_token_t audit +) +{ + uint32_t status; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + + if (global.notify_state == NULL) return KERN_SUCCESS; + + call_statistics.set_state++; + call_statistics.set_state_by_id++; + + audit_token_to_au32(audit, NULL, &uid, &gid, NULL, NULL, NULL, NULL, NULL); + + log_message(ASL_LEVEL_DEBUG, "__notify_server_set_state_2 %llu %llu\n", name_id, state); + + status = _notify_lib_set_state(global.notify_state, name_id, state, uid, gid); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_set_owner +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int uid, + int gid, + int *status, + audit_token_t audit +) +{ + uid_t auid = (uid_t)-1; + + *status = server_preflight(name, nameCnt, audit, -1, &auid, NULL, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.set_owner++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_set_owner %s %d %d\n", name, uid, gid); + + /* only root may set owner for names */ + if (auid != 0) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + *status = NOTIFY_STATUS_NOT_AUTHORIZED; + return KERN_SUCCESS; + } + + *status = _notify_lib_set_owner(global.notify_state, name, uid, gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_get_owner +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *uid, + int *gid, + int *status, + audit_token_t audit +) +{ + *uid = 0; + *gid = 0; + + *status = server_preflight(name, nameCnt, audit, -1, NULL, NULL, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.get_owner++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_get_owner %s\n", name); + + *status = _notify_lib_get_owner(global.notify_state, name, (uint32_t *)uid, (uint32_t *)gid); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_set_access +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int mode, + int *status, + audit_token_t audit +) +{ + uint32_t u, g; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + + *status = server_preflight(name, nameCnt, audit, -1, &uid, &gid, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.set_access++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_set_access %s 0x%03x\n", name, mode); + + _notify_lib_get_owner(global.notify_state, name, &u, &g); + + /* only root and owner may set access for names */ + if ((uid != 0) && (uid != u)) + { + *status = NOTIFY_STATUS_NOT_AUTHORIZED; + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; + } + + *status = _notify_lib_set_access(global.notify_state, name, mode); + if ((u != 0) || (g != 0)) *status = _notify_lib_set_owner(global.notify_state, name, u, g); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; +} + +kern_return_t __notify_server_get_access +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *mode, + int *status, + audit_token_t audit +) +{ + *mode = 0; + + *status = server_preflight(name, nameCnt, audit, -1, NULL, NULL, NULL, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.get_access++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_get_access %s\n", name); + + *status = _notify_lib_get_access(global.notify_state, name, (uint32_t *)mode); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + return KERN_SUCCESS; +} + +/* Unsupported because it makes no sense */ +kern_return_t __notify_server_release_name +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *status, + audit_token_t audit +) +{ + *status = NOTIFY_STATUS_FAILED; + return KERN_SUCCESS; +} + +kern_return_t __notify_server_monitor_file +( + mach_port_t server, + int token, + caddr_t path, + mach_msg_type_number_t pathCnt, + int flags, + int *status, + audit_token_t audit +) +{ + client_t *c; + name_info_t *n; + uid_t uid = (uid_t)-1; + gid_t gid = (gid_t)-1; + pid_t pid = (pid_t)-1; + uint32_t ubits = (uint32_t)flags; + + *status = server_preflight(path, pathCnt, audit, -1, &uid, &gid, &pid, NULL); + if (*status != NOTIFY_STATUS_OK) return KERN_SUCCESS; + + call_statistics.monitor_file++; + + log_message(ASL_LEVEL_DEBUG, "__notify_server_monitor_file %d %d %s 0x%08x\n", pid, token, path, ubits); + + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if (c == NULL) + { + vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_INVALID_REQUEST; + return KERN_SUCCESS; + } + + n = c->name_info; + if (n == NULL) + { + vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_INVALID_REQUEST; + return KERN_SUCCESS; + } + + *status = service_open_path_private(n->name, c, path, uid, gid, ubits); + vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + + return KERN_SUCCESS; +} + +kern_return_t __notify_server_monitor_file_2 +( + mach_port_t server, + int token, + caddr_t path, + mach_msg_type_number_t pathCnt, + int flags, + audit_token_t audit +) +{ + int ignored; + return __notify_server_monitor_file(server, token, path, pathCnt, flags, &ignored, audit); +} + +/* + * Original routines provide compatibility for legacy clients. + * iOS simulator uses them. + */ + +/* + * Generates a integer "token" for legacy client registrations. + */ +static int +generate_token(audit_token_t audit) +{ + static int legacy_id = 0; + + if (++legacy_id == -1) legacy_id = 1; + return legacy_id; +} + +kern_return_t __notify_server_register_plain +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *client_id, + int *status, + audit_token_t audit +) +{ + int token = generate_token(audit); + + *client_id = token; + *status = NOTIFY_STATUS_OK; + + return __notify_server_register_plain_2(server, name, nameCnt, token, audit); +} + +kern_return_t __notify_server_register_check +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int *size, + int *slot, + int *client_id, + int *status, + audit_token_t audit +) +{ + *size = 0; + *slot = 0; + *status = NOTIFY_STATUS_OK; + + uint64_t nid; + int token = generate_token(audit); + + *client_id = token; + + return __notify_server_register_check_2(server, name, nameCnt, token, size, slot, &nid, status, audit); +} + +kern_return_t __notify_server_register_signal +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int sig, + int *client_id, + int *status, + audit_token_t audit +) +{ + int token = generate_token(audit); + + *client_id = token; + *status = NOTIFY_STATUS_OK; + + return __notify_server_register_signal_2(server, name, nameCnt, token, sig, audit); +} + +kern_return_t __notify_server_register_file_descriptor +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + fileport_t fileport, + int ntoken, + int *client_id, + int *status, + audit_token_t audit +) +{ + kern_return_t kstatus; + client_t *c; + pid_t pid = (pid_t)-1; + int token = generate_token(audit); + + *client_id = token; + *status = NOTIFY_STATUS_OK; + + kstatus = __notify_server_register_file_descriptor_2(server, name, nameCnt, token, fileport, audit); + if (kstatus == KERN_SUCCESS) + { + audit_token_to_au32(audit, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL); + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if (c == NULL) *status = NOTIFY_STATUS_FAILED; + else c->send_val = ntoken; + } + + return kstatus; +} + +kern_return_t __notify_server_register_mach_port +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + mach_port_t port, + int ntoken, + int *client_id, + int *status, + audit_token_t audit +) +{ + kern_return_t kstatus; + client_t *c; + pid_t pid = (pid_t)-1; + int token; + + *client_id = 0; + *status = NOTIFY_STATUS_OK; + + if (port == MACH_PORT_DEAD) + { + if ((name != NULL) && (nameCnt > 0)) vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + *status = NOTIFY_STATUS_INVALID_REQUEST; + return KERN_SUCCESS; + } + + token = generate_token(audit); + + *client_id = token; + *status = NOTIFY_STATUS_OK; + + kstatus = __notify_server_register_mach_port_2(server, name, nameCnt, token, port, audit); + if (kstatus == KERN_SUCCESS) + { + audit_token_to_au32(audit, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL); + c = _nc_table_find_64(global.notify_state->client_table, make_client_id(pid, token)); + if (c == NULL) *status = NOTIFY_STATUS_FAILED; + else c->send_val = ntoken; + } + + return kstatus; +} + +kern_return_t __notify_server_simple_post +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + audit_token_t audit +) +{ + return __notify_server_post_4(server, name, nameCnt, audit); +} + +kern_return_t __notify_server_regenerate +( + mach_port_t server, + caddr_t name, + mach_msg_type_number_t nameCnt, + int token, + uint32_t reg_type, + mach_port_t port, + int sig, + int prev_slot, + uint64_t prev_state, + uint64_t prev_time, + caddr_t path, + mach_msg_type_number_t pathCnt, + int path_flags, + int *new_slot, + uint64_t *new_nid, + int *status, + audit_token_t audit +) +{ + kern_return_t kstatus; + pid_t pid = (pid_t)-1; + int size; + name_info_t *n; + client_t *c; + uint64_t cid; + + *new_slot = 0; + *new_nid = 0; + *status = NOTIFY_STATUS_OK; + + if (name == NULL) + { + if (path != NULL) vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_INVALID_NAME; + return KERN_SUCCESS; + } + + if (name[nameCnt] != '\0') + { + if (path != NULL) vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + *status = NOTIFY_STATUS_INVALID_NAME; + return KERN_SUCCESS; + } + + if ((path != NULL) && (path[pathCnt] != '\0')) + { + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_INVALID_REQUEST; + return KERN_SUCCESS; + } + + call_statistics.regenerate++; + + audit_token_to_au32(audit, NULL, NULL, NULL, NULL, NULL, &pid, NULL, NULL); + + log_message(ASL_LEVEL_DEBUG, "__notify_server_regenerate %s %d %d %d %u %d %d %llu %s %d\n", name, pid, token, reg_type, port, sig, prev_slot, prev_state, path, path_flags); + + cid = make_client_id(pid, token); + c = (client_t *)_nc_table_find_64(global.notify_state->client_table, cid); + if (c != NULL) + { + /* duplicate client - this should never happen */ + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + if ((path != NULL) && (pathCnt > 0)) vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_FAILED; + return KERN_SUCCESS; + } + + switch (reg_type) + { + case NOTIFY_TYPE_MEMORY: + { + /* prev_slot must be between 0 and global.nslots */ + if ((prev_slot < 0) || (prev_slot >= global.nslots)) + { + *status = NOTIFY_STATUS_INVALID_REQUEST; + return KERN_SUCCESS; + } + + kstatus = __notify_server_register_check_2(server, name, nameCnt, token, &size, new_slot, new_nid, status, audit); + if (*status == NOTIFY_STATUS_OK) + { + if ((*new_slot != UINT32_MAX) && (global.last_shm_base != NULL)) + { + global.shared_memory_base[*new_slot] = global.shared_memory_base[*new_slot] + global.last_shm_base[prev_slot] - 1; + global.last_shm_base[prev_slot] = 0; + } + } + break; + } + case NOTIFY_TYPE_PLAIN: + { + kstatus = __notify_server_register_plain_2(server, name, nameCnt, token, audit); + break; + } + case NOTIFY_TYPE_PORT: + { + kstatus = __notify_server_register_mach_port_2(server, name, nameCnt, token, port, audit); + break; + } + case NOTIFY_TYPE_SIGNAL: + { + kstatus = __notify_server_register_signal_2(server, name, nameCnt, token, sig, audit); + break; + } + case NOTIFY_TYPE_FILE: /* fall through */ + default: + { + /* can not regenerate this type */ + vm_deallocate(mach_task_self(), (vm_address_t)name, nameCnt); + if ((path != NULL) && (pathCnt > 0)) vm_deallocate(mach_task_self(), (vm_address_t)path, pathCnt); + *status = NOTIFY_STATUS_FAILED; + return KERN_SUCCESS; + } + } + + if (path != NULL) + { + __notify_server_monitor_file_2(server, token, path, pathCnt, path_flags, audit); + } + + c = (client_t *)_nc_table_find_64(global.notify_state->client_table, cid); + if (c == NULL) + { + *status = NOTIFY_STATUS_FAILED; + } + else + { + *status = NOTIFY_STATUS_OK; + n = c->name_info; + *new_nid = n->name_id; + if (prev_time > n->state_time) n->state = prev_state; + } + + return KERN_SUCCESS; +} diff --git a/libnotify/notifyd/notifyd.8 b/libnotify/notifyd/notifyd.8 new file mode 100644 index 000000000..af44803bb --- /dev/null +++ b/libnotify/notifyd/notifyd.8 @@ -0,0 +1,68 @@ +.\" Copyright (c) 2003-2013 Apple Inc. All rights reserved. +.\" +.\" @APPLE_LICENSE_HEADER_START@ +.\" +.\" Portions Copyright (c) 2003-2010 Apple Inc. All Rights Reserved. +.\" +.\" This file contains Original Code and/or Modifications of Original Code +.\" as defined in and that are subject to the Apple Public Source License +.\" Version 2.0 (the 'License'). You may not use this file except in +.\" compliance with the License. Please obtain a copy of the License at +.\" http://www.opensource.apple.com/apsl/ and read it before using this +.\" file. +.\" +.\" The Original Code and all software distributed under the License are +.\" distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +.\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +.\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +.\" Please see the License for the specific language governing rights and +.\" limitations under the License. +.\" +.\" @APPLE_LICENSE_HEADER_END@ +.\" +.\" +.Dd March 24, 2003 +.Dt notifyd 8 +.Os "Mac OS X" +.Sh NAME +.Nm notifyd +.Nd notification server +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl log_file Ar path +.Op Fl shm_pages Ar npages +.Sh DESCRIPTION +.Nm +is the server for the Mac OS X notification system described in +.Xr notify 3 . +The server is started automatically by +.Nm launchd +during system startup. +.Pp +The +.Fl d +option causes +.Nm notifyd +to log debugging messages to a log file. +Messages are not logged to ASL to avoid potential deadlocks, +since the ASL system makes use of the +.Xr notify 3 +system. +.Pp +The default log file is +.Pa /var/log/notifyd.log . +An alternate log file path may be specified following the +.Fl log_file +flag. +.Pp +The +.Fl shm_pages Ar npages +option sets the number of shared memory pages used for passive notification. +The default is one page. +If a value of zero is specified, +shared memory is disabled and passive notifications are performed +using IPC between the client and the server. +.Sh SEE ALSO +.Xr notify 3 . diff --git a/libnotify/notifyd/notifyd.c b/libnotify/notifyd/notifyd.c new file mode 100644 index 000000000..03ba1a5b5 --- /dev/null +++ b/libnotify/notifyd/notifyd.c @@ -0,0 +1,1265 @@ +/* + * Copyright (c) 2003-2012 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pathwatch.h" +#include "notifyd.h" +#include "service.h" +#include "pathwatch.h" +#include "timer.h" + +#include "notify_ipc.h" +#include "notify_private.h" + +#define forever for(;;) +#define IndexNull -1 + +/* Compile flags */ +#define RUN_TIME_CHECKS + +#if TARGET_IPHONE_SIMULATOR +static const char *_config_file_path; +#define CONFIG_FILE_PATH _config_file_path + +static const char *_debug_log_path; +#define DEBUG_LOG_PATH _debug_log_path +#else +#define CONFIG_FILE_PATH "/etc/notify.conf" +#define DEBUG_LOG_PATH "/var/log/notifyd.log" +#endif + +#define STATUS_REQUEST_SHORT 0 +#define STATUS_REQUEST_LONG 1 + +#define N_NOTIFY_TYPES 6 + +static int notifyd_token; + +static char *status_file = NULL; + +typedef union +{ + mach_msg_header_t head; + union __RequestUnion__notify_ipc_subsystem request; +} notify_request_msg; + +typedef union +{ + mach_msg_header_t head; + union __ReplyUnion__notify_ipc_subsystem reply; +} notify_reply_msg; + +extern boolean_t notify_ipc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); + +static const char * +notify_type_name(uint32_t t) +{ + switch (t) + { + case NOTIFY_TYPE_NONE: return "none "; + case NOTIFY_TYPE_MEMORY: return "memory"; + case NOTIFY_TYPE_PLAIN: return "plain "; + case NOTIFY_TYPE_PORT: return "port "; + case NOTIFY_TYPE_FILE: return "file "; + case NOTIFY_TYPE_SIGNAL: return "signal"; + default: return "unknown"; + } + + return "unknown"; +} + +static void +fprint_client(FILE *f, client_t *c) +{ + int token; + + if (c == NULL) + { + fprintf(f, "NULL client\n"); + return; + } + + token = c->client_id; + + fprintf(f, "client_id: %llu\n", c->client_id); + fprintf(f, "pid: %d\n", c->pid); + fprintf(f, "token: %d\n", token); + fprintf(f, "lastval: %u\n", c->lastval); + fprintf(f, "suspend_count: %u\n", c->suspend_count); + fprintf(f, "type: %s\n", notify_type_name(c->notify_type)); + switch(c->notify_type) + { + case NOTIFY_TYPE_NONE: + break; + + case NOTIFY_TYPE_PLAIN: + break; + + case NOTIFY_TYPE_MEMORY: + break; + + case NOTIFY_TYPE_PORT: + fprintf(f, "mach port: 0x%08x\n", c->port); + break; + + case NOTIFY_TYPE_FILE: + fprintf(f, "fd: %d\n", c->fd); + break; + + case NOTIFY_TYPE_SIGNAL: + fprintf(f, "signal: %d\n", c->sig); + break; + + default: break; + } +} + +static void +fprint_quick_client(FILE *f, client_t *c, int pname) +{ + int token; + if (c == NULL) return; + + token = c->client_id; + + if (pname == 1) fprintf(f, " [%s]", c->name_info->name); + fprintf(f, " %u %u", c->pid, token); + if (c->suspend_count > 0) fprintf(f, " suspend %d", c->suspend_count); + if (c->state & NOTIFY_CLIENT_STATE_PENDING) fprintf(f, " pending"); + if (c->state & NOTIFY_CLIENT_STATE_TIMEOUT) fprintf(f, " timeout"); + fprintf(f, " %s", notify_type_name(c->notify_type)); + if (c->notify_type == NOTIFY_TYPE_SIGNAL) fprintf(f, " %d", c->sig); + fprintf(f, "\n"); +} + +static void +fprint_quick_name_info(FILE *f, name_info_t *n) +{ + list_t *sl; + client_t *c; + + if (n == NULL) return; + + fprintf(f, "\"%s\" uid=%u gid=%u %03x", n->name, n->uid, n->gid, n->access); + if (n->slot != -1) + { + fprintf(f, " slot %u", n->slot); + if (global.shared_memory_refcount[n->slot] != -1) fprintf(f, " = %u", global.shared_memory_base[n->slot]); + } + + fprintf(f, "\n"); + + for (sl = n->subscriptions; sl != NULL; sl = _nc_list_next(sl)) + { + c = _nc_list_data(sl); + if (c == NULL) break; + + fprint_quick_client(f, c, 0); + } + + fprintf(f, "\n"); +} + +static void +fprint_name_info(FILE *f, const char *name, name_info_t *n, table_t *pid_table, pid_t *max_pid) +{ + list_t *sl; + client_t *c; + uint32_t i, reg[N_NOTIFY_TYPES]; + + if (n == NULL) + { + fprintf(f, "%s unknown\n", name); + return; + } + + fprintf(f, "name: %s\n", n->name); + fprintf(f, "id: %llu\n", n->name_id); + fprintf(f, "uid: %u\n", n->uid); + fprintf(f, "gid: %u\n", n->gid); + fprintf(f, "access: %03x\n", n->access); + fprintf(f, "refcount: %u\n", n->refcount); + if (n->slot == -1) fprintf(f, "slot: -unassigned-"); + else + { + fprintf(f, "slot: %u", n->slot); + if (global.shared_memory_refcount[n->slot] != -1) + fprintf(f, " = %u (%u)", global.shared_memory_base[n->slot], global.shared_memory_refcount[n->slot]); + } + fprintf(f, "\n"); + fprintf(f, "val: %u\n", n->val); + fprintf(f, "state: %llu\n", n->state); + + for (i = 0; i < N_NOTIFY_TYPES; i++) reg[i] = 0; + + for (sl = n->subscriptions; sl != NULL; sl = _nc_list_next(sl)) + { + list_t *l; + + c = _nc_list_data(sl); + if (c == NULL) break; + + if ((c->pid != (pid_t)-1) && (c->pid > *max_pid)) *max_pid = c->pid; + + l = _nc_table_find_n(pid_table, c->pid); + if (l == NULL) + { + _nc_table_insert_n(pid_table, (uint32_t)c->pid, _nc_list_new(c)); + } + else + { + _nc_list_concat(l, _nc_list_new(c)); + } + + switch (c->notify_type) + { + case NOTIFY_TYPE_MEMORY: reg[1]++; break; + case NOTIFY_TYPE_PLAIN: reg[2]++; break; + case NOTIFY_TYPE_PORT: reg[3]++; break; + case NOTIFY_TYPE_FILE: reg[4]++; break; + case NOTIFY_TYPE_SIGNAL: reg[5]++; break; + default: reg[0]++; + } + } + + fprintf(f, "types: none %u memory %u plain %u port %u file %u signal %u\n", reg[0], reg[1], reg[2], reg[3], reg[4], reg[5]); + + for (sl = n->subscriptions; sl != NULL; sl = _nc_list_next(sl)) + { + c = _nc_list_data(sl); + if (c == NULL) break; + + fprintf(f, "\n"); + fprint_client(f, c); + } +} + +static void +fprint_quick_status(FILE *f) +{ + void *tt; + name_info_t *n; + + tt = _nc_table_traverse_start(global.notify_state->name_table); + + while (tt != NULL) + { + n = _nc_table_traverse(global.notify_state->name_table, tt); + if (n == NULL) break; + fprint_quick_name_info(f, n); + } + + _nc_table_traverse_end(global.notify_state->name_table, tt); + fprintf(f, "\n"); +} + +static void +fprint_status(FILE *f) +{ + void *tt; + name_info_t *n; + int32_t i; + client_t *c; + svc_info_t *info; + path_node_t *node; + timer_t *timer; + table_t *pid_table; + pid_t pid, max_pid; + uint32_t count; + portproc_data_t *pdata; + + pid_table = _nc_table_new(0); + max_pid = 0; + + fprintf(f, "--- GLOBALS ---\n"); + fprintf(f, "%u slots (current id %u)\n", global.nslots, global.slot_id); + fprintf(f, "%u log_cutoff (default %u)\n", global.log_cutoff, global.log_default); + fprintf(f, "\n"); + + fprintf(f, "--- STATISTICS ---\n"); + fprintf(f, "post %llu\n", call_statistics.post); + fprintf(f, " id %llu\n", call_statistics.post_by_id); + fprintf(f, " name %llu\n", call_statistics.post_by_name); + fprintf(f, " fetch %llu\n", call_statistics.post_by_name_and_fetch_id); + fprintf(f, " no_op %llu\n", call_statistics.post_no_op); + fprintf(f, "\n"); + fprintf(f, "register %llu\n", call_statistics.reg); + fprintf(f, " plain %llu\n", call_statistics.reg_plain); + fprintf(f, " check %llu\n", call_statistics.reg_check); + fprintf(f, " signal %llu\n", call_statistics.reg_signal); + fprintf(f, " file %llu\n", call_statistics.reg_file); + fprintf(f, " port %llu\n", call_statistics.reg_port); + fprintf(f, "\n"); + fprintf(f, "check %llu\n", call_statistics.check); + fprintf(f, "cancel %llu\n", call_statistics.cancel); + fprintf(f, "cleanup %llu\n", call_statistics.cleanup); + fprintf(f, "regenerate %llu\n", call_statistics.regenerate); + fprintf(f, "\n"); + fprintf(f, "suspend %llu\n", call_statistics.suspend); + fprintf(f, "resume %llu\n", call_statistics.resume); + fprintf(f, "suspend_pid %llu\n", call_statistics.suspend_pid); + fprintf(f, "resume_pid %llu\n", call_statistics.resume_pid); + fprintf(f, "\n"); + fprintf(f, "get_state %llu\n", call_statistics.get_state); + fprintf(f, " id %llu\n", call_statistics.get_state_by_id); + fprintf(f, " client %llu\n", call_statistics.get_state_by_client); + fprintf(f, " fetch %llu\n", call_statistics.get_state_by_client_and_fetch_id); + fprintf(f, "\n"); + fprintf(f, "set_state %llu\n", call_statistics.set_state); + fprintf(f, " id %llu\n", call_statistics.set_state_by_id); + fprintf(f, " client %llu\n", call_statistics.set_state_by_client); + fprintf(f, " fetch %llu\n", call_statistics.set_state_by_client_and_fetch_id); + fprintf(f, "\n"); + fprintf(f, "get_owner %llu\n", call_statistics.get_owner); + fprintf(f, "set_owner %llu\n", call_statistics.set_owner); + fprintf(f, "\n"); + fprintf(f, "get_access %llu\n", call_statistics.get_access); + fprintf(f, "set_access %llu\n", call_statistics.set_access); + fprintf(f, "\n"); + fprintf(f, "monitor %llu\n", call_statistics.monitor_file); + fprintf(f, "svc_path %llu\n", call_statistics.service_path); + fprintf(f, "svc_timer %llu\n", call_statistics.service_timer); + + fprintf(f, "\n"); + fprintf(f, "name alloc %9u free %9u extant %9u\n", global.notify_state->stat_name_alloc , global.notify_state->stat_name_free, global.notify_state->stat_name_alloc - global.notify_state->stat_name_free); + fprintf(f, "subscription alloc %9u free %9u extant %9u\n", global.notify_state->stat_client_alloc , global.notify_state->stat_client_free, global.notify_state->stat_client_alloc - global.notify_state->stat_client_free); + fprintf(f, "portproc alloc %9u free %9u extant %9u\n", global.notify_state->stat_portproc_alloc , global.notify_state->stat_portproc_free, global.notify_state->stat_portproc_alloc - global.notify_state->stat_portproc_free); + fprintf(f, "\n"); + + count = 0; + tt = _nc_table_traverse_start(global.notify_state->port_table); + while (tt != NULL) + { + pdata = _nc_table_traverse(global.notify_state->port_table, tt); + if (pdata == NULL) break; + count++; + } + _nc_table_traverse_end(global.notify_state->port_table, tt); + fprintf(f, "port count %u\n", count); + + count = 0; + tt = _nc_table_traverse_start(global.notify_state->proc_table); + while (tt != NULL) + { + pdata = _nc_table_traverse(global.notify_state->proc_table, tt); + if (pdata == NULL) break; + count++; + } + _nc_table_traverse_end(global.notify_state->proc_table, tt); + fprintf(f, "proc count %u\n", count); + fprintf(f, "\n"); + + fprintf(f, "--- NAME TABLE ---\n"); + count = 0; + tt = _nc_table_traverse_start(global.notify_state->name_table); + + while (tt != NULL) + { + n = _nc_table_traverse(global.notify_state->name_table, tt); + if (n == NULL) break; + fprint_name_info(f, n->name, n, pid_table, &max_pid); + fprintf(f, "\n"); + count++; + } + + fprintf(f, "--- NAME COUNT %u ---\n", count); + _nc_table_traverse_end(global.notify_state->name_table, tt); + fprintf(f, "\n"); + + fprintf(f, "--- SUBSCRIPTION TABLE ---\n"); + count = 0; + tt = _nc_table_traverse_start(global.notify_state->client_table); + + while (tt != NULL) + { + c = _nc_table_traverse(global.notify_state->client_table, tt); + if (c == NULL) break; + fprint_quick_client(f, c, 1); + count++; + } + + fprintf(f, "--- SUBSCRIPTION COUNT %u ---\n", count); + _nc_table_traverse_end(global.notify_state->client_table, tt); + fprintf(f, "\n"); + + fprintf(f, "--- CONTROLLED NAME ---\n"); + for (i = 0; i < global.notify_state->controlled_name_count; i++) + { + fprintf(f, "%s %u %u %03x\n", global.notify_state->controlled_name[i]->name, global.notify_state->controlled_name[i]->uid, global.notify_state->controlled_name[i]->gid, global.notify_state->controlled_name[i]->access); + } + fprintf(f, "--- CONTROLLED NAME COUNT %u ---\n", global.notify_state->controlled_name_count); + fprintf(f, "\n"); + + fprintf(f, "--- PUBLIC SERVICE ---\n"); + count = 0; + tt = _nc_table_traverse_start(global.notify_state->name_table); + while (tt != NULL) + { + n = _nc_table_traverse(global.notify_state->name_table, tt); + if (n == NULL) break; + if (n->private == NULL) continue; + + count++; + info = (svc_info_t *)n->private; + + if (info->type == 0) + { + fprintf(f, "Null service: %s\n", n->name); + } + if (info->type == SERVICE_TYPE_PATH_PUBLIC) + { + node = (path_node_t *)info->private; + fprintf(f, "Path Service: %s <- %s\n", n->name, node->path); + } + else if (info->type == SERVICE_TYPE_TIMER_PUBLIC) + { + timer = (timer_t *)info->private; + switch (timer->type) + { + case TIME_EVENT_ONESHOT: + { + fprintf(f, "Time Service: %s <- Oneshot %llu\n", n->name, timer->start); + break; + } + case TIME_EVENT_CLOCK: + { + fprintf(f, "Time Service: %s <- Clock start %lld freq %u end %lld\n", n->name, timer->start, timer->freq, timer->end); + break; + } + case TIME_EVENT_CAL: + { + fprintf(f, "Time Service: %s <- Calendar start %lld freq %u end %lld day %d\n", n->name, timer->start, timer->freq, timer->end, timer->day); + break; + } + } + } + else + { + fprintf(f, "Unknown service: %s (%u)\n", n->name, info->type); + } + } + + fprintf(f, "--- PUBLIC SERVICE COUNT %u ---\n", count); + _nc_table_traverse_end(global.notify_state->name_table, tt); + fprintf(f, "\n"); + + fprintf(f, "--- PRIVATE SERVICE ---\n"); + count = 0; + tt = _nc_table_traverse_start(global.notify_state->client_table); + while (tt != NULL) + { + c = _nc_table_traverse(global.notify_state->client_table, tt); + if (c == NULL) break; + if (c->private == NULL) continue; + + count++; + info = (svc_info_t *)c->private; + n = c->name_info; + + if (info->type == 0) + { + fprintf(f, "PID %u Null service: %s\n", c->pid, n->name); + } + if (info->type == SERVICE_TYPE_PATH_PRIVATE) + { + node = (path_node_t *)info->private; + fprintf(f, "PID %u Path Service: %s <- %s (UID %d GID %d)\n", c->pid, n->name, node->path, node->uid, node->gid); + } + else if (info->type == SERVICE_TYPE_TIMER_PRIVATE) + { + timer = (timer_t *)info->private; + switch (timer->type) + { + case TIME_EVENT_ONESHOT: + { + fprintf(f, "PID %u Time Service: %s <- Oneshot %"PRId64"\n", c->pid, n->name, timer->start); + break; + } + case TIME_EVENT_CLOCK: + { + fprintf(f, "PID %u Time Service: %s <- Clock start %"PRId64" freq %"PRIu32" end %"PRId64"\n", c->pid, n->name, timer->start, timer->freq, timer->end); + break; + } + case TIME_EVENT_CAL: + { + fprintf(f, "PID %u Time Service: %s <- Calendar start %"PRId64" freq %"PRIu32" end %"PRId64" day %"PRId32"\n", c->pid, n->name, timer->start, timer->freq, timer->end, timer->day); + break; + } + } + } + } + + fprintf(f, "--- PRIVATE SERVICE COUNT %u ---\n", count); + _nc_table_traverse_end(global.notify_state->client_table, tt); + fprintf(f, "\n"); + + fprintf(f, "--- PROCESSES ---\n"); + for (pid = 0; pid <= max_pid; pid++) + { + int mem_count, plain_count, file_count, port_count, sig_count, com_port_count; + mach_port_t common_port = MACH_PORT_NULL; + + list_t *x; + list_t *l = _nc_table_find_n(pid_table, pid); + if (l == NULL) continue; + + mem_count = 0; + plain_count = 0; + file_count = 0; + port_count = 0; + sig_count = 0; + com_port_count = 0; + + for (x = l; x != NULL; x = _nc_list_next(x)) + { + c = _nc_list_data(x); + if (c != NULL) + { + if ((c->notify_type == NOTIFY_TYPE_PORT) && (!strcmp(c->name_info->name, COMMON_PORT_KEY))) common_port = c->port; + } + } + + for (x = l; x != NULL; x = _nc_list_next(x)) + { + c = _nc_list_data(x); + if (c != NULL) + { + switch(c->notify_type) + { + case NOTIFY_TYPE_NONE: + break; + + case NOTIFY_TYPE_PLAIN: + plain_count++; + break; + + case NOTIFY_TYPE_MEMORY: + mem_count++; + break; + + case NOTIFY_TYPE_PORT: + port_count++; + if (c->port == common_port) com_port_count++; + break; + + case NOTIFY_TYPE_FILE: + file_count++; + break; + + case NOTIFY_TYPE_SIGNAL: + sig_count++; + break; + + default: break; + } + } + } + + fprintf(f, "pid: %u ", pid); + if (file_count + sig_count == 0) + { + if (port_count == 0) fprintf(f, "regenerable / polling\n"); + else if (port_count == com_port_count) fprintf(f, "regenerable\n"); + else if (com_port_count > 0) fprintf(f, "partially regenerable\n"); + else fprintf(f, "non-regenerable\n"); + } + else + { + if (com_port_count == 0) fprintf(f, "non-regenerable\n"); + else fprintf(f, "partially regenerable\n"); + } + + fprintf(f, "memory %u plain %u port %u file %u signal %u common port %u\n", mem_count, plain_count, port_count, file_count, sig_count, com_port_count); + for (x = l; x != NULL; x = _nc_list_next(x)) + { + c = _nc_list_data(x); + if (c != NULL) + { + fprintf(f, " %s: %s\n", notify_type_name(c->notify_type), c->name_info->name); + } + } + + fprintf(f, "\n"); + _nc_list_release_list(l); + } + fprintf(f, "\n"); + _nc_table_free(pid_table); +} + +void +dump_status(uint32_t level) +{ + FILE *f; + + if (status_file == NULL) + { + asprintf(&status_file, "/var/run/notifyd_%u.status", getpid()); + if (status_file == NULL) return; + } + + unlink(status_file); + f = fopen(status_file, "w"); + if (f == NULL) return; + + if (level == STATUS_REQUEST_SHORT) fprint_quick_status(f); + else if (level == STATUS_REQUEST_LONG) fprint_status(f); + + fclose(f); +} + +void +log_message(int priority, const char *str, ...) +{ + time_t t; + char now[32]; + va_list ap; + FILE *lfp; + + if (priority > global.log_cutoff) return; + if (global.log_path == NULL) return; + + lfp = fopen(global.log_path, "a"); + if (lfp == NULL) return; + + va_start(ap, str); + + t = time(NULL); + memset(now, 0, 32); + strftime(now, 32, "%b %e %T", localtime(&t)); + + fprintf(lfp, "%s: ", now); + vfprintf(lfp, str, ap); + fflush(lfp); + + va_end(ap); + fclose(lfp); +} + +uint32_t +daemon_post(const char *name, uint32_t u, uint32_t g) +{ + name_info_t *n; + uint32_t status; + + if (name == NULL) return 0; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return 0; + + if (n->slot != (uint32_t)-1) global.shared_memory_base[n->slot]++; + + status = _notify_lib_post(global.notify_state, name, u, g); + return status; +} + +uint32_t +daemon_post_nid(uint64_t nid, uint32_t u, uint32_t g) +{ + name_info_t *n; + uint32_t status; + + n = (name_info_t *)_nc_table_find_64(global.notify_state->name_id_table, nid); + if (n == NULL) return 0; + + if (n->slot != (uint32_t)-1) global.shared_memory_base[n->slot]++; + + status = _notify_lib_post_nid(global.notify_state, nid, u, g); + return status; +} + +void +daemon_post_client(uint64_t cid) +{ + client_t *c; + + c = _nc_table_find_64(global.notify_state->client_table, cid); + if (c == NULL) return; + + if ((c->notify_type == NOTIFY_TYPE_MEMORY) && (c->name_info != NULL) && (c->name_info->slot != (uint32_t)-1)) + { + global.shared_memory_base[c->name_info->slot]++; + } + + _notify_lib_post_client(global.notify_state, c); +} + +void +daemon_set_state(const char *name, uint64_t val) +{ + name_info_t *n; + + if (name == NULL) return; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return; + + n->state = val; +} + +static void +init_launch_config(const char *name) +{ + launch_data_t tmp, pdict; + + tmp = launch_data_new_string(LAUNCH_KEY_CHECKIN); + global.launch_dict = launch_msg(tmp); + launch_data_free(tmp); + + if (global.launch_dict == NULL) + { + fprintf(stderr, "%d launchd checkin failed\n", getpid()); + exit(1); + } + + tmp = launch_data_dict_lookup(global.launch_dict, LAUNCH_JOBKEY_MACHSERVICES); + if (tmp == NULL) + { + fprintf(stderr, "%d launchd lookup of LAUNCH_JOBKEY_MACHSERVICES failed\n", getpid()); + exit(1); + } + + pdict = launch_data_dict_lookup(tmp, name); + if (pdict == NULL) + { + fprintf(stderr, "%d launchd lookup of name %s failed\n", getpid(), name); + exit(1); + } + + global.server_port = launch_data_get_machport(pdict); + if (global.server_port == MACH_PORT_NULL) + { + fprintf(stderr, "%d launchd lookup of server port for name %s failed\n", getpid(), name); + exit(1); + } +} + +static void +string_list_free(char **l) +{ + int i; + + if (l == NULL) return; + for (i = 0; l[i] != NULL; i++) + { + if (l[i] != NULL) free(l[i]); + l[i] = NULL; + } + free(l); +} + +static char ** +string_insert(char *s, char **l, unsigned int x) +{ + int i, len; + + if (s == NULL) return l; + if (l == NULL) + { + l = (char **)malloc(2 * sizeof(char *)); + l[0] = strdup(s); + l[1] = NULL; + return l; + } + + for (i = 0; l[i] != NULL; i++); + len = i + 1; /* count the NULL on the end of the list too! */ + + l = (char **)realloc(l, (len + 1) * sizeof(char *)); + + if ((x >= (len - 1)) || (x == IndexNull)) + { + l[len - 1] = strdup(s); + l[len] = NULL; + return l; + } + + for (i = len; i > x; i--) l[i] = l[i - 1]; + l[x] = strdup(s); + return l; +} + +static char ** +string_append(char *s, char **l) +{ + return string_insert(s, l, IndexNull); +} + +static unsigned int +string_list_length(char **l) +{ + int i; + + if (l == NULL) return 0; + for (i = 0; l[i] != NULL; i++); + return i; +} + +static unsigned int +string_index(char c, char *s) +{ + int i; + char *p; + + if (s == NULL) return IndexNull; + + for (i = 0, p = s; p[0] != '\0'; p++, i++) + { + if (p[0] == c) return i; + } + + return IndexNull; +} + +static char ** +explode(char *s, char *delim) +{ + char **l = NULL; + char *p, *t; + int i, n; + + if (s == NULL) return NULL; + + p = s; + while (p[0] != '\0') + { + for (i = 0; ((p[i] != '\0') && (string_index(p[i], delim) == IndexNull)); i++); + n = i; + t = malloc(n + 1); + for (i = 0; i < n; i++) t[i] = p[i]; + t[n] = '\0'; + l = string_append(t, l); + free(t); + t = NULL; + if (p[i] == '\0') return l; + if (p[i + 1] == '\0') l = string_append("", l); + p = p + i + 1; + } + return l; +} + +static uint32_t +atoaccess(char *s) +{ + uint32_t a; + + if (s == NULL) return 0; + if (strlen(s) != 6) return 0; + + a = 0; + if (s[0] == 'r') a |= (NOTIFY_ACCESS_READ << NOTIFY_ACCESS_USER_SHIFT); + if (s[1] == 'w') a |= (NOTIFY_ACCESS_WRITE << NOTIFY_ACCESS_USER_SHIFT); + + if (s[2] == 'r') a |= (NOTIFY_ACCESS_READ << NOTIFY_ACCESS_GROUP_SHIFT); + if (s[3] == 'w') a |= (NOTIFY_ACCESS_WRITE << NOTIFY_ACCESS_GROUP_SHIFT); + + if (s[4] == 'r') a |= (NOTIFY_ACCESS_READ << NOTIFY_ACCESS_OTHER_SHIFT); + if (s[5] == 'w') a |= (NOTIFY_ACCESS_WRITE << NOTIFY_ACCESS_OTHER_SHIFT); + + return a; +} + +static void +init_config() +{ + FILE *f; + struct stat sb; + char line[1024]; + char **args; + uint32_t status, argslen; + uint32_t uid, gid, access; + uint64_t nid, val64; + + /* + * Set IPC Version Number & PID + */ + val64 = getpid(); + val64 <<= 32; + val64 |= NOTIFY_IPC_VERSION; + + _notify_lib_register_plain(global.notify_state, NOTIFY_IPC_VERSION_NAME, -1, notifyd_token++, -1, 0, 0, &nid); + _notify_lib_set_state(global.notify_state, nid, val64, 0, 0); + + /* Check config file */ + if (stat(CONFIG_FILE_PATH, &sb) != 0) return; + + if (sb.st_uid != 0) + { + log_message(ASL_LEVEL_ERR, "config file %s not owned by root: ignored\n", CONFIG_FILE_PATH); + return; + } + + if (sb.st_mode & 02) + { + log_message(ASL_LEVEL_ERR, "config file %s is world-writable: ignored\n", CONFIG_FILE_PATH); + return; + } + + /* Read config file */ + f = fopen(CONFIG_FILE_PATH, "r"); + if (f == NULL) return; + + forever + { + if (fgets(line, 1024, f) == NULL) break; + if (line[0] == '\0') continue; + if (line[0] == '#') continue; + + line[strlen(line) - 1] = '\0'; + args = explode(line, "\t "); + argslen = string_list_length(args); + if (argslen == 0) continue; + + if (!strcasecmp(args[0], "monitor")) + { + if (argslen < 3) + { + string_list_free(args); + continue; + } + _notify_lib_register_plain(global.notify_state, args[1], -1, notifyd_token++, -1, 0, 0, &nid); + service_open_path(args[1], args[2], 0, 0); + } + + if (!strcasecmp(args[0], "timer")) + { + if (argslen < 3) + { + string_list_free(args); + continue; + } + _notify_lib_register_plain(global.notify_state, args[1], -1, notifyd_token++, -1, 0, 0, &nid); + status = service_open_timer(args[1], args[2]); + } + + else if (!strcasecmp(args[0], "set")) + { + if (argslen < 3) + { + string_list_free(args); + continue; + } + + val64 = atoll(args[2]); + + _notify_lib_register_plain(global.notify_state, args[1], -1, notifyd_token++, -1, 0, 0, &nid); + _notify_lib_set_state(global.notify_state, nid, val64, 0, 0); + } + + else if (!strcasecmp(args[0], "reserve")) + { + if (argslen == 1) + { + string_list_free(args); + continue; + } + + uid = 0; + gid = 0; + access = NOTIFY_ACCESS_DEFAULT; + + if (argslen > 2) uid = atoi(args[2]); + if (argslen > 3) gid = atoi(args[3]); + if (argslen > 4) access = atoaccess(args[4]); + + if ((uid != 0) || (gid != 0)) _notify_lib_set_owner(global.notify_state, args[1], uid, gid); + if (access != NOTIFY_ACCESS_DEFAULT) _notify_lib_set_access(global.notify_state, args[1], access); + } + else if (!strcasecmp(args[0], "quit")) + { + string_list_free(args); + break; + } + + string_list_free(args); + } + + fclose(f); +} + +static void +service_mach_message(bool blocking) +{ + __block kern_return_t status; + uint32_t rbits, sbits; + notify_request_msg *request; + notify_reply_msg *reply; + char rbuf[sizeof(notify_request_msg) + MAX_TRAILER_SIZE]; + char sbuf[sizeof(notify_reply_msg) + MAX_TRAILER_SIZE]; + + forever + { + memset(rbuf, 0, sizeof(rbuf)); + memset(sbuf, 0, sizeof(sbuf)); + + request = (notify_request_msg *)rbuf; + reply = (notify_reply_msg *)sbuf; + + request->head.msgh_local_port = global.server_port; + request->head.msgh_size = global.request_size; + + rbits = MACH_RCV_MSG | (blocking ? 0 : MACH_RCV_TIMEOUT) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | MACH_RCV_VOUCHER; + sbits = MACH_SEND_MSG; + + status = mach_msg(&(request->head), rbits, 0, global.request_size, global.server_port, 0, MACH_PORT_NULL); + if (status != KERN_SUCCESS) return; + + voucher_mach_msg_state_t voucher = voucher_mach_msg_adopt(&(request->head)); + +#if TARGET_OS_EMBEDDED + /* Synchronize with work_q since on embedded main() calls this + * from the global concurrent queue. */ + dispatch_sync(global.work_q, ^{ + status = notify_ipc_server(&(request->head), &(reply->head)); + }); +#else + status = notify_ipc_server(&(request->head), &(reply->head)); +#endif + + if (!status && (request->head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) + { + /* destroy the request - but not the reply port */ + request->head.msgh_remote_port = MACH_PORT_NULL; + mach_msg_destroy(&(request->head)); + } + + if (reply->head.msgh_remote_port) + { + status = mach_msg(&(reply->head), sbits, reply->head.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); + if (status == MACH_SEND_INVALID_DEST || status == MACH_SEND_TIMED_OUT) + { + /* deallocate reply port rights not consumed by failed mach_msg() send */ + mach_msg_destroy(&(reply->head)); + } + } + + voucher_mach_msg_revert(voucher); + } +} + +static int32_t +open_shared_memory(const char *name) +{ + int32_t shmfd, isnew; + uint32_t size; + + size = global.nslots * sizeof(uint32_t); + + isnew = 1; + shmfd = shm_open(name, O_RDWR, 0644); + if (shmfd != -1) + { + isnew = 0; + } + else + { + shmfd = shm_open(name, O_RDWR | O_CREAT, 0644); + } + + if (shmfd == -1) + { + char error_message[1024]; + snprintf(error_message, sizeof(error_message), "shm_open %s failed: %s\n", name, strerror(errno)); + + CRSetCrashLogMessage(error_message); + log_message(ASL_LEVEL_NOTICE, "%s", error_message); + return -1; + } + + ftruncate(shmfd, size); + global.shared_memory_base = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); + close(shmfd); + + if (isnew == 0) + { + global.last_shm_base = malloc(size); + if (global.last_shm_base != NULL) memcpy(global.last_shm_base, global.shared_memory_base, size); + } + + memset(global.shared_memory_base, 0, size); + global.shared_memory_refcount = (uint32_t *)malloc(size); + if (global.shared_memory_refcount == NULL) return -1; + + memset(global.shared_memory_refcount, 0, size); + + /* slot 0 is notifyd's pid */ + global.shared_memory_base[0] = getpid(); + global.shared_memory_refcount[0] = 1; + global.slot_id = 0; + + return 0; +} + +int +main(int argc, const char *argv[]) +{ + dispatch_queue_t main_q; + const char *service_name; + const char *shm_name; + uint32_t i, status; + struct rlimit rlim; + +#if TARGET_IPHONE_SIMULATOR + asprintf(&_config_file_path, "%s/private/etc/notify.conf", getenv("SIMULATOR_ROOT")); + asprintf(&_debug_log_path, "%s/var/log/notifyd.log", getenv("SIMULATOR_LOG_ROOT")); +#endif + + service_name = NOTIFY_SERVICE_NAME; + shm_name = SHM_ID; + + notify_set_options(NOTIFY_OPT_DISABLE); + + /* remove limit of number of file descriptors */ + rlim.rlim_max = RLIM_INFINITY; + rlim.rlim_cur = MIN(OPEN_MAX, rlim.rlim_max); + setrlimit(RLIMIT_NOFILE, &rlim); + + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + signal(SIGUSR2, SIG_IGN); + signal(SIGWINCH, SIG_IGN); + + memset(&call_statistics, 0, sizeof(struct call_statistics_s)); + + global.request_size = sizeof(notify_request_msg) + MAX_TRAILER_SIZE; + global.reply_size = sizeof(notify_reply_msg) + MAX_TRAILER_SIZE; + global.nslots = getpagesize() / sizeof(uint32_t); + global.notify_state = _notify_lib_notify_state_new(NOTIFY_STATE_ENABLE_RESEND, 1024); + global.log_cutoff = ASL_LEVEL_ERR; + global.log_path = strdup(DEBUG_LOG_PATH); + global.slot_id = (uint32_t)-1; + + for (i = 1; i < argc; i++) + { + if (!strcmp(argv[i], "-d")) + { + global.log_cutoff = ASL_LEVEL_DEBUG; + } + else if (!strcmp(argv[i], "-log_cutoff")) + { + global.log_cutoff = atoi(argv[++i]); + } + else if (!strcmp(argv[i], "-log_file")) + { + free(global.log_path); + global.log_path = strdup(argv[++i]); + } + else if (!strcmp(argv[i], "-service")) + { + service_name = argv[++i]; + } + else if (!strcmp(argv[i], "-shm")) + { + shm_name = argv[++i]; + } + else if (!strcmp(argv[i], "-shm_pages")) + { + global.nslots = atoi(argv[++i]) * (getpagesize() / sizeof(uint32_t)); + } + } + + global.log_default = global.log_cutoff; + + log_message(ASL_LEVEL_DEBUG, "--------------------\nnotifyd start PID %u\n", getpid()); + + init_launch_config(service_name); + + if (global.nslots > 0) + { + status = open_shared_memory(shm_name); + assert(status == 0); + } + + /* init from config file before starting the work queue */ + init_config(); + + main_q = dispatch_get_main_queue(); + assert(main_q != NULL); + + global.work_q = dispatch_queue_create("WorkQ", NULL); + assert(global.work_q != NULL); + +#if TARGET_OS_EMBEDDED + /* Block a thread in mach_msg() to avoid the syscall overhead of frequent + * dispatch source wakeup, and synchronize with work_q after message + * reception in service_mach_message(). */ + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + forever service_mach_message(true); + }); +#else + global.mach_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, global.server_port, 0, global.work_q); + assert(global.mach_src != NULL); + + dispatch_source_set_event_handler(global.mach_src, ^{ + service_mach_message(false); + }); + dispatch_resume(global.mach_src); +#endif + + /* Set up SIGUSR1 */ + global.sig_usr1_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)SIGUSR1, 0, main_q); + assert(global.sig_usr1_src != NULL); + dispatch_source_set_event_handler(global.sig_usr1_src, ^{ + dispatch_async(global.work_q, ^{ dump_status(STATUS_REQUEST_SHORT); }); + }); + dispatch_resume(global.sig_usr1_src); + + /* Set up SIGUSR2 */ + global.sig_usr2_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)SIGUSR2, 0, main_q); + assert(global.sig_usr2_src != NULL); + dispatch_source_set_event_handler(global.sig_usr2_src, ^{ + dispatch_async(global.work_q, ^{ dump_status(STATUS_REQUEST_LONG); }); + }); + dispatch_resume(global.sig_usr2_src); + + /* Set up SIGWINCH */ + global.sig_winch_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)SIGWINCH, 0, main_q); + assert(global.sig_winch_src != NULL); + dispatch_source_set_event_handler(global.sig_winch_src, ^{ + if (global.log_cutoff == ASL_LEVEL_DEBUG) global.log_cutoff = global.log_default; + else global.log_cutoff = ASL_LEVEL_DEBUG; + }); + dispatch_resume(global.sig_winch_src); + + dispatch_main(); + + /* NOTREACHED */ + return 0; +} diff --git a/libnotify/notifyd/notifyd.h b/libnotify/notifyd/notifyd.h new file mode 100644 index 000000000..a32856036 --- /dev/null +++ b/libnotify/notifyd/notifyd.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NOTIFY_DAEMON_H_ +#define _NOTIFY_DAEMON_H_ + +#include +#include +#include +#include + +#define NOTIFY_IPC_VERSION 2 + +struct global_s +{ + mach_port_t server_port; + launch_data_t launch_dict; + notify_state_t *notify_state; + dispatch_queue_t work_q; + dispatch_source_t mach_src; + dispatch_source_t sig_usr1_src; + dispatch_source_t sig_usr2_src; + dispatch_source_t sig_winch_src; + uint32_t request_size; + uint32_t reply_size; + uint32_t nslots; + uint32_t slot_id; + uint32_t *shared_memory_base; + uint32_t *shared_memory_refcount; + uint32_t *last_shm_base; + uint32_t log_cutoff; + uint32_t log_default; + char *log_path; +} global; + +struct call_statistics_s +{ + uint64_t post; + uint64_t post_no_op; + uint64_t post_by_id; + uint64_t post_by_name; + uint64_t post_by_name_and_fetch_id; + uint64_t reg; + uint64_t reg_plain; + uint64_t reg_check; + uint64_t reg_signal; + uint64_t reg_file; + uint64_t reg_port; + uint64_t cancel; + uint64_t suspend; + uint64_t resume; + uint64_t suspend_pid; + uint64_t resume_pid; + uint64_t check; + uint64_t get_state; + uint64_t get_state_by_client; + uint64_t get_state_by_id; + uint64_t get_state_by_client_and_fetch_id; + uint64_t set_state; + uint64_t set_state_by_client; + uint64_t set_state_by_id; + uint64_t set_state_by_client_and_fetch_id; + uint64_t get_owner; + uint64_t set_owner; + uint64_t get_access; + uint64_t set_access; + uint64_t monitor_file; + uint64_t service_timer; + uint64_t service_path; + uint64_t cleanup; + uint64_t regenerate; +} call_statistics; + +extern void log_message(int priority, const char *str, ...); +extern uint32_t daemon_post(const char *name, uint32_t u, uint32_t g); +extern uint32_t daemon_post_nid(uint64_t nid, uint32_t u, uint32_t g); +extern void daemon_post_client(uint64_t cid); +extern void daemon_set_state(const char *name, uint64_t val); +extern void dump_status(uint32_t level); + +#endif /* _NOTIFY_DAEMON_H_ */ diff --git a/libnotify/notifyd/pathwatch.c b/libnotify/notifyd/pathwatch.c new file mode 100644 index 000000000..a6a615756 --- /dev/null +++ b/libnotify/notifyd/pathwatch.c @@ -0,0 +1,953 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * These routines, accessed through path_node_create() and path_node_release(), + * provide an API for monitoring a path in the filesystem. The path may contain + * directories and symbolic links. The path may not even exist! If the path + * does exist, this code will respond to path deletions, component renaming, and + * access control changes that either delete the path or make it inaccessible to a + * target user/group. A notification will be provided if the path comes back into + * existance or again becomes accessible. + * + * path_node_create() returns a path_node_t object, which contains a dispatch_source_t. + * This source behaves very much like a DISPATCH_SOURCE_TYPE_VNODE, except that it also + * triggers on the creation of a path. + * + * Internally, the work of monitoring a path is done by a set of helper vnode_t + * objects. A vnode_t contains a dispatch_source_t (of type DISPATCH_SOURCE_TYPE_VNODE) + * for a particular vnode. When a path_node_t is created, it creates (or shares) + * vnode_t objects for each component of the desired path. For example, a path_node_t + * for "/a/b/c" will create (or share, if some other path_node_t has already created) a + * dispatch_source_t for "/", "/a", "/a/b", and "/a/b/c". If any of these sources is + * notified of a change, the vnode_t will trigger an update for all path_node_t + * objects that contain that path component. + * + * When a path_node_t update is triggered by a vnode_t component, the node re-evaluates + * the target path that it is charged with monitoring. If the path exists and the end-point + * vnode changed, then the update operation will trigger its dispatch_source_t to notify the + * end-user of the change. If an intermediate path component is removed, renamed, or becomes + * blocked by an access-control change, then the end-point dispatch_source_t is triggered to + * indicate that the path has been deleted. However, the path_node_t remains active and + * monitors the path components that still exist. Eventually, if the path is recreated or + * if access controls change so that the path becomes visible to the target user, then the + * end-point dispatch_source_t is triggered with a PATH_NODE_CREATE bit set in its data flags. + * + * path_node_releases() releases a path_node_t object and all of the vnode_t objects + * that were monitoring components of its target path. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pathwatch.h" + +#define forever for(;;) +#define streq(A,B) (strcmp(A,B)==0) +#define DISPATCH_VNODE_ALL 0x7f + +#define PATH_STAT_OK 0 +#define PATH_STAT_FAILED 1 +#define PATH_STAT_ACCESS 2 + +#define VPATH_NODE_TYPE_REG 0 +#define VPATH_NODE_TYPE_LINK 1 +#define VPATH_NODE_TYPE_DELETED 2 + +#define DISPATCH_VNODE_UNAVAIL (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE) + +/* Libinfo global */ +extern uint32_t gL1CacheEnabled; + +/* + * vnode_t represents a vnode. + * + * The dispatch source is of type DISPATCH_SOURCE_TYPE_VNODE for file descriptor fd. + * The handler for the source triggers an update routine for all the path_node_t + * objects in the path_node list. + */ +typedef struct +{ + char *path; + uint32_t type; + int fd; + struct timespec mtime; + struct timespec ctime; + dispatch_source_t src; + uint32_t path_node_count; + path_node_t **path_node; +} vnode_t; + +static struct +{ + dispatch_once_t pathwatch_init; + dispatch_queue_t pathwatch_queue; + uint32_t vnode_count; + vnode_t **vnode; + char *tzdir; + size_t tzdir_len; +} _global = {0}; + +/* forward */ +static void _path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode); + +/* + * stat() or lstat() a path as a particular user/group. + */ +static int +_path_stat(const char *path, int link, uid_t uid, gid_t gid) +{ + struct stat sb; + gid_t orig_gidset[NGROUPS_MAX]; + int ngroups, status, stat_status; + struct passwd *p; + uint32_t orig_cache_enabled; + + /* disable L1 cache to avoid notification deadlock */ + orig_cache_enabled = gL1CacheEnabled; + gL1CacheEnabled = 0; + + /* get my group list */ + memset(orig_gidset, 0, sizeof(orig_gidset)); + ngroups = getgroups(NGROUPS_MAX, orig_gidset); + if (ngroups < 0) + { + return PATH_STAT_FAILED; + } + + /* look up user name */ + p = getpwuid(uid); + if (p == NULL) + { + gL1CacheEnabled = orig_cache_enabled; + return PATH_STAT_FAILED; + } + + /* switch to user's grouplist */ + status = initgroups(p->pw_name, gid); + if (status < 0) + { + gL1CacheEnabled = orig_cache_enabled; + return PATH_STAT_FAILED; + } + + /* reset gL1CacheEnabled */ + gL1CacheEnabled = orig_cache_enabled; + + /* set thread credentials */ + pthread_setugid_np(uid, gid); + + /* stat the file */ + stat_status = -1; + if (link != 0) + { + stat_status = lstat(path, &sb); + } + else + { + stat_status = stat(path, &sb); + } + + /* unset thread credentials */ + pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE); + + /* restore original grouplist for UID 0 */ + status = syscall(SYS_initgroups, ngroups, orig_gidset, 0); + if (status < 0) + { + return PATH_STAT_FAILED; + } + + /* return status */ + if (stat_status == 0) + { + return PATH_STAT_OK; + } + + if (errno == EACCES) + { + return PATH_STAT_ACCESS; + } + + return PATH_STAT_FAILED; +} + +/* + * Check access to a path by a particular user/group. + * Sets ftype output parameter if it is non-NULL. + */ +static int +_path_stat_check_access(const char *path, uid_t uid, gid_t gid, uint32_t *ftype) +{ + struct stat sb; + char buf[MAXPATHLEN + 1]; + int status, t; + + if (path == NULL) return PATH_STAT_FAILED; + + if (ftype != NULL) *ftype = PATH_NODE_TYPE_GHOST; + + /* Paths must be absolute */ + if (path[0] != '/') return PATH_STAT_FAILED; + + /* Root dir is readable */ + if (path[1] == '\0') + { + if (ftype != NULL) *ftype = PATH_NODE_TYPE_DIR; + return PATH_STAT_OK; + } + + memset(&sb, 0, sizeof(struct stat)); + status = lstat(path, &sb); + + if (status != 0) return PATH_STAT_FAILED; + else if ((sb.st_mode & S_IFMT) == S_IFDIR) t = PATH_NODE_TYPE_DIR; + else if ((sb.st_mode & S_IFMT) == S_IFREG) t = PATH_NODE_TYPE_FILE; + else if ((sb.st_mode & S_IFMT) == S_IFLNK) t = PATH_NODE_TYPE_LINK; + else t = PATH_NODE_TYPE_OTHER; + + if (ftype != NULL) *ftype = t; + + if (t == PATH_NODE_TYPE_OTHER) return PATH_STAT_FAILED; + + /* skip access control check if uid is zero */ + if (uid == 0) return 0; + + /* special case: anything in the timezone directory is OK */ + memset(buf, 0, sizeof(buf)); + if (realpath(path, buf) == NULL) return PATH_STAT_FAILED; + if ((_global.tzdir != NULL) && (!strncasecmp(buf, _global.tzdir, _global.tzdir_len))) + { + return PATH_STAT_OK; + } + + /* call _path_stat to check access as the user/group provided */ + if (t == PATH_NODE_TYPE_FILE) + { + status = _path_stat(path, 0, uid, gid); + if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST; + return status; + } + else if (t == PATH_NODE_TYPE_LINK) + { + status = _path_stat(path, 1, uid, gid); + if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST; + return status; + } + else if (t == PATH_NODE_TYPE_DIR) + { + snprintf(buf, MAXPATHLEN, "%s/.", path); + status = _path_stat(buf, 0, uid, gid); + if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST; + return status; + } + + /* we don't ever get here, but... */ + return PATH_STAT_FAILED; +} + +/* + * Uniquely add a pnode to a vnode's list of path nodes. + */ +static void +_vnode_add_pnode(vnode_t *vnode, path_node_t *pnode) +{ + uint32_t i; + + for (i = 0; i < vnode->path_node_count; i++) + { + if (vnode->path_node[i] == pnode) return; + } + + for (i = 0; i < vnode->path_node_count; i++) + { + if (vnode->path_node[i] == NULL) + { + vnode->path_node[i] = pnode; + return; + } + } + + if (vnode->path_node_count == 0) + { + vnode->path_node = (path_node_t **)calloc(1, sizeof(path_node_t *)); + } + else + { + vnode->path_node = (path_node_t **)reallocf(vnode->path_node, (vnode->path_node_count + 1) * sizeof(path_node_t *)); + } + + assert(vnode->path_node != NULL); + + vnode->path_node[vnode->path_node_count++] = pnode; +} + +/* + * Free a vnode_t and cancel/release its dispatch source. + */ +static void +_vnode_free(vnode_t *vnode) +{ + dispatch_source_cancel(vnode->src); + + /* + * Actually free the vnode on the pathwatch queue. This allows any + * enqueued _vnode_event operations to complete before the vnode disappears. + * _vnode_event() quietly returns if the source has been cancelled. + */ + dispatch_async(_global.pathwatch_queue, ^{ + dispatch_release(vnode->src); + free(vnode->path); + free(vnode->path_node); + free(vnode); + }); +} + +/* + * Handler routine for vnode_t objects. + * Invokes the _path_node_update routine for all of the vnode's pnodes. + */ +static void +_vnode_event(vnode_t *vnode) +{ + uint32_t i, flags; + unsigned long ulf; + struct stat sb; + + if (vnode == NULL) return; + if ((vnode->src != NULL) && (dispatch_source_testcancel(vnode->src))) return; + + ulf = dispatch_source_get_data(vnode->src); + flags = ulf; + + memset(&sb, 0, sizeof(struct stat)); + if (fstat(vnode->fd, &sb) == 0) + { + if ((vnode->mtime.tv_sec != sb.st_mtimespec.tv_sec) || (vnode->mtime.tv_nsec != sb.st_mtimespec.tv_nsec)) + { + flags |= PATH_NODE_MTIME; + vnode->mtime = sb.st_mtimespec; + } + + if ((vnode->ctime.tv_sec != sb.st_ctimespec.tv_sec) || (vnode->ctime.tv_nsec != sb.st_ctimespec.tv_nsec)) + { + flags |= PATH_NODE_CTIME; + vnode->ctime = sb.st_ctimespec; + } + } + + /* + * Flag deleted sources. + * We can't delete them here, since _path_node_update may need them. + * However, _path_node_update will release them and they will get cleaned + * up in a _vnode_sweep later on. + */ + if (flags & DISPATCH_VNODE_DELETE) vnode->type = VPATH_NODE_TYPE_DELETED; + + for (i = 0; i < vnode->path_node_count; i++) + { + _path_node_update(vnode->path_node[i], flags, vnode); + } +} + +/* + * Creates a vnode_t object. + */ +static vnode_t * +_vnode_create(const char *path, uint32_t type, path_node_t *pnode) +{ + int fd, flags; + uint32_t i; + vnode_t *vnode; + dispatch_source_t src; + struct stat sb; + + if (path == NULL) path = "/"; + if (path[0] == '\0') path = "/"; + + for (i = 0; i < _global.vnode_count; i++) + { + vnode = _global.vnode[i]; + if (vnode == NULL) continue; + + if ((vnode->type == type) && (streq(path, vnode->path))) + { + _vnode_add_pnode(vnode, pnode); + return vnode; + } + } + + vnode = NULL; + + flags = O_EVTONLY; + if (type == VPATH_NODE_TYPE_LINK) flags |= O_SYMLINK; + + fd = open(path, flags, 0); + if (fd < 0) return NULL; + + src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, (uintptr_t)fd, DISPATCH_VNODE_ALL, _global.pathwatch_queue); + if (src == NULL) + { + close(fd); + return NULL; + } + + vnode = (vnode_t *)calloc(1, sizeof(vnode_t)); + assert(vnode != NULL); + + vnode->type = type; + vnode->path = strdup(path); + assert(vnode->path != NULL); + + vnode->fd = fd; + vnode->src = src; + + memset(&sb, 0, sizeof(struct stat)); + if (fstat(fd, &sb) == 0) + { + vnode->mtime = sb.st_mtimespec; + vnode->ctime = sb.st_ctimespec; + } + + _vnode_add_pnode(vnode, pnode); + + dispatch_source_set_event_handler(src, ^{ _vnode_event(vnode); }); + dispatch_source_set_cancel_handler(src, ^{ close(fd); }); + + if (_global.vnode_count == 0) + { + _global.vnode = (vnode_t **)calloc(1, sizeof(vnode_t *)); + } + else + { + _global.vnode = (vnode_t **)reallocf(_global.vnode, (_global.vnode_count + 1) * sizeof(vnode_t *)); + } + + assert(_global.vnode != NULL); + + _global.vnode[_global.vnode_count++] = vnode; + + dispatch_resume(src); + + return vnode; +} + +static vnode_t * +_vnode_create_real_path(const char *path, uint32_t type, path_node_t *pnode) +{ + char real[MAXPATHLEN + 1]; + + if (path == NULL) return _vnode_create(path, type, pnode); + + if (NULL != realpath(path, real)) return _vnode_create(real, type, pnode); + + return NULL; +} + +/* + * Examines all the vnode_t objects (held in the _global data), + * frees any that have no path nodes. + */ +static void +_vnode_sweep() +{ + uint32_t i, j, new_vnode_count, new_path_node_count; + vnode_t **new_source, *vnode; + path_node_t **new_path_node; + + new_source = NULL; + + for (i = 0; i < _global.vnode_count; i++) + { + vnode = _global.vnode[i]; + if (vnode == NULL) continue; + + new_path_node_count = 0; + new_path_node = NULL; + + for (j = 0; j < vnode->path_node_count; j++) + { + if (vnode->path_node[j] != NULL) new_path_node_count++; + } + + if (new_path_node_count == vnode->path_node_count) + { + /* no change */ + continue; + } + else if (new_path_node_count > 0) + { + new_path_node = (path_node_t **)calloc(new_path_node_count, sizeof(path_node_t *)); + assert(new_path_node != NULL); + + new_path_node_count = 0; + for (j = 0; j < vnode->path_node_count; j++) + { + if (vnode->path_node[j] != NULL) + { + new_path_node[new_path_node_count++] = vnode->path_node[j]; + } + } + } + + free(vnode->path_node); + vnode->path_node = new_path_node; + vnode->path_node_count = new_path_node_count; + } + + new_vnode_count = 0; + for (i = 0; i < _global.vnode_count; i++) + { + vnode = _global.vnode[i]; + if (vnode == NULL) continue; + if (vnode->path_node_count > 0) new_vnode_count++; + } + + if (new_vnode_count == _global.vnode_count) + { + /* no change */ + return; + } + else if (new_vnode_count > 0) + { + new_source = (vnode_t **)calloc(new_vnode_count, sizeof(vnode_t *)); + assert(new_source != NULL); + + new_vnode_count = 0; + for (i = 0; i < _global.vnode_count; i++) + { + vnode = _global.vnode[i]; + if (vnode == NULL) continue; + + if (vnode->path_node_count > 0) + { + new_source[new_vnode_count++] = vnode; + } + else + { + _vnode_free(vnode); + } + } + } + + free(_global.vnode); + _global.vnode = new_source; + _global.vnode_count = new_vnode_count; +} + +/* + * Releases sources that have a particular node on their list. + * This is a deferred release mechanism for vnode_t objects. + * The calling routine must call _vnode_sweep subsequent to + * calling this routine. + * _vnode_sweep will actually free any vnode_t objects + * that have a no path nodes. + */ +static void +_vnode_release_for_node(path_node_t *pnode) +{ + uint32_t i, j; + vnode_t *vnode; + + for (i = 0; i < _global.vnode_count; i++) + { + vnode = _global.vnode[i]; + if (vnode == NULL) continue; + + for (j = 0; j < vnode->path_node_count; j++) + { + if (vnode->path_node[j] == pnode) + { + vnode->path_node[j] = NULL; + break; + } + } + } +} + +/* + * Retain a path_node_t object. + * Dispatched on _global.pathwatch_queue. + */ +static void +_path_node_retain(path_node_t *pnode) +{ + if (pnode == NULL) return; + pnode->refcount++; +} + +/* + * Free a path_node_t object. + * Dispatched on _global.pathwatch_queue. + */ +static void +_path_node_free(path_node_t *pnode) +{ + uint32_t i, n; + + if (pnode == NULL) return; + + /* + * Remove this path node from all vnodes. + */ + _vnode_release_for_node(pnode); + _vnode_sweep(); + + free(pnode->path); + + if (pnode->pname != NULL) + { + n = pnode->pname_count; + pnode->pname_count = 0; + + for (i = 0; i < n; i++) + { + free(pnode->pname[i]); + pnode->pname[i] = NULL; + } + + free(pnode->pname); + } + + free(pnode->contextp); + + dispatch_release(pnode->src); + dispatch_release(pnode->src_queue); + + memset(pnode, 0, sizeof(path_node_t)); + free(pnode); +} + +/* + * Release a path_node_t object. + */ +static void +_path_node_release(path_node_t *pnode) +{ + if (pnode == NULL) return; + + /* + * We need to make sure that the node's event handler isn't currently + * executing before freeing the node. We dispatch on the src_queue, so + * that when the block executes there will be no more events in the queue. + * From there, we dispatch async back to the pathwatch_queue to do the + * data structure cleanup. + */ + dispatch_async(pnode->src_queue, ^{ + dispatch_async(_global.pathwatch_queue, ^{ + if (pnode->refcount > 0) pnode->refcount--; + if (pnode->refcount == 0) _path_node_free(pnode); + }); + }); +} + +/* + * Frees a path_node_t object. + * The work is actually done on the global pathwatch_queue to make this safe. + */ +void +path_node_close(path_node_t *pnode) +{ + if (pnode == NULL) return; + + if (pnode->src != NULL) dispatch_source_cancel(pnode->src); + _path_node_release(pnode); +} + +static void +_pathwatch_init() +{ + char buf[MAXPATHLEN]; + + /* Create serial queue for node creation / deletion operations */ + _global.pathwatch_queue = dispatch_queue_create("pathwatch", NULL); + + _global.tzdir = NULL; + _global.tzdir_len = 0; + + /* Get the real path to TZDIR */ + if (realpath(TZDIR, buf) != NULL) + { + _global.tzdir_len = strlen(buf); + _global.tzdir = strdup(buf); + if (_global.tzdir == NULL) _global.tzdir_len = 0; + } +} + +/* + * _path_node_init is responsible for allocating a path_node_t structure, + * and for creating the pname array and setting the path component. + * The path is a sanatized version of the caller's path with redundant "/" + * characters stripped out. The pname array contains each "/" separated + * component of the path. + * + * For example, _path_node_init("///foo////bar//baz/") creates: + * pnode->path = "/foo/bar/baz" + * pnode->pname_count = 3 + * pnode->pname[0] = "foo" + * pnode->pname[1] = "bar" + * pnode->pname[2] = "baz" + */ +static path_node_t * +_path_node_init(const char *path) +{ + size_t len; + uint32_t i; + path_node_t *pnode; + const char *start, *end; + char *name; + + if (path == NULL) path = "/"; + if (path[0] != '/') return NULL; + + pnode = (path_node_t *)calloc(1, sizeof(path_node_t)); + assert(pnode != NULL); + + pnode->plen = 1; + start = path; + while (*start == '/') start++; + + forever + { + end = strchr(start, '/'); + if (end == NULL) end = strchr(start, '\0'); + + len = end - start; + if (len == 0) break; + + pnode->plen += (len + 1); + + name = NULL; + if (end == NULL) + { + name = strdup(start); + } + else + { + name = malloc(len + 1); + assert(name != NULL); + strncpy(name, start, len); + name[len] = '\0'; + } + + if (pnode->pname_count == 0) + { + pnode->pname = (char **)calloc(1, sizeof(char *)); + } + else + { + pnode->pname = (char **)reallocf(pnode->pname, (pnode->pname_count + 1) * sizeof(char *)); + } + + assert(pnode->pname != NULL); + pnode->pname[pnode->pname_count] = name; + pnode->pname_count++; + + start = end; + if (start != NULL) + { + /* skip '/' chars */ + while (*start == '/') start++; + } + } + + pnode->path = calloc(1, pnode->plen); + assert(pnode->path != NULL); + /* + * Reconstruct the path here to strip out excess "/" chars. + * This ensures that path comparisons in _path_node_update are correct. + */ + for (i = 0; i < pnode->pname_count; i++) + { + strlcat(pnode->path, "/", pnode->plen); + strlcat(pnode->path, pnode->pname[i], pnode->plen); + } + + return pnode; +} + +/* dispatched on _global.pathwatch_queue */ +static void +_path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode) +{ + char *buf, fixed[MAXPATHLEN + 1]; + uint32_t i, old_type; + int status; + unsigned long data; + struct stat sb; + + if (pnode == NULL) return; + if ((pnode->src != NULL) && (dispatch_source_testcancel(pnode->src))) return; + + old_type = pnode->type; + + status = _path_stat_check_access(pnode->path, pnode->uid, pnode->gid, &(pnode->type)); + if (status == PATH_STAT_ACCESS) flags |= DISPATCH_VNODE_REVOKE; + + data = 0; + + if (vnode != NULL) + { + /* update status */ + + if (flags & DISPATCH_VNODE_UNAVAIL) + { + pnode->type = PATH_NODE_TYPE_GHOST; + data |= (flags & DISPATCH_VNODE_UNAVAIL); + data |= DISPATCH_VNODE_DELETE; + } + + if ((vnode->path != NULL) && (pnode->path != NULL) && streq(vnode->path, pnode->path)) + { + /* this is the target VNODE - transfer flags to src data */ + data |= flags; + } + + if (old_type == PATH_NODE_TYPE_GHOST) + { + /* transition from ghost to non-ghost */ + if (pnode->type != PATH_NODE_TYPE_GHOST) + { + data |= PATH_NODE_CREATE; + } + else + { + data = 0; + } + } + else if (pnode->type == PATH_NODE_TYPE_GHOST) + { + /* transition from non-ghost to ghost */ + data |= PATH_NODE_DELETE; + } + + data &= (pnode->flags & PATH_NODE_ALL); + if (data != 0) + { + if ((pnode->flags & PATH_SRC_SUSPENDED) == 0) + { + /* suspend pnode->src, and fire it after PNODE_COALESCE_TIME */ + pnode->flags |= PATH_SRC_SUSPENDED; + dispatch_suspend(pnode->src); + + dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, PNODE_COALESCE_TIME); + _path_node_retain(pnode); + + dispatch_after(delay, _global.pathwatch_queue, ^{ + pnode->flags &= ~PATH_SRC_SUSPENDED; + dispatch_resume(pnode->src); + _path_node_release(pnode); + }); + } + + dispatch_source_merge_data(pnode->src, data); + } + } + + buf = NULL; + if (pnode->plen < MAXPATHLEN) buf = fixed; + else buf = malloc(pnode->plen); + assert(buf != NULL); + + /* "autorelease" current sources (_vnode_sweep() will delete those with zero refcount) */ + _vnode_release_for_node(pnode); + + /* create new sources (may re-use existing sources) */ + _vnode_create(NULL, 0, pnode); + + memset(buf, 0, pnode->plen); + for (i = 0; i < pnode->pname_count; i++) + { + assert((strlen(buf) + 1) <= pnode->plen); + strlcat(buf, "/", pnode->plen); + + assert(pnode->pname[i] != NULL); + assert((strlen(buf) + strlen(pnode->pname[i])) <= pnode->plen); + strlcat(buf, pnode->pname[i], pnode->plen); + + memset(&sb, 0, sizeof(struct stat)); + if (lstat(buf, &sb) < 0) + { + /* the path stops existing here */ + break; + } + + if ((sb.st_mode & S_IFMT) == S_IFLNK) + { + /* open the symlink itself */ + _vnode_create(buf, VPATH_NODE_TYPE_LINK, pnode); + + /* open the symlink target */ + _vnode_create_real_path(buf, 0, pnode); + } + else + { + _vnode_create(buf, 0, pnode); + } + } + + /* sweep source list (deletes those with zero refcount) */ + _vnode_sweep(); + + if (buf != fixed) free(buf); +} + +/* + * Creates a dispatch source that activates when a path changes. + * Internally, creates a data structure (path_node_t) that represents the entire path. + * Also creates dispatch sources (vnode_t) for each path component. These vnodes may + * be shared with other path_node_t structures. + */ +path_node_t * +path_node_create(const char *path, uid_t uid, gid_t gid, uint32_t mask, dispatch_queue_t queue) +{ + path_node_t *pnode; + + dispatch_once(&(_global.pathwatch_init), ^{ _pathwatch_init(); }); + + pnode = _path_node_init(path); + if (pnode == NULL) return NULL; + + pnode->refcount = 1; + pnode->uid = uid; + pnode->gid = gid; + + dispatch_sync(_global.pathwatch_queue, ^{ _path_node_update(pnode, 0, NULL); }); + + dispatch_retain(queue); + + pnode->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, queue); + pnode->src_queue = queue; + pnode->flags = mask & PATH_NODE_ALL; + + return pnode; +} diff --git a/libnotify/notifyd/pathwatch.h b/libnotify/notifyd/pathwatch.h new file mode 100644 index 000000000..ab0eb26cf --- /dev/null +++ b/libnotify/notifyd/pathwatch.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _PATHWATCH_H_ +#define _PATHWATCH_H_ + +#include +/* + * types for virtual path nodes (path_node_t) + */ +#define PATH_NODE_TYPE_GHOST 0 +#define PATH_NODE_TYPE_FILE 1 +#define PATH_NODE_TYPE_LINK 2 +#define PATH_NODE_TYPE_DIR 3 +#define PATH_NODE_TYPE_OTHER 4 + + +enum +{ + PATH_NODE_DELETE = 0x0001, /* node or path deleted */ + PATH_NODE_WRITE = 0x0002, /* node written */ + PATH_NODE_EXTEND = 0x0004, /* node extended */ + PATH_NODE_ATTRIB = 0x0008, /* node attributes changed (mtime or ctime) */ + PATH_NODE_LINK = 0x0010, /* node link count changed */ + PATH_NODE_RENAME = 0x0020, /* node renamed, always accompanied by PATH_NODE_DELETE */ + PATH_NODE_REVOKE = 0x0040, /* access revoked, always accompanied by PATH_NODE_DELETE */ + PATH_NODE_CREATE = 0x0080, /* path created or access re-acquired */ + PATH_NODE_MTIME = 0x0100, /* path mtime changed, always accompanied by PATH_NODE_ATTRIB */ + PATH_NODE_CTIME = 0x0200 /* path ctime changed, always accompanied by PATH_NODE_ATTRIB */ +}; + +/* all bits mask */ +#define PATH_NODE_ALL 0x000003ff +/* src is suspended */ +#define PATH_SRC_SUSPENDED 0x10000000 + +/* Path changes coalesce for 100 milliseconds */ +#define PNODE_COALESCE_TIME 100000000 + +/* + * path_node_t represents a virtual path + */ +typedef struct +{ + char *path; + size_t plen; + uid_t uid; + gid_t gid; + uint32_t pname_count; + char **pname; + uint32_t type; + uint32_t flags; + dispatch_source_t src; + dispatch_queue_t src_queue; + void *contextp; + uint32_t context32; + uint64_t context64; + uint32_t refcount; +} path_node_t; + +path_node_t *path_node_create(const char *path, uid_t uid, gid_t gid, uint32_t mask, dispatch_queue_t queue); +void path_node_close(path_node_t *pnode); + +#endif /* _PATHWATCH_H_ */ diff --git a/libnotify/notifyd/service.c b/libnotify/notifyd/service.c new file mode 100644 index 000000000..1fef06576 --- /dev/null +++ b/libnotify/notifyd/service.c @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include "notify.h" +#include "notifyd.h" +#include "service.h" +#include "pathwatch.h" +#include "timer.h" + +#define NOTIFY_PATH_SERVICE "path:" +#define NOTIFY_PATH_SERVICE_LEN 5 +#define NOTIFY_TIMER_SERVICE "timer:" +#define NOTIFY_TIMER_SERVICE_LEN 6 + +/* Libinfo global */ +extern uint32_t gL1CacheEnabled; + +static uint32_t +service_type(const char *name) +{ + uint32_t len; + + len = SERVICE_PREFIX_LEN; + if (strncmp(name, SERVICE_PREFIX, len)) return SERVICE_TYPE_NONE; + else if (!strncmp(name + len, NOTIFY_PATH_SERVICE, NOTIFY_PATH_SERVICE_LEN)) return SERVICE_TYPE_PATH_PRIVATE; + else if (!strncmp(name + len, NOTIFY_TIMER_SERVICE, NOTIFY_TIMER_SERVICE_LEN)) return SERVICE_TYPE_TIMER_PRIVATE; + + return SERVICE_TYPE_NONE; +} + +/* + * Request notifications for changes on a filesystem path. + * This creates a new pathwatch node and sets it to post notifications for + * the specified name. + * + * If the notify name already has a pathwatch node for this path, this routine + * does nothing and allows the client to piggypack on the existing path watcher. + * + * Note that this routine is only called for path monitoring as directed by + * a "monitor" command in /etc/notify.conf, so only an admin can set up a path + * that gets public notifications. A client being serviced by the server-side + * routines in notify_proc.c will only be able to register for a private + * (per-client) notification for a path. This prevents a client from + * piggybacking on another client's notifications, and thus prevents the client + * from getting notifications for a path to which they don't have access. + */ +int +service_open_path(const char *name, const char *path, uid_t uid, gid_t gid) +{ + name_info_t *n; + svc_info_t *info; + path_node_t *node; + + call_statistics.service_path++; + + if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; + + if (n->private != NULL) + { + /* a notify key may only have one service associated with it */ + info = (svc_info_t *)n->private; + if (info->type != SERVICE_TYPE_PATH_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client must be asking for the same path that is being monitored */ + node = (path_node_t *)info->private; + if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the name is already getting notifications for this path */ + return NOTIFY_STATUS_OK; + } + + node = path_node_create(path, uid, gid, PATH_NODE_ALL, dispatch_get_main_queue()); + if (node == NULL) return NOTIFY_STATUS_FAILED; + + node->contextp = strdup(name); + + info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); + assert(info != NULL); + + info->type = SERVICE_TYPE_PATH_PUBLIC; + info->private = node; + n->private = info; + + dispatch_source_set_event_handler(node->src, ^{ + dispatch_async(global.work_q, ^{ + if (0 == dispatch_source_testcancel(node->src)) + { + daemon_post((const char *)node->contextp, uid, gid); + } + }); + }); + + dispatch_resume(node->src); + + return NOTIFY_STATUS_OK; +} + +/* + * The private (per-client) path watch service. + */ +int +service_open_path_private(const char *name, client_t *c, const char *path, uid_t uid, gid_t gid, uint32_t flags) +{ + name_info_t *n; + svc_info_t *info; + path_node_t *node; + + call_statistics.service_path++; + + if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (c == NULL) return NOTIFY_STATUS_FAILED; + + if (c->private != NULL) + { + /* a client may only have one service */ + info = (svc_info_t *)c->private; + if (info->type != SERVICE_TYPE_PATH_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client must be asking for the same path that is being monitored */ + node = (path_node_t *)info->private; + if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client is already getting notifications for this path */ + return NOTIFY_STATUS_OK; + } + + if (flags == 0) flags = PATH_NODE_ALL; + + node = path_node_create(path, uid, gid, flags, dispatch_get_main_queue()); + if (node == NULL) return NOTIFY_STATUS_FAILED; + + node->context64 = c->client_id; + + info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); + assert(info != NULL); + + info->type = SERVICE_TYPE_PATH_PRIVATE; + info->private = node; + c->private = info; + + dispatch_source_set_event_handler(node->src, ^{ + dispatch_async(global.work_q, ^{ + if (0 == dispatch_source_testcancel(node->src)) + { + daemon_post_client(node->context64); + } + }); + }); + + dispatch_resume(node->src); + + return NOTIFY_STATUS_OK; +} + +/* format: [+]nnnn[s|m|h|d] */ +static int +parse_single_arg(const char *arg, int relative_ok, time_t *t) +{ + const char *p, *q; + time_t now, val; + + if (arg == NULL) return -1; + p = arg; + + now = 0; + + if ((relative_ok != 0) && ((*p == '+') || (*p == '-'))) + { + p++; + now = time(NULL); + } + + if ((*p < '0') || (*p > '9')) return -1; + + q = strchr(p, '.'); + if (q != NULL) q--; + else q = arg + strlen(arg) - 1; + +#ifdef __LP64__ + val = (time_t)atoll(p); +#else + val = (time_t)atoi(p); +#endif + + if ((*q >= '0') && (*q <= '9')) + {} + else if (*q == 's') + {} + else if (*q == 'm') + { + val *= 60; + } + else if (*q == 'h') + { + val *= 3600; + } + else if (*q == 'd') + { + val *= 86400; + } + else + { + return -1; + } + + if (*arg == '-') *t = now - val; + else *t = now + val; + + return 0; +} + +static uint32_t +parse_timer_args(const char *args, time_t *s, time_t *f, time_t *e, int32_t *d) +{ + char *p; + uint32_t t; + + if (args == NULL) return TIME_EVENT_NONE; + + /* first arg is start time */ + if (parse_single_arg(args, 1, s) != 0) return TIME_EVENT_NONE; + t = TIME_EVENT_ONESHOT; + + p = strchr(args, '.'); + if (p != NULL) + { + /* second arg is frequency */ + p++; + if (parse_single_arg(p, 0, f) != 0) return TIME_EVENT_NONE; + t = TIME_EVENT_CLOCK; + + p = strchr(p, '.'); + if (p != NULL) + { + /* third arg is end time */ + p++; + if (parse_single_arg(args, 1, e) != 0) return TIME_EVENT_NONE; + + p = strchr(p, '.'); + if (p != NULL) + { + /* fourth arg is day number */ + p++; + *d = atoi(p); + t = TIME_EVENT_CAL; + } + } + } + + if (f == 0) t = TIME_EVENT_ONESHOT; + + return t; +} + +int +service_open_timer(const char *name, const char *args) +{ + uint32_t t; + time_t s, f, e; + int32_t d; + name_info_t *n; + svc_info_t *info; + timer_t *timer; + + call_statistics.service_timer++; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; + + s = f = e = 0; + d = 0; + + t = parse_timer_args(args, &s, &f, &e, &d); + if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST; + + if (n->private != NULL) + { + /* a notify key may only have one service associated with it */ + info = (svc_info_t *)n->private; + if (info->type != SERVICE_TYPE_TIMER_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client must be asking for the same timer that is active */ + timer = (timer_t *)info->private; + if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the name is already getting notifications for this timer */ + return NOTIFY_STATUS_OK; + } + + switch (t) + { + case TIME_EVENT_ONESHOT: + { + timer = timer_oneshot(s, dispatch_get_main_queue()); + break; + } + case TIME_EVENT_CLOCK: + { + timer = timer_clock(s, f, e, dispatch_get_main_queue()); + break; + } + case TIME_EVENT_CAL: + { + timer = timer_calendar(s, f, d, e, dispatch_get_main_queue()); + break; + } + default: + { + return NOTIFY_STATUS_FAILED; + } + } + + if (timer == NULL) return NOTIFY_STATUS_FAILED; + timer->contextp = strdup(name); + + info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); + assert(info != NULL); + + info->type = SERVICE_TYPE_TIMER_PUBLIC; + info->private = timer; + n->private = info; + + dispatch_source_set_event_handler(timer->src, ^{ + dispatch_async(global.work_q, ^{ + if (0 == dispatch_source_testcancel(timer->src)) + { + daemon_post((const char *)timer->contextp, 0, 0); + } + }); + }); + + dispatch_resume(timer->src); + + return NOTIFY_STATUS_OK; +} + +int +service_open_timer_private(const char *name, client_t *c, const char *args) +{ + uint32_t t; + time_t s, f, e; + int32_t d; + name_info_t *n; + svc_info_t *info; + timer_t *timer; + + call_statistics.service_timer++; + + n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); + if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; + if (c == NULL) return NOTIFY_STATUS_FAILED; + + s = f = e = 0; + d = 0; + + t = parse_timer_args(args, &s, &f, &e, &d); + if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST; + + if (c->private != NULL) + { + /* a client may only have one service */ + info = (svc_info_t *)c->private; + if (info->type != SERVICE_TYPE_TIMER_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client must be asking for the same timer that is active */ + timer = (timer_t *)info->private; + if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST; + + /* the client is already getting notifications for this timer */ + return NOTIFY_STATUS_OK; + } + + switch (t) + { + case TIME_EVENT_ONESHOT: + { + timer = timer_oneshot(s, dispatch_get_main_queue()); + break; + } + case TIME_EVENT_CLOCK: + { + timer = timer_clock(s, f, e, dispatch_get_main_queue()); + break; + } + case TIME_EVENT_CAL: + { + timer = timer_calendar(s, f, d, e, dispatch_get_main_queue()); + break; + } + default: + { + return NOTIFY_STATUS_FAILED; + } + } + + if (timer == NULL) return NOTIFY_STATUS_FAILED; + timer->context64 = c->client_id; + + info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); + assert(info != NULL); + + info->type = SERVICE_TYPE_TIMER_PRIVATE; + info->private = timer; + c->private = info; + + dispatch_source_set_event_handler(timer->src, ^{ + dispatch_async(global.work_q, ^{ + if (0 == dispatch_source_testcancel(timer->src)) + { + daemon_post_client(timer->context64); + } + }); + }); + + dispatch_resume(timer->src); + + return NOTIFY_STATUS_OK; +} + +/* called from server-side routines in notify_proc - services are private to the client */ +int +service_open(const char *name, client_t *client, uint32_t uid, uint32_t gid) +{ + uint32_t t, flags; + char *p, *q; + + t = service_type(name); + + switch (t) + { + case SERVICE_TYPE_NONE: + { + return NOTIFY_STATUS_OK; + } + case SERVICE_TYPE_PATH_PRIVATE: + { + p = strchr(name, ':'); + if (p != NULL) p++; + + flags = 0; + + q = strchr(p, ':'); + if (q != NULL) + { + flags = strtol(p, NULL, 0); + p = q + 1; + } + + return service_open_path_private(name, client, p, uid, gid, flags); + } + case SERVICE_TYPE_TIMER_PRIVATE: + { + p = strchr(name, ':'); + if (p != NULL) p++; + return service_open_timer_private(name, client, p); + } + default: + { + return NOTIFY_STATUS_INVALID_REQUEST; + } + } + + return NOTIFY_STATUS_INVALID_REQUEST; +} + +void +service_close(svc_info_t *info) +{ + if (info == NULL) return; + + switch (info->type) + { + case SERVICE_TYPE_PATH_PUBLIC: + case SERVICE_TYPE_PATH_PRIVATE: + { + path_node_close((path_node_t *)info->private); + break; + } + case SERVICE_TYPE_TIMER_PUBLIC: + case SERVICE_TYPE_TIMER_PRIVATE: + { + timer_close((timer_t *)info->private); + break; + } + default: + { + } + } + + free(info); +} diff --git a/libnotify/notifyd/service.h b/libnotify/notifyd/service.h new file mode 100644 index 000000000..bc27071ab --- /dev/null +++ b/libnotify/notifyd/service.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2003-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NOTIFY_SERVICE_H_ +#define _NOTIFY_SERVICE_H_ + +#define SERVICE_TYPE_NONE 0 +#define SERVICE_TYPE_PATH_PUBLIC 1 +#define SERVICE_TYPE_PATH_PRIVATE 2 +#define SERVICE_TYPE_TIMER_PUBLIC 3 +#define SERVICE_TYPE_TIMER_PRIVATE 4 + +#define SERVICE_PREFIX "com.apple.system.notify.service." +#define SERVICE_PREFIX_LEN 32 + +typedef struct +{ + uint32_t type; + void *private; +} svc_info_t; + +int service_open(const char *name, client_t *client, uint32_t uid, uint32_t gid); +int service_open_path(const char *name, const char *path, uid_t uid, gid_t gid); +int service_open_path_private(const char *name, client_t *client, const char *path, uid_t uid, gid_t gid, uint32_t flags); +int service_open_timer(const char *name, const char *args); +int service_open_timer_private(const char *name, client_t *client, const char *args); +void service_close(svc_info_t *info); + +#endif /* _NOTIFY_SERVICE_H_ */ diff --git a/libnotify/notifyd/table.h b/libnotify/notifyd/table.h new file mode 100644 index 000000000..f7afb37f1 --- /dev/null +++ b/libnotify/notifyd/table.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2003-2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NOTIFY_TABLE_H_ +#define _NOTIFY_TABLE_H_ + +#include + +typedef struct __table_private table_t; +typedef struct __list_private list_t; + +extern table_t *_nc_table_new(uint32_t n); + +extern void _nc_table_insert(table_t *t, const char *key, void *datum); +extern void _nc_table_insert_no_copy(table_t *t, const char *key, void *datum); +extern void _nc_table_insert_n(table_t *t, uint32_t key, void *datum); +extern void _nc_table_insert_64(table_t *t, uint64_t key, void *datum); + +extern void *_nc_table_find(table_t *t, const char *key); +extern void *_nc_table_find_n(table_t *t, uint32_t key); +extern void *_nc_table_find_64(table_t *t, uint64_t key); + +extern void _nc_table_delete(table_t *t, const char *key); +extern void _nc_table_delete_n(table_t *t, uint32_t key); +extern void _nc_table_delete_64(table_t *t, uint64_t key); + +extern void *_nc_table_traverse_start(table_t *tin); +extern void *_nc_table_traverse(table_t *tin, void *ttin); +extern void _nc_table_traverse_end(table_t *tin, void *ttin); + +extern void _nc_table_free(table_t *tin); + +extern list_t *_nc_list_new(void *d); + +extern list_t *_nc_list_retain(list_t *l); +extern list_t *_nc_list_retain_list(list_t *l); + +extern void _nc_list_release(list_t *l); +extern void _nc_list_release_list(list_t *l); + +extern list_t *_nc_list_prev(list_t *l); +extern list_t *_nc_list_next(list_t *l); + +extern void _nc_list_set_next(list_t *l, list_t *n); +extern void _nc_list_set_prev(list_t *l, list_t *p); + +extern list_t *_nc_list_head(list_t *l); +extern list_t *_nc_list_tail(list_t *l); + +extern list_t *_nc_list_prepend(list_t *l, list_t *n); +extern list_t *_nc_list_append(list_t *l, list_t *n); + +extern list_t *_nc_list_concat(list_t *a, list_t *b); + +extern void *_nc_list_data(list_t *l); +extern void _nc_list_set_data(list_t *l, void *d); + +extern list_t *_nc_list_find(list_t *l, void *d); +extern list_t *_nc_list_find_release(list_t *l, void *d); + +extern list_t * _nc_list_reverse(list_t *l); +extern uint32_t _nc_list_count(list_t *l); +extern list_t *_nc_list_extract(list_t *n); +extern list_t *_nc_list_chop(list_t *l); + +#endif /* _NOTIFY_TABLE_H_ */ diff --git a/libnotify/notifyd/timer.c b/libnotify/notifyd/timer.c new file mode 100644 index 000000000..03ae0a331 --- /dev/null +++ b/libnotify/notifyd/timer.c @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include "timer.h" + +#define MINUTE 60 +#define HOUR 3600 +#define DAY 86400 + +static const uint8_t mlen[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +/* + * Timed events + * + * Supported event types: + * + * Oneshot + * Every n seconds/minutes/hours/days/weeks + * Specific day of the month, every n months + * Specific weekday following specific day of the month, every n months + * + */ +static time_t +timer_next(timer_t *t, time_t now) +{ + uint32_t y, m; + int32_t d, x, a, b, dd, wd; + struct tm tmp; + time_t next, tt, tod; + + if (t == NULL) return 0; + + switch (t->type) + { + case TIME_EVENT_ONESHOT: + { + /* + * oneshot time event + */ + if (t->start < now) return 0; + return t->start; + } + case TIME_EVENT_CLOCK: + { + /* + * event recurs every t->freq seconds + */ + + /* t->end is the cut-off. If it's in the past, return 0 */ + if ((t->end != 0) && (t->end < now)) return 0; + + /* If the start time is in the future, that's the next occurrence */ + if (t->start >= now) return t->start; + + /* shouldn't happen, as TIME_EVENT_CLOCK should always recur */ + if (t->freq == 0) return 0; + + x = ((t->freq - 1) + now - t->start) / t->freq; + next = t->start + (x * t->freq); + return next; + } + case TIME_EVENT_CAL: + { + /* + * event recurs every t->freq months + * t->base gives us the starting month and year, and the time of day + * t->day specifies the day of the month (negative means relative to last day of the month) + * t->day is > 100 or < 100, then it means a weekday + * 101 = first monday, 102 = first tuesday, ..., 108 = second monday, and so on + * -101 = last monday, -102 = last tuesday, ..., -108 = second last monday, and so on + */ + + /* t->end is the cut-off. If it's in the past, return 0 */ + if ((t->end != 0) && (t->end < now)) return 0; + + /* If the start time is in the future, that's the next occurrence */ + if (t->start >= now) return t->start; + + next = t->start; + + /* If t->next is set from the last time we ran, and it is in the past, we can start there. */ + if ((t->next > 0) && (t->next < now)) next = t->next; + + while (next < now) + { + /* determine year, month, and time-of-day (clock time) of the next occurance */ + memset(&tmp, 0, sizeof(struct tm)); + localtime_r((const time_t *)&(next), &tmp); + y = tmp.tm_year; + m = tmp.tm_mon; + tod = tmp.tm_sec + (MINUTE * tmp.tm_min) + (HOUR * tmp.tm_hour); + + m += t->freq; + if (m > 11) + { + y += (m / 12); + m %= 12; + } + + /* we now have a year (y), a month (m), and a time of day (tod) */ + + if (t->day > 0) + { + if (t->day < 100) + { + /* easy case: day is the date of the month */ + + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = m; + tmp.tm_mday = t->day; + tmp.tm_isdst = -1; + next = mktime(&tmp) + tod; + continue; + } + else + { + /* t->day is a weekday */ + + wd = t->day - 100; + + /* start by finding out the weekday of the first of the month */ + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = m; + tmp.tm_mday = 1; + tmp.tm_isdst = -1; + tt = mktime(&tmp); + localtime_r((const time_t *)&tt, &tmp); + + if (tmp.tm_wday == 0) tmp.tm_wday = 7; + + x = 0; + if (tmp.tm_wday > (wd % 7)) x = (wd + 7) - tmp.tm_wday; + else x = wd - tmp.tm_wday; + + tmp.tm_mday += x; + tmp.tm_isdst = -1; + next = mktime(&tmp) + tod; + continue; + } + } + + if (t->day > -100) + { + /* nth day from the end of the month */ + if (m == 1) + { + /* determine weekday of last day of February (== March 0) */ + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = 2; + tmp.tm_mday = 0; + tmp.tm_isdst = -1; + tt = mktime(&tmp); + memset(&tmp, 0, sizeof(struct tm)); + localtime_r((const time_t *)&(tt), &tmp); + d = tmp.tm_mday + t->day; + } + else + { + d = mlen[m] + t->day; + } + + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = m; + tmp.tm_mday = d; + tmp.tm_isdst = -1; + next = mktime(&tmp) + tod; + continue; + } + + /* t->day is a weekday relative to the end of the month */ + if (m == 1) + { + /* determine weekday of last day of February (== March 0) */ + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = 2; + tmp.tm_mday = 0; + tmp.tm_isdst = -1; + tt = mktime(&tmp); + memset(&tmp, 0, sizeof(struct tm)); + localtime_r((const time_t *)&(tt), &tmp); + d = tmp.tm_mday; + } + else + { + d = mlen[m]; + } + + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = y; + tmp.tm_mon = m; + tmp.tm_mday = d; + tmp.tm_isdst = -1; + + dd = -1 * (t->day + 100); + a = dd % 7; + b = (dd + 6) / 7; + if (a <= tmp.tm_wday) b--; + tmp.tm_mday = ((a - tmp.tm_wday) + d) - (b * 7); + next = mktime(&tmp) + tod; + } + + t->next = next; + return next; + } + default: + { + return 0; + } + } + + return 0; +} + +/* + * This does the actual free. + * It is dispatched on the timer's dispatch source queue to make it safe. + */ +static void +timer_free(timer_t *t) +{ + if (t == NULL) return; + if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler); + if (t->contextp != NULL) free(t->contextp); + + dispatch_release(t->t_src); + dispatch_release(t->t_queue); + + memset(t, 0, sizeof(timer_t)); + free(t); +} + +void +timer_close(timer_t *t) +{ + if (t == NULL) return; + + if (t->t_src != NULL) dispatch_source_cancel(t->t_src); + + /* + * We need to make sure that the source's event handler isn't currently running + * before we free the timer. We let the source's queue do the actual free. + */ + dispatch_async(t->t_queue, ^{ timer_free(t); }); +} + +timer_t * +timer_oneshot(time_t when, dispatch_queue_t queue) +{ + timer_t *t; + time_t now; + dispatch_time_t trigger; + + /* refuse a trigger time in the past */ + now = time(0); + if (when <= now) return NULL; + + t = calloc(1, sizeof(timer_t)); + if (t == NULL) return NULL; + + dispatch_retain(queue); + + t->type = TIME_EVENT_ONESHOT; + t->start = when; + t->t_queue = queue; + + t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue); + + trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC); + dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0); + + dispatch_source_set_event_handler(t->t_src, ^{ + dispatch_source_merge_data(t->src, 1); + dispatch_source_cancel(t->t_src); + if (t->deactivation_handler != NULL) + { + dispatch_async(t->t_queue, ^{ t->deactivation_handler(); }); + } + }); + + dispatch_resume(t->t_src); + return t; +} + +void +timer_set_deactivation_handler(timer_t *t, void(^handler)()) +{ + if (t == NULL) return; + + if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler); + t->deactivation_handler = Block_copy(handler); +} + +timer_t * +timer_clock(time_t first, time_t freq_sec, time_t end, dispatch_queue_t queue) +{ + timer_t *t; + time_t now; + dispatch_time_t trigger; + int64_t x; + + if (freq_sec == 0) return timer_oneshot(first, queue); + + now = time(0); + + t = calloc(1, sizeof(timer_t)); + if (t == NULL) return NULL; + + t->type = TIME_EVENT_CLOCK; + + if (first < now) + { + x = ((freq_sec - 1) + now - first) / freq_sec; + t->start = first + (x * freq_sec); + } + else + { + t->start = first; + } + + t->end = end; + t->freq = freq_sec; + t->t_queue = queue; + + t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue); + + trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC); + dispatch_source_set_timer(t->t_src, trigger, freq_sec * NSEC_PER_SEC, 0); + + dispatch_source_set_event_handler(t->t_src, ^{ + unsigned long n = dispatch_source_get_data(t->t_src); + dispatch_source_merge_data(t->src, n); + + /* deactivate if this is the last time we want to trigger the client source */ + if ((t->end > 0) && (t->end < (time(0) + freq_sec))) + { + dispatch_source_cancel(t->t_src); + if (t->deactivation_handler != NULL) + { + dispatch_async(t->t_queue, ^{ t->deactivation_handler(); }); + } + } + }); + + dispatch_resume(t->t_src); + + return t; +} + +timer_t * +timer_calendar(time_t first, time_t freq_mth, time_t end, int day, dispatch_queue_t queue) +{ + timer_t *t; + time_t next, now; + dispatch_time_t trigger; + + if (freq_mth == 0) return timer_oneshot(first, queue); + + now = time(0); + + t = calloc(1, sizeof(timer_t)); + if (t == NULL) return NULL; + + t->type = TIME_EVENT_CAL; + t->start = first; + t->day = day; + t->end = end; + t->freq = freq_mth; + t->t_queue = queue; + + t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue); + + next = timer_next(t, now); + trigger = dispatch_walltime(NULL, (next - now) * NSEC_PER_SEC); + dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0); + + dispatch_source_set_event_handler(t->t_src, ^{ + unsigned long n = dispatch_source_get_data(t->t_src); + dispatch_source_merge_data(t->src, n); + + time_t now = time(0); + time_t x = timer_next(t, now); + + /* deactivate when there is no next time */ + if (x == 0) + { + dispatch_source_cancel(t->t_src); + if (t->deactivation_handler != NULL) + { + dispatch_async(t->t_queue, ^{ t->deactivation_handler(); }); + } + } + else + { + dispatch_source_set_timer(t->t_src, dispatch_walltime(NULL, (x - now) * NSEC_PER_SEC), NSEC_PER_SEC, 0); + } + }); + + dispatch_resume(t->t_src); + + return t; +} + +timer_t * +timer_calendar_long(uint32_t start_year, uint32_t start_month, uint32_t start_day, uint32_t start_hour, uint32_t start_min, uint32_t start_sec, time_t freq, int day, uint32_t end_year, uint32_t end_month, uint32_t end_day, uint32_t end_hour, uint32_t end_min, uint32_t end_sec, dispatch_queue_t queue) +{ + struct tm tmp; + time_t first, last; + + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = start_year - 1900; + tmp.tm_mon = start_month; + tmp.tm_mday = start_day; + tmp.tm_isdst = -1; + tmp.tm_hour = start_hour; + tmp.tm_min = start_min; + tmp.tm_sec = start_sec; + + first = mktime(&tmp); + + if (freq == 0) return timer_oneshot(first, queue); + + memset(&tmp, 0, sizeof(struct tm)); + tmp.tm_year = end_year; + tmp.tm_mon = end_month; + tmp.tm_mday = end_day; + tmp.tm_isdst = -1; + tmp.tm_hour = end_hour; + tmp.tm_min = end_min; + tmp.tm_sec = end_sec; + + last = mktime(&tmp); + + return timer_calendar(first, freq, day, last, queue); +} diff --git a/libnotify/notifyd/timer.h b/libnotify/notifyd/timer.h new file mode 100644 index 000000000..9979656d2 --- /dev/null +++ b/libnotify/notifyd/timer.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2009-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include + +#define TIME_EVENT_NONE 0 +#define TIME_EVENT_ONESHOT 1 +#define TIME_EVENT_CLOCK 2 +#define TIME_EVENT_CAL 3 + +/* + * Timer Event + */ +typedef struct +{ + uint32_t type; + int64_t start; + int64_t end; + uint32_t freq; + int32_t day; + int64_t next; + void (^deactivation_handler)(); + dispatch_source_t src; + dispatch_source_t t_src; + dispatch_queue_t t_queue; + void *contextp; + uint32_t context32; + uint64_t context64; +} timer_t; + +timer_t *timer_oneshot(time_t when, dispatch_queue_t queue); +timer_t *timer_clock(time_t first, time_t freq_sec, time_t end, dispatch_queue_t queue); +timer_t *timer_calendar(time_t first, time_t freq_mth, time_t end, int day, dispatch_queue_t queue); +timer_t *timer_calendar_long(uint32_t yf, uint32_t mf, uint32_t df, uint32_t hf, uint32_t nf, uint32_t sf, time_t fm, int d, uint32_t ye, uint32_t me, uint32_t de, uint32_t he, uint32_t ne, uint32_t se, dispatch_queue_t queue); + +void timer_set_deactivation_handler(timer_t *t, void(^handler)()); +void timer_close(timer_t *t); diff --git a/libnotify/notifyd/xcodescripts/mk_notify_conf.sh b/libnotify/notifyd/xcodescripts/mk_notify_conf.sh new file mode 100755 index 000000000..f03a3a0ac --- /dev/null +++ b/libnotify/notifyd/xcodescripts/mk_notify_conf.sh @@ -0,0 +1,6 @@ +set -e -x +ETCDIR="$DSTROOT$INSTALL_PATH_PREFIX"/private/etc +install -d -o root -g wheel -m 0755 "$ETCDIR" +install -c -o root -g wheel -m 0644 \ + "$SRCROOT"/notifyd/"$NOTIFY_CONFIG" \ + "$ETCDIR"/notify.conf diff --git a/libnotify/notifyutil/notifyutil.1 b/libnotify/notifyutil/notifyutil.1 new file mode 100644 index 000000000..01c30d375 --- /dev/null +++ b/libnotify/notifyutil/notifyutil.1 @@ -0,0 +1,220 @@ +.\" Copyright (c) 2006-2011 Apple Inc. All rights reserved. +.\" +.\" @APPLE_LICENSE_HEADER_START@ +.\" +.\" This file contains Original Code and/or Modifications of Original Code +.\" as defined in and that are subject to the Apple Public Source License +.\" Version 2.0 (the 'License'). You may not use this file except in +.\" compliance with the License. Please obtain a copy of the License at +.\" http://www.opensource.apple.com/apsl/ and read it before using this +.\" file. +.\" +.\" The Original Code and all software distributed under the License are +.\" distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +.\" EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +.\" INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +.\" FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +.\" Please see the License for the specific language governing rights and +.\" limitations under the License. +.\" +.\" @APPLE_LICENSE_HEADER_END@ +.\" +.\" +.Dd November 4, 2011 +.Dt notifyutil 1 +.Os "Mac OS X" +.Sh NAME +.Nm notifyutil +.Nd notification command line utility +.Sh SYNOPSIS +.Nm +.Op Fl q +.Op Fl v +.Op Fl z Ar msec +.Op Fl M +.Op Fl R +.Op command Li ... +.Pp +.Sh DESCRIPTION +.Nm +is a command-line utility for interacting with the +.Xr notify 3 +notification system and the +.Xr notifyd 8 +server. +It may be used to post notifications, detect and report notifications, +and to examine and set the state values associated with notification keys. +.Pp +If +.Nm +is used to monitor one or more notification keys, +it prints the notification key when the corresponding notification is received. +The +.Fl v +(verbose) +and +.Fl q +(quiet) flags, if specified, modify the output behavior. +.Pp +The +.Fl v +flag causes +.Nm +to print a time stamp, the notification key, the current state value for that key, +and the type of the notification (port, file, etc). +The +.Fl q +flag supresses any output except for state values fetched following a +.Fl g +command. +.Pp +Commands listed in the table below are processed in left to right order from the command line. +.Pp +.Bl -tag -width "-signal [#]" -compact -offset indent +.It Fl p Ar key +Post a notification for +.Ar key . +.It Fl w Ar key +Register for +.Ar key +and wait forever for notifications. +.It Fl Ar # Ar key +Register for +.Ar key +and wait for +.Ar # +(an integer) notifications. +.It "" +.Li E.g. +.Fl 1 Ar key +waits for a single notification. +.It Fl g Ar key +Get state value for +.Ar key . +.It Fl s Ar key Ar val +Set state value for +.Ar key . +.It Fl port +Use mach port notifications for subsequent +.Fl w +or +.Fl Ar # +registrations. +.It "" +This is the default registration type. +.It Fl file +Use file descriptor notifications for subsequent registrations. +.It Fl check +Use shared memory notifications for subsequent registrations. +.It Fl signal Op Ar # +Use signal notifications for subsequent registrations. +.It "" +Signal 1 (HUP) is the default, but an alternate signal may be specified. +.It Fl dispatch +Use dispatch for subsequent registrations. +.El +.Pp +When invoked with any combination of +.Fl w +and +.Fl Ar # +actions, +.Nm +registers for notification for the specified key(s). +If any key is given with a +.Fl w +action, +.Nm +runs until interrupted with Control-C. +If all registrations are invoked with +.Fl Ar # , +the program continues to run until the corresponding number of notifications for each key have been received. +.Pp +By default, +.Nm +uses mach port registration (using +.Fn notify_register_mach_port ) +for keys given with a +.Fl w +or +.Fl Ar # +flag. +The +.Fl file +command causes +.Nm +to use +.Fn notify_register_file_descriptor +for any subsequent +.Fl w +or +.Fl Ar # +registrations. +Similarly, +.Fl check +causes +.Nm +to use +.Fn notify_register_check +for subsequent registrations, +.Fl signal +switches to +.Fn notify_register_signal , +and +.Fl dispatch +causes it to use +.Fn notify_register_dispatch +for subsequent registrations. +.Pp +If any registrations are made following the use of the +.Fl check +command, +.Nm +will start a timer and check for shared memory notifications every 100 milliseconds. +An alternate timer value may be set following the +.Fl z +flag. +.Pp +The +.Fl M +flag causes +.Nm +to use multiplex all notifications over a single mach connection with +.Nm notifyd . +Notifications (except shared memory notifications) +are received and redistributed by a dispatch handler. +.Pp +The +.Fl R +flag causes +.Nm notifyutil +to regenerate all its registrations in the unlikely event that +.Nm notifyd +restarts. +.Pp +Note that a notification key and its associated state variable only exist +when there are one or more current registrations for that key. +Setting the state for a key that has no registrations has no effect. +Thus the command +.Pp +.Dl notifyutil -s foo.bar 123 -g foo.bar +.Pp +will print +.Pp +.Dl foo.bar 0 +.Pp +unless foo.bar is registered by some other process. +However, the command +.Pp +.Dl notifyutil -w foo.bar -s foo.bar 123 -g foo.bar +.Pp +prints +.Pp +.Dl foo.bar 123 +.Pp +since the +.Dq -w foo.bar +registration ensures the key and its state variable exist before the value is set, +and continue to exist when the value is fetched. +.Sh SEE ALSO +notify(3), notifyd(8) diff --git a/libnotify/notifyutil/notifyutil.c b/libnotify/notifyutil/notifyutil.c new file mode 100644 index 000000000..4aabd85b8 --- /dev/null +++ b/libnotify/notifyutil/notifyutil.c @@ -0,0 +1,751 @@ +/* + * Copyright (c) 2006-2010 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define forever for(;;) +#define IndexNull ((uint32_t)-1) + +#define PRINT_QUIET 0x00000000 +#define PRINT_KEY 0x00000001 +#define PRINT_STATE 0x00000002 +#define PRINT_TIME 0x00000004 +#define PRINT_TYPE 0x00000008 +#define PRINT_VERBOSE 0xffffffff + +#ifndef USEC_PER_SEC +#define USEC_PER_SEC 1000000 +#endif + +#define TYPE_NULL 0 +#define TYPE_PORT 1 +#define TYPE_FILE 2 +#define TYPE_DISPATCH 3 +#define TYPE_SIGNAL 4 +#define TYPE_CHECK 5 +#define TYPE_PLAIN 6 + +static const char *typename[] = +{ + "unknown", + "port", + "file", + "dispatch", + "signal", + "check", + "plain" +}; + +extern uint32_t notify_register_plain(const char *name, int *out_token); + +typedef struct +{ + uint32_t token; + uint32_t type; + uint32_t signum; + uint32_t count; + char *name; +} reg_entry_t; + +static reg_entry_t *reg; +static uint32_t reg_count = 0; + +static int printopt; +static int port_flag; +static int file_flag; +static int watch_file; +static mach_port_t watch_port; +dispatch_source_t timer_src; +dispatch_source_t port_src; +dispatch_source_t file_src; +dispatch_source_t sig_src[__DARWIN_NSIG]; +dispatch_queue_t watch_queue; + +static void +usage(const char *name) +{ + fprintf(stderr, "usage: %s [-q] [-v] [-z msec] [-M] [-R] [command ...]\n", name); + fprintf(stderr, " -q quiet mode\n"); + fprintf(stderr, " -v verbose - prints time, key, state value, and type\n"); + fprintf(stderr, " -z msec pause msec milliseconds after posting [default 100]\n"); + fprintf(stderr, " -M multiplex notifications from notifyd over a single mach port\n"); + fprintf(stderr, " -R regenerate registrations if notifyd restarts\n"); + fprintf(stderr, "commands:\n"); + fprintf(stderr, " -port switch to mach port for subsequent registrations [default]\n"); + fprintf(stderr, " -file switch to file descriptor for subsequent registrations\n"); + fprintf(stderr, " -check switch to shared memory for subsequent registrations\n"); + fprintf(stderr, " -signal [#] switch to signal [#] for subsequent registrations\n"); + fprintf(stderr, " initial default for signal is 1 (SIGHUP)\n"); + fprintf(stderr, " -dispatch switch to dispatch for subsequent registrations\n"); + fprintf(stderr, " -p key post a notifcation for key\n"); + fprintf(stderr, " -w key register for key and report notifications\n"); + fprintf(stderr, " -# key (# is an integer value, eg \"-1\") register for key and report # notifications\n"); + fprintf(stderr, " -g key get state value for key\n"); + fprintf(stderr, " -s key val set state value for key\n"); +} + +static const char * +notify_status_strerror(int status) +{ + switch (status) + { + case NOTIFY_STATUS_OK: return("OK"); + case NOTIFY_STATUS_INVALID_NAME: return "Invalid Name"; + case NOTIFY_STATUS_INVALID_TOKEN: return "Invalid Token"; + case NOTIFY_STATUS_INVALID_PORT: return "Invalid Port"; + case NOTIFY_STATUS_INVALID_FILE: return "Invalid File"; + case NOTIFY_STATUS_INVALID_SIGNAL: return "Invalid Signal"; + case NOTIFY_STATUS_INVALID_REQUEST: return "Invalid Request"; + case NOTIFY_STATUS_NOT_AUTHORIZED: return "Not Authorized"; + case NOTIFY_STATUS_FAILED: + default: return "Failed"; + } +} + +static void +reg_add(uint32_t tid, uint32_t type, uint32_t signum, uint32_t count, const char *name) +{ + if (name == NULL) return; + + reg = (reg_entry_t *)reallocf(reg, (reg_count + 1) * sizeof(reg_entry_t)); + if (reg == NULL) + { + fprintf(stderr, "Can't allocate memory!\n"); + reg_count = 0; + return; + } + + reg[reg_count].token = tid; + reg[reg_count].type = type; + reg[reg_count].signum = signum; + reg[reg_count].count = count; + reg[reg_count].name = strdup(name); + if (reg[reg_count].name == NULL) + { + fprintf(stderr, "Can't allocate memory!\n"); + reg = NULL; + reg_count = 0; + return; + } + + reg_count++; +} + +static void +reg_delete(uint32_t index) +{ + uint32_t i; + + if (index == IndexNull) return; + if (index >= reg_count) return; + + free(reg[index].name); + + for (i = index + 1; i < reg_count; i++) reg[i - 1] = reg[i]; + reg_count--; + + if (reg_count == 0) + { + free(reg); + reg = NULL; + } + else + { + reg = (reg_entry_t *)reallocf(reg, reg_count * sizeof(reg_entry_t)); + if (reg == NULL) + { + fprintf(stderr, "Can't allocate memory!\n"); + reg_count = 0; + } + } +} + +static uint32_t +reg_find_name(const char *name) +{ + uint32_t i; + + for (i = 0; i < reg_count; i++) if (!strcmp(reg[i].name, name)) return i; + return IndexNull; +} + +static uint32_t +reg_find_token(uint32_t tid) +{ + uint32_t i; + + for (i = 0; i < reg_count; i++) if (tid == reg[i].token) return i; + return IndexNull; +} + +static void +process_event(int tid) +{ + struct timeval now; + char tstr[32]; + int status, index, needspace; + uint64_t state; + + gettimeofday(&now, NULL); + + index = reg_find_token(tid); + if (index == IndexNull) return; + + needspace = 0; + + if (printopt & PRINT_TIME) + { + snprintf(tstr, sizeof(tstr), "%llu", now.tv_usec + USEC_PER_SEC + 500); + tstr[4] = '\0'; + printf("%d.%s", (int)now.tv_sec, tstr+1); + needspace = 1; + } + + if (printopt & PRINT_KEY) + { + if (needspace) printf(" "); + printf("%s", reg[index].name); + needspace = 1; + } + + if (printopt & PRINT_STATE) + { + if (needspace) printf(" "); + state = 0; + status = notify_get_state(tid, &state); + if (status == NOTIFY_STATUS_OK) printf("%llu",(unsigned long long)state); + else printf(": %s", notify_status_strerror(status)); + needspace = 1; + } + + if (printopt & PRINT_TYPE) + { + if (needspace) printf(" "); + printf("%s", typename[reg[index].type]); + needspace = 1; + } + + if (printopt != PRINT_QUIET) printf("\n"); + + if ((reg[index].count != IndexNull) && (reg[index].count != 0)) reg[index].count--; + if (reg[index].count == 0) + { + status = notify_cancel(tid); + reg_delete(index); + } +} + +static void +file_handler(int fd) +{ + ssize_t i; + int tid; + + if (fd < 0) return; + + i = read(fd, &tid, sizeof(tid)); + if (i < 0) return; + + tid = ntohl(tid); + process_event(tid); + + if (reg_count == 0) exit(0); +} + +static void +port_handler(mach_port_t port) +{ + int tid; + mach_msg_empty_rcv_t msg; + kern_return_t status; + + if (port == MACH_PORT_NULL) return; + + memset(&msg, 0, sizeof(msg)); + status = mach_msg(&msg.header, MACH_RCV_MSG, 0, sizeof(msg), port, 0, MACH_PORT_NULL); + if (status != KERN_SUCCESS) return; + + tid = msg.header.msgh_id; + process_event(tid); + + if (reg_count == 0) exit(0); +} + +static void +signal_handler(uint32_t sig) +{ + uint32_t i, status; + int check; + + if (printopt != PRINT_QUIET) printf("SIGNAL %u\n", sig); + for (i = 0; i < reg_count; i++) + { + if ((reg[i].type == TYPE_SIGNAL) && (reg[i].signum == sig)) + { + check = 0; + status = notify_check(reg[i].token, &check); + if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token); + } + } + + if (reg_count == 0) exit(0); +} + +static void +dispatch_handler(const char *name) +{ + uint32_t index = reg_find_name(name); + if (index == IndexNull) return; + + process_event(reg[index].token); +} + +static void +timer_handler(void) +{ + uint32_t i, status; + int check; + + for (i = 0; i < reg_count; i++) + { + if ((reg[i].type == TYPE_CHECK) || (reg[i].type == TYPE_PLAIN)) + { + check = 0; + status = notify_check(reg[i].token, &check); + if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token); + } + } + + if (reg_count == 0) exit(0); +} + +static uint32_t +do_register(const char *name, uint32_t type, uint32_t signum, uint32_t count) +{ + int tid, check; + uint32_t status; + + switch (type) + { + case TYPE_PORT: + { + status = notify_register_mach_port(name, &watch_port, port_flag, &tid); + if (status != NOTIFY_STATUS_OK) return status; + + port_flag = NOTIFY_REUSE; + if (port_src == NULL) + { + port_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, watch_port, 0, watch_queue); + dispatch_source_set_event_handler(port_src, ^{ + port_handler(watch_port); + }); + dispatch_resume(port_src); + } + + break; + } + + case TYPE_FILE: + { + status = notify_register_file_descriptor(name, &watch_file, file_flag, &tid); + if (status != NOTIFY_STATUS_OK) return status; + + file_flag = NOTIFY_REUSE; + if (file_src == NULL) + { + file_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)watch_file, 0, watch_queue); + dispatch_source_set_event_handler(file_src, ^{ + file_handler(watch_file); + }); + dispatch_resume(file_src); + } + + break; + } + + case TYPE_SIGNAL: + { + signal(signum, SIG_IGN); + + status = notify_register_signal(name, signum, &tid); + if (status != NOTIFY_STATUS_OK) return status; + + status = notify_check(tid, &check); + + if (sig_src[signum] == NULL) + { + sig_src[signum] = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)signum, 0, watch_queue); + dispatch_source_set_event_handler(sig_src[signum], ^{ + signal_handler(signum); + }); + dispatch_resume(sig_src[signum]); + } + + break; + } + + case TYPE_DISPATCH: + { + status = notify_register_dispatch(name, &tid, watch_queue, ^(int x){ dispatch_handler(name); }); + if (status != NOTIFY_STATUS_OK) return status; + break; + } + + case TYPE_CHECK: + { + status = notify_register_check(name, &tid); + if (status != NOTIFY_STATUS_OK) return status; + + status = notify_check(tid, &check); + + if (timer_src == NULL) + { + timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue); + dispatch_source_set_event_handler(timer_src, ^{ + timer_handler(); + }); + dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0); + dispatch_resume(timer_src); + } + + break; + } + + case TYPE_PLAIN: + { + status = notify_register_plain(name, &tid); + if (status != NOTIFY_STATUS_OK) return status; + + status = notify_check(tid, &check); + + if (timer_src == NULL) + { + timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue); + dispatch_source_set_event_handler(timer_src, ^{ + timer_handler(); + }); + dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0); + dispatch_resume(timer_src); + } + + break; + } + + default: return NOTIFY_STATUS_FAILED; + } + + reg_add(tid, type, signum, count, name); + return NOTIFY_STATUS_OK; +} + +int +main(int argc, const char *argv[]) +{ + const char *name; + uint32_t i, n, index, signum, ntype, status, opts, nap; + int tid; + uint64_t state; + + for (i = 0; i < __DARWIN_NSIG; i++) sig_src[i] = NULL; + + ntype = TYPE_PORT; + signum = 1; + watch_file = -1; + watch_port = MACH_PORT_NULL; + printopt = PRINT_KEY; + opts = 0; + nap = 100000; + + watch_queue = dispatch_queue_create("Watch Q", NULL); + + name = strrchr(argv[0], '/'); + if (name == NULL) name = argv[0]; + else name++; + + for (i = 1; i < argc; i++) + { + if ((!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "-h"))) + { + usage(name); + exit(0); + } + else if (!strcmp(argv[i], "-q")) + { + printopt = PRINT_QUIET; + } + else if (!strcmp(argv[i], "-v")) + { + printopt |= PRINT_VERBOSE; + } + else if (!strcmp(argv[i], "-M")) + { + opts |= NOTIFY_OPT_DEMUX; + } + else if (!strcmp(argv[i], "-R")) + { + opts |= NOTIFY_OPT_REGEN; + } + else if (!strcmp(argv[i], "-z")) + { + if ((i + 1) >= argc) + { + fprintf(stderr, "timer value must be supplied following -z\n"); + usage(name); + exit(1); + } + + i++; + + if ((argv[i][0] < '0') || (argv[i][1] > '9')) + { + fprintf(stderr, "-z %s is invalid\n", argv[i]); + fprintf(stderr, "timer value must be an integer\n"); + usage(name); + exit(1); + } + nap = 1000 * atoi(argv[i]); + } + else if (!strcmp(argv[i], "-port")) + {} + else if (!strcmp(argv[i], "-file")) + {} + else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal"))) + { + if ((i + 1) >= argc) continue; + if (argv[i + 1][0] == '-') continue; + + i++; + + if ((argv[i][0] < '0') || (argv[i][1] > '9')) + { + fprintf(stderr, "-signal %s is invalid\n", argv[i]); + fprintf(stderr, "signals must be specified as integer values\n"); + usage(name); + exit(1); + } + + } + else if (!strcmp(argv[i], "-dispatch")) + {} + else if (!strcmp(argv[i], "-check")) + {} + else if (!strcmp(argv[i], "-plain")) + {} + else if (!strcmp(argv[i], "-p")) + { + if ((i + 1) >= argc) + { + fprintf(stderr, "name required following -p\n"); + usage(name); + exit(1); + } + + i++; + } + else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9')))) + { + if ((i + 1) >= argc) + { + fprintf(stderr, "name required following %s\n", argv[i]); + usage(name); + exit(1); + } + + i++; + } + else if (!strcmp(argv[i], "-g")) + { + if ((i + 1) >= argc) + { + fprintf(stderr, "name required following -g\n"); + usage(name); + exit(1); + } + + i++; + } + else if (!strcmp(argv[i], "-s")) + { + if ((i + 1) >= argc) + { + fprintf(stderr, "name required following -s\n"); + usage(name); + exit(1); + } + + i++; + + if ((i + 1) >= argc) + { + fprintf(stderr, "value required following -s name\n"); + usage(name); + exit(1); + } + + i++; + state = atoll(argv[i]); + if ((state == 0) && (strcmp(argv[i], "0"))) + { + fprintf(stderr, "value following -s name must be a 64-bit integer\n"); + } + } + else + { + fprintf(stderr, "unrecognized option: %s\n", argv[i]); + usage(name); + exit(1); + } + } + + if (opts != 0) notify_set_options(opts); + + for (i = 1; i < argc; i++) + { + if (!strcmp(argv[i], "-port")) + { + ntype = TYPE_PORT; + } + else if (!strcmp(argv[i], "-file")) + { + ntype = TYPE_FILE; + } + else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal"))) + { + + ntype = TYPE_SIGNAL; + if (((i + 1) < argc) && (argv[i + 1][0] != '-')) + { + i++; + signum = atoi(argv[i]); + } + } + else if (!strcmp(argv[i], "-dispatch")) + { + ntype = TYPE_DISPATCH; + } + else if (!strcmp(argv[i], "-check")) + { + ntype = TYPE_CHECK; + } + else if (!strcmp(argv[i], "-plain")) + { + ntype = TYPE_PLAIN; + } + else if (!strcmp(argv[i], "-p")) + { + if ((i + 1) >= argc) + { + usage(name); + exit(1); + } + + i++; + + status = notify_post(argv[i]); + if (status != NOTIFY_STATUS_OK) printf("%s: %s\n", argv[i], notify_status_strerror(status)); + else if (nap > 0) usleep(nap); + } + else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9')))) + { + if ((i + 1) >= argc) + { + usage(name); + exit(1); + } + + n = IndexNull; + if (argv[i][1] != 'w') n = atoi(argv[i] + 1); + + i++; + tid = IndexNull; + + index = reg_find_name(argv[i]); + if (index != IndexNull) + { + fprintf(stderr, "Already watching for %s\n", argv[i]); + continue; + } + + status = do_register(argv[i], ntype, signum, n); + if (status != NOTIFY_STATUS_OK) printf("%s: %s\n", argv[i], notify_status_strerror(status)); + } + else if (!strcmp(argv[i], "-g")) + { + if ((i + 1) >= argc) + { + usage(name); + exit(1); + } + + i++; + state = 0; + tid = IndexNull; + + status = notify_register_plain(argv[i], &tid); + if (status == NOTIFY_STATUS_OK) + { + status = notify_get_state(tid, &state); + notify_cancel(tid); + } + + if (status == NOTIFY_STATUS_OK) printf("%s %llu\n", argv[i], (unsigned long long)state); + else printf("%s: %s\n", argv[i], notify_status_strerror(status)); + } + else if (!strcmp(argv[i], "-s")) + { + if ((i + 2) >= argc) + { + usage(name); + exit(1); + } + + i++; + tid = IndexNull; + status = notify_register_plain(argv[i], &tid); + if (status == NOTIFY_STATUS_OK) + { + state = atoll(argv[i + 1]); + status = notify_set_state(tid, state); + notify_cancel(tid); + } + + if (status != NOTIFY_STATUS_OK) printf("%s: %s\n", argv[i], notify_status_strerror(status)); + i++; + } + } + + if (reg_count == 0) exit(0); + + dispatch_main(); +} diff --git a/libnotify/table.c b/libnotify/table.c new file mode 100644 index 000000000..97e36c9b0 --- /dev/null +++ b/libnotify/table.c @@ -0,0 +1,763 @@ +/* + * Copyright (c) 2003-2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include +#include +#include +#include +#include +#include "table.h" + +#define KEY_UNKNOWN 0 +#define KEY_INT 1 +#define KEY_STR_MINE 2 +#define KEY_STR_SHARED 3 + +#define DEFAULT_SIZE 256 + +typedef struct table_node_s +{ + union + { + char *string; + const char *const_string; + uint64_t uint64; + } key; + void *datum; + struct table_node_s *next; +} table_node_t; + +typedef struct __table_private +{ + uint32_t type; + uint32_t bucket_count; + table_node_t **bucket; +} table_private_t; + +typedef struct +{ + uint32_t bucket_index; + table_node_t *node; +} table_traverse_t; + +typedef struct __list_private +{ + struct __list_private *prev; + struct __list_private *next; + uint32_t refcount; + void *data; +} list_private_t; + +table_t * +_nc_table_new(uint32_t n) +{ + table_private_t *t; + + t = (table_t *)malloc(sizeof(table_t)); + if (t == NULL) return NULL; + + if (n == 0) n = DEFAULT_SIZE; + + t->type = KEY_UNKNOWN; + t->bucket_count = n; + t->bucket = (table_node_t **)calloc(t->bucket_count, sizeof(table_node_t *)); + if (t->bucket == NULL) + { + free(t); + return NULL; + } + + return (table_t *)t; +} + +static uint32_t +hash_key(int size, const char *key) +{ + uint32_t v; + char *p; + + if (key == NULL) return 0; + + v = 0; + for (p = (char *)key; *p != '\0'; p++) + { + v = (v << 1) ^ (v ^ *p); + } + + v %= size; + return v; +} + +static uint32_t +hash_nkey(uint32_t size, uint64_t key) +{ + uint32_t x = key; + uint32_t y = key >> 32; + return ((x ^ y) % size); +} + +void * +_nc_table_find_get_key(table_t *tin, const char *key, const char **shared_key) +{ + table_private_t *t; + table_node_t *n; + uint32_t b; + + if (tin == NULL) return NULL; + if (key == NULL) return NULL; + + if (shared_key != NULL) *shared_key = NULL; + + t = (table_private_t *)tin; + b = hash_key(t->bucket_count, key); + + for (n = t->bucket[b]; n != NULL; n = n->next) + { + if ((n->key.string != NULL) && (!strcmp(key, n->key.string))) + { + if (shared_key != NULL) *shared_key = n->key.const_string; + return n->datum; + } + } + + return NULL; +} + +void * +_nc_table_find(table_t *tin, const char *key) +{ + return _nc_table_find_get_key(tin, key, NULL); +} + +void * +_nc_table_find_64(table_t *tin, uint64_t key) +{ + table_private_t *t; + table_node_t *n; + uint32_t b; + + if (tin == NULL) return NULL; + + t = (table_private_t *)tin; + b = hash_nkey(t->bucket_count, key); + + for (n = t->bucket[b]; n != NULL; n = n->next) + { + if ((n->key.uint64 != (uint64_t)-1) && (key == n->key.uint64)) return n->datum; + } + + return NULL; +} + +void * +_nc_table_find_n(table_t *tin, uint32_t key) +{ + uint64_t n64 = key; + return _nc_table_find_64(tin, n64); +} + +static void +_nc_table_insert_type(table_t *tin, int type, char *key, const char *ckey, void *datum) +{ + table_private_t *t; + table_node_t *n; + uint32_t b; + + if (tin == NULL) return; + if ((key == NULL) && (ckey == NULL)) return; + if (datum == NULL) return; + + t = (table_private_t *)tin; + if (t->type == KEY_UNKNOWN) t->type = type; + else os_assumes(t->type == type); + + n = (table_node_t *)malloc(sizeof(table_node_t)); + + if (key != NULL) + { + b = hash_key(t->bucket_count, key); + n->key.string = key; + } + else + { + b = hash_key(t->bucket_count, ckey); + n->key.const_string = ckey; + } + + n->datum = datum; + n->next = t->bucket[b]; + t->bucket[b] = n; +} + +void +_nc_table_insert(table_t *tin, const char *key, void *datum) +{ + char *dup; + + if (tin == NULL) return; + if (key == NULL) return; + if (datum == NULL) return; + + dup = strdup(key); + if (dup == NULL) return; + + _nc_table_insert_type(tin, KEY_STR_MINE, dup, NULL, datum); +} + +void +_nc_table_insert_no_copy(table_t *tin, const char *key, void *datum) +{ + if (tin == NULL) return; + if (key == NULL) return; + if (datum == NULL) return; + + _nc_table_insert_type(tin, KEY_STR_SHARED, NULL, key, datum); +} + +void +_nc_table_insert_pass(table_t *tin, char *key, void *datum) +{ + if (tin == NULL) return; + if (key == NULL) return; + if (datum == NULL) return; + + _nc_table_insert_type(tin, KEY_STR_MINE, key, NULL, datum); +} + +void +_nc_table_insert_64(table_t *tin, uint64_t key, void *datum) +{ + table_private_t *t; + table_node_t *n; + uint32_t b; + + if (tin == NULL) return; + if (datum == NULL) return; + + t = (table_private_t *)tin; + if (t->type == KEY_UNKNOWN) t->type = KEY_INT; + else os_assumes(t->type == KEY_INT); + + b = hash_nkey(t->bucket_count, key); + n = (table_node_t *)malloc(sizeof(table_node_t)); + n->key.uint64 = key; + n->datum = datum; + n->next = t->bucket[b]; + t->bucket[b] = n; +} + +void +_nc_table_insert_n(table_t *tin, uint32_t key, void *datum) +{ + uint64_t n64 = key; + _nc_table_insert_64(tin, n64, datum); +} + +void +_nc_table_delete(table_t *tin, const char *key) +{ + table_private_t *t; + table_node_t *n, *p; + uint32_t b; + + if (tin == NULL) return; + if (key == NULL) return; + + t = (table_private_t *)tin; + os_assumes((t->type == KEY_STR_MINE) || (t->type == KEY_STR_SHARED)); + + b = hash_key(t->bucket_count, key); + + p = NULL; + for (n = t->bucket[b]; n != NULL; n = n->next) + { + if ((n->key.string != NULL) && (!strcmp(key, n->key.string))) + { + if (p == NULL) t->bucket[b] = n->next; + else p->next = n->next; + if (t->type == KEY_STR_MINE) free(n->key.string); + free(n); + return; + } + p = n; + } +} + +void +_nc_table_delete_64(table_t *tin, uint64_t key) +{ + table_private_t *t; + table_node_t *n, *p; + uint32_t b; + + if (tin == NULL) return; + + t = (table_private_t *)tin; + os_assumes(t->type == KEY_INT); + + b = hash_nkey(t->bucket_count, key); + + p = NULL; + for (n = t->bucket[b]; n != NULL; n = n->next) + { + if ((n->key.uint64 != (uint64_t)-1) && (key == n->key.uint64)) + { + if (p == NULL) t->bucket[b] = n->next; + else p->next = n->next; + free(n); + return; + } + p = n; + } +} + +void +_nc_table_delete_n(table_t *tin, uint32_t key) +{ + uint64_t n64 = key; + _nc_table_delete_64(tin, n64); +} + +void * +_nc_table_traverse_start(table_t *tin) +{ + table_traverse_t *tt; + table_private_t *t; + uint32_t b; + + if (tin == NULL) return NULL; + + t = (table_private_t *)tin; + if (t->bucket_count == 0) return NULL; + + for (b = 0; b < t->bucket_count; b++) + { + if (t->bucket[b] != NULL) + { + tt = (table_traverse_t *)malloc(sizeof(table_traverse_t)); + if (tt == NULL) return NULL; + tt->bucket_index = b; + tt->node = t->bucket[b]; + return (void *)tt; + } + } + + return NULL; +} + +void * +_nc_table_traverse(table_t *tin, void *ttin) +{ + table_private_t *t; + table_traverse_t *tt; + void *datum; + uint32_t b; + + if (tin == NULL) return NULL; + if (ttin == NULL) return NULL; + + t = (table_private_t *)tin; + tt = (table_traverse_t *)ttin; + + if (tt->node == NULL) return NULL; + + datum = tt->node->datum; + tt->node = tt->node->next; + if (tt->node != NULL) return datum; + + for (b = tt->bucket_index + 1; b < t->bucket_count; b++) + { + if (t->bucket[b] != NULL) + { + tt->bucket_index = b; + tt->node = t->bucket[b]; + return datum; + } + } + + tt->bucket_index = b; + tt->node = NULL; + + return datum; +} + +void +_nc_table_traverse_end(table_t *tin, void *ttin) +{ + if (ttin == NULL) return; + free(ttin); +} + +void +_nc_table_free(table_t *tin) +{ + table_private_t *t; + table_node_t *n, *x; + uint32_t b; + + if (tin == NULL) return; + + t = (table_private_t *)tin; + + for (b = 0; b < t->bucket_count; b++) + { + x = NULL; + for (n = t->bucket[b]; n != NULL; n = x) + { + x = n->next; + if (t->type == KEY_STR_MINE) free(n->key.string); + free(n); + } + } + + free(t->bucket); + free(t); +} + +/* Linked List */ + +/* + * Make a new node + */ +list_t * +_nc_list_new(void *d) +{ + list_t *n; + + n = (list_t *)calloc(1, sizeof(list_t)); + if (n == NULL) return NULL; + + n->refcount = 1; + n->data = d; + return n; +} + +/* + * Release a node + */ +void +_nc_list_release(list_t *l) +{ + if (l == NULL) return; + l->refcount--; + if (l->refcount > 0) return; + + free(l); +} + +/* + * Retain a node + */ +list_t * +_nc_list_retain(list_t *l) +{ + if (l == NULL) return NULL; + l->refcount++; + return l; +} + +/* + * Retain a list + */ +list_t * +_nc_list_retain_list(list_t *l) +{ + list_t *n; + + for (n = l; n != NULL; n = n->next) n->refcount++; + return l; +} + +/* + * Get previous node + */ +list_t * +_nc_list_prev(list_t *l) +{ + if (l == NULL) return NULL; + return l->prev; +} + +/* + * Get next node + */ +list_t * +_nc_list_next(list_t *l) +{ + if (l == NULL) return NULL; + return l->next; +} + +/* + * Get head (first node) of list + */ +list_t * +_nc_list_head(list_t *l) +{ + list_t *p; + + if (l == NULL) return NULL; + + for (p = l; p->prev != NULL; p = p->prev); + + return p; +} + +/* + * Get tail (last node) of list + */ +list_t * +_nc_list_tail(list_t *l) +{ + list_t *p; + + if (l == NULL) return NULL; + + for (p = l; p->next != NULL; p = p->next); + + return p; +} + +/* + * Insert a node in front of another node. + * Cuts list if n is NULL. + */ +list_t * +_nc_list_prepend(list_t *l, list_t *n) +{ + if (l == NULL) return n; + + if (n != NULL) + { + n->next = l; + n->prev = l->prev; + } + + if (l->prev != NULL) l->prev->next = n; + l->prev = n; + + return n; +} + +/* + * Append a node after another node. + * Cuts list if n is NULL. + */ +list_t * +_nc_list_append(list_t *l, list_t *n) +{ + if (l == NULL) return n; + + if (n != NULL) + { + n->prev = l; + n->next = l->next; + } + + if (l->next != NULL) n->next->prev = n; + l->next = n; + + return n; +} + +/* + * Set next pointer - use with care. + */ +void +_nc_list_set_next(list_t *l, list_t *n) +{ + if (l == NULL) return; + l->next = n; +} + +/* + * Set prev pointer - use with care. + */ +void +_nc_list_set_prev(list_t *l, list_t *p) +{ + if (l == NULL) return; + l->prev = p; +} + +/* + * Concatenate two lists. + * Returns new head. + */ +list_t * +_nc_list_concat(list_t *a, list_t *b) +{ + list_t *p; + + if (a == NULL) return b; + if (b == NULL) return a; + + for (p = a; p->next != NULL; p = p->next); + + p->next = b; + b->prev = p; + + for (p = a; p->prev != NULL; p = p->prev); + + return p; +} + +uint32_t +_nc_list_count(list_t *l) +{ + uint32_t n; + list_t *p; + + n = 0; + for (p = l; p != NULL; p = p->next) n++; + return n; +} + +void * +_nc_list_data(list_t *l) +{ + if (l == NULL) return NULL; + return l->data; +} + +void +_nc_list_set_data(list_t *l, void *d) +{ + if (l != NULL) l->data = d; +} + +list_t * +_nc_list_find(list_t *l, void *d) +{ + list_t *p; + + if (l == NULL) return NULL; + + for (p = l; p != NULL; p = p->next) + { + if (p->data == d) return p; + } + + return NULL; +} + +list_t * +_nc_list_find_release(list_t *l, void *d) +{ + list_t *p; + + if (l == NULL) return NULL; + + if (l->data == d) + { + p = l->next; + if (p != NULL) p->prev = NULL; + _nc_list_release(l); + return p; + } + + for (p = l->next; p != NULL; p = p->next) + { + if (p->data == d) + { + p->prev->next = p->next; + if (p->next != NULL) p->next->prev = p->prev; + _nc_list_release(p); + return l; + } + } + + return l; +} + +list_t * +_nc_list_reverse(list_t *l) +{ + list_t *x, *s, *r; + + if (l == NULL) return NULL; + + x = l->prev; + r = l; + s = l->next; + + while (s != NULL) + { + s = r->next; + r->next = r->prev; + r->prev = s; + if (s != NULL) r = s; + } + + if (x != NULL) + { + x->next = r; + r->prev = x; + } + + return r; +} + +list_t * +_nc_list_extract(list_t *n) +{ + if (n == NULL) return NULL; + + if (n->prev != NULL) n->prev->next = n->next; + if (n->next != NULL) n->next->prev = n->prev; + + n->prev = NULL; + n->next = NULL; + + return n; +} + +list_t * +_nc_list_chop(list_t *l) +{ + list_t *p; + + if (l == NULL) return NULL; + p = l->next; + if (p != NULL) p->prev = NULL; + + _nc_list_release(l); + return p; +} + +void +_nc_list_release_list(list_t *l) +{ + list_t *p, *n; + + if (l == NULL) return; + if (l->prev != NULL) l->prev->next = NULL; + + p = l; + while (p != NULL) + { + n = p->next; + _nc_list_release(p); + p = n; + } +} diff --git a/libnotify/table.h b/libnotify/table.h new file mode 100644 index 000000000..9ca80cd0e --- /dev/null +++ b/libnotify/table.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2003-2011 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#ifndef _NOTIFY_TABLE_H_ +#define _NOTIFY_TABLE_H_ + +#include + +typedef struct __table_private table_t; +typedef struct __list_private list_t; + +extern table_t *_nc_table_new(uint32_t n); + +extern void _nc_table_insert(table_t *t, const char *key, void *datum); +extern void _nc_table_insert_no_copy(table_t *t, const char *key, void *datum); +extern void _nc_table_insert_pass(table_t *t, char *key, void *datum); +extern void _nc_table_insert_n(table_t *t, uint32_t key, void *datum); +extern void _nc_table_insert_64(table_t *t, uint64_t key, void *datum); + +extern void *_nc_table_find(table_t *t, const char *key); +extern void *_nc_table_find_get_key(table_t *tin, const char *key, const char **shared_key); +extern void *_nc_table_find_n(table_t *t, uint32_t key); +extern void *_nc_table_find_64(table_t *t, uint64_t key); + +extern void _nc_table_delete(table_t *t, const char *key); +extern void _nc_table_delete_n(table_t *t, uint32_t key); +extern void _nc_table_delete_64(table_t *t, uint64_t key); + +extern void *_nc_table_traverse_start(table_t *tin); +extern void *_nc_table_traverse(table_t *tin, void *ttin); +extern void _nc_table_traverse_end(table_t *tin, void *ttin); + +extern void _nc_table_free(table_t *tin); + +extern list_t *_nc_list_new(void *d); + +extern list_t *_nc_list_retain(list_t *l); +extern list_t *_nc_list_retain_list(list_t *l); + +extern void _nc_list_release(list_t *l); +extern void _nc_list_release_list(list_t *l); + +extern list_t *_nc_list_prev(list_t *l); +extern list_t *_nc_list_next(list_t *l); + +extern void _nc_list_set_next(list_t *l, list_t *n); +extern void _nc_list_set_prev(list_t *l, list_t *p); + +extern list_t *_nc_list_head(list_t *l); +extern list_t *_nc_list_tail(list_t *l); + +extern list_t *_nc_list_prepend(list_t *l, list_t *n); +extern list_t *_nc_list_append(list_t *l, list_t *n); + +extern list_t *_nc_list_concat(list_t *a, list_t *b); + +extern void *_nc_list_data(list_t *l); +extern void _nc_list_set_data(list_t *l, void *d); + +extern list_t *_nc_list_find(list_t *l, void *d); +extern list_t *_nc_list_find_release(list_t *l, void *d); + +extern list_t * _nc_list_reverse(list_t *l); +extern uint32_t _nc_list_count(list_t *l); +extern list_t *_nc_list_extract(list_t *n); +extern list_t *_nc_list_chop(list_t *l); + +#endif /* _NOTIFY_TABLE_H_ */ diff --git a/libnotify/xcodeconfig/base.xcconfig b/libnotify/xcodeconfig/base.xcconfig new file mode 100644 index 000000000..e09d92619 --- /dev/null +++ b/libnotify/xcodeconfig/base.xcconfig @@ -0,0 +1,29 @@ +#include "/Makefiles/CoreOS/Xcode/BSD.xcconfig" +#include "/AppleInternal/XcodeConfig/SimulatorSupport.xcconfig" + +INSTALL_PATH[sdk=macosx*] = $(INSTALL_PATH_ACTUAL) + +ALWAYS_SEARCH_USER_PATHS = YES +HEADER_SEARCH_PATHS = $(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders $(PROJECT_DIR) +ARCHS = $(ARCHS_STANDARD_32_64_BIT) +CODE_SIGN_IDENTITY = - +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym +GCC_ENABLE_CPP_EXCEPTIONS = NO +GCC_ENABLE_CPP_RTTI = NO +GCC_ENABLE_OBJC_EXCEPTIONS = NO +GCC_PREPROCESSOR_DEFINITIONS = __DARWIN_NON_CANCELABLE=1 +GCC_C_LANGUAGE_STANDARD = gnu99 +GCC_WARN_ABOUT_RETURN_TYPE = YES +GCC_WARN_UNUSED_VARIABLE = YES +//GCC_WARN_64_TO_32_BIT_CONVERSION = YES +//GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES +//GCC_WARN_ABOUT_RETURN_TYPE = YES +OTHER_CFLAGS = -fno-exceptions +OTHER_CFLAGS_debug = -O0 +CURRENT_PROJECT_VERSION = +CURRENT_PROJECT_VERSION = $(RC_ProjectSourceVersion) +VERSION_INFO_PREFIX = __ +VERSIONING_SYSTEM = apple-generic +PREBINDING = NO +NOTIFY_CONFIG = notify.conf.MacOSX +NOTIFY_CONFIG[sdk=iphoneos*] = notify.conf.iPhone diff --git a/libnotify/xcodeconfig/libnotify.xcconfig b/libnotify/xcodeconfig/libnotify.xcconfig new file mode 100644 index 000000000..7a045d981 --- /dev/null +++ b/libnotify/xcodeconfig/libnotify.xcconfig @@ -0,0 +1,31 @@ +#include "base.xcconfig" + +PRODUCT_NAME = libsystem_notify +INSTALL_PATH_ACTUAL = /usr/lib/system +PRIVATE_HEADERS_FOLDER_PATH = $(INSTALL_PATH_PREFIX)/usr/local/include +PUBLIC_HEADERS_FOLDER_PATH = $(INSTALL_PATH_PREFIX)/usr/include +DYLIB_CURRENT_VERSION = $(CURRENT_PROJECT_VERSION) +EXECUTABLE_PREFIX = +BUILD_VARIANTS = normal +CURRENT_PROJECT_VERSION = $(RC_ProjectSourceVersion) +VERSION_INFO_PREFIX = __ +VERSIONING_SYSTEM = apple-generic + +LINK_WITH_STANDARD_LIBRARIES = NO +OTHER_LDFLAGS = -umbrella System -L/usr/lib/system $(LDFLAGS_DYLD) $(LDFLAGS_COMPILER_RT) $(LDFLAGS_SYSCALL) $(LDFLAGS_PLATFORM) $(LDFLAGS_PTHREAD) $(LDFLAGS_MALLOC) $(LDFLAGS_C) $(LDFLAGS_BLOCKS) $(LDFLAGS_DISPATCH) $(LDFLAGS_XPC) + +LDFLAGS_DYLD = -ldyld +LDFLAGS_COMPILER_RT = -lcompiler_rt +LDFLAGS_SYSCALL = -lsystem_kernel +LDFLAGS_SYSCALL[sdk=iphonesimulator*] = -lsystem_sim_kernel +LDFLAGS_PLATFORM = -lsystem_platform +LDFLAGS_PLATFORM[sdk=iphonesimulator*] = -lsystem_sim_platform +LDFLAGS_PTHREAD = -lsystem_pthread +LDFLAGS_PTHREAD[sdk=iphonesimulator*] = -lsystem_sim_pthread +LDFLAGS_MALLOC = -lsystem_malloc +LDFLAGS_C = -lsystem_c +LDFLAGS_C[sdk=iphonesimulator*] = -lsystem_sim_c +LDFLAGS_BLOCKS = -lsystem_blocks +LDFLAGS_BLOCKS[sdk=iphonesimulator*] = -lsystem_sim_blocks +LDFLAGS_DISPATCH = -ldispatch +LDFLAGS_XPC = -lxpc diff --git a/libnotify/xcodeconfig/notifyd.xcconfig b/libnotify/xcodeconfig/notifyd.xcconfig new file mode 100644 index 000000000..53faec8d5 --- /dev/null +++ b/libnotify/xcodeconfig/notifyd.xcconfig @@ -0,0 +1,5 @@ +#include "base.xcconfig" + +INSTALL_PATH_ACTUAL = /usr/sbin + +OTHER_LDFLAGS = -lCrashReporterClient diff --git a/libnotify/xcodeconfig/notifyutil.xcconfig b/libnotify/xcodeconfig/notifyutil.xcconfig new file mode 100644 index 000000000..b4d6d9338 --- /dev/null +++ b/libnotify/xcodeconfig/notifyutil.xcconfig @@ -0,0 +1,4 @@ +#include "base.xcconfig" + +INSTALL_PATH_ACTUAL = /usr/bin + diff --git a/libnotify/xcodescripts/no-sim-man.sh b/libnotify/xcodescripts/no-sim-man.sh new file mode 100755 index 000000000..282dfe11c --- /dev/null +++ b/libnotify/xcodescripts/no-sim-man.sh @@ -0,0 +1,5 @@ +#!/bin/bash -ex + +if [[ "${PLATFORM_NAME}" =~ "simulator" ]]; then + rm -rf ${DSTROOT}${INSTALL_PATH_PREFIX}/usr/share/man +fi diff --git a/libnotify/xcodescripts/sim-compat-symlink.sh b/libnotify/xcodescripts/sim-compat-symlink.sh new file mode 100755 index 000000000..a837bff4b --- /dev/null +++ b/libnotify/xcodescripts/sim-compat-symlink.sh @@ -0,0 +1,5 @@ +#!/bin/bash -ex + +if [[ "${PLATFORM_NAME}" =~ "simulator" ]]; then + ln -s libsystem_notify.dylib ${DSTROOT}${INSTALL_PATH}/libnotify_sim.dylib +fi diff --git a/platform-include/dns_sd.h b/platform-include/dns_sd.h new file mode 100644 index 000000000..7ff30fca1 --- /dev/null +++ b/platform-include/dns_sd.h @@ -0,0 +1,2711 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2013 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/*! @header DNS Service Discovery + * + * @discussion This section describes the functions, callbacks, and data structures + * that make up the DNS Service Discovery API. + * + * The DNS Service Discovery API is part of Bonjour, Apple's implementation + * of zero-configuration networking (ZEROCONF). + * + * Bonjour allows you to register a network service, such as a + * printer or file server, so that it can be found by name or browsed + * for by service type and domain. Using Bonjour, applications can + * discover what services are available on the network, along with + * all the information -- such as name, IP address, and port -- + * necessary to access a particular service. + * + * In effect, Bonjour combines the functions of a local DNS server and + * AppleTalk. Bonjour allows applications to provide user-friendly printer + * and server browsing, among other things, over standard IP networks. + * This behavior is a result of combining protocols such as multicast and + * DNS to add new functionality to the network (such as multicast DNS). + * + * Bonjour gives applications easy access to services over local IP + * networks without requiring the service or the application to support + * an AppleTalk or a Netbeui stack, and without requiring a DNS server + * for the local network. + */ + +/* _DNS_SD_H contains the API version number for this header file + * The API version defined in this header file symbol allows for compile-time + * checking, so that C code building with earlier versions of the header file + * can avoid compile errors trying to use functions that aren't even defined + * in those earlier versions. Similar checks may also be performed at run-time: + * => weak linking -- to avoid link failures if run with an earlier + * version of the library that's missing some desired symbol, or + * => DNSServiceGetProperty(DaemonVersion) -- to verify whether the running daemon + * ("system service" on Windows) meets some required minimum functionality level. + */ + +#ifndef _DNS_SD_H +#define _DNS_SD_H 5610101 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Set to 1 if libdispatch is supported + * Note: May also be set by project and/or Makefile + */ +#ifndef _DNS_SD_LIBDISPATCH +#define _DNS_SD_LIBDISPATCH 1 +#endif /* ndef _DNS_SD_LIBDISPATCH */ + +/* standard calling convention under Win32 is __stdcall */ +/* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */ +/* _WIN32 symbol is defined by the compiler even though it's NOT compiling code for Windows32 */ +#if defined(_WIN32) && !defined(EFI32) && !defined(EFI64) +#define DNSSD_API __stdcall +#else +#define DNSSD_API +#endif + +/* stdint.h does not exist on FreeBSD 4.x; its types are defined in sys/types.h instead */ +#if defined(__FreeBSD__) && (__FreeBSD__ < 5) +#include + +/* Likewise, on Sun, standard integer types are in sys/types.h */ +#elif defined(__sun__) +#include + +/* EFI does not have stdint.h, or anything else equivalent */ +#elif defined(EFI32) || defined(EFI64) || defined(EFIX64) +#include "Tiano.h" +#if !defined(_STDINT_H_) +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif +/* Windows has its own differences */ +#elif defined(_WIN32) +#include +#define _UNUSED +#ifndef _MSL_STDINT_H +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif + +/* All other Posix platforms use stdint.h */ +#else +#include +#endif + +#if _DNS_SD_LIBDISPATCH +#include +#endif + +/* DNSServiceRef, DNSRecordRef + * + * Opaque internal data types. + * Note: client is responsible for serializing access to these structures if + * they are shared between concurrent threads. + */ + +typedef struct _DNSServiceRef_t *DNSServiceRef; +typedef struct _DNSRecordRef_t *DNSRecordRef; + +struct sockaddr; + +/*! @enum General flags + * Most DNS-SD API functions and callbacks include a DNSServiceFlags parameter. + * As a general rule, any given bit in the 32-bit flags field has a specific fixed meaning, + * regardless of the function or callback being used. For any given function or callback, + * typically only a subset of the possible flags are meaningful, and all others should be zero. + * The discussion section for each API call describes which flags are valid for that call + * and callback. In some cases, for a particular call, it may be that no flags are currently + * defined, in which case the DNSServiceFlags parameter exists purely to allow future expansion. + * In all cases, developers should expect that in future releases, it is possible that new flag + * values will be defined, and write code with this in mind. For example, code that tests + * if (flags == kDNSServiceFlagsAdd) ... + * will fail if, in a future release, another bit in the 32-bit flags field is also set. + * The reliable way to test whether a particular bit is set is not with an equality test, + * but with a bitwise mask: + * if (flags & kDNSServiceFlagsAdd) ... + * With the exception of kDNSServiceFlagsValidate, each flag can be valid(be set) + * EITHER only as an input to one of the DNSService*() APIs OR only as an output + * (provide status) through any of the callbacks used. For example, kDNSServiceFlagsAdd + * can be set only as an output in the callback, whereas the kDNSServiceFlagsIncludeP2P + * can be set only as an input to the DNSService*() APIs. See comments on kDNSServiceFlagsValidate + * defined in enum below. + */ +enum +{ + kDNSServiceFlagsMoreComing = 0x1, + /* MoreComing indicates to a callback that at least one more result is + * queued and will be delivered following immediately after this one. + * When the MoreComing flag is set, applications should not immediately + * update their UI, because this can result in a great deal of ugly flickering + * on the screen, and can waste a great deal of CPU time repeatedly updating + * the screen with content that is then immediately erased, over and over. + * Applications should wait until MoreComing is not set, and then + * update their UI when no more changes are imminent. + * When MoreComing is not set, that doesn't mean there will be no more + * answers EVER, just that there are no more answers immediately + * available right now at this instant. If more answers become available + * in the future they will be delivered as usual. + */ + + kDNSServiceFlagsAdd = 0x2, + kDNSServiceFlagsDefault = 0x4, + /* Flags for domain enumeration and browse/query reply callbacks. + * "Default" applies only to enumeration and is only valid in + * conjunction with "Add". An enumeration callback with the "Add" + * flag NOT set indicates a "Remove", i.e. the domain is no longer + * valid. + */ + + kDNSServiceFlagsNoAutoRename = 0x8, + /* Flag for specifying renaming behavior on name conflict when registering + * non-shared records. By default, name conflicts are automatically handled + * by renaming the service. NoAutoRename overrides this behavior - with this + * flag set, name conflicts will result in a callback. The NoAutorename flag + * is only valid if a name is explicitly specified when registering a service + * (i.e. the default name is not used.) + */ + + kDNSServiceFlagsShared = 0x10, + kDNSServiceFlagsUnique = 0x20, + /* Flag for registering individual records on a connected + * DNSServiceRef. Shared indicates that there may be multiple records + * with this name on the network (e.g. PTR records). Unique indicates that the + * record's name is to be unique on the network (e.g. SRV records). + */ + + kDNSServiceFlagsBrowseDomains = 0x40, + kDNSServiceFlagsRegistrationDomains = 0x80, + /* Flags for specifying domain enumeration type in DNSServiceEnumerateDomains. + * BrowseDomains enumerates domains recommended for browsing, RegistrationDomains + * enumerates domains recommended for registration. + */ + + kDNSServiceFlagsLongLivedQuery = 0x100, + /* Flag for creating a long-lived unicast query for the DNSServiceQueryRecord call. */ + + kDNSServiceFlagsAllowRemoteQuery = 0x200, + /* Flag for creating a record for which we will answer remote queries + * (queries from hosts more than one hop away; hosts not directly connected to the local link). + */ + + kDNSServiceFlagsForceMulticast = 0x400, + /* Flag for signifying that a query or registration should be performed exclusively via multicast + * DNS, even for a name in a domain (e.g. foo.apple.com.) that would normally imply unicast DNS. + */ + + kDNSServiceFlagsForce = 0x800, // This flag is deprecated. + + kDNSServiceFlagsKnownUnique = 0x800, + /* + * Client guarantees that record names are unique, so we can skip sending out initial + * probe messages. Standard name conflict resolution is still done if a conflict is discovered. + * Currently only valid for a DNSServiceRegister call. + */ + + kDNSServiceFlagsReturnIntermediates = 0x1000, + /* Flag for returning intermediate results. + * For example, if a query results in an authoritative NXDomain (name does not exist) + * then that result is returned to the client. However the query is not implicitly + * cancelled -- it remains active and if the answer subsequently changes + * (e.g. because a VPN tunnel is subsequently established) then that positive + * result will still be returned to the client. + * Similarly, if a query results in a CNAME record, then in addition to following + * the CNAME referral, the intermediate CNAME result is also returned to the client. + * When this flag is not set, NXDomain errors are not returned, and CNAME records + * are followed silently without informing the client of the intermediate steps. + * (In earlier builds this flag was briefly calledkDNSServiceFlagsReturnCNAME) + */ + + kDNSServiceFlagsNonBrowsable = 0x2000, + /* A service registered with the NonBrowsable flag set can be resolved using + * DNSServiceResolve(), but will not be discoverable using DNSServiceBrowse(). + * This is for cases where the name is actually a GUID; it is found by other means; + * there is no end-user benefit to browsing to find a long list of opaque GUIDs. + * Using the NonBrowsable flag creates SRV+TXT without the cost of also advertising + * an associated PTR record. + */ + + kDNSServiceFlagsShareConnection = 0x4000, + /* For efficiency, clients that perform many concurrent operations may want to use a + * single Unix Domain Socket connection with the background daemon, instead of having a + * separate connection for each independent operation. To use this mode, clients first + * call DNSServiceCreateConnection(&MainRef) to initialize the main DNSServiceRef. + * For each subsequent operation that is to share that same connection, the client copies + * the MainRef, and then passes the address of that copy, setting the ShareConnection flag + * to tell the library that this DNSServiceRef is not a typical uninitialized DNSServiceRef; + * it's a copy of an existing DNSServiceRef whose connection information should be reused. + * + * For example: + * + * DNSServiceErrorType error; + * DNSServiceRef MainRef; + * error = DNSServiceCreateConnection(&MainRef); + * if (error) ... + * DNSServiceRef BrowseRef = MainRef; // Important: COPY the primary DNSServiceRef first... + * error = DNSServiceBrowse(&BrowseRef, kDNSServiceFlagsShareConnection, ...); // then use the copy + * if (error) ... + * ... + * DNSServiceRefDeallocate(BrowseRef); // Terminate the browse operation + * DNSServiceRefDeallocate(MainRef); // Terminate the shared connection + * Also see Point 4.(Don't Double-Deallocate if the MainRef has been Deallocated) in Notes below: + * + * Notes: + * + * 1. Collective kDNSServiceFlagsMoreComing flag + * When callbacks are invoked using a shared DNSServiceRef, the + * kDNSServiceFlagsMoreComing flag applies collectively to *all* active + * operations sharing the same parent DNSServiceRef. If the MoreComing flag is + * set it means that there are more results queued on this parent DNSServiceRef, + * but not necessarily more results for this particular callback function. + * The implication of this for client programmers is that when a callback + * is invoked with the MoreComing flag set, the code should update its + * internal data structures with the new result, and set a variable indicating + * that its UI needs to be updated. Then, later when a callback is eventually + * invoked with the MoreComing flag not set, the code should update *all* + * stale UI elements related to that shared parent DNSServiceRef that need + * updating, not just the UI elements related to the particular callback + * that happened to be the last one to be invoked. + * + * 2. Canceling operations and kDNSServiceFlagsMoreComing + * Whenever you cancel any operation for which you had deferred UI updates + * waiting because of a kDNSServiceFlagsMoreComing flag, you should perform + * those deferred UI updates. This is because, after cancelling the operation, + * you can no longer wait for a callback *without* MoreComing set, to tell + * you do perform your deferred UI updates (the operation has been canceled, + * so there will be no more callbacks). An implication of the collective + * kDNSServiceFlagsMoreComing flag for shared connections is that this + * guideline applies more broadly -- any time you cancel an operation on + * a shared connection, you should perform all deferred UI updates for all + * operations sharing that connection. This is because the MoreComing flag + * might have been referring to events coming for the operation you canceled, + * which will now not be coming because the operation has been canceled. + * + * 3. Only share DNSServiceRef's created with DNSServiceCreateConnection + * Calling DNSServiceCreateConnection(&ref) creates a special shareable DNSServiceRef. + * DNSServiceRef's created by other calls like DNSServiceBrowse() or DNSServiceResolve() + * cannot be shared by copying them and using kDNSServiceFlagsShareConnection. + * + * 4. Don't Double-Deallocate if the MainRef has been Deallocated + * Calling DNSServiceRefDeallocate(ref) for a particular operation's DNSServiceRef terminates + * just that operation. Calling DNSServiceRefDeallocate(ref) for the main shared DNSServiceRef + * (the parent DNSServiceRef, originally created by DNSServiceCreateConnection(&ref)) + * automatically terminates the shared connection and all operations that were still using it. + * After doing this, DO NOT then attempt to deallocate any remaining subordinate DNSServiceRef's. + * The memory used by those subordinate DNSServiceRef's has already been freed, so any attempt + * to do a DNSServiceRefDeallocate (or any other operation) on them will result in accesses + * to freed memory, leading to crashes or other equally undesirable results. + * + * 5. Thread Safety + * The dns_sd.h API does not presuppose any particular threading model, and consequently + * does no locking of its own (which would require linking some specific threading library). + * If client code calls API routines on the same DNSServiceRef concurrently + * from multiple threads, it is the client's responsibility to use a mutext + * lock or take similar appropriate precautions to serialize those calls. + */ + + kDNSServiceFlagsSuppressUnusable = 0x8000, + /* + * This flag is meaningful only in DNSServiceQueryRecord which suppresses unusable queries on the + * wire. If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to look up IPv6 addresses + * for "hostname", since any addresses it found would be unlikely to be of any use anyway. Similarly, + * if this host has no routable IPv4 address, the call will not try to look up IPv4 addresses for + * "hostname". + */ + + kDNSServiceFlagsTimeout = 0x10000, + /* + * When kDNServiceFlagsTimeout is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo, the query is + * stopped after a certain number of seconds have elapsed. The time at which the query will be stopped + * is determined by the system and cannot be configured by the user. The query will be stopped irrespective + * of whether a response was given earlier or not. When the query is stopped, the callback will be called + * with an error code of kDNSServiceErr_Timeout and a NULL sockaddr will be returned for DNSServiceGetAddrInfo + * and zero length rdata will be returned for DNSServiceQueryRecord. + */ + + kDNSServiceFlagsIncludeP2P = 0x20000, + /* + * Include P2P interfaces when kDNSServiceInterfaceIndexAny is specified. + * By default, specifying kDNSServiceInterfaceIndexAny does not include P2P interfaces. + */ + + kDNSServiceFlagsWakeOnResolve = 0x40000, + /* + * This flag is meaningful only in DNSServiceResolve. When set, it tries to send a magic packet + * to wake up the client. + */ + + kDNSServiceFlagsBackgroundTrafficClass = 0x80000, + /* + * This flag is meaningful for Unicast DNS queries. When set, it uses the background traffic + * class for packets that service the request. + */ + + kDNSServiceFlagsIncludeAWDL = 0x100000, + /* + * Include AWDL interface when kDNSServiceInterfaceIndexAny is specified. + */ + + kDNSServiceFlagsValidate = 0x200000, + /* + * This flag is meaningful in DNSServiceGetAddrInfo and DNSServiceQueryRecord. This is the ONLY flag to be valid + * as an input to the APIs and also an output through the callbacks in the APIs. + * + * When this flag is passed to DNSServiceQueryRecord and DNSServiceGetAddrInfo to resolve unicast names, + * the response will be validated using DNSSEC. The validation results are delivered using the flags field in + * the callback and kDNSServiceFlagsValidate is marked in the flags to indicate that DNSSEC status is also available. + * When the callback is called to deliver the query results, the validation results may or may not be available. + * If it is not delivered along with the results, the validation status is delivered when the validation completes. + * + * When the validation results are delivered in the callback, it is indicated by marking the flags with + * kDNSServiceFlagsValidate and kDNSServiceFlagsAdd along with the DNSSEC status flags (described below) and a NULL + * sockaddr will be returned for DNSServiceGetAddrInfo and zero length rdata will be returned for DNSServiceQueryRecord. + * DNSSEC validation results are for the whole RRSet and not just individual records delivered in the callback. When + * kDNSServiceFlagsAdd is not set in the flags, applications should implicitly assume that the DNSSEC status of the + * RRSet that has been delivered up until that point is not valid anymore, till another callback is called with + * kDNSServiceFlagsAdd and kDNSServiceFlagsValidate. + * + * The following four flags indicate the status of the DNSSEC validation and marked in the flags field of the callback. + * When any of the four flags is set, kDNSServiceFlagsValidate will also be set. To check the validation status, the + * other applicable output flags should be masked. See kDNSServiceOutputFlags below. + */ + + kDNSServiceFlagsSecure = 0x200010, + /* + * The response has been validated by verifying all the signaures in the response and was able to + * build a successful authentication chain starting from a known trust anchor. + */ + + kDNSServiceFlagsInsecure = 0x200020, + /* + * A chain of trust cannot be built starting from a known trust anchor to the response. + */ + + kDNSServiceFlagsBogus = 0x200040, + /* + * If the response cannot be verified to be secure due to expired signatures, missing signatures etc., + * then the results are considered to be bogus. + */ + + kDNSServiceFlagsIndeterminate = 0x200080, + /* + * There is no valid trust anchor that can be used to determine whether a response is secure or not. + */ + + kDNSServiceFlagsUnicastResponse = 0x400000, + /* + * Request unicast response to query. + */ + kDNSServiceFlagsValidateOptional = 0x800000, + + /* + * This flag is identical to kDNSServiceFlagsValidate except for the case where the response + * cannot be validated. If this flag is set in DNSServiceQueryRecord or DNSServiceGetAddrInfo, + * the DNSSEC records will be requested for validation. If they cannot be received for some reason + * during the validation (e.g., zone is not signed, zone is signed but cannot be traced back to + * root, recursive server does not understand DNSSEC etc.), then this will fallback to the default + * behavior where the validation will not be performed and no DNSSEC results will be provided. + * + * If the zone is signed and there is a valid path to a known trust anchor configured in the system + * and the application requires DNSSEC validation irrespective of the DNSSEC awareness in the current + * network, then this option MUST not be used. This is only intended to be used during the transition + * period where the different nodes participating in the DNS resolution may not understand DNSSEC or + * managed properly (e.g. missing DS record) but still want to be able to resolve DNS successfully. + */ + + kDNSServiceFlagsWakeOnlyService = 0x1000000, + /* + * This flag is meaningful only in DNSServiceRegister. When set, the service will not be registered + * with sleep proxy server during sleep. + */ + + kDNSServiceFlagsThresholdOne = 0x2000000, + kDNSServiceFlagsThresholdFinder = 0x4000000, + kDNSServiceFlagsThresholdReached = kDNSServiceFlagsThresholdOne, + /* + * kDNSServiceFlagsThresholdOne is meaningful only in DNSServiceBrowse. When set, + * the system will stop issuing browse queries on the network once the number + * of answers returned is one or more. It will issue queries on the network + * again if the number of answers drops to zero. + * This flag is for Apple internal use only. Third party developers + * should not rely on this behavior being supported in any given software release. + * + * kDNSServiceFlagsThresholdFinder is meaningful only in DNSServiceBrowse. When set, + * the system will stop issuing browse queries on the network once the number + * of answers has reached the threshold set for Finder. + * It will issue queries on the network again if the number of answers drops below + * this threshold. + * This flag is for Apple internal use only. Third party developers + * should not rely on this behavior being supported in any given software release. + * + * When kDNSServiceFlagsThresholdReached is set in the client callback add or remove event, + * it indicates that the browse answer threshold has been reached and no + * browse requests will be generated on the network until the number of answers falls + * below the threshold value. Add and remove events can still occur based + * on incoming Bonjour traffic observed by the system. + * The set of services return to the client is not guaranteed to represent the + * entire set of services present on the network once the threshold has been reached. + * + * Note, while kDNSServiceFlagsThresholdReached and kDNSServiceFlagsThresholdOne + * have the same value, there isn't a conflict because kDNSServiceFlagsThresholdReached + * is only set in the callbacks and kDNSServiceFlagsThresholdOne is only set on + * input to a DNSServiceBrowse call. + */ + kDNSServiceFlagsDenyCellular = 0x8000000, + /* + * This flag is meaningful only for Unicast DNS queries. When set, the kernel will restrict + * DNS resolutions on the cellular interface for that request. + */ + + kDNSServiceFlagsServiceIndex = 0x10000000, + /* + * This flag is meaningful only for DNSServiceGetAddrInfo() for Unicast DNS queries. + * When set, DNSServiceGetAddrInfo() will interpret the "interfaceIndex" argument of the call + * as the "serviceIndex". + */ + + kDNSServiceFlagsDenyExpensive = 0x20000000 + /* + * This flag is meaningful only for Unicast DNS queries. When set, the kernel will restrict + * DNS resolutions on interfaces defined as expensive for that request. + */ + +}; + +#define kDNSServiceOutputFlags (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional | kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd | kDNSServiceFlagsDefault) + /* All the output flags excluding the DNSSEC Status flags. Typically used to check DNSSEC Status */ + +/* Possible protocol values */ +enum +{ + /* for DNSServiceGetAddrInfo() */ + kDNSServiceProtocol_IPv4 = 0x01, + kDNSServiceProtocol_IPv6 = 0x02, + /* 0x04 and 0x08 reserved for future internetwork protocols */ + + /* for DNSServiceNATPortMappingCreate() */ + kDNSServiceProtocol_UDP = 0x10, + kDNSServiceProtocol_TCP = 0x20 + /* 0x40 and 0x80 reserved for future transport protocols, e.g. SCTP [RFC 2960] + * or DCCP [RFC 4340]. If future NAT gateways are created that support port + * mappings for these protocols, new constants will be defined here. + */ +}; + +/* + * The values for DNS Classes and Types are listed in RFC 1035, and are available + * on every OS in its DNS header file. Unfortunately every OS does not have the + * same header file containing DNS Class and Type constants, and the names of + * the constants are not consistent. For example, BIND 8 uses "T_A", + * BIND 9 uses "ns_t_a", Windows uses "DNS_TYPE_A", etc. + * For this reason, these constants are also listed here, so that code using + * the DNS-SD programming APIs can use these constants, so that the same code + * can compile on all our supported platforms. + */ + +enum +{ + kDNSServiceClass_IN = 1 /* Internet */ +}; + +enum +{ + kDNSServiceType_A = 1, /* Host address. */ + kDNSServiceType_NS = 2, /* Authoritative server. */ + kDNSServiceType_MD = 3, /* Mail destination. */ + kDNSServiceType_MF = 4, /* Mail forwarder. */ + kDNSServiceType_CNAME = 5, /* Canonical name. */ + kDNSServiceType_SOA = 6, /* Start of authority zone. */ + kDNSServiceType_MB = 7, /* Mailbox domain name. */ + kDNSServiceType_MG = 8, /* Mail group member. */ + kDNSServiceType_MR = 9, /* Mail rename name. */ + kDNSServiceType_NULL = 10, /* Null resource record. */ + kDNSServiceType_WKS = 11, /* Well known service. */ + kDNSServiceType_PTR = 12, /* Domain name pointer. */ + kDNSServiceType_HINFO = 13, /* Host information. */ + kDNSServiceType_MINFO = 14, /* Mailbox information. */ + kDNSServiceType_MX = 15, /* Mail routing information. */ + kDNSServiceType_TXT = 16, /* One or more text strings (NOT "zero or more..."). */ + kDNSServiceType_RP = 17, /* Responsible person. */ + kDNSServiceType_AFSDB = 18, /* AFS cell database. */ + kDNSServiceType_X25 = 19, /* X_25 calling address. */ + kDNSServiceType_ISDN = 20, /* ISDN calling address. */ + kDNSServiceType_RT = 21, /* Router. */ + kDNSServiceType_NSAP = 22, /* NSAP address. */ + kDNSServiceType_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ + kDNSServiceType_SIG = 24, /* Security signature. */ + kDNSServiceType_KEY = 25, /* Security key. */ + kDNSServiceType_PX = 26, /* X.400 mail mapping. */ + kDNSServiceType_GPOS = 27, /* Geographical position (withdrawn). */ + kDNSServiceType_AAAA = 28, /* IPv6 Address. */ + kDNSServiceType_LOC = 29, /* Location Information. */ + kDNSServiceType_NXT = 30, /* Next domain (security). */ + kDNSServiceType_EID = 31, /* Endpoint identifier. */ + kDNSServiceType_NIMLOC = 32, /* Nimrod Locator. */ + kDNSServiceType_SRV = 33, /* Server Selection. */ + kDNSServiceType_ATMA = 34, /* ATM Address */ + kDNSServiceType_NAPTR = 35, /* Naming Authority PoinTeR */ + kDNSServiceType_KX = 36, /* Key Exchange */ + kDNSServiceType_CERT = 37, /* Certification record */ + kDNSServiceType_A6 = 38, /* IPv6 Address (deprecated) */ + kDNSServiceType_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ + kDNSServiceType_SINK = 40, /* Kitchen sink (experimental) */ + kDNSServiceType_OPT = 41, /* EDNS0 option (meta-RR) */ + kDNSServiceType_APL = 42, /* Address Prefix List */ + kDNSServiceType_DS = 43, /* Delegation Signer */ + kDNSServiceType_SSHFP = 44, /* SSH Key Fingerprint */ + kDNSServiceType_IPSECKEY = 45, /* IPSECKEY */ + kDNSServiceType_RRSIG = 46, /* RRSIG */ + kDNSServiceType_NSEC = 47, /* Denial of Existence */ + kDNSServiceType_DNSKEY = 48, /* DNSKEY */ + kDNSServiceType_DHCID = 49, /* DHCP Client Identifier */ + kDNSServiceType_NSEC3 = 50, /* Hashed Authenticated Denial of Existence */ + kDNSServiceType_NSEC3PARAM = 51, /* Hashed Authenticated Denial of Existence */ + + kDNSServiceType_HIP = 55, /* Host Identity Protocol */ + + kDNSServiceType_SPF = 99, /* Sender Policy Framework for E-Mail */ + kDNSServiceType_UINFO = 100, /* IANA-Reserved */ + kDNSServiceType_UID = 101, /* IANA-Reserved */ + kDNSServiceType_GID = 102, /* IANA-Reserved */ + kDNSServiceType_UNSPEC = 103, /* IANA-Reserved */ + + kDNSServiceType_TKEY = 249, /* Transaction key */ + kDNSServiceType_TSIG = 250, /* Transaction signature. */ + kDNSServiceType_IXFR = 251, /* Incremental zone transfer. */ + kDNSServiceType_AXFR = 252, /* Transfer zone of authority. */ + kDNSServiceType_MAILB = 253, /* Transfer mailbox records. */ + kDNSServiceType_MAILA = 254, /* Transfer mail agent records. */ + kDNSServiceType_ANY = 255 /* Wildcard match. */ +}; + +/* possible error code values */ +enum +{ + kDNSServiceErr_NoError = 0, + kDNSServiceErr_Unknown = -65537, /* 0xFFFE FFFF */ + kDNSServiceErr_NoSuchName = -65538, + kDNSServiceErr_NoMemory = -65539, + kDNSServiceErr_BadParam = -65540, + kDNSServiceErr_BadReference = -65541, + kDNSServiceErr_BadState = -65542, + kDNSServiceErr_BadFlags = -65543, + kDNSServiceErr_Unsupported = -65544, + kDNSServiceErr_NotInitialized = -65545, + kDNSServiceErr_AlreadyRegistered = -65547, + kDNSServiceErr_NameConflict = -65548, + kDNSServiceErr_Invalid = -65549, + kDNSServiceErr_Firewall = -65550, + kDNSServiceErr_Incompatible = -65551, /* client library incompatible with daemon */ + kDNSServiceErr_BadInterfaceIndex = -65552, + kDNSServiceErr_Refused = -65553, + kDNSServiceErr_NoSuchRecord = -65554, + kDNSServiceErr_NoAuth = -65555, + kDNSServiceErr_NoSuchKey = -65556, + kDNSServiceErr_NATTraversal = -65557, + kDNSServiceErr_DoubleNAT = -65558, + kDNSServiceErr_BadTime = -65559, /* Codes up to here existed in Tiger */ + kDNSServiceErr_BadSig = -65560, + kDNSServiceErr_BadKey = -65561, + kDNSServiceErr_Transient = -65562, + kDNSServiceErr_ServiceNotRunning = -65563, /* Background daemon not running */ + kDNSServiceErr_NATPortMappingUnsupported = -65564, /* NAT doesn't support PCP, NAT-PMP or UPnP */ + kDNSServiceErr_NATPortMappingDisabled = -65565, /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ + kDNSServiceErr_NoRouter = -65566, /* No router currently configured (probably no network connectivity) */ + kDNSServiceErr_PollingMode = -65567, + kDNSServiceErr_Timeout = -65568 + + /* mDNS Error codes are in the range + * FFFE FF00 (-65792) to FFFE FFFF (-65537) */ +}; + +/* Maximum length, in bytes, of a service name represented as a */ +/* literal C-String, including the terminating NULL at the end. */ + +#define kDNSServiceMaxServiceName 64 + +/* Maximum length, in bytes, of a domain name represented as an *escaped* C-String */ +/* including the final trailing dot, and the C-String terminating NULL at the end. */ + +#define kDNSServiceMaxDomainName 1009 + +/* + * Notes on DNS Name Escaping + * -- or -- + * "Why is kDNSServiceMaxDomainName 1009, when the maximum legal domain name is 256 bytes?" + * + * All strings used in the DNS-SD APIs are UTF-8 strings. Apart from the exceptions noted below, + * the APIs expect the strings to be properly escaped, using the conventional DNS escaping rules: + * + * '\\' represents a single literal '\' in the name + * '\.' represents a single literal '.' in the name + * '\ddd', where ddd is a three-digit decimal value from 000 to 255, + * represents a single literal byte with that value. + * A bare unescaped '.' is a label separator, marking a boundary between domain and subdomain. + * + * The exceptions, that do not use escaping, are the routines where the full + * DNS name of a resource is broken, for convenience, into servicename/regtype/domain. + * In these routines, the "servicename" is NOT escaped. It does not need to be, since + * it is, by definition, just a single literal string. Any characters in that string + * represent exactly what they are. The "regtype" portion is, technically speaking, + * escaped, but since legal regtypes are only allowed to contain letters, digits, + * and hyphens, there is nothing to escape, so the issue is moot. The "domain" + * portion is also escaped, though most domains in use on the public Internet + * today, like regtypes, don't contain any characters that need to be escaped. + * As DNS-SD becomes more popular, rich-text domains for service discovery will + * become common, so software should be written to cope with domains with escaping. + * + * The servicename may be up to 63 bytes of UTF-8 text (not counting the C-String + * terminating NULL at the end). The regtype is of the form _service._tcp or + * _service._udp, where the "service" part is 1-15 characters, which may be + * letters, digits, or hyphens. The domain part of the three-part name may be + * any legal domain, providing that the resulting servicename+regtype+domain + * name does not exceed 256 bytes. + * + * For most software, these issues are transparent. When browsing, the discovered + * servicenames should simply be displayed as-is. When resolving, the discovered + * servicename/regtype/domain are simply passed unchanged to DNSServiceResolve(). + * When a DNSServiceResolve() succeeds, the returned fullname is already in + * the correct format to pass to standard system DNS APIs such as res_query(). + * For converting from servicename/regtype/domain to a single properly-escaped + * full DNS name, the helper function DNSServiceConstructFullName() is provided. + * + * The following (highly contrived) example illustrates the escaping process. + * Suppose you have an service called "Dr. Smith\Dr. Johnson", of type "_ftp._tcp" + * in subdomain "4th. Floor" of subdomain "Building 2" of domain "apple.com." + * The full (escaped) DNS name of this service's SRV record would be: + * Dr\.\032Smith\\Dr\.\032Johnson._ftp._tcp.4th\.\032Floor.Building\0322.apple.com. + */ + + +/* + * Constants for specifying an interface index + * + * Specific interface indexes are identified via a 32-bit unsigned integer returned + * by the if_nametoindex() family of calls. + * + * If the client passes 0 for interface index, that means "do the right thing", + * which (at present) means, "if the name is in an mDNS local multicast domain + * (e.g. 'local.', '254.169.in-addr.arpa.', '{8,9,A,B}.E.F.ip6.arpa.') then multicast + * on all applicable interfaces, otherwise send via unicast to the appropriate + * DNS server." Normally, most clients will use 0 for interface index to + * automatically get the default sensible behaviour. + * + * If the client passes a positive interface index, then for multicast names that + * indicates to do the operation only on that one interface. For unicast names the + * interface index is ignored unless kDNSServiceFlagsForceMulticast is also set. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when registering + * a service, then that service will be found *only* by other local clients + * on the same machine that are browsing using kDNSServiceInterfaceIndexLocalOnly + * or kDNSServiceInterfaceIndexAny. + * If a client has a 'private' service, accessible only to other processes + * running on the same machine, this allows the client to advertise that service + * in a way such that it does not inadvertently appear in service lists on + * all the other machines on the network. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when browsing + * then it will find *all* records registered on that same local machine. + * Clients explicitly wishing to discover *only* LocalOnly services can + * accomplish this by inspecting the interfaceIndex of each service reported + * to their DNSServiceBrowseReply() callback function, and discarding those + * where the interface index is not kDNSServiceInterfaceIndexLocalOnly. + * + * kDNSServiceInterfaceIndexP2P is meaningful only in Browse, QueryRecord, Register, + * and Resolve operations. It should not be used in other DNSService APIs. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceBrowse or + * DNSServiceQueryRecord, it restricts the operation to P2P. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceRegister, it is + * mapped internally to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P + * set. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceResolve, it is + * mapped internally to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P + * set, because resolving a P2P service may create and/or enable an interface whose + * index is not known a priori. The resolve callback will indicate the index of the + * interface via which the service can be accessed. + * + * If applications pass kDNSServiceInterfaceIndexAny to DNSServiceBrowse + * or DNSServiceQueryRecord, they must set the kDNSServiceFlagsIncludeP2P flag + * to include P2P. In this case, if a service instance or the record being queried + * is found over P2P, the resulting ADD event will indicate kDNSServiceInterfaceIndexP2P + * as the interface index. + */ + +#define kDNSServiceInterfaceIndexAny 0 +#define kDNSServiceInterfaceIndexLocalOnly ((uint32_t)-1) +#define kDNSServiceInterfaceIndexUnicast ((uint32_t)-2) +#define kDNSServiceInterfaceIndexP2P ((uint32_t)-3) + +typedef uint32_t DNSServiceFlags; +typedef uint32_t DNSServiceProtocol; +typedef int32_t DNSServiceErrorType; + + +/********************************************************************************************* +* +* Version checking +* +*********************************************************************************************/ + +/* DNSServiceGetProperty() Parameters: + * + * property: The requested property. + * Currently the only property defined is kDNSServiceProperty_DaemonVersion. + * + * result: Place to store result. + * For retrieving DaemonVersion, this should be the address of a uint32_t. + * + * size: Pointer to uint32_t containing size of the result location. + * For retrieving DaemonVersion, this should be sizeof(uint32_t). + * On return the uint32_t is updated to the size of the data returned. + * For DaemonVersion, the returned size is always sizeof(uint32_t), but + * future properties could be defined which return variable-sized results. + * + * return value: Returns kDNSServiceErr_NoError on success, or kDNSServiceErr_ServiceNotRunning + * if the daemon (or "system service" on Windows) is not running. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetProperty +( + const char *property, /* Requested property (i.e. kDNSServiceProperty_DaemonVersion) */ + void *result, /* Pointer to place to store result */ + uint32_t *size /* size of result location */ +); + +/* + * When requesting kDNSServiceProperty_DaemonVersion, the result pointer must point + * to a 32-bit unsigned integer, and the size parameter must be set to sizeof(uint32_t). + * + * On return, the 32-bit unsigned integer contains the API version number + * + * For example, Mac OS X 10.4.9 has API version 1080400. + * This allows applications to do simple greater-than and less-than comparisons: + * e.g. an application that requires at least API version 1080400 can check: + * if (version >= 1080400) ... + * + * Example usage: + * uint32_t version; + * uint32_t size = sizeof(version); + * DNSServiceErrorType err = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &version, &size); + * if (!err) printf("DNS_SD API version is %d.%d\n", version / 10000, version / 100 % 100); + */ + +#define kDNSServiceProperty_DaemonVersion "DaemonVersion" + + +// Map the source port of the local UDP socket that was opened for sending the DNS query +// to the process ID of the application that triggered the DNS resolution. +// +/* DNSServiceGetPID() Parameters: + * + * srcport: Source port (in network byte order) of the UDP socket that was created by + * the daemon to send the DNS query on the wire. + * + * pid: Process ID of the application that started the name resolution which triggered + * the daemon to send the query on the wire. The value can be -1 if the srcport + * cannot be mapped. + * + * return value: Returns kDNSServiceErr_NoError on success, or kDNSServiceErr_ServiceNotRunning + * if the daemon is not running. The value of the pid is undefined if the return + * value has error. + */ +DNSServiceErrorType DNSSD_API DNSServiceGetPID +( + uint16_t srcport, + int32_t *pid +); + +/********************************************************************************************* +* +* Unix Domain Socket access, DNSServiceRef deallocation, and data processing functions +* +*********************************************************************************************/ + +/* DNSServiceRefSockFD() + * + * Access underlying Unix domain socket for an initialized DNSServiceRef. + * The DNS Service Discovery implementation uses this socket to communicate between the client and + * the daemon. The application MUST NOT directly read from or write to this socket. + * Access to the socket is provided so that it can be used as a kqueue event source, a CFRunLoop + * event source, in a select() loop, etc. When the underlying event management subsystem (kqueue/ + * select/CFRunLoop etc.) indicates to the client that data is available for reading on the + * socket, the client should call DNSServiceProcessResult(), which will extract the daemon's + * reply from the socket, and pass it to the appropriate application callback. By using a run + * loop or select(), results from the daemon can be processed asynchronously. Alternatively, + * a client can choose to fork a thread and have it loop calling "DNSServiceProcessResult(ref);" + * If DNSServiceProcessResult() is called when no data is available for reading on the socket, it + * will block until data does become available, and then process the data and return to the caller. + * The application is reponsible for checking the return value of DNSServiceProcessResult() to determine + * if the socket is valid and if it should continue to process data on the socket. + * When data arrives on the socket, the client is responsible for calling DNSServiceProcessResult(ref) + * in a timely fashion -- if the client allows a large backlog of data to build up the daemon + * may terminate the connection. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + * return value: The DNSServiceRef's underlying socket descriptor, or -1 on + * error. + */ + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef); + + +/* DNSServiceProcessResult() + * + * Read a reply from the daemon, calling the appropriate application callback. This call will + * block until the daemon's response is received. Use DNSServiceRefSockFD() in + * conjunction with a run loop or select() to determine the presence of a response from the + * server before calling this function to process the reply without blocking. Call this function + * at any point if it is acceptable to block until the daemon's response arrives. Note that the + * client is responsible for ensuring that DNSServiceProcessResult() is called whenever there is + * a reply from the daemon - the daemon may terminate its connection with a client that does not + * process the daemon's responses. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls + * that take a callback parameter. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef); + + +/* DNSServiceRefDeallocate() + * + * Terminate a connection with the daemon and free memory associated with the DNSServiceRef. + * Any services or records registered with this DNSServiceRef will be deregistered. Any + * Browse, Resolve, or Query operations called with this reference will be terminated. + * + * Note: If the reference's underlying socket is used in a run loop or select() call, it should + * be removed BEFORE DNSServiceRefDeallocate() is called, as this function closes the reference's + * socket. + * + * Note: If the reference was initialized with DNSServiceCreateConnection(), any DNSRecordRefs + * created via this reference will be invalidated by this call - the resource records are + * deregistered, and their DNSRecordRefs may not be used in subsequent functions. Similarly, + * if the reference was initialized with DNSServiceRegister, and an extra resource record was + * added to the service via DNSServiceAddRecord(), the DNSRecordRef created by the Add() call + * is invalidated when this function is called - the DNSRecordRef may not be used in subsequent + * functions. + * + * Note: This call is to be used only with the DNSServiceRef defined by this API. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + */ + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef); + + +/********************************************************************************************* +* +* Domain Enumeration +* +*********************************************************************************************/ + +/* DNSServiceEnumerateDomains() + * + * Asynchronously enumerate domains available for browsing and registration. + * + * The enumeration MUST be cancelled via DNSServiceRefDeallocate() when no more domains + * are to be found. + * + * Note that the names returned are (like all of DNS-SD) UTF-8 strings, + * and are escaped using standard DNS escaping rules. + * (See "Notes on DNS Name Escaping" earlier in this file for more details.) + * A graphical browser displaying a hierarchical tree-structured view should cut + * the names at the bare dots to yield individual labels, then de-escape each + * label according to the escaping rules, and then display the resulting UTF-8 text. + * + * DNSServiceDomainEnumReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceEnumerateDomains(). + * + * flags: Possible values are: + * kDNSServiceFlagsMoreComing + * kDNSServiceFlagsAdd + * kDNSServiceFlagsDefault + * + * interfaceIndex: Specifies the interface on which the domain exists. (The index for a given + * interface is determined via the if_nametoindex() family of calls.) + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise indicates + * the failure that occurred (other parameters are undefined if errorCode is nonzero). + * + * replyDomain: The name of the domain. + * + * context: The context pointer passed to DNSServiceEnumerateDomains. + * + */ + +typedef void (DNSSD_API *DNSServiceDomainEnumReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *replyDomain, + void *context +); + + +/* DNSServiceEnumerateDomains() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the enumeration operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Possible values are: + * kDNSServiceFlagsBrowseDomains to enumerate domains recommended for browsing. + * kDNSServiceFlagsRegistrationDomains to enumerate domains recommended + * for registration. + * + * interfaceIndex: If non-zero, specifies the interface on which to look for domains. + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to enumerate domains on + * all interfaces. See "Constants for specifying an interface index" for more details. + * + * callBack: The function to be called when a domain is found or the call asynchronously + * fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Service Registration +* +*********************************************************************************************/ + +/* Register a service that is discovered via Browse() and Resolve() calls. + * + * DNSServiceRegisterReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceRegister(). + * + * flags: When a name is successfully registered, the callback will be + * invoked with the kDNSServiceFlagsAdd flag set. When Wide-Area + * DNS-SD is in use, it is possible for a single service to get + * more than one success callback (e.g. one in the "local" multicast + * DNS domain, and another in a wide-area unicast DNS domain). + * If a successfully-registered name later suffers a name conflict + * or similar problem and has to be deregistered, the callback will + * be invoked with the kDNSServiceFlagsAdd flag not set. The callback + * is *not* invoked in the case where the caller explicitly terminates + * the service registration by calling DNSServiceRefDeallocate(ref); + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts, + * if the kDNSServiceFlagsNoAutoRename flag was used when registering.) + * Other parameters are undefined if errorCode is nonzero. + * + * name: The service name registered (if the application did not specify a name in + * DNSServiceRegister(), this indicates what name was automatically chosen). + * + * regtype: The type of service registered, as it was passed to the callout. + * + * domain: The domain on which the service was registered (if the application did not + * specify a domain in DNSServiceRegister(), this indicates the default domain + * on which the service was registered). + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context +); + + +/* DNSServiceRegister() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the registration will remain active indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the service + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to register on all + * available interfaces. See "Constants for specifying an interface index" for more details. + * + * flags: Indicates the renaming behavior on name conflict (most applications + * will pass 0). See flag definitions above for details. + * + * name: If non-NULL, specifies the service name to be registered. + * Most applications will not specify a name, in which case the computer + * name is used (this name is communicated to the client via the callback). + * If a name is specified, it must be 1-63 bytes of UTF-8 text. + * If the name is longer than 63 bytes it will be automatically truncated + * to a legal length, unless the NoAutoRename flag is set, + * in which case kDNSServiceErr_BadParam will be returned. + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). The service type must be an underscore, followed + * by 1-15 characters, which may be letters, digits, or hyphens. + * The transport protocol must be "_tcp" or "_udp". New service types + * should be registered at . + * + * Additional subtypes of the primary service type (where a service + * type has defined subtypes) follow the primary service type in a + * comma-separated list, with no additional spaces, e.g. + * "_primarytype._tcp,_subtype1,_subtype2,_subtype3" + * Subtypes provide a mechanism for filtered browsing: A client browsing + * for "_primarytype._tcp" will discover all instances of this type; + * a client browsing for "_primarytype._tcp,_subtype2" will discover only + * those instances that were registered with "_subtype2" in their list of + * registered subtypes. + * + * The subtype mechanism can be illustrated with some examples using the + * dns-sd command-line tool: + * + * % dns-sd -R Simple _test._tcp "" 1001 & + * % dns-sd -R Better _test._tcp,HasFeatureA "" 1002 & + * % dns-sd -R Best _test._tcp,HasFeatureA,HasFeatureB "" 1003 & + * + * Now: + * % dns-sd -B _test._tcp # will find all three services + * % dns-sd -B _test._tcp,HasFeatureA # finds "Better" and "Best" + * % dns-sd -B _test._tcp,HasFeatureB # finds only "Best" + * + * Subtype labels may be up to 63 bytes long, and may contain any eight- + * bit byte values, including zero bytes. However, due to the nature of + * using a C-string-based API, conventional DNS escaping must be used for + * dots ('.'), commas (','), backslashes ('\') and zero bytes, as shown below: + * + * % dns-sd -R Test '_test._tcp,s\.one,s\,two,s\\three,s\000four' local 123 + * + * When a service is registered, all the clients browsing for the registered + * type ("regtype") will discover it. If the discovery should be + * restricted to a smaller set of well known peers, the service can be + * registered with additional data (group identifier) that is known + * only to a smaller set of peers. The group identifier should follow primary + * service type using a colon (":") as a delimeter. If subtypes are also present, + * it should be given before the subtype as shown below. + * + * % dns-sd -R _test1 _http._tcp:mygroup1 local 1001 + * % dns-sd -R _test2 _http._tcp:mygroup2 local 1001 + * % dns-sd -R _test3 _http._tcp:mygroup3,HasFeatureA local 1001 + * + * Now: + * % dns-sd -B _http._tcp:"mygroup1" # will discover only test1 + * % dns-sd -B _http._tcp:"mygroup2" # will discover only test2 + * % dns-sd -B _http._tcp:"mygroup3",HasFeatureA # will discover only test3 + * + * By specifying the group information, only the members of that group are + * discovered. + * + * The group identifier itself is not sent in clear. Only a hash of the group + * identifier is sent and the clients discover them anonymously. The group identifier + * may be up to 256 bytes long and may contain any eight bit values except comma which + * should be escaped. + * + * domain: If non-NULL, specifies the domain on which to advertise the service. + * Most applications will not specify a domain, instead automatically + * registering in the default domain(s). + * + * host: If non-NULL, specifies the SRV target host name. Most applications + * will not specify a host, instead automatically using the machine's + * default host name(s). Note that specifying a non-NULL host does NOT + * create an address record for that host - the application is responsible + * for ensuring that the appropriate address record exists, or creating it + * via DNSServiceRegisterRecord(). + * + * port: The port, in network byte order, on which the service accepts connections. + * Pass 0 for a "placeholder" service (i.e. a service that will not be discovered + * by browsing, but will cause a name conflict if another client tries to + * register that same name). Most clients will not use placeholder services. + * + * txtLen: The length of the txtRecord, in bytes. Must be zero if the txtRecord is NULL. + * + * txtRecord: The TXT record rdata. A non-NULL txtRecord MUST be a properly formatted DNS + * TXT record, i.e. ... + * Passing NULL for the txtRecord is allowed as a synonym for txtLen=1, txtRecord="", + * i.e. it creates a TXT record of length one containing a single empty string. + * RFC 1035 doesn't allow a TXT record to contain *zero* strings, so a single empty + * string is the smallest legal DNS TXT record. + * As with the other parameters, the DNSServiceRegister call copies the txtRecord + * data; e.g. if you allocated the storage for the txtRecord parameter with malloc() + * then you can safely free that memory right after the DNSServiceRegister call returns. + * + * callBack: The function to be called when the registration completes or asynchronously + * fails. The client MAY pass NULL for the callback - The client will NOT be notified + * of the default values picked on its behalf, and the client will NOT be notified of any + * asynchronous errors (e.g. out of memory errors, etc.) that may prevent the registration + * of the service. The client may NOT pass the NoAutoRename flag if the callback is NULL. + * The client may still deregister the service at any time via DNSServiceRefDeallocate(). + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegister +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, /* may be NULL */ + const char *regtype, + const char *domain, /* may be NULL */ + const char *host, /* may be NULL */ + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const void *txtRecord, /* may be NULL */ + DNSServiceRegisterReply callBack, /* may be NULL */ + void *context /* may be NULL */ +); + + +/* DNSServiceAddRecord() + * + * Add a record to a registered service. The name of the record will be the same as the + * registered service's name. + * The record can later be updated or deregistered by passing the RecordRef initialized + * by this function to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * + * Note that the DNSServiceAddRecord/UpdateRecord/RemoveRecord are *NOT* thread-safe + * with respect to a single DNSServiceRef. If you plan to have multiple threads + * in your program simultaneously add, update, or remove records from the same + * DNSServiceRef, then it's the caller's responsibility to use a mutext lock + * or take similar appropriate precautions to serialize those calls. + * + * Parameters; + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * If the above DNSServiceRef is passed to DNSServiceRefDeallocate(), RecordRef is also + * invalidated and may not be used further. + * + * flags: Currently ignored, reserved for future use. + * + * rrtype: The type of the record (e.g. kDNSServiceType_TXT, kDNSServiceType_SRV, etc) + * + * rdlen: The length, in bytes, of the rdata. + * + * rdata: The raw rdata to be contained in the added resource record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred (the RecordRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceAddRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +); + + +/* DNSServiceUpdateRecord + * + * Update a registered resource record. The record must either be: + * - The primary txt record of a service registered via DNSServiceRegister() + * - A record added to a registered service via DNSServiceAddRecord() + * - An individual record registered by DNSServiceRegisterRecord() + * + * Parameters: + * + * sdRef: A DNSServiceRef that was initialized by DNSServiceRegister() + * or DNSServiceCreateConnection(). + * + * RecordRef: A DNSRecordRef initialized by DNSServiceAddRecord, or NULL to update the + * service's primary txt record. + * + * flags: Currently ignored, reserved for future use. + * + * rdlen: The length, in bytes, of the new rdata. + * + * rdata: The new rdata to be contained in the updated resource record. + * + * ttl: The time to live of the updated resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, /* may be NULL */ + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +); + + +/* DNSServiceRemoveRecord + * + * Remove a record previously added to a service record set via DNSServiceAddRecord(), or deregister + * an record registered individually via DNSServiceRegisterRecord(). + * + * Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister() (if the + * record being removed was registered via DNSServiceAddRecord()) or by + * DNSServiceCreateConnection() (if the record being removed was registered via + * DNSServiceRegisterRecord()). + * + * recordRef: A DNSRecordRef initialized by a successful call to DNSServiceAddRecord() + * or DNSServiceRegisterRecord(). + * + * flags: Currently ignored, reserved for future use. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags +); + + +/********************************************************************************************* +* +* Service Discovery +* +*********************************************************************************************/ + +/* Browse for instances of a service. + * + * DNSServiceBrowseReply() Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceBrowse(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and kDNSServiceFlagsAdd. + * See flag definitions for details. + * + * interfaceIndex: The interface on which the service is advertised. This index should + * be passed to DNSServiceResolve() when resolving the service. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * serviceName: The discovered service name. This name should be displayed to the user, + * and stored for subsequent use in the DNSServiceResolve() call. + * + * regtype: The service type, which is usually (but not always) the same as was passed + * to DNSServiceBrowse(). One case where the discovered service type may + * not be the same as the requested service type is when using subtypes: + * The client may want to browse for only those ftp servers that allow + * anonymous connections. The client will pass the string "_ftp._tcp,_anon" + * to DNSServiceBrowse(), but the type of the service that's discovered + * is simply "_ftp._tcp". The regtype for each discovered service instance + * should be stored along with the name, so that it can be passed to + * DNSServiceResolve() when the service is later resolved. + * + * domain: The domain of the discovered service instance. This may or may not be the + * same as the domain that was passed to DNSServiceBrowse(). The domain for each + * discovered service instance should be stored along with the name, so that + * it can be passed to DNSServiceResolve() when the service is later resolved. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceBrowseReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context +); + + +/* DNSServiceBrowse() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the browse operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: If non-zero, specifies the interface on which to browse for services + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to browse on all available + * interfaces. See "Constants for specifying an interface index" for more details. + * + * regtype: The service type being browsed for followed by the protocol, separated by a + * dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + * A client may optionally specify a single subtype to perform filtered browsing: + * e.g. browsing for "_primarytype._tcp,_subtype" will discover only those + * instances of "_primarytype._tcp" that were registered specifying "_subtype" + * in their list of registered subtypes. Additionally, a group identifier may + * also be specified before the subtype e.g., _primarytype._tcp:GroupID, which + * will discover only the members that register the service with GroupID. See + * DNSServiceRegister for more details. + * + * domain: If non-NULL, specifies the domain on which to browse for services. + * Most applications will not specify a domain, instead browsing on the + * default domain(s). + * + * callBack: The function to be called when an instance of the service being browsed for + * is found, or if the call asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceBrowse +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callBack, + void *context /* may be NULL */ +); + + +/* DNSServiceResolve() + * + * Resolve a service name discovered via DNSServiceBrowse() to a target host name, port number, and + * txt record. + * + * Note: Applications should NOT use DNSServiceResolve() solely for txt record monitoring - use + * DNSServiceQueryRecord() instead, as it is more efficient for this task. + * + * Note: When the desired results have been returned, the client MUST terminate the resolve by calling + * DNSServiceRefDeallocate(). + * + * Note: DNSServiceResolve() behaves correctly for typical services that have a single SRV record + * and a single TXT record. To resolve non-standard services with multiple SRV or TXT records, + * DNSServiceQueryRecord() should be used. + * + * DNSServiceResolveReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceResolve(). + * + * flags: Possible values: kDNSServiceFlagsMoreComing + * + * interfaceIndex: The interface on which the service was resolved. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * fullname: The full service domain name, in the form ... + * (This name is escaped following standard DNS rules, making it suitable for + * passing to standard system DNS APIs such as res_query(), or to the + * special-purpose functions included in this API that take fullname parameters. + * See "Notes on DNS Name Escaping" earlier in this file for more details.) + * + * hosttarget: The target hostname of the machine providing the service. This name can + * be passed to functions like gethostbyname() to identify the host's IP address. + * + * port: The port, in network byte order, on which connections are accepted for this service. + * + * txtLen: The length of the txt record, in bytes. + * + * txtRecord: The service's primary txt record, in standard txt record format. + * + * context: The context pointer that was passed to the callout. + * + * NOTE: In earlier versions of this header file, the txtRecord parameter was declared "const char *" + * This is incorrect, since it contains length bytes which are values in the range 0 to 255, not -128 to +127. + * Depending on your compiler settings, this change may cause signed/unsigned mismatch warnings. + * These should be fixed by updating your own callback function definition to match the corrected + * function signature using "const unsigned char *txtRecord". Making this change may also fix inadvertent + * bugs in your callback function, where it could have incorrectly interpreted a length byte with value 250 + * as being -6 instead, with various bad consequences ranging from incorrect operation to software crashes. + * If you need to maintain portable code that will compile cleanly with both the old and new versions of + * this header file, you should update your callback function definition to use the correct unsigned value, + * and then in the place where you pass your callback function to DNSServiceResolve(), use a cast to eliminate + * the compiler warning, e.g.: + * DNSServiceResolve(sd, flags, index, name, regtype, domain, (DNSServiceResolveReply)MyCallback, context); + * This will ensure that your code compiles cleanly without warnings (and more importantly, works correctly) + * with both the old header and with the new corrected version. + * + */ + +typedef void (DNSSD_API *DNSServiceResolveReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char *txtRecord, + void *context +); + + +/* DNSServiceResolve() Parameters + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the resolve operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Specifying kDNSServiceFlagsForceMulticast will cause query to be + * performed with a link-local mDNS query, even if the name is an + * apparently non-local name (i.e. a name not ending in ".local.") + * + * interfaceIndex: The interface on which to resolve the service. If this resolve call is + * as a result of a currently active DNSServiceBrowse() operation, then the + * interfaceIndex should be the index reported in the DNSServiceBrowseReply + * callback. If this resolve call is using information previously saved + * (e.g. in a preference file) for later use, then use interfaceIndex 0, because + * the desired service may now be reachable via a different physical interface. + * See "Constants for specifying an interface index" for more details. + * + * name: The name of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * regtype: The type of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * domain: The domain of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceResolve +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Querying Individual Specific Records +* +*********************************************************************************************/ + +/* DNSServiceQueryRecord + * + * Query for an arbitrary DNS record. + * + * DNSServiceQueryRecordReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceQueryRecord(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. The Add flag is NOT set for PTR records + * with a ttl of 0, i.e. "Remove" events. + * + * interfaceIndex: The interface on which the query was resolved (the index for a given + * interface is determined via the if_nametoindex() family of calls). + * See "Constants for specifying an interface index" for more details. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * errorCode is nonzero. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceQueryRecordReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context +); + + +/* DNSServiceQueryRecord() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the query operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast or kDNSServiceFlagsLongLivedQuery. + * Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query to a unicast DNS server that implements the protocol. This flag + * has no effect on link-local multicast queries. + * + * interfaceIndex: If non-zero, specifies the interface on which to issue the query + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the name to be queried for on all + * interfaces. See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record to be queried for. + * + * rrtype: The numerical type of the resource record to be queried for + * (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Unified lookup of both IPv4 and IPv6 addresses for a fully qualified hostname +* +*********************************************************************************************/ + +/* DNSServiceGetAddrInfo + * + * Queries for the IP address of a hostname by using either Multicast or Unicast DNS. + * + * DNSServiceGetAddrInfoReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceGetAddrInfo(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. + * + * interfaceIndex: The interface to which the answers pertain. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are + * undefined if errorCode is nonzero. + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * address: IPv4 or IPv6 address. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceGetAddrInfoReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t ttl, + void *context +); + + +/* DNSServiceGetAddrInfo() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the query + * begins and will last indefinitely until the client terminates the query + * by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast + * + * interfaceIndex: The interface on which to issue the query. Passing 0 causes the query to be + * sent on all active interfaces via Multicast or the primary interface via Unicast. + * + * protocol: Pass in kDNSServiceProtocol_IPv4 to look up IPv4 addresses, or kDNSServiceProtocol_IPv6 + * to look up IPv6 addresses, or both to look up both kinds. If neither flag is + * set, the system will apply an intelligent heuristic, which is (currently) + * that it will attempt to look up both, except: + * + * * If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to + * look up IPv6 addresses for "hostname", since any addresses it found would be + * unlikely to be of any use anyway. Similarly, if this host has no routable + * IPv4 address, the call will not try to look up IPv4 addresses for "hostname". + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * callBack: The function to be called when the query succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Special Purpose Calls: +* DNSServiceCreateConnection(), DNSServiceRegisterRecord(), DNSServiceReconfirmRecord() +* (most applications will not use these) +* +*********************************************************************************************/ + +/* DNSServiceCreateConnection() + * + * Create a connection to the daemon allowing efficient registration of + * multiple individual records. + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef); + +/* DNSServiceRegisterRecord + * + * Register an individual resource record on a connected DNSServiceRef. + * + * Note that name conflicts occurring for records registered via this call must be handled + * by the client in the callback. + * + * DNSServiceRegisterRecordReply() parameters: + * + * sdRef: The connected DNSServiceRef initialized by + * DNSServiceCreateConnection(). + * + * RecordRef: The DNSRecordRef initialized by DNSServiceRegisterRecord(). If the above + * DNSServiceRef is passed to DNSServiceRefDeallocate(), this DNSRecordRef is + * invalidated, and may not be used further. + * + * flags: Currently unused, reserved for future use. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts.) + * Other parameters are undefined if errorCode is nonzero. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterRecordReply) +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + void *context +); + + +/* DNSServiceRegisterRecord() Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceCreateConnection(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * (To deregister ALL records registered on a single connected DNSServiceRef + * and deallocate each of their corresponding DNSServiceRecordRefs, call + * DNSServiceRefDeallocate()). + * + * flags: Possible values are kDNSServiceFlagsShared or kDNSServiceFlagsUnique + * (see flag type definitions for details). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the record + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the record to be registered on all interfaces. + * See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record. + * + * rrtype: The numerical type of the resource record (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN) + * + * rdlen: Length, in bytes, of the rdata. + * + * rdata: A pointer to the raw rdata, as it is to appear in the DNS record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails (e.g. because of a name conflict.) + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSRecordRef is + * not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context /* may be NULL */ +); + + +/* DNSServiceReconfirmRecord + * + * Instruct the daemon to verify the validity of a resource record that appears + * to be out of date (e.g. because TCP connection to a service's target failed.) + * Causes the record to be flushed from the daemon's cache (as well as all other + * daemons' caches on the network) if the record is determined to be invalid. + * Use this routine conservatively. Reconfirming a record necessarily consumes + * network bandwidth, so this should not be done indiscriminately. + * + * Parameters: + * + * flags: Not currently used. + * + * interfaceIndex: Specifies the interface of the record in question. + * The caller must specify the interface. + * This API (by design) causes increased network traffic, so it requires + * the caller to be precise about which record should be reconfirmed. + * It is not possible to pass zero for the interface index to perform + * a "wildcard" reconfirmation, where *all* matching records are reconfirmed. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord +( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata +); + +#ifndef __OPEN_SOURCE__ + +/* PeerConnectionRelease() Parameters + * + * Release P2P connection resources associated with the service instance. + * When a service is resolved over a P2P interface, a connection is brought up to the + * peer advertising the service instance. This call will free the resources associated + * with that connection. Note that the reference to the service instance will only + * be maintained by the daemon while the browse for the service type is still + * running. Thus the sequence of calls to discover, resolve, and then terminate the connection + * associated with a given P2P service instance would be: + * + * DNSServiceRef BrowseRef, ResolveRef; + * DNSServiceBrowse(&BrowseRef, ...) // browse for all instances of the service + * DNSServiceResolve(&ResolveRef, ...) // resolving a service instance creates a + * // connection to the peer device advertising that service + * DNSServiceRefDeallocate(ResolveRef) // Stop the resolve, which does not close the peer connection + * + * // Communicate with the peer application. + * + * PeerConnectionRelease() // release the connection to the peer device for the specified service instance + * + * DNSServiceRefDeallocate(BrowseRef) // stop the browse + * // Any further calls to PeerConnectionRelease() will have no affect since the + * // service instance to peer connection relationship is only maintained by the + * // daemon while the browse is running. + * + * + * flags: Not currently used. + * + * name: The name of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * regtype: The type of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * domain: The domain of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * return value: Returns kDNSServiceErr_NoError on success or the error that occurred. + */ + +DNSServiceErrorType DNSSD_API PeerConnectionRelease +( + DNSServiceFlags flags, + const char *name, + const char *regtype, + const char *domain +); + +#endif // __OPEN_SOURCE__ + +/********************************************************************************************* +* +* NAT Port Mapping +* +*********************************************************************************************/ + +/* DNSServiceNATPortMappingCreate + * + * Request a port mapping in the NAT gateway, which maps a port on the local machine + * to an external port on the NAT. The NAT should support either PCP, NAT-PMP or the + * UPnP/IGD protocol for this API to create a successful mapping. Note that this API + * currently supports IPv4 addresses/mappings only. If the NAT gateway supports PCP and + * returns an IPv6 address (incorrectly, since this API specifically requests IPv4 + * addresses), the DNSServiceNATPortMappingReply callback will be invoked with errorCode + * kDNSServiceErr_NATPortMappingUnsupported. + * + * The port mapping will be renewed indefinitely until the client process exits, or + * explicitly terminates the port mapping request by calling DNSServiceRefDeallocate(). + * The client callback will be invoked, informing the client of the NAT gateway's + * external IP address and the external port that has been allocated for this client. + * The client should then record this external IP address and port using whatever + * directory service mechanism it is using to enable peers to connect to it. + * (Clients advertising services using Wide-Area DNS-SD DO NOT need to use this API + * -- when a client calls DNSServiceRegister() NAT mappings are automatically created + * and the external IP address and port for the service are recorded in the global DNS. + * Only clients using some directory mechanism other than Wide-Area DNS-SD need to use + * this API to explicitly map their own ports.) + * + * It's possible that the client callback could be called multiple times, for example + * if the NAT gateway's IP address changes, or if a configuration change results in a + * different external port being mapped for this client. Over the lifetime of any long-lived + * port mapping, the client should be prepared to handle these notifications of changes + * in the environment, and should update its recorded address and/or port as appropriate. + * + * NOTE: There are two unusual aspects of how the DNSServiceNATPortMappingCreate API works, + * which were intentionally designed to help simplify client code: + * + * 1. It's not an error to request a NAT mapping when the machine is not behind a NAT gateway. + * In other NAT mapping APIs, if you request a NAT mapping and the machine is not behind a NAT + * gateway, then the API returns an error code -- it can't get you a NAT mapping if there's no + * NAT gateway. The DNSServiceNATPortMappingCreate API takes a different view. Working out + * whether or not you need a NAT mapping can be tricky and non-obvious, particularly on + * a machine with multiple active network interfaces. Rather than make every client recreate + * this logic for deciding whether a NAT mapping is required, the PortMapping API does that + * work for you. If the client calls the PortMapping API when the machine already has a + * routable public IP address, then instead of complaining about it and giving an error, + * the PortMapping API just invokes your callback, giving the machine's public address + * and your own port number. This means you don't need to write code to work out whether + * your client needs to call the PortMapping API -- just call it anyway, and if it wasn't + * necessary, no harm is done: + * + * - If the machine already has a routable public IP address, then your callback + * will just be invoked giving your own address and port. + * - If a NAT mapping is required and obtained, then your callback will be invoked + * giving you the external address and port. + * - If a NAT mapping is required but not obtained from the local NAT gateway, + * or the machine has no network connectivity, then your callback will be + * invoked giving zero address and port. + * + * 2. In other NAT mapping APIs, if a laptop computer is put to sleep and woken up on a new + * network, it's the client's job to notice this, and work out whether a NAT mapping + * is required on the new network, and make a new NAT mapping request if necessary. + * The DNSServiceNATPortMappingCreate API does this for you, automatically. + * The client just needs to make one call to the PortMapping API, and its callback will + * be invoked any time the mapping state changes. This property complements point (1) above. + * If the client didn't make a NAT mapping request just because it determined that one was + * not required at that particular moment in time, the client would then have to monitor + * for network state changes to determine if a NAT port mapping later became necessary. + * By unconditionally making a NAT mapping request, even when a NAT mapping not to be + * necessary, the PortMapping API will then begin monitoring network state changes on behalf of + * the client, and if a NAT mapping later becomes necessary, it will automatically create a NAT + * mapping and inform the client with a new callback giving the new address and port information. + * + * DNSServiceNATPortMappingReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceNATPortMappingCreate(). + * + * flags: Currently unused, reserved for future use. + * + * interfaceIndex: The interface through which the NAT gateway is reached. + * + * errorCode: Will be kDNSServiceErr_NoError on success. + * Will be kDNSServiceErr_DoubleNAT when the NAT gateway is itself behind one or + * more layers of NAT, in which case the other parameters have the defined values. + * For other failures, will indicate the failure that occurred, and the other + * parameters are undefined. + * + * externalAddress: Four byte IPv4 address in network byte order. + * + * protocol: Will be kDNSServiceProtocol_UDP or kDNSServiceProtocol_TCP or both. + * + * internalPort: The port on the local machine that was mapped. + * + * externalPort: The actual external port in the NAT gateway that was mapped. + * This is likely to be different than the requested external port. + * + * ttl: The lifetime of the NAT port mapping created on the gateway. + * This controls how quickly stale mappings will be garbage-collected + * if the client machine crashes, suffers a power failure, is disconnected + * from the network, or suffers some other unfortunate demise which + * causes it to vanish without explicitly removing its NAT port mapping. + * It's possible that the ttl value will differ from the requested ttl value. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceNATPortMappingReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + uint32_t externalAddress, /* four byte IPv4 address in network byte order */ + DNSServiceProtocol protocol, + uint16_t internalPort, /* In network byte order */ + uint16_t externalPort, /* In network byte order and may be different than the requested port */ + uint32_t ttl, /* may be different than the requested ttl */ + void *context +); + + +/* DNSServiceNATPortMappingCreate() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the nat + * port mapping will last indefinitely until the client terminates the port + * mapping request by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: The interface on which to create port mappings in a NAT gateway. Passing 0 causes + * the port mapping request to be sent on the primary interface. + * + * protocol: To request a port mapping, pass in kDNSServiceProtocol_UDP, or kDNSServiceProtocol_TCP, + * or (kDNSServiceProtocol_UDP | kDNSServiceProtocol_TCP) to map both. + * The local listening port number must also be specified in the internalPort parameter. + * To just discover the NAT gateway's external IP address, pass zero for protocol, + * internalPort, externalPort and ttl. + * + * internalPort: The port number in network byte order on the local machine which is listening for packets. + * + * externalPort: The requested external port in network byte order in the NAT gateway that you would + * like to map to the internal port. Pass 0 if you don't care which external port is chosen for you. + * + * ttl: The requested renewal period of the NAT port mapping, in seconds. + * If the client machine crashes, suffers a power failure, is disconnected from + * the network, or suffers some other unfortunate demise which causes it to vanish + * unexpectedly without explicitly removing its NAT port mappings, then the NAT gateway + * will garbage-collect old stale NAT port mappings when their lifetime expires. + * Requesting a short TTL causes such orphaned mappings to be garbage-collected + * more promptly, but consumes system resources and network bandwidth with + * frequent renewal packets to keep the mapping from expiring. + * Requesting a long TTL is more efficient on the network, but in the event of the + * client vanishing, stale NAT port mappings will not be garbage-collected as quickly. + * Most clients should pass 0 to use a system-wide default value. + * + * callBack: The function to be called when the port mapping request succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + * + * If you don't actually want a port mapped, and are just calling the API + * because you want to find out the NAT's external IP address (e.g. for UI + * display) then pass zero for protocol, internalPort, externalPort and ttl. + */ + +DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, /* TCP and/or UDP */ + uint16_t internalPort, /* network byte order */ + uint16_t externalPort, /* network byte order */ + uint32_t ttl, /* time to live in seconds */ + DNSServiceNATPortMappingReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* General Utility Functions +* +*********************************************************************************************/ + +/* DNSServiceConstructFullName() + * + * Concatenate a three-part domain name (as returned by the above callbacks) into a + * properly-escaped full domain name. Note that callbacks in the above functions ALREADY ESCAPE + * strings where necessary. + * + * Parameters: + * + * fullName: A pointer to a buffer that where the resulting full domain name is to be written. + * The buffer must be kDNSServiceMaxDomainName (1009) bytes in length to + * accommodate the longest legal domain name without buffer overrun. + * + * service: The service name - any dots or backslashes must NOT be escaped. + * May be NULL (to construct a PTR record name, e.g. + * "_ftp._tcp.apple.com."). + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). + * + * domain: The domain name, e.g. "apple.com.". Literal dots or backslashes, + * if any, must be escaped, e.g. "1st\. Floor.apple.com." + * + * return value: Returns kDNSServiceErr_NoError (0) on success, kDNSServiceErr_BadParam on error. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceConstructFullName +( + char * const fullName, + const char * const service, /* may be NULL */ + const char * const regtype, + const char * const domain +); + + +/********************************************************************************************* +* +* TXT Record Construction Functions +* +*********************************************************************************************/ + +/* + * A typical calling sequence for TXT record construction is something like: + * + * Client allocates storage for TXTRecord data (e.g. declare buffer on the stack) + * TXTRecordCreate(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * ... + * DNSServiceRegister( ... TXTRecordGetLength(), TXTRecordGetBytesPtr() ... ); + * TXTRecordDeallocate(); + * Explicitly deallocate storage for TXTRecord data (if not allocated on the stack) + */ + + +/* TXTRecordRef + * + * Opaque internal data type. + * Note: Represents a DNS-SD TXT record. + */ + +typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignment; } TXTRecordRef; + + +/* TXTRecordCreate() + * + * Creates a new empty TXTRecordRef referencing the specified storage. + * + * If the buffer parameter is NULL, or the specified storage size is not + * large enough to hold a key subsequently added using TXTRecordSetValue(), + * then additional memory will be added as needed using malloc(). + * + * On some platforms, when memory is low, malloc() may fail. In this + * case, TXTRecordSetValue() will return kDNSServiceErr_NoMemory, and this + * error condition will need to be handled as appropriate by the caller. + * + * You can avoid the need to handle this error condition if you ensure + * that the storage you initially provide is large enough to hold all + * the key/value pairs that are to be added to the record. + * The caller can precompute the exact length required for all of the + * key/value pairs to be added, or simply provide a fixed-sized buffer + * known in advance to be large enough. + * A no-value (key-only) key requires (1 + key length) bytes. + * A key with empty value requires (1 + key length + 1) bytes. + * A key with non-empty value requires (1 + key length + 1 + value length). + * For most applications, DNS-SD TXT records are generally + * less than 100 bytes, so in most cases a simple fixed-sized + * 256-byte buffer will be more than sufficient. + * Recommended size limits for DNS-SD TXT Records are discussed in + * + * + * Note: When passing parameters to and from these TXT record APIs, + * the key name does not include the '=' character. The '=' character + * is the separator between the key and value in the on-the-wire + * packet format; it is not part of either the key or the value. + * + * txtRecord: A pointer to an uninitialized TXTRecordRef. + * + * bufferLen: The size of the storage provided in the "buffer" parameter. + * + * buffer: Optional caller-supplied storage used to hold the TXTRecord data. + * This storage must remain valid for as long as + * the TXTRecordRef. + */ + +void DNSSD_API TXTRecordCreate +( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer +); + + +/* TXTRecordDeallocate() + * + * Releases any resources allocated in the course of preparing a TXT Record + * using TXTRecordCreate()/TXTRecordSetValue()/TXTRecordRemoveValue(). + * Ownership of the buffer provided in TXTRecordCreate() returns to the client. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + */ + +void DNSSD_API TXTRecordDeallocate +( + TXTRecordRef *txtRecord +); + + +/* TXTRecordSetValue() + * + * Adds a key (optionally with value) to a TXTRecordRef. If the "key" already + * exists in the TXTRecordRef, then the current value will be replaced with + * the new value. + * Keys may exist in four states with respect to a given TXT record: + * - Absent (key does not appear at all) + * - Present with no value ("key" appears alone) + * - Present with empty value ("key=" appears in TXT record) + * - Present with non-empty value ("key=value" appears in TXT record) + * For more details refer to "Data Syntax for DNS-SD TXT Records" in + * + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A null-terminated string which only contains printable ASCII + * values (0x20-0x7E), excluding '=' (0x3D). Keys should be + * 9 characters or fewer (not counting the terminating null). + * + * valueSize: The size of the value. + * + * value: Any binary value. For values that represent + * textual data, UTF-8 is STRONGLY recommended. + * For values that represent textual data, valueSize + * should NOT include the terminating null (if any) + * at the end of the string. + * If NULL, then "key" will be added with no value. + * If non-NULL but valueSize is zero, then "key=" will be + * added with empty value. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_Invalid if the "key" string contains + * illegal characters. + * Returns kDNSServiceErr_NoMemory if adding this key would + * exceed the available storage. + */ + +DNSServiceErrorType DNSSD_API TXTRecordSetValue +( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, /* may be zero */ + const void *value /* may be NULL */ +); + + +/* TXTRecordRemoveValue() + * + * Removes a key from a TXTRecordRef. The "key" must be an + * ASCII string which exists in the TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A key name which exists in the TXTRecordRef. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoSuchKey if the "key" does not + * exist in the TXTRecordRef. + */ + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue +( + TXTRecordRef *txtRecord, + const char *key +); + + +/* TXTRecordGetLength() + * + * Allows you to determine the length of the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns the size of the raw bytes inside a TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + * Returns 0 if the TXTRecordRef is empty. + */ + +uint16_t DNSSD_API TXTRecordGetLength +( + const TXTRecordRef *txtRecord +); + + +/* TXTRecordGetBytesPtr() + * + * Allows you to retrieve a pointer to the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns a pointer to the raw bytes inside the TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + */ + +const void * DNSSD_API TXTRecordGetBytesPtr +( + const TXTRecordRef *txtRecord +); + + +/********************************************************************************************* +* +* TXT Record Parsing Functions +* +*********************************************************************************************/ + +/* + * A typical calling sequence for TXT record parsing is something like: + * + * Receive TXT record data in DNSServiceResolve() callback + * if (TXTRecordContainsKey(txtLen, txtRecord, "key")) then do something + * val1ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key1", &len1); + * val2ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key2", &len2); + * ... + * memcpy(myval1, val1ptr, len1); + * memcpy(myval2, val2ptr, len2); + * ... + * return; + * + * If you wish to retain the values after return from the DNSServiceResolve() + * callback, then you need to copy the data to your own storage using memcpy() + * or similar, as shown in the example above. + * + * If for some reason you need to parse a TXT record you built yourself + * using the TXT record construction functions above, then you can do + * that using TXTRecordGetLength and TXTRecordGetBytesPtr calls: + * TXTRecordGetValue(TXTRecordGetLength(x), TXTRecordGetBytesPtr(x), key, &len); + * + * Most applications only fetch keys they know about from a TXT record and + * ignore the rest. + * However, some debugging tools wish to fetch and display all keys. + * To do that, use the TXTRecordGetCount() and TXTRecordGetItemAtIndex() calls. + */ + +/* TXTRecordContainsKey() + * + * Allows you to determine if a given TXT Record contains a specified key. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * return value: Returns 1 if the TXT Record contains the specified key. + * Otherwise, it returns 0. + */ + +int DNSSD_API TXTRecordContainsKey +( + uint16_t txtLen, + const void *txtRecord, + const char *key +); + + +/* TXTRecordGetValuePtr() + * + * Allows you to retrieve the value for a given key from a TXT Record. + * + * txtLen: The size of the received TXT Record + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * return value: Returns NULL if the key does not exist in this TXT record, + * or exists with no value (to differentiate between + * these two cases use TXTRecordContainsKey()). + * Returns pointer to location within TXT Record bytes + * if the key exists with empty or non-empty value. + * For empty value, valueLen will be zero. + * For non-empty value, valueLen will be length of value data. + */ + +const void * DNSSD_API TXTRecordGetValuePtr +( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen +); + + +/* TXTRecordGetCount() + * + * Returns the number of keys stored in the TXT Record. The count + * can be used with TXTRecordGetItemAtIndex() to iterate through the keys. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * return value: Returns the total number of keys in the TXT Record. + * + */ + +uint16_t DNSSD_API TXTRecordGetCount +( + uint16_t txtLen, + const void *txtRecord +); + + +/* TXTRecordGetItemAtIndex() + * + * Allows you to retrieve a key name and value pointer, given an index into + * a TXT Record. Legal index values range from zero to TXTRecordGetCount()-1. + * It's also possible to iterate through keys in a TXT record by simply + * calling TXTRecordGetItemAtIndex() repeatedly, beginning with index zero + * and increasing until TXTRecordGetItemAtIndex() returns kDNSServiceErr_Invalid. + * + * On return: + * For keys with no value, *value is set to NULL and *valueLen is zero. + * For keys with empty value, *value is non-NULL and *valueLen is zero. + * For keys with non-empty value, *value is non-NULL and *valueLen is non-zero. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * itemIndex: An index into the TXT Record. + * + * keyBufLen: The size of the string buffer being supplied. + * + * key: A string buffer used to store the key name. + * On return, the buffer contains a null-terminated C string + * giving the key name. DNS-SD TXT keys are usually + * 9 characters or fewer. To hold the maximum possible + * key name, the buffer should be 256 bytes long. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * value: On output, *value is set to point to location within TXT + * Record bytes that holds the value data. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if keyBufLen is too short. + * Returns kDNSServiceErr_Invalid if index is greater than + * TXTRecordGetCount()-1. + */ + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex +( + uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value +); + +#if _DNS_SD_LIBDISPATCH +/* + * DNSServiceSetDispatchQueue + * + * Allows you to schedule a DNSServiceRef on a serial dispatch queue for receiving asynchronous + * callbacks. It's the clients responsibility to ensure that the provided dispatch queue is running. + * + * A typical application that uses CFRunLoopRun or dispatch_main on its main thread will + * usually schedule DNSServiceRefs on its main queue (which is always a serial queue) + * using "DNSServiceSetDispatchQueue(sdref, dispatch_get_main_queue());" + * + * If there is any error during the processing of events, the application callback will + * be called with an error code. For shared connections, each subordinate DNSServiceRef + * will get its own error callback. Currently these error callbacks only happen + * if the daemon is manually terminated or crashes, and the error + * code in this case is kDNSServiceErr_ServiceNotRunning. The application must call + * DNSServiceRefDeallocate to free the DNSServiceRef when it gets such an error code. + * These error callbacks are rare and should not normally happen on customer machines, + * but application code should be written defensively to handle such error callbacks + * gracefully if they occur. + * + * After using DNSServiceSetDispatchQueue on a DNSServiceRef, calling DNSServiceProcessResult + * on the same DNSServiceRef will result in undefined behavior and should be avoided. + * + * Once the application successfully schedules a DNSServiceRef on a serial dispatch queue using + * DNSServiceSetDispatchQueue, it cannot remove the DNSServiceRef from the dispatch queue, or use + * DNSServiceSetDispatchQueue a second time to schedule the DNSServiceRef onto a different serial dispatch + * queue. Once scheduled onto a dispatch queue a DNSServiceRef will deliver events to that queue until + * the application no longer requires that operation and terminates it using DNSServiceRefDeallocate. + * + * service: DNSServiceRef that was allocated and returned to the application, when the + * application calls one of the DNSService API. + * + * queue: dispatch queue where the application callback will be scheduled + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if it cannot create a dispatch source + * Returns kDNSServiceErr_BadParam if the service param is invalid or the + * queue param is invalid + */ + +DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue +( + DNSServiceRef service, + dispatch_queue_t queue +); +#endif //_DNS_SD_LIBDISPATCH + +#if !defined(_WIN32) +typedef void (DNSSD_API *DNSServiceSleepKeepaliveReply) +( + DNSServiceRef sdRef, + DNSServiceErrorType errorCode, + void *context +); +DNSServiceErrorType DNSSD_API DNSServiceSleepKeepalive +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + int fd, + unsigned int timeout, + DNSServiceSleepKeepaliveReply callBack, + void *context +); +#endif + +#ifdef APPLE_OSX_mDNSResponder +/* DNSServiceCreateDelegateConnection() + * + * Create a delegate connection to the daemon allowing efficient registration of + * multiple individual records. + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * pid : Process ID of the delegate + * + * uuid: UUID of the delegate + * + * Note that only one of the two arguments (pid or uuid) can be specified. If pid + * is zero, uuid will be assumed to be a valid value; otherwise pid will be used. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). kDNSServiceErr_NotAuth is + * returned to indicate that the calling process does not have entitlements + * to use this API. + */ +DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid); +#endif + +#ifdef __APPLE_API_PRIVATE + +#define kDNSServiceCompPrivateDNS "PrivateDNS" +#define kDNSServiceCompMulticastDNS "MulticastDNS" + +#endif //__APPLE_API_PRIVATE + +/* Some C compiler cleverness. We can make the compiler check certain things for us, + * and report errors at compile-time if anything is wrong. The usual way to do this would + * be to use a run-time "if" statement or the conventional run-time "assert" mechanism, but + * then you don't find out what's wrong until you run the software. This way, if the assertion + * condition is false, the array size is negative, and the complier complains immediately. + */ + +struct CompileTimeAssertionChecks_DNS_SD +{ + char assert0[(sizeof(union _TXTRecordRef_t) == 16) ? 1 : -1]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _DNS_SD_H */