1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This module contains functions to extract XDG variables to either what they are 7 configured or the fallback according to the standard at [XDG Base Directory 8 Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). 9 */ 10 module my.xdg; 11 12 import std.array : empty; 13 14 import my.path; 15 16 /** Returns the directory to use for program runtime data for the current 17 * user with a fallback for older OS:es. 18 * 19 * `$XDG_RUNTIME_DIR` isn't set on all OS such as older versions of CentOS. If 20 * such is the case a directory with equivalent properties when it comes to the 21 * permissions are created inside `falllback` and returned. This means that 22 * this function should in most cases work. When it fails it means something 23 * funky is happening such as someone is trying to hijack your data or 24 * `fallback` isn't writable. This is the only case when it will throw an 25 * exception. 26 * 27 * From the specification: 28 * 29 * $XDG_RUNTIME_DIR defines the base directory relative to which user-specific 30 * non-essential runtime files and other file objects (such as sockets, named 31 * pipes, ...) should be stored. The directory MUST be owned by the user, and 32 * he MUST be the only one having read and write access to it. Its Unix access 33 * mode MUST be 0700. 34 * 35 * The lifetime of the directory MUST be bound to the user being logged in. It 36 * MUST be created when the user first logs in and if the user fully logs out 37 * the directory MUST be removed. If the user logs in more than once he should 38 * get pointed to the same directory, and it is mandatory that the directory 39 * continues to exist from his first login to his last logout on the system, 40 * and not removed in between. Files in the directory MUST not survive reboot 41 * or a full logout/login cycle. 42 * 43 * The directory MUST be on a local file system and not shared with any other 44 * system. The directory MUST by fully-featured by the standards of the 45 * operating system. More specifically, on Unix-like operating systems AF_UNIX 46 * sockets, symbolic links, hard links, proper permissions, file locking, 47 * sparse files, memory mapping, file change notifications, a reliable hard 48 * link count must be supported, and no restrictions on the file name character 49 * set should be imposed. Files in this directory MAY be subjected to periodic 50 * clean-up. To ensure that your files are not removed, they should have their 51 * access time timestamp modified at least once every 6 hours of monotonic time 52 * or the 'sticky' bit should be set on the file. 53 * 54 * If $XDG_RUNTIME_DIR is not set applications should fall back to a 55 * replacement directory with similar capabilities and print a warning message. 56 * Applications should use this directory for communication and synchronization 57 * purposes and should not place larger files in it, since it might reside in 58 * runtime memory and cannot necessarily be swapped out to disk. 59 */ 60 Path xdgRuntimeDir(Path fallback = Path("/tmp")) @safe { 61 import std.process : environment; 62 63 Path backup() @trusted { 64 import core.stdc.stdio : perror; 65 import core.sys.posix.sys.stat : mkdir; 66 import core.sys.posix.sys.stat; 67 import core.sys.posix.unistd : getuid; 68 import std.file : exists; 69 import std.format : format; 70 import std..string : toStringz; 71 72 const base = fallback ~ format!"user_%s"(getuid); 73 string rval; 74 75 foreach (i; 0 .. 1000) { 76 // create 77 rval = format!"%s_%s"(base, i); 78 const cstr = rval.toStringz; 79 80 if (!exists(rval)) { 81 if (mkdir(cstr, S_IRWXU) != 0) { 82 continue; 83 } 84 } 85 86 // validate 87 stat_t st; 88 stat(cstr, &st); 89 if (st.st_uid == getuid && (st.st_mode & S_IFDIR) != 0 90 && ((st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == S_IRWXU)) { 91 break; 92 } 93 94 // try again 95 rval = null; 96 } 97 98 if (rval.empty) { 99 perror(null); 100 throw new Exception("Unable to create XDG_RUNTIME_DIR " ~ rval); 101 } 102 return Path(rval); 103 } 104 105 auto xdg = environment.get("XDG_RUNTIME_DIR").Path; 106 if (xdg.empty) 107 xdg = backup; 108 return xdg; 109 } 110 111 @("shall return the XDG runtime directory") 112 unittest { 113 import std.process : environment; 114 115 auto xdg = xdgRuntimeDir; 116 auto hostEnv = environment.get("XDG_RUNTIME_DIR"); 117 if (!hostEnv.empty) 118 assert(xdg == hostEnv); 119 }