1 /** 
2  * Provides simple getters for XDG base directories and FHS
3  * Apropiate directoies a choosen based on whether the program is run as root or not.
4  * All directories are namespaced with tagion.
5 **/
6 module tagion.basic.dir;
7 
8 @safe:
9 
10 import core.sys.posix.unistd;
11 
12 import std.process : environment;
13 import std.path;
14 import std.conv;
15 
16 import tagion.basic.basic : isinit;
17 
18 struct Dir {
19     enum program_name = "tagion";
20 
21     /// The effective user permissions, so running with sudo or doas counts as well
22     const bool isRoot;
23     const uint euid;
24     this(uint _euid) nothrow {
25         euid = _euid;
26         isRoot = (_euid == 0);
27     }
28 
29     string _home;
30 
31     /// the home directory
32     string home() {
33         if (_home.isinit) {
34             _home = environment.get("HOME");
35             // '/' is set if user is 'nobody'
36             assert(_home !is string.init && _home != "/", "This system is not for homeless users");
37         }
38         return _home;
39     }
40 
41     private string xdg_dir(const(string) XDG_SPEC, lazy string fallback)
42     out (dir; dir.isValidPath)
43     out (dir; dir.isRooted) {
44         const dir = environment.get(XDG_SPEC, buildPath(home, fallback));
45         return buildPath(dir, program_name);
46     }
47 
48     private string root_dir(lazy string name)
49     out (dir; dir.isValidPath)
50     out (dir; dir.isRooted) {
51         return buildPath("/", name, program_name);
52     }
53 
54     private void set_val(ref string var, lazy string root_val, lazy string user_val) {
55         if (var.isinit) {
56             if (isRoot) {
57                 var = root_val;
58             }
59             else {
60                 var = user_val;
61             }
62         }
63     }
64 
65     private string _data;
66     /// Site specific data
67     string data() {
68         set_val(_data, root_dir("srv"), xdg_dir("XDG_DATA_HOME", ".local/share"));
69         return _data;
70     }
71 
72     private string _config;
73     /// static program config files
74     string config() {
75         set_val(_config, root_dir("etc"), xdg_dir("XDG_CONFIG_HOME", ".config/"));
76         return _config;
77     }
78 
79     private string _cache;
80     /// Cached data, data that is a result expensive computation or I/O. 
81     /// The cached files can be deleted without loss of data. 
82     string cache() {
83         set_val(_cache, root_dir("/var/cache"), xdg_dir("XDG_CACHE_HOME", ".cache"));
84         return _cache;
85     }
86 
87     private string _run;
88     /// This directory contains system information data describing the system since it was booted
89     string run() {
90         set_val(_run,
91                 root_dir("run"),
92                 environment.get("XDG_RUNTIME_DIR", buildPath("/run", "user", euid.to!string, program_name)
93         ));
94         return _run;
95     }
96 
97     private string _log;
98     /// Log files
99     string log() {
100         set_val(_log, root_dir("/var/log"), xdg_dir("XDG_STATE_HOME", ".local/state"));
101         return _log;
102     }
103 }
104 
105 static Dir base_dir;
106 static this() {
107     base_dir = Dir(geteuid);
108 }
109 
110 unittest {
111     auto my_dirs = Dir(1000); // A normal user
112     enum homeless = "/home/less/";
113     environment["HOME"] = homeless;
114     environment["XDG_CACHE_HOME"] = homeless ~ ".cache";
115     assert(my_dirs.cache == "/home/less/.cache/tagion", my_dirs.cache);
116 
117     environment.remove("XDG_RUNTIME_DIR");
118     assert(my_dirs.run == "/run/user/1000/tagion", my_dirs.run);
119 
120     environment.remove("XDG_DATA_HOME");
121     assert(my_dirs.data == buildPath(homeless ~ ".local/share/tagion"), my_dirs.data);
122 
123     auto root_dirs = Dir(0);
124     assert(root_dirs.run == "/run/tagion", root_dirs.run);
125 }