PageRenderTime 1397ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/path.c

https://github.com/VonC/git
C | 823 lines | 586 code | 90 blank | 147 comment | 201 complexity | 4015d6a60e9b67345d92b32b9e6ff217 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0, BSD-2-Clause
  1. /*
  2. * Utilities for paths and pathnames
  3. */
  4. #include "cache.h"
  5. #include "strbuf.h"
  6. #include "string-list.h"
  7. static int get_st_mode_bits(const char *path, int *mode)
  8. {
  9. struct stat st;
  10. if (lstat(path, &st) < 0)
  11. return -1;
  12. *mode = st.st_mode;
  13. return 0;
  14. }
  15. static char bad_path[] = "/bad-path/";
  16. static char *get_pathname(void)
  17. {
  18. static char pathname_array[4][PATH_MAX];
  19. static int index;
  20. return pathname_array[3 & ++index];
  21. }
  22. static char *cleanup_path(char *path)
  23. {
  24. /* Clean it up */
  25. if (!memcmp(path, "./", 2)) {
  26. path += 2;
  27. while (*path == '/')
  28. path++;
  29. }
  30. return path;
  31. }
  32. char *mksnpath(char *buf, size_t n, const char *fmt, ...)
  33. {
  34. va_list args;
  35. unsigned len;
  36. va_start(args, fmt);
  37. len = vsnprintf(buf, n, fmt, args);
  38. va_end(args);
  39. if (len >= n) {
  40. strlcpy(buf, bad_path, n);
  41. return buf;
  42. }
  43. return cleanup_path(buf);
  44. }
  45. static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
  46. {
  47. const char *git_dir = get_git_dir();
  48. size_t len;
  49. len = strlen(git_dir);
  50. if (n < len + 1)
  51. goto bad;
  52. memcpy(buf, git_dir, len);
  53. if (len && !is_dir_sep(git_dir[len-1]))
  54. buf[len++] = '/';
  55. len += vsnprintf(buf + len, n - len, fmt, args);
  56. if (len >= n)
  57. goto bad;
  58. return cleanup_path(buf);
  59. bad:
  60. strlcpy(buf, bad_path, n);
  61. return buf;
  62. }
  63. char *git_snpath(char *buf, size_t n, const char *fmt, ...)
  64. {
  65. char *ret;
  66. va_list args;
  67. va_start(args, fmt);
  68. ret = vsnpath(buf, n, fmt, args);
  69. va_end(args);
  70. return ret;
  71. }
  72. char *git_pathdup(const char *fmt, ...)
  73. {
  74. char path[PATH_MAX], *ret;
  75. va_list args;
  76. va_start(args, fmt);
  77. ret = vsnpath(path, sizeof(path), fmt, args);
  78. va_end(args);
  79. return xstrdup(ret);
  80. }
  81. char *mkpathdup(const char *fmt, ...)
  82. {
  83. char *path;
  84. struct strbuf sb = STRBUF_INIT;
  85. va_list args;
  86. va_start(args, fmt);
  87. strbuf_vaddf(&sb, fmt, args);
  88. va_end(args);
  89. path = xstrdup(cleanup_path(sb.buf));
  90. strbuf_release(&sb);
  91. return path;
  92. }
  93. char *mkpath(const char *fmt, ...)
  94. {
  95. va_list args;
  96. unsigned len;
  97. char *pathname = get_pathname();
  98. va_start(args, fmt);
  99. len = vsnprintf(pathname, PATH_MAX, fmt, args);
  100. va_end(args);
  101. if (len >= PATH_MAX)
  102. return bad_path;
  103. return cleanup_path(pathname);
  104. }
  105. char *git_path(const char *fmt, ...)
  106. {
  107. char *pathname = get_pathname();
  108. va_list args;
  109. char *ret;
  110. va_start(args, fmt);
  111. ret = vsnpath(pathname, PATH_MAX, fmt, args);
  112. va_end(args);
  113. return ret;
  114. }
  115. void home_config_paths(char **global, char **xdg, char *file)
  116. {
  117. char *xdg_home = getenv("XDG_CONFIG_HOME");
  118. char *home = getenv("HOME");
  119. char *to_free = NULL;
  120. if (!home) {
  121. if (global)
  122. *global = NULL;
  123. } else {
  124. if (!xdg_home) {
  125. to_free = mkpathdup("%s/.config", home);
  126. xdg_home = to_free;
  127. }
  128. if (global)
  129. *global = mkpathdup("%s/.gitconfig", home);
  130. }
  131. if (!xdg_home)
  132. *xdg = NULL;
  133. else
  134. *xdg = mkpathdup("%s/git/%s", xdg_home, file);
  135. free(to_free);
  136. }
  137. char *git_path_submodule(const char *path, const char *fmt, ...)
  138. {
  139. char *pathname = get_pathname();
  140. struct strbuf buf = STRBUF_INIT;
  141. const char *git_dir;
  142. va_list args;
  143. unsigned len;
  144. len = strlen(path);
  145. if (len > PATH_MAX-100)
  146. return bad_path;
  147. strbuf_addstr(&buf, path);
  148. if (len && path[len-1] != '/')
  149. strbuf_addch(&buf, '/');
  150. strbuf_addstr(&buf, ".git");
  151. git_dir = read_gitfile(buf.buf);
  152. if (git_dir) {
  153. strbuf_reset(&buf);
  154. strbuf_addstr(&buf, git_dir);
  155. }
  156. strbuf_addch(&buf, '/');
  157. if (buf.len >= PATH_MAX)
  158. return bad_path;
  159. memcpy(pathname, buf.buf, buf.len + 1);
  160. strbuf_release(&buf);
  161. len = strlen(pathname);
  162. va_start(args, fmt);
  163. len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
  164. va_end(args);
  165. if (len >= PATH_MAX)
  166. return bad_path;
  167. return cleanup_path(pathname);
  168. }
  169. int validate_headref(const char *path)
  170. {
  171. struct stat st;
  172. char *buf, buffer[256];
  173. unsigned char sha1[20];
  174. int fd;
  175. ssize_t len;
  176. if (lstat(path, &st) < 0)
  177. return -1;
  178. /* Make sure it is a "refs/.." symlink */
  179. if (S_ISLNK(st.st_mode)) {
  180. len = readlink(path, buffer, sizeof(buffer)-1);
  181. if (len >= 5 && !memcmp("refs/", buffer, 5))
  182. return 0;
  183. return -1;
  184. }
  185. /*
  186. * Anything else, just open it and try to see if it is a symbolic ref.
  187. */
  188. fd = open(path, O_RDONLY);
  189. if (fd < 0)
  190. return -1;
  191. len = read_in_full(fd, buffer, sizeof(buffer)-1);
  192. close(fd);
  193. /*
  194. * Is it a symbolic ref?
  195. */
  196. if (len < 4)
  197. return -1;
  198. if (!memcmp("ref:", buffer, 4)) {
  199. buf = buffer + 4;
  200. len -= 4;
  201. while (len && isspace(*buf))
  202. buf++, len--;
  203. if (len >= 5 && !memcmp("refs/", buf, 5))
  204. return 0;
  205. }
  206. /*
  207. * Is this a detached HEAD?
  208. */
  209. if (!get_sha1_hex(buffer, sha1))
  210. return 0;
  211. return -1;
  212. }
  213. static struct passwd *getpw_str(const char *username, size_t len)
  214. {
  215. struct passwd *pw;
  216. char *username_z = xmemdupz(username, len);
  217. pw = getpwnam(username_z);
  218. free(username_z);
  219. return pw;
  220. }
  221. /*
  222. * Return a string with ~ and ~user expanded via getpw*. If buf != NULL,
  223. * then it is a newly allocated string. Returns NULL on getpw failure or
  224. * if path is NULL.
  225. */
  226. char *expand_user_path(const char *path)
  227. {
  228. struct strbuf user_path = STRBUF_INIT;
  229. const char *to_copy = path;
  230. if (path == NULL)
  231. goto return_null;
  232. if (path[0] == '~') {
  233. const char *first_slash = strchrnul(path, '/');
  234. const char *username = path + 1;
  235. size_t username_len = first_slash - username;
  236. if (username_len == 0) {
  237. const char *home = getenv("HOME");
  238. if (!home)
  239. goto return_null;
  240. strbuf_addstr(&user_path, home);
  241. } else {
  242. struct passwd *pw = getpw_str(username, username_len);
  243. if (!pw)
  244. goto return_null;
  245. strbuf_addstr(&user_path, pw->pw_dir);
  246. }
  247. to_copy = first_slash;
  248. }
  249. strbuf_addstr(&user_path, to_copy);
  250. return strbuf_detach(&user_path, NULL);
  251. return_null:
  252. strbuf_release(&user_path);
  253. return NULL;
  254. }
  255. /*
  256. * First, one directory to try is determined by the following algorithm.
  257. *
  258. * (0) If "strict" is given, the path is used as given and no DWIM is
  259. * done. Otherwise:
  260. * (1) "~/path" to mean path under the running user's home directory;
  261. * (2) "~user/path" to mean path under named user's home directory;
  262. * (3) "relative/path" to mean cwd relative directory; or
  263. * (4) "/absolute/path" to mean absolute directory.
  264. *
  265. * Unless "strict" is given, we try access() for existence of "%s.git/.git",
  266. * "%s/.git", "%s.git", "%s" in this order. The first one that exists is
  267. * what we try.
  268. *
  269. * Second, we try chdir() to that. Upon failure, we return NULL.
  270. *
  271. * Then, we try if the current directory is a valid git repository.
  272. * Upon failure, we return NULL.
  273. *
  274. * If all goes well, we return the directory we used to chdir() (but
  275. * before ~user is expanded), avoiding getcwd() resolving symbolic
  276. * links. User relative paths are also returned as they are given,
  277. * except DWIM suffixing.
  278. */
  279. const char *enter_repo(const char *path, int strict)
  280. {
  281. static char used_path[PATH_MAX];
  282. static char validated_path[PATH_MAX];
  283. if (!path)
  284. return NULL;
  285. if (!strict) {
  286. static const char *suffix[] = {
  287. "/.git", "", ".git/.git", ".git", NULL,
  288. };
  289. const char *gitfile;
  290. int len = strlen(path);
  291. int i;
  292. while ((1 < len) && (path[len-1] == '/'))
  293. len--;
  294. if (PATH_MAX <= len)
  295. return NULL;
  296. strncpy(used_path, path, len); used_path[len] = 0 ;
  297. strcpy(validated_path, used_path);
  298. if (used_path[0] == '~') {
  299. char *newpath = expand_user_path(used_path);
  300. if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
  301. free(newpath);
  302. return NULL;
  303. }
  304. /*
  305. * Copy back into the static buffer. A pity
  306. * since newpath was not bounded, but other
  307. * branches of the if are limited by PATH_MAX
  308. * anyway.
  309. */
  310. strcpy(used_path, newpath); free(newpath);
  311. }
  312. else if (PATH_MAX - 10 < len)
  313. return NULL;
  314. len = strlen(used_path);
  315. for (i = 0; suffix[i]; i++) {
  316. struct stat st;
  317. strcpy(used_path + len, suffix[i]);
  318. if (!stat(used_path, &st) &&
  319. (S_ISREG(st.st_mode) ||
  320. (S_ISDIR(st.st_mode) && is_git_directory(used_path)))) {
  321. strcat(validated_path, suffix[i]);
  322. break;
  323. }
  324. }
  325. if (!suffix[i])
  326. return NULL;
  327. gitfile = read_gitfile(used_path) ;
  328. if (gitfile)
  329. strcpy(used_path, gitfile);
  330. if (chdir(used_path))
  331. return NULL;
  332. path = validated_path;
  333. }
  334. else if (chdir(path))
  335. return NULL;
  336. if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
  337. validate_headref("HEAD") == 0) {
  338. set_git_dir(".");
  339. check_repository_format();
  340. return path;
  341. }
  342. return NULL;
  343. }
  344. static int calc_shared_perm(int mode)
  345. {
  346. int tweak;
  347. if (shared_repository < 0)
  348. tweak = -shared_repository;
  349. else
  350. tweak = shared_repository;
  351. if (!(mode & S_IWUSR))
  352. tweak &= ~0222;
  353. if (mode & S_IXUSR)
  354. /* Copy read bits to execute bits */
  355. tweak |= (tweak & 0444) >> 2;
  356. if (shared_repository < 0)
  357. mode = (mode & ~0777) | tweak;
  358. else
  359. mode |= tweak;
  360. return mode;
  361. }
  362. int adjust_shared_perm(const char *path)
  363. {
  364. int old_mode, new_mode;
  365. if (!shared_repository)
  366. return 0;
  367. if (get_st_mode_bits(path, &old_mode) < 0)
  368. return -1;
  369. new_mode = calc_shared_perm(old_mode);
  370. if (S_ISDIR(old_mode)) {
  371. /* Copy read bits to execute bits */
  372. new_mode |= (new_mode & 0444) >> 2;
  373. new_mode |= FORCE_DIR_SET_GID;
  374. }
  375. if (((old_mode ^ new_mode) & ~S_IFMT) &&
  376. chmod(path, (new_mode & ~S_IFMT)) < 0)
  377. return -2;
  378. return 0;
  379. }
  380. static int have_same_root(const char *path1, const char *path2)
  381. {
  382. int is_abs1, is_abs2;
  383. is_abs1 = is_absolute_path(path1);
  384. is_abs2 = is_absolute_path(path2);
  385. return (is_abs1 && is_abs2 && tolower(path1[0]) == tolower(path2[0])) ||
  386. (!is_abs1 && !is_abs2);
  387. }
  388. /*
  389. * Give path as relative to prefix.
  390. *
  391. * The strbuf may or may not be used, so do not assume it contains the
  392. * returned path.
  393. */
  394. const char *relative_path(const char *in, const char *prefix,
  395. struct strbuf *sb)
  396. {
  397. int in_len = in ? strlen(in) : 0;
  398. int prefix_len = prefix ? strlen(prefix) : 0;
  399. int in_off = 0;
  400. int prefix_off = 0;
  401. int i = 0, j = 0;
  402. if (!in_len)
  403. return "./";
  404. else if (!prefix_len)
  405. return in;
  406. if (have_same_root(in, prefix)) {
  407. /* bypass dos_drive, for "c:" is identical to "C:" */
  408. if (has_dos_drive_prefix(in)) {
  409. i = 2;
  410. j = 2;
  411. }
  412. } else {
  413. return in;
  414. }
  415. while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
  416. if (is_dir_sep(prefix[i])) {
  417. while (is_dir_sep(prefix[i]))
  418. i++;
  419. while (is_dir_sep(in[j]))
  420. j++;
  421. prefix_off = i;
  422. in_off = j;
  423. } else {
  424. i++;
  425. j++;
  426. }
  427. }
  428. if (
  429. /* "prefix" seems like prefix of "in" */
  430. i >= prefix_len &&
  431. /*
  432. * but "/foo" is not a prefix of "/foobar"
  433. * (i.e. prefix not end with '/')
  434. */
  435. prefix_off < prefix_len) {
  436. if (j >= in_len) {
  437. /* in="/a/b", prefix="/a/b" */
  438. in_off = in_len;
  439. } else if (is_dir_sep(in[j])) {
  440. /* in="/a/b/c", prefix="/a/b" */
  441. while (is_dir_sep(in[j]))
  442. j++;
  443. in_off = j;
  444. } else {
  445. /* in="/a/bbb/c", prefix="/a/b" */
  446. i = prefix_off;
  447. }
  448. } else if (
  449. /* "in" is short than "prefix" */
  450. j >= in_len &&
  451. /* "in" not end with '/' */
  452. in_off < in_len) {
  453. if (is_dir_sep(prefix[i])) {
  454. /* in="/a/b", prefix="/a/b/c/" */
  455. while (is_dir_sep(prefix[i]))
  456. i++;
  457. in_off = in_len;
  458. }
  459. }
  460. in += in_off;
  461. in_len -= in_off;
  462. if (i >= prefix_len) {
  463. if (!in_len)
  464. return "./";
  465. else
  466. return in;
  467. }
  468. strbuf_reset(sb);
  469. strbuf_grow(sb, in_len);
  470. while (i < prefix_len) {
  471. if (is_dir_sep(prefix[i])) {
  472. strbuf_addstr(sb, "../");
  473. while (is_dir_sep(prefix[i]))
  474. i++;
  475. continue;
  476. }
  477. i++;
  478. }
  479. if (!is_dir_sep(prefix[prefix_len - 1]))
  480. strbuf_addstr(sb, "../");
  481. strbuf_addstr(sb, in);
  482. return sb->buf;
  483. }
  484. /*
  485. * A simpler implementation of relative_path
  486. *
  487. * Get relative path by removing "prefix" from "in". This function
  488. * first appears in v1.5.6-1-g044bbbc, and makes git_dir shorter
  489. * to increase performance when traversing the path to work_tree.
  490. */
  491. const char *remove_leading_path(const char *in, const char *prefix)
  492. {
  493. static char buf[PATH_MAX + 1];
  494. int i = 0, j = 0;
  495. if (!prefix || !prefix[0])
  496. return in;
  497. while (prefix[i]) {
  498. if (is_dir_sep(prefix[i])) {
  499. if (!is_dir_sep(in[j]))
  500. return in;
  501. while (is_dir_sep(prefix[i]))
  502. i++;
  503. while (is_dir_sep(in[j]))
  504. j++;
  505. continue;
  506. } else if (in[j] != prefix[i]) {
  507. return in;
  508. }
  509. i++;
  510. j++;
  511. }
  512. if (
  513. /* "/foo" is a prefix of "/foo" */
  514. in[j] &&
  515. /* "/foo" is not a prefix of "/foobar" */
  516. !is_dir_sep(prefix[i-1]) && !is_dir_sep(in[j])
  517. )
  518. return in;
  519. while (is_dir_sep(in[j]))
  520. j++;
  521. if (!in[j])
  522. strcpy(buf, ".");
  523. else
  524. strcpy(buf, in + j);
  525. return buf;
  526. }
  527. /*
  528. * It is okay if dst == src, but they should not overlap otherwise.
  529. *
  530. * Performs the following normalizations on src, storing the result in dst:
  531. * - Ensures that components are separated by '/' (Windows only)
  532. * - Squashes sequences of '/'.
  533. * - Removes "." components.
  534. * - Removes ".." components, and the components the precede them.
  535. * Returns failure (non-zero) if a ".." component appears as first path
  536. * component anytime during the normalization. Otherwise, returns success (0).
  537. *
  538. * Note that this function is purely textual. It does not follow symlinks,
  539. * verify the existence of the path, or make any system calls.
  540. *
  541. * prefix_len != NULL is for a specific case of prefix_pathspec():
  542. * assume that src == dst and src[0..prefix_len-1] is already
  543. * normalized, any time "../" eats up to the prefix_len part,
  544. * prefix_len is reduced. In the end prefix_len is the remaining
  545. * prefix that has not been overridden by user pathspec.
  546. */
  547. int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
  548. {
  549. char *dst0;
  550. if (has_dos_drive_prefix(src)) {
  551. *dst++ = *src++;
  552. *dst++ = *src++;
  553. }
  554. dst0 = dst;
  555. if (is_dir_sep(*src)) {
  556. *dst++ = '/';
  557. while (is_dir_sep(*src))
  558. src++;
  559. }
  560. for (;;) {
  561. char c = *src;
  562. /*
  563. * A path component that begins with . could be
  564. * special:
  565. * (1) "." and ends -- ignore and terminate.
  566. * (2) "./" -- ignore them, eat slash and continue.
  567. * (3) ".." and ends -- strip one and terminate.
  568. * (4) "../" -- strip one, eat slash and continue.
  569. */
  570. if (c == '.') {
  571. if (!src[1]) {
  572. /* (1) */
  573. src++;
  574. } else if (is_dir_sep(src[1])) {
  575. /* (2) */
  576. src += 2;
  577. while (is_dir_sep(*src))
  578. src++;
  579. continue;
  580. } else if (src[1] == '.') {
  581. if (!src[2]) {
  582. /* (3) */
  583. src += 2;
  584. goto up_one;
  585. } else if (is_dir_sep(src[2])) {
  586. /* (4) */
  587. src += 3;
  588. while (is_dir_sep(*src))
  589. src++;
  590. goto up_one;
  591. }
  592. }
  593. }
  594. /* copy up to the next '/', and eat all '/' */
  595. while ((c = *src++) != '\0' && !is_dir_sep(c))
  596. *dst++ = c;
  597. if (is_dir_sep(c)) {
  598. *dst++ = '/';
  599. while (is_dir_sep(c))
  600. c = *src++;
  601. src--;
  602. } else if (!c)
  603. break;
  604. continue;
  605. up_one:
  606. /*
  607. * dst0..dst is prefix portion, and dst[-1] is '/';
  608. * go up one level.
  609. */
  610. dst--; /* go to trailing '/' */
  611. if (dst <= dst0)
  612. return -1;
  613. /* Windows: dst[-1] cannot be backslash anymore */
  614. while (dst0 < dst && dst[-1] != '/')
  615. dst--;
  616. if (prefix_len && *prefix_len > dst - dst0)
  617. *prefix_len = dst - dst0;
  618. }
  619. *dst = '\0';
  620. return 0;
  621. }
  622. int normalize_path_copy(char *dst, const char *src)
  623. {
  624. return normalize_path_copy_len(dst, src, NULL);
  625. }
  626. /*
  627. * path = Canonical absolute path
  628. * prefixes = string_list containing normalized, absolute paths without
  629. * trailing slashes (except for the root directory, which is denoted by "/").
  630. *
  631. * Determines, for each path in prefixes, whether the "prefix"
  632. * is an ancestor directory of path. Returns the length of the longest
  633. * ancestor directory, excluding any trailing slashes, or -1 if no prefix
  634. * is an ancestor. (Note that this means 0 is returned if prefixes is
  635. * ["/"].) "/foo" is not considered an ancestor of "/foobar". Directories
  636. * are not considered to be their own ancestors. path must be in a
  637. * canonical form: empty components, or "." or ".." components are not
  638. * allowed.
  639. */
  640. int longest_ancestor_length(const char *path, struct string_list *prefixes)
  641. {
  642. int i, max_len = -1;
  643. if (!strcmp(path, "/"))
  644. return -1;
  645. for (i = 0; i < prefixes->nr; i++) {
  646. const char *ceil = prefixes->items[i].string;
  647. int len = strlen(ceil);
  648. if (len == 1 && ceil[0] == '/')
  649. len = 0; /* root matches anything, with length 0 */
  650. else if (!strncmp(path, ceil, len) && path[len] == '/')
  651. ; /* match of length len */
  652. else
  653. continue; /* no match */
  654. if (len > max_len)
  655. max_len = len;
  656. }
  657. return max_len;
  658. }
  659. /* strip arbitrary amount of directory separators at end of path */
  660. static inline int chomp_trailing_dir_sep(const char *path, int len)
  661. {
  662. while (len && is_dir_sep(path[len - 1]))
  663. len--;
  664. return len;
  665. }
  666. /*
  667. * If path ends with suffix (complete path components), returns the
  668. * part before suffix (sans trailing directory separators).
  669. * Otherwise returns NULL.
  670. */
  671. char *strip_path_suffix(const char *path, const char *suffix)
  672. {
  673. int path_len = strlen(path), suffix_len = strlen(suffix);
  674. while (suffix_len) {
  675. if (!path_len)
  676. return NULL;
  677. if (is_dir_sep(path[path_len - 1])) {
  678. if (!is_dir_sep(suffix[suffix_len - 1]))
  679. return NULL;
  680. path_len = chomp_trailing_dir_sep(path, path_len);
  681. suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
  682. }
  683. else if (path[--path_len] != suffix[--suffix_len])
  684. return NULL;
  685. }
  686. if (path_len && !is_dir_sep(path[path_len - 1]))
  687. return NULL;
  688. return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
  689. }
  690. int daemon_avoid_alias(const char *p)
  691. {
  692. int sl, ndot;
  693. /*
  694. * This resurrects the belts and suspenders paranoia check by HPA
  695. * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
  696. * does not do getcwd() based path canonicalization.
  697. *
  698. * sl becomes true immediately after seeing '/' and continues to
  699. * be true as long as dots continue after that without intervening
  700. * non-dot character.
  701. */
  702. if (!p || (*p != '/' && *p != '~'))
  703. return -1;
  704. sl = 1; ndot = 0;
  705. p++;
  706. while (1) {
  707. char ch = *p++;
  708. if (sl) {
  709. if (ch == '.')
  710. ndot++;
  711. else if (ch == '/') {
  712. if (ndot < 3)
  713. /* reject //, /./ and /../ */
  714. return -1;
  715. ndot = 0;
  716. }
  717. else if (ch == 0) {
  718. if (0 < ndot && ndot < 3)
  719. /* reject /.$ and /..$ */
  720. return -1;
  721. return 0;
  722. }
  723. else
  724. sl = ndot = 0;
  725. }
  726. else if (ch == 0)
  727. return 0;
  728. else if (ch == '/') {
  729. sl = 1;
  730. ndot = 0;
  731. }
  732. }
  733. }