1 /* Copyright 2012-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4 #include "watchman.h"
5 #include <system_error>
6 #ifndef _WIN32
7 #include <dirent.h>
8 #endif
9 #ifdef __APPLE__
10 # include <sys/utsname.h>
11 # include <sys/attr.h>
12 # include <sys/vnode.h>
13 #endif
14 #include "FileDescriptor.h"
15
16 using watchman::FileDescriptor;
17 using watchman::OpenFileHandleOptions;
18
19 #ifdef HAVE_GETATTRLISTBULK
20 typedef struct {
21 uint32_t len;
22 attribute_set_t returned;
23 uint32_t err;
24
25 /* The attribute data length will not be greater than NAME_MAX + 1
26 * characters, which is NAME_MAX * 3 + 1 bytes (as one UTF-8-encoded
27 * character may take up to three bytes
28 */
29 attrreference_t name; // ATTR_CMN_NAME
30 dev_t dev; // ATTR_CMN_DEVID
31 fsobj_type_t objtype; // ATTR_CMN_OBJTYPE
32 struct timespec mtime; // ATTR_CMN_MODTIME
33 struct timespec ctime; // ATTR_CMN_CHGTIME
34 struct timespec atime; // ATTR_CMN_ACCTIME
35 uid_t uid; // ATTR_CMN_OWNERID
36 gid_t gid; // ATTR_CMN_GRPID
37 uint32_t mode; // ATTR_CMN_ACCESSMASK, Only the permission bits of st_mode
38 // are valid; other bits should be ignored,
39 // e.g., by masking with ~S_IFMT.
40 uint64_t ino; // ATTR_CMN_FILEID
41 uint32_t link; // ATTR_FILE_LINKCOUNT or ATTR_DIR_LINKCOUNT
42 off_t file_size; // ATTR_FILE_TOTALSIZE
43
44 } __attribute__((packed)) bulk_attr_item;
45 #endif
46
47 #ifndef _WIN32
48 class DirHandle : public watchman_dir_handle {
49 #ifdef HAVE_GETATTRLISTBULK
50 FileDescriptor fd_;
51 struct attrlist attrlist_;
52 int retcount_{0};
53 char buf_[64 * (sizeof(bulk_attr_item) + NAME_MAX * 3 + 1)];
54 char *cursor_{nullptr};
55 #endif
56 DIR *d_{nullptr};
57 struct watchman_dir_ent ent_;
58
59 public:
60 explicit DirHandle(const char* path, bool strict);
61 ~DirHandle() override;
62 const watchman_dir_ent* readDir() override;
63 int getFd() const override;
64 };
65 #else
w_basename(const char * path)66 static const char *w_basename(const char *path) {
67 const char *last = path + strlen(path) - 1;
68 while (last >= path) {
69 if (*last == '/' || *last == '\\') {
70 return last + 1;
71 }
72 --last;
73 }
74 return NULL;
75 }
76 #endif
77
78 #ifndef _WIN32
79 /* Opens a directory making sure it's not a symlink */
opendir_nofollow(const char * path)80 static DIR *opendir_nofollow(const char *path)
81 {
82 auto fd = openFileHandle(path, OpenFileHandleOptions::strictOpenDir());
83 # if !defined(HAVE_FDOPENDIR) || defined(__APPLE__)
84 /* fdopendir doesn't work on earlier versions OS X, and we don't
85 * use this function since 10.10, as we prefer to use getattrlistbulk
86 * in that case */
87 return opendir(path);
88 # else
89 // errno should be set appropriately if this is not a directory
90 auto d = fdopendir(fd.fd());
91 if (d) {
92 fd.release();
93 }
94 return d;
95 # endif
96 }
97 #endif
98
99 #ifdef HAVE_GETATTRLISTBULK
100 // I've seen bulkstat report incorrect sizes on kernel version 14.5.0.
101 // (That's OSX 10.10.5).
102 // Let's avoid it for major kernel versions < 15.
103 // Using statics here to avoid querying the uname on every opendir.
104 // There is opportunity for a data race the first time through, but the
105 // worst case side effect is wasted compute early on.
use_bulkstat_by_default(void)106 static bool use_bulkstat_by_default(void) {
107 static bool probed = false;
108 static bool safe = false;
109
110 if (!probed) {
111 struct utsname name;
112 if (uname(&name) == 0) {
113 int maj = 0, min = 0, patch = 0;
114 sscanf(name.release, "%d.%d.%d", &maj, &min, &patch);
115 if (maj >= 15) {
116 safe = true;
117 }
118 }
119 probed = true;
120 }
121
122 return safe;
123 }
124 #endif
125
126 #ifndef _WIN32
w_dir_open(const char * path,bool strict)127 std::unique_ptr<watchman_dir_handle> w_dir_open(const char* path, bool strict) {
128 return watchman::make_unique<DirHandle>(path, strict);
129 }
130
DirHandle(const char * path,bool strict)131 DirHandle::DirHandle(const char* path, bool strict) {
132 #ifdef HAVE_GETATTRLISTBULK
133 if (cfg_get_bool("_use_bulkstat", use_bulkstat_by_default())) {
134 auto opts = strict ? OpenFileHandleOptions::strictOpenDir()
135 : OpenFileHandleOptions::openDir();
136
137 fd_ = openFileHandle(path, opts);
138
139 auto info = fd_.getInfo();
140
141 if (!info.isDir()) {
142 throw std::system_error(ENOTDIR, std::generic_category(), path);
143 }
144
145 memset(&attrlist_, 0, sizeof(attrlist_));
146 attrlist_.bitmapcount = ATTR_BIT_MAP_COUNT;
147 attrlist_.commonattr = ATTR_CMN_RETURNED_ATTRS |
148 ATTR_CMN_ERROR |
149 ATTR_CMN_NAME |
150 ATTR_CMN_DEVID |
151 ATTR_CMN_OBJTYPE |
152 ATTR_CMN_MODTIME |
153 ATTR_CMN_CHGTIME |
154 ATTR_CMN_ACCTIME |
155 ATTR_CMN_OWNERID |
156 ATTR_CMN_GRPID |
157 ATTR_CMN_ACCESSMASK |
158 ATTR_CMN_FILEID;
159 attrlist_.dirattr = ATTR_DIR_LINKCOUNT;
160 attrlist_.fileattr = ATTR_FILE_TOTALSIZE | ATTR_FILE_LINKCOUNT;
161 return;
162 }
163 #endif
164 d_ = strict ? opendir_nofollow(path) : opendir(path);
165
166 if (!d_) {
167 throw std::system_error(
168 errno,
169 std::generic_category(),
170 std::string(strict ? "opendir_nofollow: " : "opendir: ") + path);
171 }
172 }
173
readDir()174 const watchman_dir_ent* DirHandle::readDir() {
175 #ifdef HAVE_GETATTRLISTBULK
176 if (fd_) {
177 bulk_attr_item *item;
178
179 if (!cursor_) {
180 // Read the next batch of results
181 int retcount;
182
183 errno = 0;
184 retcount = getattrlistbulk(
185 fd_.fd(), &attrlist_, buf_, sizeof(buf_), FSOPT_PACK_INVAL_ATTRS);
186 if (retcount == -1) {
187 throw std::system_error(
188 errno, std::generic_category(), "getattrlistbulk");
189 }
190 if (retcount == 0) {
191 // End of the stream
192 return nullptr;
193 }
194
195 retcount_ = retcount;
196 cursor_ = buf_;
197 }
198
199 // Decode the next item
200 item = (bulk_attr_item*)cursor_;
201 cursor_ += item->len;
202 if (--retcount_ == 0) {
203 cursor_ = nullptr;
204 }
205
206 ent_.d_name = ((char*)&item->name) + item->name.attr_dataoffset;
207 if (item->err) {
208 w_log(
209 W_LOG_ERR,
210 "item error %s: %d %s\n",
211 ent_.d_name,
212 item->err,
213 strerror(item->err));
214 // We got the name, so we can return something useful
215 ent_.has_stat = false;
216 return &ent_;
217 }
218
219 memset(&ent_.stat, 0, sizeof(ent_.stat));
220
221 ent_.stat.dev = item->dev;
222 memcpy(&ent_.stat.mtime, &item->mtime, sizeof(item->mtime));
223 memcpy(&ent_.stat.ctime, &item->ctime, sizeof(item->ctime));
224 memcpy(&ent_.stat.atime, &item->atime, sizeof(item->atime));
225 ent_.stat.uid = item->uid;
226 ent_.stat.gid = item->gid;
227 ent_.stat.mode = item->mode & ~S_IFMT;
228 ent_.stat.ino = item->ino;
229
230 switch (item->objtype) {
231 case VREG:
232 ent_.stat.mode |= S_IFREG;
233 ent_.stat.size = item->file_size;
234 ent_.stat.nlink = item->link;
235 break;
236 case VDIR:
237 ent_.stat.mode |= S_IFDIR;
238 ent_.stat.nlink = item->link;
239 break;
240 case VLNK:
241 ent_.stat.mode |= S_IFLNK;
242 ent_.stat.size = item->file_size;
243 break;
244 case VBLK:
245 ent_.stat.mode |= S_IFBLK;
246 break;
247 case VCHR:
248 ent_.stat.mode |= S_IFCHR;
249 break;
250 case VFIFO:
251 ent_.stat.mode |= S_IFIFO;
252 break;
253 case VSOCK:
254 ent_.stat.mode |= S_IFSOCK;
255 break;
256 }
257 ent_.has_stat = true;
258 return &ent_;
259 }
260 #endif
261
262 if (!d_) {
263 return nullptr;
264 }
265 errno = 0;
266 auto dent = readdir(d_);
267 if (!dent) {
268 if (errno) {
269 throw std::system_error(errno, std::generic_category(), "readdir");
270 }
271 return nullptr;
272 }
273
274 ent_.d_name = dent->d_name;
275 ent_.has_stat = false;
276 return &ent_;
277 }
278
~DirHandle()279 DirHandle::~DirHandle() {
280 if (d_) {
281 closedir(d_);
282 }
283 }
284
getFd() const285 int DirHandle::getFd() const {
286 #ifdef HAVE_GETATTRLISTBULK
287 return fd_.fd();
288 #else
289 return dirfd(d_);
290 #endif
291 }
292 #endif
293
294 /* vim:ts=2:sw=2:et:
295 */
296