Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lack of cross-platform function to create temporary directory #189

Open
sogaiu opened this issue Jul 16, 2024 · 2 comments
Open

Lack of cross-platform function to create temporary directory #189

sogaiu opened this issue Jul 16, 2024 · 2 comments

Comments

@sogaiu
Copy link
Contributor

sogaiu commented Jul 16, 2024

On more than one occasion, I've wanted to create a (previously non-existent) temporary directory to dump some files in. I'm guessing others have too (^^;

By "cross-platform", I'm mostly meaning various BSDs, Linux, macos, and recent Windows.

Did a bit of searching and have come up with the following links:

Following is a sketch of a possible flow:

  1. find "system" / "user" temporary directory
  2. verify it exists (it could disappear while doing the following step, but we'll ignore that for now)
  3. keep trying to make a new subdirectory of located + verified directory from steps 1 and 2 until success or some number of failures

The focus here is not on hiding things away (like is done in parts of Python's tempfile library), but just to have some place that won't accidentally get used.

Does this sound like:

  • it might work
  • something worth having in spork?
  • code that exists already?
@sogaiu
Copy link
Contributor Author

sogaiu commented Jul 16, 2024

Below is an initial sketch.

# https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppath2w
(defn windows-temp-root
  []
  (os/getenv "TMP"
             (os/getenv "TEMP"
                        (os/getenv "USERPROFILE"
                                   # XXX
                                   (os/getenv "WINDIR")))))


# https://en.cppreference.com/w/cpp/filesystem/temp_directory_path
(defn posix-temp-root
  []
  (os/getenv "TMPDIR"
             (os/getenv "TMP"
                        (os/getenv "TEMP"
                                   (os/getenv "TEMPDIR" "/tmp")))))

(defn temp-root
  []
  (case (os/which)
    # see comment above function definition
    :windows (windows-temp-root)
    # XXX: unsure
    :mingw "/tmp"
    # XXX: unsure, but https://cygwin.com/cygwin-ug-net/setup-env.html
    :cygwin "/tmp"
    # https://ss64.com/mac/syntax-env_vars.html
    :macos (os/getenv "TMPDIR")
    # https://emscripten.org/docs/api_reference/Filesystem-API.html
    :web "/tmp"
    # https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
    :linux "/tmp"
    # https://www.freebsd.org/cgi/man.cgi?query=hier&sektion=7
    :freebsd "/tmp"
    # https://man.openbsd.org/hier.7
    :openbsd "/tmp"
    # https://man.netbsd.org/hier.7
    :netbsd "/tmp"
    # https://leaf.dragonflybsd.org/cgi/web-man?command=hier&section=7
    :dragonfly "/tmp"
    # based on the *bsd info above, following seems reasonable
    :bsd "/tmp"
    # see comment above function definition
    :posix (posix-temp-root)
    (errorf "unrecognized os: %n" (os/which))))

(defn mk-temp-dir
  ``
  Tries to create a new subdirectory of a system-specific temporary
  directory.  Optional argument `template` is used to specify a
  template for the new subdirectory's name.  Each forward slash (`/`)
  in the template is replaced with some hex value (0-9, a-f) to result
  in a candidate name.  The default value of `template` is `//////`.
  Optional argument `tries` is the maximum number of subdirectory
  creation attempts.  The default value of `tries` is 5.  Upon
  success, returns the full path of the newly created subdirectory.
  ``
  [&opt template tries]
  (default template "//////")
  (default tries 5)
  (def fs-sep (if (= :windows (os/which)) `\` "/"))

  (assert (not (empty? template))
          "template should be a non-empty string")
  (assert (and (nat? tries) (pos? tries))
          (string/format "tries should be a positive integer, not: %d"
                         tries))

  (def tmp-root (temp-root))
  (assert (= :directory (os/stat tmp-root :mode))
          (string/format "failed to find temp root `%s` for os `%s"
                         tmp-root (os/which)))

  (def rng (math/rng (os/cryptorand 8)))
  (defn rand-hex [_] (string/format "%x" (math/rng-int rng 16)))
  (var result nil)
  (for i 0 tries
    (def cand-path
      (string tmp-root fs-sep (string/replace-all "/" rand-hex template)))
    (when (os/mkdir cand-path)
      (set result cand-path)
      (break result)))

  (when (not result)
    (errorf "failed to create new temp directory after %d tries" tries))

  result)

(comment

  (peg/match ~(repeat 6 :h)
             (reverse (mk-temp-dir)))
  # =>
  @[]

  (peg/match ~(sequence (thru "hello-")
                        (repeat 3 :h))
             (mk-temp-dir "hello-///"))
  # =>
  @[]

  (do
    (def [success? _] (protect (mk-temp-dir "")))
    (not success?))
  # =>
  true

  )

@sogaiu
Copy link
Contributor Author

sogaiu commented Jul 16, 2024

Perhaps it could be useful to honor certain environment variables if users would prefer use of non-system locations.

The current sketch does something like this for some cases but not all. Candidate env vars for this purpose include:

May be better to avoid this kind of complication initially (^^;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant