xref: /trafficserver/src/tscore/ink_file.cc (revision 4cfd5a73)
1 /** @file
2 
3   File manipulation routines for libts
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include <unistd.h>
25 #include <climits>
26 #include "tscore/ink_platform.h"
27 #include "tscore/ink_file.h"
28 #include "tscore/ink_string.h"
29 #include "tscore/ink_memory.h"
30 
31 #if HAVE_SYS_STAT_H
32 #include <sys/stat.h>
33 #endif
34 
35 #if HAVE_SYS_PARAM_H
36 #include <sys/param.h>
37 #endif
38 
39 #if HAVE_SYS_DISK_H
40 #include <sys/disk.h>
41 #endif
42 
43 #if HAVE_SYS_DISKLABEL_H
44 #include <sys/disklabel.h>
45 #endif
46 
47 #if HAVE_SYS_IOCTL_H
48 #include <sys/ioctl.h>
49 #endif
50 
51 #if HAVE_LINUX_HDREG_H
52 #include <linux/hdreg.h> /* for struct hd_geometry */
53 #endif
54 
55 #if HAVE_LINUX_FS_H
56 #include <linux/fs.h> /* for BLKGETSIZE.  sys/mount.h is another candidate */
57 #endif
58 
59 typedef union {
60   uint64_t u64;
61   uint32_t u32;
62   off_t off;
63 } ioctl_arg_t;
64 
65 int
ink_fputln(FILE * stream,const char * s)66 ink_fputln(FILE *stream, const char *s)
67 {
68   if (stream && s) {
69     int rc = fputs(s, stream);
70     if (rc > 0) {
71       rc += fputc('\n', stream);
72     }
73     return rc;
74   } else {
75     return -EINVAL;
76   }
77 } /* End ink_fgets */
78 
79 /*---------------------------------------------------------------------------*
80 
81   int ink_file_fd_readline(int fd, int bufsz, char *buf)
82 
83   This routine reads bytes from <fd> into the buffer pointed to by <buf>.
84   The reading stops when (a) a LF is read into the buffer, (b) the end of
85   file is reached, or (c) <bufsz> - 1 bytes are read.  The <bufsz> parameter
86   must be >= 2.
87 
88   The data pointed to by <buf> is always NUL terminated, and the LF is
89   left in the data.  This routine can be used as a replacement for
90   fgets-like functions.  If the <bufsz> is too small to hold a complete line,
91   the partial line will be stored, and subsequent reads will read more data.
92 
93   This routine returns the number of bytes read, 0 on end of file, or
94   a negative errno on error.
95 
96  *---------------------------------------------------------------------------*/
97 
98 int
ink_file_fd_readline(int fd,int bufsz,char * buf)99 ink_file_fd_readline(int fd, int bufsz, char *buf)
100 {
101   char c;
102   int i = 0;
103 
104   if (bufsz < 2) {
105     return (-EINVAL); /* bufsz must by >= 2 */
106   }
107 
108   while (i < bufsz - 1) {    /* leave 1 byte for NUL */
109     int n = read(fd, &c, 1); /* read 1 byte */
110     if (n == 0) {
111       break; /* EOF */
112     }
113     if (n < 0) {
114       return (n); /* error */
115     }
116     buf[i++] = c; /* store in buffer */
117     if (c == '\n') {
118       break; /* stop if stored a LF */
119     }
120   }
121 
122   buf[i] = '\0'; /* NUL terminate buffer */
123   return (i);    /* number of bytes read */
124 } /* End ink_file_fd_readline */
125 
126 /* Write until NUL */
127 int
ink_file_fd_writestring(int fd,const char * buf)128 ink_file_fd_writestring(int fd, const char *buf)
129 {
130   int len, i = 0;
131 
132   if (buf && (len = strlen(buf)) > 0 && (i = static_cast<int>(write(fd, buf, static_cast<size_t>(len))) != len)) {
133     i = -1;
134   }
135 
136   return i; /* return chars written */
137 } /* End ink_file_fd_writestring */
138 
139 int
ink_filepath_merge(char * path,int pathsz,const char * rootpath,const char * addpath,int flags)140 ink_filepath_merge(char *path, int pathsz, const char *rootpath, const char *addpath, int flags)
141 {
142   size_t rootlen; // is the length of the src rootpath
143   size_t maxlen;  // maximum total path length
144   size_t keptlen; // is the length of the retained rootpath
145   size_t pathlen; // is the length of the result path
146   size_t seglen;  // is the end of the current segment
147   char curdir[PATH_NAME_MAX];
148 
149   /* Treat null as an empty path.
150    */
151   if (!addpath) {
152     addpath = "";
153   }
154 
155   if (addpath[0] == '/') {
156     // If addpath is rooted, then rootpath is unused.
157     // Ths violates any INK_FILEPATH_SECUREROOTTEST and
158     // INK_FILEPATH_NOTABSOLUTE flags specified.
159     //
160     if (flags & INK_FILEPATH_SECUREROOTTEST) {
161       return EACCES; // APR_EABOVEROOT;
162     }
163     if (flags & INK_FILEPATH_NOTABSOLUTE) {
164       return EISDIR; // APR_EABSOLUTE;
165     }
166 
167     // If INK_FILEPATH_NOTABOVEROOT wasn't specified,
168     // we won't test the root again, it's ignored.
169     // Waste no CPU retrieving the working path.
170     //
171     if (!rootpath && !(flags & INK_FILEPATH_NOTABOVEROOT)) {
172       rootpath = "";
173     }
174   } else {
175     // If INK_FILEPATH_NOTABSOLUTE is specified, the caller
176     // requires a relative result.  If the rootpath is
177     // omitted, we do not retrieve the working path,
178     // if rootpath was supplied as absolute then fail.
179     //
180     if (flags & INK_FILEPATH_NOTABSOLUTE) {
181       if (!rootpath) {
182         rootpath = "";
183       } else if (rootpath[0] == '/') {
184         return EISDIR; // APR_EABSOLUTE;
185       }
186     }
187   }
188   if (!rootpath) {
189     // Start with the current working path.  This is bass akwards,
190     // but required since the compiler (at least vc) doesn't like
191     // passing the address of a const char* for a char** arg.
192     //
193     if (!getcwd(curdir, sizeof(curdir))) {
194       return errno;
195     }
196     rootpath = curdir;
197   }
198   rootlen = strlen(rootpath);
199   maxlen  = rootlen + strlen(addpath) + 4; // 4 for slashes at start, after
200                                            // root, and at end, plus trailing
201                                            // null
202   if (maxlen > static_cast<size_t>(pathsz)) {
203     return E2BIG; // APR_ENAMETOOLONG;
204   }
205   if (addpath[0] == '/') {
206     // Ignore the given root path, strip off leading
207     // '/'s to a single leading '/' from the addpath,
208     // and leave addpath at the first non-'/' character.
209     //
210     keptlen = 0;
211     while (addpath[0] == '/') {
212       ++addpath;
213     }
214     path[0] = '/';
215     pathlen = 1;
216   } else {
217     // If both paths are relative, fail early
218     //
219     if (rootpath[0] != '/' && (flags & INK_FILEPATH_NOTRELATIVE)) {
220       return EBADF; // APR_ERELATIVE;
221     }
222 
223     // Base the result path on the rootpath
224     //
225     keptlen = rootlen;
226     memcpy(path, rootpath, rootlen);
227 
228     // Always '/' terminate the given root path
229     //
230     if (keptlen && path[keptlen - 1] != '/') {
231       path[keptlen++] = '/';
232     }
233     pathlen = keptlen;
234   }
235 
236   while (*addpath) {
237     // Parse each segment, find the closing '/'
238     //
239     const char *next = addpath;
240     while (*next && (*next != '/')) {
241       ++next;
242     }
243     seglen = next - addpath;
244 
245     if (seglen == 0 || (seglen == 1 && addpath[0] == '.')) {
246       // noop segment (/ or ./) so skip it
247       //
248     } else if (seglen == 2 && addpath[0] == '.' && addpath[1] == '.') {
249       // backpath (../)
250       if (pathlen == 1 && path[0] == '/') {
251         // Attempt to move above root.  Always die if the
252         // INK_FILEPATH_SECUREROOTTEST flag is specified.
253         //
254         if (flags & INK_FILEPATH_SECUREROOTTEST) {
255           return EACCES; // APR_EABOVEROOT;
256         }
257 
258         // Otherwise this is simply a noop, above root is root.
259         // Flag that rootpath was entirely replaced.
260         //
261         keptlen = 0;
262       } else if (pathlen == 0 || (pathlen == 3 && !memcmp(path + pathlen - 3, "../", 3)) ||
263                  (pathlen > 3 && !memcmp(path + pathlen - 4, "/../", 4))) {
264         // Path is already backpathed or empty, if the
265         // INK_FILEPATH_SECUREROOTTEST.was given die now.
266         //
267         if (flags & INK_FILEPATH_SECUREROOTTEST) {
268           return EACCES; // APR_EABOVEROOT;
269         }
270 
271         // Otherwise append another backpath, including
272         // trailing slash if present.
273         //
274         memcpy(path + pathlen, "../", *next ? 3 : 2);
275         pathlen += *next ? 3 : 2;
276       } else {
277         // otherwise crop the prior segment
278         //
279         do {
280           --pathlen;
281         } while (pathlen && path[pathlen - 1] != '/');
282       }
283 
284       // Now test if we are above where we started and back up
285       // the keptlen offset to reflect the added/altered path.
286       //
287       if (pathlen < keptlen) {
288         if (flags & INK_FILEPATH_SECUREROOTTEST) {
289           return EACCES; // APR_EABOVEROOT;
290         }
291         keptlen = pathlen;
292       }
293     } else {
294       // An actual segment, append it to the destination path
295       //
296       if (*next) {
297         seglen++;
298       }
299       memcpy(path + pathlen, addpath, seglen);
300       pathlen += seglen;
301     }
302 
303     // Skip over trailing slash to the next segment
304     //
305     if (*next) {
306       ++next;
307     }
308 
309     addpath = next;
310   }
311   path[pathlen] = '\0';
312   if (pathlen > 1 && path[pathlen - 1] == '/') {
313     // Trim trailing slash unless requested
314     size_t es = strlen(addpath);
315     if (es == 0 || addpath[es - 1] != '/') {
316       --pathlen;
317       path[pathlen] = '\0';
318     }
319   }
320 
321   // keptlen will be the rootlen unless the addpath contained
322   // backpath elements.  If so, and INK_FILEPATH_NOTABOVEROOT
323   // is specified (INK_FILEPATH_SECUREROOTTEST was caught above),
324   // compare the original root to assure the result path is
325   // still within given root path.
326   //
327   if ((flags & INK_FILEPATH_NOTABOVEROOT) && keptlen < rootlen) {
328     if (strncmp(rootpath, path, rootlen)) {
329       return EACCES; // APR_EABOVEROOT;
330     }
331     if (rootpath[rootlen - 1] != '/' && path[rootlen] && path[rootlen] != '/') {
332       return EACCES; // APR_EABOVEROOT;
333     }
334   }
335 
336   return 0;
337 }
338 
339 int
ink_filepath_make(char * path,int pathsz,const char * rootpath,const char * addpath)340 ink_filepath_make(char *path, int pathsz, const char *rootpath, const char *addpath)
341 {
342   size_t rootlen; // is the length of the src rootpath
343   size_t maxlen;  // maximum total path length
344 
345   /* Treat null as an empty path.
346    */
347   if (!addpath) {
348     addpath = "";
349   }
350 
351   if (addpath[0] == '/') {
352     // If addpath is rooted, then rootpath is unused.
353     ink_strlcpy(path, addpath, pathsz);
354     return 0;
355   }
356   if (!rootpath || !*rootpath) {
357     // If there's no rootpath return the addpath
358     ink_strlcpy(path, addpath, pathsz);
359     return 0;
360   }
361   rootlen = strlen(rootpath);
362   maxlen  = strlen(addpath) + 2;
363   if (maxlen > static_cast<size_t>(pathsz)) {
364     *path = '\0';
365     return static_cast<int>(maxlen);
366   }
367   ink_strlcpy(path, rootpath, pathsz);
368   path += rootlen;
369   pathsz -= rootlen;
370   if (*(path - 1) != '/') {
371     *(path++) = '/';
372     --pathsz;
373   }
374   ink_strlcpy(path, addpath, pathsz);
375   return 0;
376 }
377 
378 int
ink_file_fd_zerofill(int fd,off_t size)379 ink_file_fd_zerofill(int fd, off_t size)
380 {
381   // Clear the file by truncating it to zero and then to the desired size.
382   if (ftruncate(fd, 0) < 0) {
383     return errno;
384   }
385 
386 // ZFS does not implement posix_fallocate() and fails with EINVAL. As a general workaround,
387 // just fall back to ftrucate if the preallocation fails.
388 #if HAVE_POSIX_FALLOCATE
389   if (posix_fallocate(fd, 0, size) == 0) {
390     return 0;
391   }
392 #endif
393 
394   if (ftruncate(fd, size) < 0) {
395     return errno;
396   }
397 
398   return 0;
399 }
400 
401 bool
ink_file_is_directory(const char * path)402 ink_file_is_directory(const char *path)
403 {
404   struct stat sbuf;
405 
406   if (stat(path, &sbuf) == -1) {
407     return false;
408   }
409 
410   return S_ISDIR(sbuf.st_mode);
411 }
412 
413 bool
ink_file_is_mmappable(mode_t st_mode)414 ink_file_is_mmappable(mode_t st_mode)
415 {
416   // Regular files are always ok;
417   if (S_ISREG(st_mode)) {
418     return true;
419   }
420 
421 #if defined(linux)
422   // Disks cannot be mmapped.
423   if (S_ISBLK(st_mode)) {
424     return false;
425   }
426 
427   // At least some character devices can be mmapped. But should you?
428   if (S_ISCHR(st_mode)) {
429     return true;
430   }
431 #endif
432 
433 #if defined(solaris)
434   // All file types on Solaris can be mmap(2)'ed.
435   return true;
436 #endif
437 
438   return false;
439 }
440 
441 bool
ink_file_get_geometry(int fd ATS_UNUSED,ink_device_geometry & geometry)442 ink_file_get_geometry(int fd ATS_UNUSED, ink_device_geometry &geometry)
443 {
444   ink_zero(geometry);
445 
446 #if defined(freebsd) || defined(darwin) || defined(openbsd)
447   ioctl_arg_t arg ATS_UNUSED;
448 
449 // These IOCTLs are standard across the BSD family; Darwin has a different set though.
450 #if defined(DIOCGMEDIASIZE)
451   if (ioctl(fd, DIOCGMEDIASIZE, &arg.off) == 0) {
452     geometry.totalsz = arg.off;
453   }
454 #endif
455 
456 #if defined(DIOCGSECTORSIZE)
457   if (ioctl(fd, DIOCGSECTORSIZE, &arg.u32) == 0) {
458     geometry.blocksz = arg.u32;
459   }
460 #endif
461 
462 #if !defined(DIOCGMEDIASIZE) || !defined(DIOCGSECTORSIZE)
463   errno = ENOTSUP;
464 #endif
465 
466 #elif defined(solaris)
467   struct stat sbuf;
468 
469   if (fstat(fd, &sbuf) == 0) {
470     geometry.totalsz = sbuf.st_size;
471     geometry.blocksz = sbuf.st_blksize;
472   }
473 
474 #elif defined(linux)
475   ioctl_arg_t arg;
476 
477   // The following set of ioctls work for both block and character devices. You can use the
478   // test_geometry program to test what happens for any specific use case or kernel.
479 
480   // BLKGETSIZE64 gets the block device size in bytes.
481   if (ioctl(fd, BLKGETSIZE64, &arg.u64) == 0) {
482     geometry.totalsz = arg.u64;
483   }
484 
485   // BLKSSZGET gets the logical block size in bytes.
486   if (ioctl(fd, BLKSSZGET, &arg.u32) == 0) {
487     geometry.blocksz = arg.u32;
488   }
489 
490 #if defined(BLKALIGNOFF)
491   // BLKALIGNOFF gets the number of bytes needed to align the I/Os to the block device with
492   // and underlying block devices. This might be non-zero when you are using a logical volume
493   // backed by JBOD or RAID device(s). BLKALIGNOFF was added in 2.6.32, so it's not present in
494   // RHEL 5.
495   if (ioctl(fd, BLKALIGNOFF, &arg.u32) == 0) {
496     geometry.alignsz = arg.u32;
497   }
498 #endif
499 
500 #else /* No raw device support on this platform. */
501 
502   errno = ENOTSUP;
503   return false;
504 
505 #endif
506 
507   if (geometry.totalsz == 0 || geometry.blocksz == 0) {
508     return false;
509   }
510 
511   return true;
512 }
513 
514 size_t
ink_file_namemax(const char * path)515 ink_file_namemax(const char *path)
516 {
517   long namemax = pathconf(path, _PC_NAME_MAX);
518   if (namemax > 0) {
519     return namemax;
520   }
521 
522 #if defined(NAME_MAX)
523   return NAME_MAX;
524 #else
525   return 255;
526 #endif
527 }
528 
529 int
ink_fileperm_parse(const char * perms)530 ink_fileperm_parse(const char *perms)
531 {
532   if (perms && strlen(perms) == 9) {
533     int re  = 0;
534     char *c = const_cast<char *>(perms);
535     if (*c == 'r') {
536       re |= S_IRUSR;
537     }
538     c++;
539     if (*c == 'w') {
540       re |= S_IWUSR;
541     }
542     c++;
543     if (*c == 'x') {
544       re |= S_IXUSR;
545     }
546     c++;
547     if (*c == 'r') {
548       re |= S_IRGRP;
549     }
550     c++;
551     if (*c == 'w') {
552       re |= S_IWGRP;
553     }
554     c++;
555     if (*c == 'x') {
556       re |= S_IXGRP;
557     }
558     c++;
559     if (*c == 'r') {
560       re |= S_IROTH;
561     }
562     c++;
563     if (*c == 'w') {
564       re |= S_IWOTH;
565     }
566     c++;
567     if (*c == 'x') {
568       re |= S_IXOTH;
569     }
570     return re;
571   }
572   return -1;
573 }
574