Defer the retrieval of owner/group names until they are needed
[libs/core.git] / source / fs / stat.cpp
1 #ifdef WIN32
2 #include <windows.h>
3 #include <aclapi.h>
4 #else
5 #define _FILE_OFFSET_BITS 64
6 #include <cerrno>
7 #include <unistd.h>
8 #include <sys/stat.h>
9 #include <grp.h>
10 #include <pwd.h>
11 #endif
12 #include <msp/core/systemerror.h>
13 #include <msp/strings/format.h>
14 #include <msp/time/rawtime_private.h>
15 #include "path.h"
16 #include "stat.h"
17
18 using namespace std;
19
20 namespace {
21
22 #ifdef WIN32
23 PSID copy_sid(PSID sid)
24 {
25         if(!sid || !IsValidSid(sid))
26                 return 0;
27         DWORD len = GetLengthSid(sid);
28         PSID copy = reinterpret_cast<PSID>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len));
29         if(!CopySid(len, copy, sid))
30         {
31                 DWORD err = GetLastError();
32                 HeapFree(GetProcessHeap(), 0, copy);
33                 throw Msp::system_error("CopySid", err);
34         }
35         return copy;
36 }
37
38 string get_account_name(PSID sid)
39 {
40         char name[1024];
41         DWORD nlen = sizeof(name);
42         char domain[1024];
43         DWORD dlen = sizeof(domain);
44         SID_NAME_USE use;
45         if(!LookupAccountSid(0, sid, name, &nlen, domain, &dlen, &use))
46                 throw Msp::system_error("LookupAccountSid");
47         return Msp::format("%s/%s", name, domain);
48 }
49 #endif
50
51 }
52
53
54 namespace Msp {
55 namespace FS {
56
57 struct Stat::Private
58 {
59 #ifdef WIN32
60         PSID owner_id;
61         PSID group_id;
62 #else
63         uid_t owner_id;
64         gid_t group_id;
65 #endif
66
67         Private();
68         Private(const Private &);
69         ~Private();
70
71 #ifndef WIN32
72         /* This is here because it needs access to private members of Stat, but we
73         can't expose the system stat struct in the public header */
74         static Stat from_struct_stat(const struct stat &);
75 #endif
76
77         void fill_owner_info(Stat::OwnerInfo &);
78 };
79
80 Stat::Private::Private():
81         owner_id(0),
82         group_id(0)
83 { }
84
85 Stat::Private::Private(const Private &other):
86 #ifdef WIN32
87         owner_id(copy_sid(other.owner_id)),
88         group_id(copy_sid(other.group_id))
89 #else
90         owner_id(other.owner_id),
91         group_id(other.group_id)
92 #endif
93 { }
94
95 Stat::Private::~Private()
96 {
97 #ifdef WIN32
98         if(owner_id)
99                 HeapFree(GetProcessHeap(), 0, owner_id);
100         if(group_id)
101                 HeapFree(GetProcessHeap(), 0, group_id);
102 #endif
103 }
104
105 #ifndef WIN32
106 Stat Stat::Private::from_struct_stat(const struct stat &st)
107 {
108         Stat result;
109         result.exists = true;
110         if(S_ISREG(st.st_mode))
111                 result.type = REGULAR;
112         else if(S_ISDIR(st.st_mode))
113                 result.type = DIRECTORY;
114         else if(S_ISLNK(st.st_mode))
115                 result.type = SYMLINK;
116         else
117                 result.type = UNKNOWN;
118         result.size = st.st_size;
119         result.alloc_size = st.st_blocks*512;
120         result.mtime = Time::TimeStamp::from_unixtime(st.st_mtime);
121
122         result.priv = new Private;
123         result.priv->owner_id = st.st_uid;
124         result.priv->group_id = st.st_gid;
125
126         return result;
127 }
128 #endif
129
130 void Stat::Private::fill_owner_info(Stat::OwnerInfo &result)
131 {
132 #ifdef WIN32
133         if(owner_id)
134                 result.owner = get_account_name(owner_id);
135         else
136                 result.owner = "None";
137
138         if(group_id)
139                 result.group = get_account_name(group_id);
140         else
141                 result.group = "None";
142 #else
143         char buf[1024];
144
145         struct passwd pw;
146         struct passwd *owner;
147         if(!getpwuid_r(owner_id, &pw, buf, sizeof(buf), &owner) && owner)
148                 result.owner = owner->pw_name;
149         else
150                 result.owner = format("%d", owner_id);
151
152         struct group gr;
153         struct group *group;
154         if(!getgrgid_r(group_id, &gr, buf, sizeof(buf), &group) && group)
155                 result.group = group->gr_name;
156         else
157                 result.group = format("%d", group_id);
158 #endif
159 }
160
161
162 Stat::Stat():
163         exists(false),
164         type(UNKNOWN),
165         size(0),
166         alloc_size(0),
167         priv(0)
168 { }
169
170 Stat::Stat(const Stat &other):
171         exists(other.exists),
172         type(other.type),
173         size(other.type),
174         alloc_size(other.alloc_size),
175         mtime(other.mtime),
176         owner_info(other.owner_info),
177         priv(other.priv ? new Private(*other.priv) : 0)
178 { }
179
180 Stat &Stat::operator=(const Stat &other)
181 {
182         exists = other.exists;
183         type = other.type;
184         size = other.size;
185         alloc_size = other.alloc_size;
186         mtime = other.mtime;
187         owner_info = other.owner_info;
188         delete priv;
189         priv = (other.priv ? new Private(*other.priv) : 0);
190
191         return *this;
192 }
193
194 Stat::~Stat()
195 {
196         delete priv;
197 }
198
199 Stat Stat::stat(const Path &path)
200 {
201 #ifdef WIN32
202         HANDLE handle;
203         handle = CreateFile(path.str().c_str(), READ_CONTROL, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, 0);
204         if(handle==INVALID_HANDLE_VALUE)
205         {
206                 DWORD err = GetLastError();
207                 if(err==ERROR_FILE_NOT_FOUND)
208                         return Stat();
209                 else
210                         throw system_error("CreateFile", err);
211         }
212
213         BY_HANDLE_FILE_INFORMATION info;
214         if(!GetFileInformationByHandle(handle, &info))
215         {
216                 CloseHandle(handle);
217                 throw system_error("GetFileInformationByHandle");
218         }
219
220         Stat result;
221         if(info.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
222                 result.type = DIRECTORY;
223         else
224                 result.type = REGULAR;
225
226         result.size = FileSize(info.nFileSizeHigh)<<32 | info.nFileSizeLow;
227         result.alloc_size = (result.size+511)&~511;
228         result.mtime = Time::TimeStamp(Time::filetime_to_rawtime(info.ftLastWriteTime));
229
230         PSECURITY_DESCRIPTOR sec_desc;
231         PSID owner = 0;
232         PSID group = 0;
233         const SECURITY_INFORMATION sec_info = OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION;
234         DWORD err = GetSecurityInfo(handle, SE_FILE_OBJECT, sec_info, &owner, &group, 0, 0, &sec_desc);
235         if(err)
236         {
237                 CloseHandle(handle);
238                 throw system_error("GetSecurityInfo", err);
239         }
240
241         result.priv = new Private;
242         result.priv->owner_id = copy_sid(owner);
243         result.priv->group_id = copy_sid(group);
244
245         LocalFree(sec_desc);
246
247         CloseHandle(handle);
248
249         return result;
250 #else
251         struct stat st;
252         int ret = ::stat(path.str().c_str(), &st);
253         if(ret==-1)
254         {
255                 if(errno==ENOENT)
256                         return Stat();
257                 else
258                         throw system_error("stat");
259         }
260
261         return Private::from_struct_stat(st);
262 #endif
263 }
264
265 const std::string &Stat::get_owner() const
266 {
267         if(priv && owner_info.owner.empty())
268                 priv->fill_owner_info(owner_info);
269         return owner_info.owner;
270 }
271
272 const std::string &Stat::get_group() const
273 {
274         if(priv && owner_info.group.empty())
275                 priv->fill_owner_info(owner_info);
276         return owner_info.group;
277 }
278
279 Stat Stat::lstat(const Path &path)
280 {
281 #ifdef WIN32
282         return stat(path);
283 #else
284         struct stat st;
285         int ret = ::lstat(path.str().c_str(), &st);
286         if(ret==-1)
287         {
288                 if(errno==ENOENT)
289                         return Stat();
290                 else
291                         throw system_error("lstat");
292         }
293
294         return Private::from_struct_stat(st);
295 #endif
296 }
297
298 bool exists(const Path &path)
299 {
300 #ifdef WIN32
301         return GetFileAttributes(path.str().c_str())!=INVALID_FILE_ATTRIBUTES;
302 #else
303         return access(path.str().c_str(), F_OK)==0;
304 #endif
305 }
306
307 } // namespace FS
308 } // namespace Msp