gix-tempfile-15.0.0/.cargo_vcs_info.json0000644000000001520000000000100135070ustar { "git": { "sha1": "3f7e8ee2c5107aec009eada1a05af7941da9cb4d" }, "path_in_vcs": "gix-tempfile" }gix-tempfile-15.0.0/Cargo.lock0000644000000272500000000000100114720ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "dashmap" version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown", "lock_api", "once_cell", "parking_lot_core", ] [[package]] name = "document-features" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" dependencies = [ "litrs", ] [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" dependencies = [ "serde", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "gix-features" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e0eb9efdf96c35c0bed7596d1bef2d4ce6360a1d09738001f9d3e402aa7ba3e" dependencies = [ "gix-hash", "gix-trace", "gix-utils", "libc", ] [[package]] name = "gix-fs" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34740384d8d763975858fa2c176b68652a6fcc09f616e24e3ce967b0d370e4d8" dependencies = [ "fastrand", "gix-features", "gix-utils", ] [[package]] name = "gix-hash" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "952c3a29f1bc1007cc901abce7479943abfa42016db089de33d0a4fa3c85bfe8" dependencies = [ "faster-hex", "thiserror", ] [[package]] name = "gix-tempfile" version = "15.0.0" dependencies = [ "dashmap", "document-features", "gix-fs", "libc", "once_cell", "parking_lot", "signal-hook", "signal-hook-registry", "tempfile", ] [[package]] name = "gix-trace" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" [[package]] name = "gix-utils" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" dependencies = [ "fastrand", "unicode-normalization", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litrs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "thiserror" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" gix-tempfile-15.0.0/Cargo.toml0000644000000075640000000000100115230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65" name = "gix-tempfile" version = "15.0.0" authors = ["Sebastian Thiel "] build = false include = [ "src/**/*", "LICENSE-*", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A tempfile implementation with a global registry to assure cleanup" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/GitoxideLabs/gitoxide" [package.metadata.docs.rs] all-features = true features = ["document-features"] [lib] name = "gix_tempfile" path = "src/lib.rs" test = true doctest = false [dependencies.dashmap] version = "6.0.1" optional = true [dependencies.document-features] version = "0.2.0" optional = true [dependencies.gix-fs] version = "^0.12.0" [dependencies.once_cell] version = "1.8.0" features = [ "race", "std", ] default-features = false [dependencies.parking_lot] version = "0.12.1" [dependencies.signal-hook] version = "0.3.9" optional = true default-features = false [dependencies.signal-hook-registry] version = "1.4.0" optional = true [dependencies.tempfile] version = "3.10.0" [features] default = ["hp-hashmap"] hp-hashmap = ["dep:dashmap"] signals = [ "dep:signal-hook", "dep:signal-hook-registry", ] [target."cfg(not(windows))".dependencies.libc] version = "0.2.98" default-features = false [lints.clippy] bool_to_int_with_if = "allow" borrow_as_ptr = "allow" cast_lossless = "allow" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" checked_conversions = "allow" copy_iterator = "allow" default_trait_access = "allow" doc_markdown = "allow" empty_docs = "allow" enum_glob_use = "allow" explicit_deref_methods = "allow" explicit_into_iter_loop = "allow" explicit_iter_loop = "allow" filter_map_next = "allow" fn_params_excessive_bools = "allow" from_iter_instead_of_collect = "allow" if_not_else = "allow" ignored_unit_patterns = "allow" implicit_clone = "allow" inconsistent_struct_constructor = "allow" inefficient_to_string = "allow" inline_always = "allow" items_after_statements = "allow" iter_not_returning_iterator = "allow" iter_without_into_iter = "allow" manual_assert = "allow" manual_is_variant_and = "allow" manual_let_else = "allow" manual_string_new = "allow" many_single_char_names = "allow" match_bool = "allow" match_same_arms = "allow" match_wild_err_arm = "allow" match_wildcard_for_single_variants = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" mut_mut = "allow" naive_bytecount = "allow" needless_for_each = "allow" needless_pass_by_value = "allow" needless_raw_string_hashes = "allow" no_effect_underscore_binding = "allow" option_option = "allow" range_plus_one = "allow" redundant_else = "allow" return_self_not_must_use = "allow" should_panic_without_expect = "allow" similar_names = "allow" single_match_else = "allow" stable_sort_primitive = "allow" struct_excessive_bools = "allow" struct_field_names = "allow" too_long_first_doc_paragraph = "allow" too_many_lines = "allow" transmute_ptr_to_ptr = "allow" trivially_copy_pass_by_ref = "allow" unnecessary_join = "allow" unnecessary_wraps = "allow" unreadable_literal = "allow" unused_self = "allow" used_underscore_binding = "allow" wildcard_imports = "allow" [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] gix-tempfile-15.0.0/Cargo.toml.orig000064400000000000000000000036301046102023000151720ustar 00000000000000lints.workspace = true [package] name = "gix-tempfile" version = "15.0.0" repository = "https://github.com/GitoxideLabs/gitoxide" license = "MIT OR Apache-2.0" description = "A tempfile implementation with a global registry to assure cleanup" authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*", "README.md"] rust-version = "1.65" [[example]] name = "delete-tempfiles-on-sigterm" path = "examples/delete-tempfiles-on-sigterm.rs" required-features = ["signals"] [[example]] name = "delete-tempfiles-on-sigterm-interactive" path = "examples/delete-tempfiles-on-sigterm-interactive.rs" required-features = ["signals"] [[example]] name = "try-deadlock-on-cleanup" path = "examples/try-deadlock-on-cleanup.rs" required-features = ["signals"] [lib] doctest = false test = true [dependencies] gix-fs = { version = "^0.12.0", path = "../gix-fs" } parking_lot = "0.12.1" dashmap = { version = "6.0.1", optional = true } once_cell = { version = "1.8.0", default-features = false, features = ["race", "std"] } tempfile = "3.10.0" signal-hook = { version = "0.3.9", default-features = false, optional = true } signal-hook-registry = { version = "1.4.0", optional = true } document-features = { version = "0.2.0", optional = true } [features] default = ["hp-hashmap"] ## Support for signal handlers to cleanup tempfiles when a signal is received. signals = ["dep:signal-hook", "dep:signal-hook-registry"] ## Use a high-performance concurrent hashmap implementation for optimal performance with less contention if there are many tempfiles opening at the same time. ## It also allows to potentially cleanup more tempfiles in a signal handler as the hashmap can be partially locked. hp-hashmap = ["dep:dashmap"] [target.'cfg(not(windows))'.dependencies] libc = { version = "0.2.98", default-features = false } [package.metadata.docs.rs] all-features = true features = ["document-features"] gix-tempfile-15.0.0/LICENSE-APACHE000064400000000000000000000247461046102023000142420ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gix-tempfile-15.0.0/LICENSE-MIT000064400000000000000000000017771046102023000137510ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-tempfile-15.0.0/README.md000064400000000000000000000014601046102023000135610ustar 00000000000000Use tempfiles to minimize the risk of resource leakage when preparing to overwrite or create a file with new content in a signal-safe way, making the change atomic. Tempfiles can also be used as locks as only one tempfile can exist at a given path at a time. * [x] registered temporary files which are deleted automatically as the process terminates or on drop * [x] write to temporary file and persist it under new name * [x] close temporary files to convert them into a marker while saving system resources * [x] mark paths with a closed temporary file * [x] persist temporary files to prevent them from perishing. * [x] signal-handler integration with `gix` to clean lockfiles before the process is aborted. * [x] use a temporary file transparently due thanks to implementations of `std::io` traits gix-tempfile-15.0.0/src/forksafe.rs000064400000000000000000000073021046102023000152400ustar 00000000000000use std::path::Path; use tempfile::{NamedTempFile, TempPath}; use crate::{handle, AutoRemove}; enum TempfileOrTemppath { Tempfile(NamedTempFile), Temppath(TempPath), } pub(crate) struct ForksafeTempfile { inner: TempfileOrTemppath, cleanup: AutoRemove, pub owning_process_id: u32, } impl ForksafeTempfile { pub fn new(tempfile: NamedTempFile, cleanup: AutoRemove, mode: handle::Mode) -> Self { use handle::Mode::*; ForksafeTempfile { inner: match mode { Closed => TempfileOrTemppath::Temppath(tempfile.into_temp_path()), Writable => TempfileOrTemppath::Tempfile(tempfile), }, cleanup, owning_process_id: std::process::id(), } } } impl ForksafeTempfile { pub fn as_mut_tempfile(&mut self) -> Option<&mut NamedTempFile> { match &mut self.inner { TempfileOrTemppath::Tempfile(file) => Some(file), TempfileOrTemppath::Temppath(_) => None, } } pub fn close(self) -> Self { if let TempfileOrTemppath::Tempfile(file) = self.inner { ForksafeTempfile { inner: TempfileOrTemppath::Temppath(file.into_temp_path()), cleanup: self.cleanup, owning_process_id: self.owning_process_id, } } else { self } } pub fn persist(self, path: impl AsRef) -> Result, (std::io::Error, Self)> { self.persist_inner(path.as_ref()) } fn persist_inner(mut self, path: &Path) -> Result, (std::io::Error, Self)> { match self.inner { TempfileOrTemppath::Tempfile(file) => match file.persist(path) { Ok(file) => Ok(Some(file)), Err(err) => Err((err.error, { self.inner = TempfileOrTemppath::Tempfile(err.file); self })), }, TempfileOrTemppath::Temppath(temppath) => match temppath.persist(path) { Ok(_) => Ok(None), Err(err) => Err((err.error, { self.inner = TempfileOrTemppath::Temppath(err.path); self })), }, } } pub fn into_temppath(self) -> TempPath { match self.inner { TempfileOrTemppath::Tempfile(file) => file.into_temp_path(), TempfileOrTemppath::Temppath(path) => path, } } pub fn into_tempfile(self) -> Option { match self.inner { TempfileOrTemppath::Tempfile(file) => Some(file), TempfileOrTemppath::Temppath(_) => None, } } pub fn drop_impl(self) { let file_path = match self.inner { TempfileOrTemppath::Tempfile(file) => file.path().to_owned(), TempfileOrTemppath::Temppath(path) => path.to_path_buf(), }; let parent_directory = file_path.parent().expect("every tempfile has a parent directory"); self.cleanup.execute_best_effort(parent_directory); } pub fn drop_without_deallocation(self) { use std::io::Write; let temppath = match self.inner { TempfileOrTemppath::Tempfile(file) => { let (mut file, temppath) = file.into_parts(); file.flush().ok(); temppath } TempfileOrTemppath::Temppath(path) => path, }; std::fs::remove_file(&temppath).ok(); std::mem::forget( self.cleanup .execute_best_effort(temppath.parent().expect("every file has a directory")), ); std::mem::forget(temppath); // leak memory to prevent deallocation } } gix-tempfile-15.0.0/src/handle.rs000064400000000000000000000326061046102023000147000ustar 00000000000000//! #![allow(clippy::empty_docs)] use std::{io, path::Path}; use tempfile::{NamedTempFile, TempPath}; use crate::{AutoRemove, ContainingDirectory, ForksafeTempfile, Handle, NEXT_MAP_INDEX, REGISTRY}; /// Marker to signal the Registration is an open file able to be written to. #[derive(Debug)] pub struct Writable; /// Marker to signal the Registration is a closed file that consumes no additional process resources. /// /// It can't ever be written to unless reopened after persisting it. #[derive(Debug)] pub struct Closed; pub(crate) enum Mode { Writable, Closed, } /// Utilities impl Handle<()> { fn at_path( path: &Path, directory: ContainingDirectory, cleanup: AutoRemove, mode: Mode, permissions: Option, ) -> io::Result { let tempfile = { let mut builder = tempfile::Builder::new(); let dot_ext_storage; match path.file_stem() { Some(stem) => builder.prefix(stem), None => builder.prefix(""), }; if let Some(ext) = path.extension() { dot_ext_storage = format!(".{}", ext.to_string_lossy()); builder.suffix(&dot_ext_storage); } if let Some(permissions) = permissions { builder.permissions(permissions); } let parent_dir = path.parent().expect("parent directory is present"); let parent_dir = directory.resolve(parent_dir)?; ForksafeTempfile::new(builder.rand_bytes(0).tempfile_in(parent_dir)?, cleanup, mode) }; let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst); expect_none(REGISTRY.insert(id, Some(tempfile))); Ok(id) } fn new_writable_inner( containing_directory: &Path, directory: ContainingDirectory, cleanup: AutoRemove, mode: Mode, ) -> io::Result { let containing_directory = directory.resolve(containing_directory)?; let id = NEXT_MAP_INDEX.fetch_add(1, std::sync::atomic::Ordering::SeqCst); expect_none(REGISTRY.insert( id, Some(ForksafeTempfile::new( NamedTempFile::new_in(containing_directory)?, cleanup, mode, )), )); Ok(id) } } /// Creation and ownership transfer impl Handle { /// Create a registered tempfile at the given `path`, where `path` includes the desired filename and close it immediately. /// /// Depending on the `directory` configuration, intermediate directories will be created, and depending on `cleanup` empty /// intermediate directories will be removed. /// /// ### Warning of potential leaks /// /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. pub fn at(path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result { Ok(Handle { id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, None)?, _marker: Default::default(), }) } /// Like [`at`](Self::at()), but with support for filesystem `permissions`. pub fn at_with_permissions( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, permissions: std::fs::Permissions, ) -> io::Result { Ok(Handle { id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Closed, Some(permissions))?, _marker: Default::default(), }) } /// Take ownership of the temporary file path, which deletes it when dropped without persisting it beforehand. /// /// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option` pub fn take(self) -> Option { let res = REGISTRY.remove(&self.id); std::mem::forget(self); res.and_then(|(_k, v)| v.map(ForksafeTempfile::into_temppath)) } } /// Creation and ownership transfer impl Handle { /// Create a registered tempfile at the given `path`, where `path` includes the desired filename. /// /// Depending on the `directory` configuration, intermediate directories will be created, and depending on `cleanup` empty /// intermediate directories will be removed. /// /// ### Warning of potential leaks /// /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. pub fn at(path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove) -> io::Result { Ok(Handle { id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, None)?, _marker: Default::default(), }) } /// Like [`at`](Self::at()), but with support for filesystem `permissions`. pub fn at_with_permissions( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, permissions: std::fs::Permissions, ) -> io::Result { Ok(Handle { id: Handle::<()>::at_path(path.as_ref(), directory, cleanup, Mode::Writable, Some(permissions))?, _marker: Default::default(), }) } /// Create a registered tempfile within `containing_directory` with a name that won't clash, and clean it up as specified with `cleanup`. /// Control how to deal with intermediate directories with `directory`. /// The temporary file is opened and can be written to using the [`with_mut()`][Handle::with_mut()] method. /// /// ### Warning of potential leaks /// /// Without [signal handlers](crate::signal) installed, tempfiles will remain once a termination /// signal is encountered as destructors won't run. See [the top-level documentation](crate) for more. pub fn new( containing_directory: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, ) -> io::Result { Ok(Handle { id: Handle::<()>::new_writable_inner(containing_directory.as_ref(), directory, cleanup, Mode::Writable)?, _marker: Default::default(), }) } /// Take ownership of the temporary file. /// /// It's a theoretical possibility that the file isn't present anymore if signals interfere, hence the `Option` pub fn take(self) -> Option { let res = REGISTRY.remove(&self.id); std::mem::forget(self); res.and_then(|(_k, v)| v.map(|v| v.into_tempfile().expect("correct runtime typing"))) } /// Close the underlying file handle but keep track of the temporary file as before for automatic cleanup. /// /// This saves system resources in situations where one opens a tempfile file at a time, writes a new value, and closes /// it right after to perform more updates of this kind in other tempfiles. When all succeed, they can be renamed one after /// another. pub fn close(self) -> std::io::Result> { match REGISTRY.remove(&self.id) { Some((id, Some(t))) => { std::mem::forget(self); expect_none(REGISTRY.insert(id, Some(t.close()))); Ok(Handle:: { id, _marker: Default::default(), }) } None | Some((_, None)) => Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("The tempfile with id {} wasn't available anymore", self.id), )), } } } /// Mutation impl Handle { /// Obtain a mutable handler to the underlying named tempfile and call `f(&mut named_tempfile)` on it. /// /// Note that for the duration of the call, a signal interrupting the operation will cause the tempfile not to be cleaned up /// as it is not visible anymore to the signal handler. /// /// # Assumptions /// The caller must assure that the signal handler for cleanup will be followed by an abort call so that /// this code won't run again on a removed instance. An error will occur otherwise. pub fn with_mut(&mut self, once: impl FnOnce(&mut NamedTempFile) -> T) -> std::io::Result { match REGISTRY.remove(&self.id) { Some((id, Some(mut t))) => { let res = once(t.as_mut_tempfile().expect("correct runtime typing")); expect_none(REGISTRY.insert(id, Some(t))); Ok(res) } None | Some((_, None)) => Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("The tempfile with id {} wasn't available anymore", self.id), )), } } } mod io_impls { use std::{io, io::SeekFrom}; use super::{Handle, Writable}; impl io::Write for Handle { fn write(&mut self, buf: &[u8]) -> io::Result { self.with_mut(|f| f.write(buf))? } fn flush(&mut self) -> io::Result<()> { self.with_mut(io::Write::flush)? } } impl io::Seek for Handle { fn seek(&mut self, pos: SeekFrom) -> io::Result { self.with_mut(|f| f.seek(pos))? } } impl io::Read for Handle { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.with_mut(|f| f.read(buf))? } } } /// pub mod persist { use std::path::Path; use crate::{ handle::{expect_none, Closed, Writable}, Handle, REGISTRY, }; mod error { use std::fmt::{self, Debug, Display}; use crate::Handle; /// The error returned by various [`persist(…)`][Handle::persist()] methods #[derive(Debug)] pub struct Error { /// The io error that prevented the attempt to succeed pub error: std::io::Error, /// The registered handle to the tempfile which couldn't be persisted. pub handle: Handle, } impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.error, f) } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.error.source() } } } pub use error::Error; impl Handle { /// Persist this tempfile to replace the file at the given `path` if necessary, in a way that recovers the original instance /// on error or returns the open now persisted former tempfile. /// Note that it might not exist anymore if an interrupt handler managed to steal it and allowed the program to return to /// its normal flow. pub fn persist(self, path: impl AsRef) -> Result, Error> { let res = REGISTRY.remove(&self.id); match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) { Some(Ok(Some(file))) => { std::mem::forget(self); Ok(Some(file)) } None => { std::mem::forget(self); Ok(None) } Some(Err((err, tempfile))) => { expect_none(REGISTRY.insert(self.id, Some(tempfile))); Err(Error:: { error: err, handle: self, }) } Some(Ok(None)) => unreachable!("no open files in an open handle"), } } } impl Handle { /// Persist this tempfile to replace the file at the given `path` if necessary, in a way that recovers the original instance /// on error. pub fn persist(self, path: impl AsRef) -> Result<(), Error> { let res = REGISTRY.remove(&self.id); match res.and_then(|(_k, v)| v.map(|v| v.persist(path))) { None | Some(Ok(None)) => { std::mem::forget(self); Ok(()) } Some(Err((err, tempfile))) => { expect_none(REGISTRY.insert(self.id, Some(tempfile))); Err(Error:: { error: err, handle: self, }) } Some(Ok(Some(_file))) => unreachable!("no open files in a closed handle"), } } } } impl ContainingDirectory { fn resolve(self, dir: &Path) -> std::io::Result<&Path> { match self { ContainingDirectory::Exists => Ok(dir), ContainingDirectory::CreateAllRaceProof(retries) => crate::create_dir::all(dir, retries), } } } fn expect_none(v: Option) { assert!( v.is_none(), "there should never be conflicts or old values as ids are never reused." ); } impl Drop for Handle { fn drop(&mut self) { if let Some((_id, Some(tempfile))) = REGISTRY.remove(&self.id) { tempfile.drop_impl(); } } } gix-tempfile-15.0.0/src/lib.rs000064400000000000000000000207531046102023000142130ustar 00000000000000//! git-style registered tempfiles that are removed upon typical termination signals. //! //! To register signal handlers in a typical application that doesn't have its own, call //! [`gix_tempfile::signal::setup(Default::default())`][signal::setup()] before creating the first tempfile. //! //! Signal handlers are powered by [`signal-hook`] to get notified when the application is told to shut down //! to assure tempfiles are deleted. The deletion is filtered by process id to allow forks to have their own //! set of tempfiles that won't get deleted when the parent process exits. //! //! ### Initial Setup //! //! As no handlers for `TERMination` are installed, it is required to call [`signal::setup()`] before creating //! the first tempfile. This also allows to control how this crate integrates with //! other handlers under application control. //! //! As a general rule of thumb, use `Default::default()` as argument to emulate the default behaviour and //! abort the process after cleaning temporary files. Read more about options in [`signal::handler::Mode`]. //! //! # Limitations //! //! ## Tempfiles might remain on disk //! //! * Uninterruptible signals are received like `SIGKILL` //! * The application is performing a write operation on the tempfile when a signal arrives, preventing this tempfile to be removed, //! but not others. Any other operation dealing with the tempfile suffers from the same issue. //! //! [`signal-hook`]: https://docs.rs/signal-hook //! //! ## Feature Flags #![cfg_attr( all(doc, feature = "document-features"), doc = ::document_features::document_features!() )] #![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg, doc_auto_cfg))] #![deny(missing_docs, rust_2018_idioms, unsafe_code)] use std::{ io, marker::PhantomData, path::{Path, PathBuf}, sync::atomic::AtomicUsize, }; use once_cell::sync::Lazy; #[cfg(feature = "hp-hashmap")] type HashMap = dashmap::DashMap; #[cfg(not(feature = "hp-hashmap"))] mod hashmap { use std::collections::HashMap; use parking_lot::Mutex; // TODO(performance): use the `gix-hashtable` slot-map once available. It seems quite fast already though, so experiment. pub struct Concurrent { inner: Mutex>, } impl Default for Concurrent where K: Eq + std::hash::Hash, { fn default() -> Self { Concurrent { inner: Default::default(), } } } impl Concurrent where K: Eq + std::hash::Hash + Clone, { pub fn insert(&self, key: K, value: V) -> Option { self.inner.lock().insert(key, value) } pub fn remove(&self, key: &K) -> Option<(K, V)> { self.inner.lock().remove(key).map(|v| (key.clone(), v)) } pub fn for_each(&self, cb: F) where Self: Sized, F: FnMut(&mut V), { if let Some(mut guard) = self.inner.try_lock() { guard.values_mut().for_each(cb); } } } } #[cfg(not(feature = "hp-hashmap"))] type HashMap = hashmap::Concurrent; pub use gix_fs::dir::{create as create_dir, remove as remove_dir}; /// signal setup and reusable handlers. #[cfg(feature = "signals")] pub mod signal; mod forksafe; use forksafe::ForksafeTempfile; pub mod handle; use crate::handle::{Closed, Writable}; /// pub mod registry; static NEXT_MAP_INDEX: AtomicUsize = AtomicUsize::new(0); static REGISTRY: Lazy>> = Lazy::new(|| { #[cfg(feature = "signals")] if signal::handler::MODE.load(std::sync::atomic::Ordering::SeqCst) != signal::handler::Mode::None as usize { for sig in signal_hook::consts::TERM_SIGNALS { // SAFETY: handlers are considered unsafe because a lot can go wrong. See `cleanup_tempfiles()` for details on safety. #[allow(unsafe_code)] unsafe { #[cfg(not(windows))] { signal_hook_registry::register_sigaction(*sig, signal::handler::cleanup_tempfiles_nix) } #[cfg(windows)] { signal_hook::low_level::register(*sig, signal::handler::cleanup_tempfiles_windows) } } .expect("signals can always be installed"); } } HashMap::default() }); /// A type expressing the ways we can deal with directories containing a tempfile. #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] pub enum ContainingDirectory { /// Assume the directory for the tempfile exists and cause failure if it doesn't Exists, /// Create the directory recursively with the given amount of retries in a way that is somewhat race resistant /// depending on the amount of retries. CreateAllRaceProof(create_dir::Retries), } /// A type expressing the ways we cleanup after ourselves to remove resources we created. /// Note that cleanup has no effect if the tempfile is persisted. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum AutoRemove { /// Remove the temporary file after usage if it wasn't persisted. Tempfile, /// Remove the temporary file as well the containing directories if they are empty until the given `directory`. TempfileAndEmptyParentDirectoriesUntil { /// The directory which shall not be removed even if it is empty. boundary_directory: PathBuf, }, } impl AutoRemove { fn execute_best_effort(self, directory_to_potentially_delete: &Path) -> Option { match self { AutoRemove::Tempfile => None, AutoRemove::TempfileAndEmptyParentDirectoriesUntil { boundary_directory } => { remove_dir::empty_upward_until_boundary(directory_to_potentially_delete, &boundary_directory).ok(); Some(boundary_directory) } } } } /// A registered temporary file which will delete itself on drop or if the program is receiving signals that /// should cause it to terminate. /// /// # Note /// /// Signals interrupting the calling thread right after taking ownership of the registered tempfile /// will cause all but this tempfile to be removed automatically. In the common case it will persist on disk as destructors /// were not called or didn't get to remove the file. /// /// In the best case the file is a true temporary with a non-clashing name that 'only' fills up the disk, /// in the worst case the temporary file is used as a lock file which may leave the repository in a locked /// state forever. /// /// This kind of raciness exists whenever [`take()`][Handle::take()] is used and can't be circumvented. #[derive(Debug)] #[must_use = "A handle that is immediately dropped doesn't lock a resource meaningfully"] pub struct Handle { id: usize, _marker: PhantomData, } /// A shortcut to [`Handle::::new()`], creating a writable temporary file with non-clashing name in a directory. pub fn new( containing_directory: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, ) -> io::Result> { Handle::::new(containing_directory, directory, cleanup) } /// A shortcut to [`Handle::::at()`] providing a writable temporary file at the given path. pub fn writable_at( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, ) -> io::Result> { Handle::::at(path, directory, cleanup) } /// Like [`writable_at`], but allows to set the given filesystem `permissions`. pub fn writable_at_with_permissions( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, permissions: std::fs::Permissions, ) -> io::Result> { Handle::::at_with_permissions(path, directory, cleanup, permissions) } /// A shortcut to [`Handle::::at()`] providing a closed temporary file to mark the presence of something. pub fn mark_at( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, ) -> io::Result> { Handle::::at(path, directory, cleanup) } /// Like [`mark_at`], but allows to set the given filesystem `permissions`. pub fn mark_at_with_permissions( path: impl AsRef, directory: ContainingDirectory, cleanup: AutoRemove, permissions: std::fs::Permissions, ) -> io::Result> { Handle::::at_with_permissions(path, directory, cleanup, permissions) } gix-tempfile-15.0.0/src/registry.rs000064400000000000000000000047651046102023000153220ustar 00000000000000use crate::REGISTRY; /// Remove all tempfiles still registered on our global registry, and leak their data to be signal-safe. /// This happens on a best-effort basis with all errors being ignored. /// /// # Safety /// Note that Mutexes of any kind are not allowed, and so aren't allocation or deallocation of memory. /// We are using lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating. /// Most importantly, we use `try_lock()` which uses an atomic int only without blocking, making our register method safe to use, /// at the expense of possibly missing a lock file if another thread wants to obtain it or put it back /// (i.e. mutates the registry shard). pub fn cleanup_tempfiles_signal_safe() { let current_pid = std::process::id(); #[cfg(feature = "hp-hashmap")] { use std::sync::atomic::Ordering; use crate::NEXT_MAP_INDEX; let one_past_last_index = NEXT_MAP_INDEX.load(Ordering::SeqCst); for idx in 0..one_past_last_index { if let Some(entry) = REGISTRY.try_entry(idx) { entry.and_modify(|tempfile| { if tempfile .as_ref() .map_or(false, |tf| tf.owning_process_id == current_pid) { if let Some(tempfile) = tempfile.take() { tempfile.drop_without_deallocation(); } } }); } } } #[cfg(not(feature = "hp-hashmap"))] { REGISTRY.for_each(|tf| { if tf.as_ref().map_or(false, |tf| tf.owning_process_id == current_pid) { if let Some(tf) = tf.take() { tf.drop_without_deallocation(); } } }); } } /// Remove all tempfiles still registered on our global registry. /// This happens on a best-effort basis with all errors being ignored. /// /// # Note /// /// Must not be called from within signal hooks. For that, use [`cleanup_tempfiles_signal_safe()`]. pub fn cleanup_tempfiles() { let current_pid = std::process::id(); #[cfg(feature = "hp-hashmap")] REGISTRY.iter_mut().for_each(|mut tf| { if tf.as_ref().map_or(false, |tf| tf.owning_process_id == current_pid) { tf.take(); } }); #[cfg(not(feature = "hp-hashmap"))] REGISTRY.for_each(|tf| { if tf.as_ref().map_or(false, |tf| tf.owning_process_id == current_pid) { tf.take(); } }); } gix-tempfile-15.0.0/src/signal.rs000064400000000000000000000077321046102023000147240ustar 00000000000000use once_cell::sync::Lazy; use crate::REGISTRY; /// Initialize signal handlers and other state to keep track of tempfiles, and **must be called before the first tempfile is created**, /// allowing to set the `mode` in which signal handlers are installed. /// /// Only has an effect the first time it is called. /// /// Note that it is possible to not call this function and instead call /// [`registry::cleanup_tempfiles_signal_safe()`][crate::registry::cleanup_tempfiles_signal_safe()] /// from a signal handler under the application's control. pub fn setup(mode: handler::Mode) { handler::MODE.store(mode as usize, std::sync::atomic::Ordering::SeqCst); Lazy::force(®ISTRY); } /// pub mod handler { use std::sync::atomic::AtomicUsize; pub(crate) static MODE: AtomicUsize = AtomicUsize::new(Mode::None as usize); /// Define how our signal handlers act #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] pub enum Mode { /// Do not install a signal handler at all, but have somebody else call our handler directly. None = 0, /// Delete all remaining registered tempfiles on termination. DeleteTempfilesOnTermination = 1, /// Delete all remaining registered tempfiles on termination and emulate the default handler behaviour. /// /// This typically leads to the process being aborted. DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour = 2, } impl Default for Mode { /// By default we will emulate the default behaviour and abort the process. /// /// While testing, we will not abort the process. fn default() -> Self { if cfg!(test) { Mode::DeleteTempfilesOnTermination } else { Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour } } } /// On linux we can handle the actual signal as we know it. #[cfg(not(windows))] pub(crate) fn cleanup_tempfiles_nix(sig: &libc::siginfo_t) { crate::registry::cleanup_tempfiles_signal_safe(); let restore_original_behaviour = Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize; if MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour { signal_hook::low_level::emulate_default_handler(sig.si_signo).ok(); } } /// On windows, assume sig-term and emulate sig-term unconditionally. #[cfg(windows)] pub(crate) fn cleanup_tempfiles_windows() { crate::registry::cleanup_tempfiles_signal_safe(); let restore_original_behaviour = Mode::DeleteTempfilesOnTerminationAndRestoreDefaultBehaviour as usize; if MODE.load(std::sync::atomic::Ordering::SeqCst) == restore_original_behaviour { signal_hook::low_level::emulate_default_handler(signal_hook::consts::SIGTERM).ok(); } } #[cfg(test)] mod tests { use std::path::Path; use crate::{AutoRemove, ContainingDirectory}; fn filecount_in(path: impl AsRef) -> usize { std::fs::read_dir(path).expect("valid dir").count() } #[test] fn various_termination_signals_remove_tempfiles_unconditionally() -> Result<(), Box> { crate::signal::setup(Default::default()); let dir = tempfile::tempdir()?; for sig in signal_hook::consts::TERM_SIGNALS { let _tempfile = crate::new(dir.path(), ContainingDirectory::Exists, AutoRemove::Tempfile)?; assert_eq!( filecount_in(dir.path()), 1, "only one tempfile exists no matter the iteration" ); signal_hook::low_level::raise(*sig)?; assert_eq!( filecount_in(dir.path()), 0, "the signal triggers removal but won't terminate the process (anymore)" ); } Ok(()) } } }