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