CMS 3D CMS Logo

LocalFileSystem.cc
Go to the documentation of this file.
1 #define _GNU_SOURCE 1
2 #define _FILE_OFFSET_BITS 64
5 #include <cerrno>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <cstring>
9 #include <cassert>
10 #include <sys/param.h>
11 #if BSD
12 # include <sys/statvfs.h>
13 # include <sys/ucred.h>
14 # include <sys/mount.h>
15 #else
16 # include <mntent.h>
17 # include <sys/vfs.h>
18 #endif
19 #include <sys/stat.h>
20 #include <unistd.h>
21 #include <iostream>
22 #include <atomic>
23 
24 #pragma GCC diagnostic ignored "-Wformat" // shut warning on '%z'
25 
28 {
29  char *fsname; //< file system name
30  char *type; //< file system type
31  char *dir; //< mount point directory
32  char *origin = nullptr; //< mount origin
33  dev_t dev; //< device id
34  long fstype; //< file system id
35  double freespc; //< free space in megabytes
36  unsigned local : 1; //< flag for local device
37  unsigned bind : 1; //< flag for bind mounts
38  std::atomic<bool> checked ; //< flag for valid dev, fstype
39 };
40 
60 int
62 {
63  int ret = 0;
64 
65 #if __linux__
66  static const char procfs[] = "/proc/filesystems";
67  FILE *fs = fopen(procfs, "r");
68  if (! fs)
69  {
70  int nerr = errno;
71  edm::LogWarning("LocalFileSystem::readFSTypes()")
72  << "Cannot read '" << procfs << "': "
73  << strerror(nerr) << " (error " << nerr << ")";
74  return -1;
75  }
76 
77  ssize_t nread;
78  int line = 0;
79  while (! feof(fs))
80  {
81  char *type = nullptr;
82  char *fstype = nullptr;
83  size_t len = 0;
84  ++line;
85 
86  if ((nread = getdelim(&type, &len, '\t', fs)) == -1 && ! feof(fs))
87  {
88  fprintf(stderr, "%s:%d: %s (%zd; 1)\n",
89  procfs, line, strerror(errno), nread);
90  free(type);
91  ret = -1;
92  break;
93  }
94 
95  if ((nread = getdelim(&fstype, &len, '\n', fs)) == -1 && ! feof(fs))
96  {
97  fprintf(stderr, "%s:%d: %s (%zd; 2)\n",
98  procfs, line, strerror(errno), nread);
99  free(type);
100  free(fstype);
101  ret = -1;
102  break;
103  }
104 
105  if (feof (fs))
106  {
107  free(type);
108  free(fstype);
109  break;
110  }
111 
112  if (! strcmp(type, "nodev\t")
113  || ! strcmp(fstype, "lustre\n")
114  || ! strncmp(fstype, "fuse", 4))
115  {
116  free(type);
117  free(fstype);
118  continue;
119  }
120 
121  assert(nread >= 1);
122  fstype[nread-1] = 0;
123  fstypes_.push_back(fstype);
124  free(fstype);
125  free(type);
126  }
127 
128  fclose(fs);
129 #endif // __linux__
130 
131  return ret;
132 }
133 
144 {
145 #if BSD
146  struct statfs *m = static_cast<struct statfs *>(arg);
147  size_t infolen = sizeof(struct FSInfo);
148  size_t fslen = strlen(m->f_mntfromname) + 1;
149  size_t dirlen = strlen(m->f_mntonname) + 1;
150  size_t typelen = strlen(m->f_fstypename) + 1;
151  size_t totlen = infolen + fslen + dirlen + typelen;
152  FSInfo *i = (FSInfo *) malloc(totlen);
153  char *p = (char *) i;
154  i->fsname = strncpy(p += infolen, m->f_mntfromname, fslen);
155  i->type = strncpy(p += fslen, m->f_fstypename, typelen);
156  i->dir = strncpy(p += typelen, m->f_mntonname, dirlen);
157  i->dev = m->f_fsid.val[0];
158  i->fstype = m->f_type;
159  i->freespc = 0;
160  i->bind = 0;
161  i->origin = nullptr;
162  if (m->f_bsize > 0)
163  {
164  i->freespc = m->f_bavail;
165  i->freespc *= m->f_bsize;
166  i->freespc /= 1024. * 1024. * 1024.;
167  }
168  /* FIXME: This incorrectly says that mounted disk images are local,
169  even if it was mounted from a network server. The alternative is
170  to walk up the device tree using either a) process IORegistry to
171  get the device tree, which lists devices for disk images, and from
172  there translate volume uuid to a mount point; b) parse output from
173  'hdiutil info -plist' to determine image-path / dev-entry map. */
174  i->local = ((m->f_flags & MNT_LOCAL) ? 1 : 0);
175  i->checked = 1;
176  return i;
177 
178 #else // ! BSD
179  mntent *m = static_cast<mntent *>(arg);
180  size_t infolen = sizeof(struct FSInfo);
181  size_t fslen = strlen(m->mnt_fsname) + 1;
182  size_t dirlen = strlen(m->mnt_dir) + 1;
183  size_t typelen = strlen(m->mnt_type) + 1;
184  size_t originlen = strlen(m->mnt_fsname) + 1;
185  size_t totlen = infolen + fslen + dirlen + typelen + originlen;
186  FSInfo *i = (FSInfo *) malloc(totlen);
187  char *p = (char *) i;
188  i->fsname = strncpy(p += infolen, m->mnt_fsname, fslen);
189  i->type = strncpy(p += fslen, m->mnt_type, typelen);
190  i->dir = strncpy(p += typelen, m->mnt_dir, dirlen);
191  i->origin = strncpy(p += dirlen, m->mnt_fsname, originlen);
192  i->dev = -1;
193  i->fstype = -1;
194  i->freespc = 0;
195  i->local = 0;
196  i->checked = false;
197  i->bind = strstr(m->mnt_opts, "bind") != nullptr;
198 
199  for (size_t j = 0; j < fstypes_.size() && ! i->local; ++j)
200  if (fstypes_[j] == i->type)
201  i->local = 1;
202 #endif // BSD
203 
204  return i;
205 }
206 
213 int
215 {
216 #if BSD
217  int rc;
218  struct statfs *mtab = 0;
219  if ((rc = getmntinfo(&mtab, MNT_NOWAIT)) < 0)
220  {
221  int nerr = errno;
222  edm::LogWarning("LocalFileSystem::initFSList()")
223  << "getmntinfo() failed: " << strerror(nerr)
224  << " (error " << nerr << ")";
225  return -1;
226  }
227 
228  fs_.reserve(rc);
229  for (int ix = 0; ix < rc; ++ix)
230  fs_.push_back(initFSInfo(&mtab[ix]));
231 
232  free(mtab);
233 #else
234  const char * const _PATH_MOUNTED_LINUX = "/proc/self/mounts";
235  struct mntent *m;
236  FILE *mtab = setmntent(_PATH_MOUNTED_LINUX, "r");
237  if (! mtab)
238  {
239  int nerr = errno;
240  edm::LogWarning("LocalFileSystem::initFSList()")
241  << "Cannot read '" << _PATH_MOUNTED_LINUX << "': "
242  << strerror(nerr) << " (error " << nerr << ")";
243  return -1;
244  }
245 
246  fs_.reserve(20);
247  while ((m = getmntent(mtab)))
248  fs_.push_back(initFSInfo(m));
249 
250  endmntent(mtab);
251 #endif
252 
253  return 0;
254 }
255 
264 int
266 {
267  int ret = 0;
268  struct stat s;
269  struct statfs sfs;
270 
271  if (! i->checked)
272  {
273  if (lstat(i->dir, &s) < 0)
274  {
275  i->checked = true;
276 
277  int nerr = errno;
278  if (nerr != ENOENT && nerr != EACCES)
279  edm::LogWarning("LocalFileSystem::statFSInfo()")
280  << "Cannot lstat('" << i->dir << "'): "
281  << strerror(nerr) << " (error " << nerr << ")";
282  return -1;
283  }
284 
285  if (statfs(i->dir, &sfs) < 0)
286  {
287  i->checked = true;
288  int nerr = errno;
289  edm::LogWarning("LocalFileSystem::statFSInfo()")
290  << "Cannot statfs('" << i->dir << "'): "
291  << strerror(nerr) << " (error " << nerr << ")";
292  return -1;
293  }
294 
295  i->dev = s.st_dev;
296  i->fstype = sfs.f_type;
297  if (sfs.f_bsize > 0)
298  {
299  i->freespc = sfs.f_bavail;
300  i->freespc *= sfs.f_bsize;
301  i->freespc /= 1024. * 1024. * 1024.;
302  }
303  i->checked = true;
304  }
305  else if (i->fstype == -1)
306  {
307  errno = ENOENT;
308  ret = -1;
309  }
310 
311  return ret;
312 }
313 
328 LocalFileSystem::findMount(const char *path, struct statfs *sfs, struct stat *s, std::vector<std::string> &prev_paths) const
329 {
330  for (const auto & old_path : prev_paths)
331  {
332  if (!strcmp(old_path.c_str(), path))
333  {
334  edm::LogWarning("LocalFileSystem::findMount()")
335  << "Found a loop in bind mounts; stopping evaluation.";
336  return nullptr;
337  }
338  }
339 
340  FSInfo *best = nullptr;
341  size_t bestlen = 0;
342  size_t len = strlen(path);
343  for (size_t i = 0; i < fs_.size(); ++i)
344  {
345  // First match simply against the file system path. We don't
346  // touch the file system until the path prefix matches.
347  // When we have a path prefix match, check the file system if
348  // we don't have a best match candidate yet, OR
349  // this match is longer (more specific) than the previous best OR
350  // this match is the same length and the previous best isn't local
351  // The final condition handles cases such as '/' that can appear twice
352  // in the file system list, once as 'rootfs' and once as local fs.
353  size_t fslen = strlen(fs_[i]->dir);
354  if (! strncmp(fs_[i]->dir, path, fslen)
355  && ((fslen == 1 && fs_[i]->dir[0] == '/')
356  || len == fslen || path[fslen] == '/')
357  && (! best || fslen > bestlen || (fslen == bestlen && !best->local)))
358  {
359  // Get the file system device and file system ids.
360  if (statFSInfo(fs_[i]) < 0)
361  return nullptr;
362 
363  // Check the path is on the same device / file system. If this
364  // fails, we found a better prefix match on path, but it's the
365  // wrong device, so reset our idea of the best match: it can't
366  // be the outer mount any more. Not sure this is the right
367  // thing to do with e.g. loop-back or union mounts.
368  if (fs_[i]->dev != s->st_dev || fs_[i]->fstype != sfs->f_type)
369  {
370  best = nullptr;
371  continue;
372  }
373 
374  // OK this is better than anything else we found so far.
375  best = fs_[i];
376  bestlen = fslen;
377  }
378  }
379  // In the case of a bind mount, try looking again at the source directory.
380  if (best && best->bind && best->origin)
381  {
382  struct stat s2;
383  struct statfs sfs2;
384  char *fullpath = realpath(best->origin, nullptr);
385 
386  if (! fullpath)
387  fullpath = strdup(best->origin);
388 
389  if (lstat(fullpath, &s2) < 0)
390  {
391  int nerr = errno;
392  edm::LogWarning("LocalFileSystem::findMount()")
393  << "Cannot lstat('" << fullpath << "' alias '"
394  << path << "'): " << strerror(nerr) << " (error "
395  << nerr << ")";
396  free(fullpath);
397  return best;
398  }
399 
400  if (statfs(fullpath, &sfs2) < 0)
401  {
402  int nerr = errno;
403  edm::LogWarning("LocalFileSystem::findMount()")
404  << "Cannot statfs('" << fullpath << "' alias '"
405  << path << "'): " << strerror(nerr) << " (error "
406  << nerr << ")";
407  free(fullpath);
408  return best;
409  }
410 
411  prev_paths.push_back(path);
412  LocalFileSystem::FSInfo *new_best = findMount(fullpath, &sfs2, &s2, prev_paths);
413  return new_best ? new_best : best;
414  }
415 
416  return best;
417 }
418 
428 bool
430 {
431  struct stat s;
432  struct statfs sfs;
433  char *fullpath = realpath(path.c_str(), nullptr);
434 
435  if (! fullpath)
436  fullpath = strdup(path.c_str());
437 
438  if (lstat(fullpath, &s) < 0)
439  {
440  int nerr = errno;
441  edm::LogWarning("LocalFileSystem::isLocalPath()")
442  << "Cannot lstat('" << fullpath << "' alias '"
443  << path << "'): " << strerror(nerr) << " (error "
444  << nerr << ")";
445  free(fullpath);
446  return false;
447  }
448 
449  if (statfs(fullpath, &sfs) < 0)
450  {
451  int nerr = errno;
452  edm::LogWarning("LocalFileSystem::isLocalPath()")
453  << "Cannot statfs('" << fullpath << "' alias '"
454  << path << "'): " << strerror(nerr) << " (error "
455  << nerr << ")";
456  free(fullpath);
457  return false;
458  }
459 
460  std::vector<std::string> prev_paths;
461  FSInfo *m = findMount(fullpath, &sfs, &s, prev_paths);
462  free(fullpath);
463 
464  return m ? m->local : false;
465 }
466 
485 std::pair<std::string, std::string>
486 LocalFileSystem::findCachePath(const std::vector<std::string> &paths,
487  double minFreeSpace) const
488 {
489  struct stat s;
490  struct statfs sfs;
491  std::ostringstream warningst;
492  warningst << "Cannot use lazy-download because:\n";
493 
494  for (size_t i = 0, e = paths.size(); i < e; ++i)
495  {
496  char *fullpath;
497  const char *inpath = paths[i].c_str();
498  const char *path = inpath;
499 
500  if (*path == '$')
501  {
502  char *p = getenv(path+1);
503  if (p && *p)
504  path = p;
505  else if (! strcmp(path, "$TMPDIR"))
506  path = "/tmp";
507  }
508 
509  if (! (fullpath = realpath(path, nullptr)))
510  fullpath = strdup(path);
511 
512 #if 0
513  std::cerr /* edm::LogInfo("LocalFileSystem") */
514  << "Checking if '" << fullpath << "', from '"
515  << inpath << "' is valid cache path with "
516  << minFreeSpace << " free space" << std::endl;
517 #endif
518 
519  if (lstat(fullpath, &s) < 0)
520  {
521  int nerr = errno;
522  if (nerr != ENOENT && nerr != EACCES)
523  edm::LogWarning("LocalFileSystem::findCachePath()")
524  << "Cannot lstat('" << fullpath << "', from '"
525  << inpath << "'): " << strerror(nerr) << " (error "
526  << nerr << ")";
527  free(fullpath);
528  continue;
529  }
530 
531  if (statfs(fullpath, &sfs) < 0)
532  {
533  int nerr = errno;
534  edm::LogWarning("LocalFileSystem::findCachePath()")
535  << "Cannot statfs('" << fullpath << "', from '"
536  << inpath << "'): " << strerror(nerr) << " (error "
537  << nerr << ")";
538  free(fullpath);
539  continue;
540  }
541 
542  std::vector<std::string> prev_paths;
543  FSInfo *m = findMount(fullpath, &sfs, &s, prev_paths);
544 #if 0
545  std::cerr /* edm::LogInfo("LocalFileSystem") */
546  << "Candidate '" << fullpath << "': "
547  << "found=" << (m ? 1 : 0)
548  << " local=" << (m && m->local)
549  << " free=" << (m ? m->freespc : 0)
550  << " access=" << access(fullpath, W_OK)
551  << std::endl;
552 #endif
553 
554  if (m
555  && m->local
556  && m->freespc >= minFreeSpace
557  && access(fullpath, W_OK) == 0)
558  {
559  std::string result(fullpath);
560  free(fullpath);
561  return std::make_pair(result,std::string());
562  }
563  else if (m)
564  {
565  if (!m->local)
566  {
567  warningst << "- The mount " << fullpath << " is not local.\n";
568  }
569  else if (m->freespc < minFreeSpace)
570  {
571  warningst << " - The mount at " << fullpath << " has only " << m->freespc << " GB free; a minumum of " << minFreeSpace << " GB is required.\n";
572  }
573  else if (access(fullpath, W_OK))
574  {
575  warningst << " - The process has no permission to write into " << fullpath << "\n";
576  }
577  }
578 
579  free(fullpath);
580  }
581 
582  std::string warning_str = warningst.str();
583  if (!warning_str.empty())
584  {
585  warning_str = warning_str.substr(0, warning_str.size()-2);
586  }
587 
588  return std::make_pair(std::string(), std::move(warning_str));
589 }
590 
593 {
594  if (readFSTypes() < 0)
595  return;
596 
597  if (initFSList() < 0)
598  return;
599 }
600 
603 {
604  for (size_t i = 0, e = fs_.size(); i < e; ++i)
605  free(fs_[i]);
606 }
int statFSInfo(FSInfo *i) const
type
Definition: HCALResponse.h:21
Information about file systems on this node.
A arg
Definition: Factorize.h:37
int readFSTypes(void)
std::vector< std::string > fstypes_
FSInfo * initFSInfo(void *p)
bool isLocalPath(const std::string &path) const
std::atomic< bool > checked
std::pair< std::string, std::string > findCachePath(const std::vector< std::string > &paths, double minFreeSpace) const
std::vector< FSInfo * > fs_
def move(src, dest)
Definition: eostools.py:510
FSInfo * findMount(const char *path, struct statfs *sfs, struct stat *s, std::vector< std::string > &) const