coreutils8.32 pwd命令和源码分析

GNU因为命令的兼容性,自己实现了一个getcwd函数(robust_getcwd)函数实现有点难看懂。

跳过roust_getcwd函数的话,这源码也是挺简单的

从main开始看

/* pwd - print current directory
   Copyright (C) 1994-2020 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <config.h>
#include <getopt.h>
#include <stdio.h>
#include <sys/types.h>

#include "system.h"
#include "die.h"
#include "error.h"
#include "quote.h"
#include "root-dev-ino.h"
#include "xgetcwd.h"

/* The official name of this program (e.g., no 'g' prefix).  */
/* 程序名 */
#define PROGRAM_NAME "pwd"

#define AUTHORS proper_name ("Jim Meyering") /* 作者 */

/* 文件名称 */
struct file_name
{
  char *buf;  /* 整个结构体的缓冲区 */
  size_t n_alloc;  /* 整个结构体的大小 */
  char *start;  /* 用于保存获取的路径名的缓冲区 */
};

/* 长选项结构体 */
static struct option const longopts[] =
{
  {"logical", no_argument, NULL, 'L'},
  {"physical", no_argument, NULL, 'P'},
  {GETOPT_HELP_OPTION_DECL},
  {GETOPT_VERSION_OPTION_DECL},
  {NULL, 0, NULL, 0}
};

/* 用法函数 */
void
usage (int status)
{
  if (status != EXIT_SUCCESS)
    emit_try_help ();
  else
    {
      printf (_("Usage: %s [OPTION]...\n"), program_name);
      fputs (_("\
Print the full filename of the current working directory.\n\
\n\
"), stdout);
      fputs (_("\
  -L, --logical   use PWD from environment, even if it contains symlinks\n\
  -P, --physical  avoid all symlinks\n\
"), stdout);
      fputs (HELP_OPTION_DESCRIPTION, stdout);
      fputs (VERSION_OPTION_DESCRIPTION, stdout);
      fputs (_("\n\
If no option is specified, -P is assumed.\n\
"), stdout);
      printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
      emit_ancillary_info (PROGRAM_NAME);
    }
  exit (status);
}

static void
file_name_free (struct file_name *p)
{
  free (p->buf);
  free (p);
}

static struct file_name *
file_name_init (void)
{
  struct file_name *p = xmalloc (sizeof *p);  /* 为p指针开辟内存 */

  /* Start with a buffer larger than PATH_MAX, but beware of systems
     on which PATH_MAX is very large -- e.g., INT_MAX.  */
  p->n_alloc = MIN (2 * PATH_MAX, 32 * 1024);  /* 设置内存大小 */

  p->buf = xmalloc (p->n_alloc);  /* 按照设置好的大小开辟内存 */
  p->start = p->buf + (p->n_alloc - 1); /* 修改p结构体的内存大小(结构体大小=结构体自身+用于存储路径的bug) */
  p->start[0] = '\0';  /* 清空start */
  return p;
}

/* Prepend the name S of length S_LEN, to the growing file_name, P.  */
/* 将长度为S_LEN的名称S作为扩展文件名P的前缀。*/
static void
file_name_prepend (struct file_name *p, char const *s, size_t s_len)
{
  size_t n_free = p->start - p->buf;
  if (n_free < 1 + s_len)
    {
      size_t half = p->n_alloc + 1 + s_len;
      /* Use xnmalloc+free rather than xnrealloc, since with the latter
         we'd end up copying the data twice: once via realloc, then again
         to align it with the end of the new buffer.  With xnmalloc, we
         copy it only once.  */
      char *q = xnmalloc (2, half);
      size_t n_used = p->n_alloc - n_free;
      p->start = q + 2 * half - n_used;
      memcpy (p->start, p->buf + n_free, n_used);
      free (p->buf);
      p->buf = q;
      p->n_alloc = 2 * half;
    }

  p->start -= 1 + s_len;
  p->start[0] = '/';
  memcpy (p->start + 1, s, s_len);
}

/* Return a string (malloc'd) consisting of N '/'-separated ".." components.  */
static char *
nth_parent (size_t n)
{
  char *buf = xnmalloc (3, n);
  char *p = buf;

  for (size_t i = 0; i < n; i++)
    {
      memcpy (p, "../", 3);
      p += 3;
    }
  p[-1] = '\0';
  return buf;
}

/* Determine the basename of the current directory, where DOT_SB is the
   result of lstat'ing "." and prepend that to the file name in *FILE_NAME.
   Find the directory entry in '..' that matches the dev/i-node of DOT_SB.
   Upon success, update *DOT_SB with stat information of '..', chdir to '..',
   and prepend "/basename" to FILE_NAME.
   Otherwise, exit with a diagnostic.
   PARENT_HEIGHT is the number of levels '..' is above the starting directory.
   The first time this function is called (from the initial directory),
   PARENT_HEIGHT is 1.  This is solely for diagnostics.
   Exit nonzero upon error.  */

static void
find_dir_entry (struct stat *dot_sb, struct file_name *file_name,
                size_t parent_height)
{
  DIR *dirp;  /* 目录指针 */
  int fd;  /* 描述符 */
  struct stat parent_sb;
  bool use_lstat;   /* 用于判断父子目录是否在同一个设备上(true:不是在同一个设备上。flase:在同一个设备上) */
  bool found;

  dirp = opendir ("..");  /* 打开工作目录的父级 */
  if (dirp == NULL)  /* 判断是否成功打开父级目录 */
    die (EXIT_FAILURE, errno, _("cannot open directory %s"),
         quote (nth_parent (parent_height)));

  fd = dirfd (dirp);  /* 将目录指针转为描述符 */
  if ((0 <= fd ? fchdir (fd) : chdir ("..")) < 0)  /* 先判断是否成功转为文件描述符,该三元表达式主要用于判断fd打开的是否是目录,是目录的话则直接返回目录名给fd(fchdir内部实现是函数正确返回是返回fd.name),不是的话则将工作目录更改为父级目录".." */
    die (EXIT_FAILURE, errno, _("failed to chdir to %s"),
         quote (nth_parent (parent_height)));

  /* 先判断fd描述符是否正确,是的话就获取fd指向的目录文件信息保存。否则用.来填充stat结构体(获取当前工作目录的文件信息) */
  if ((0 <= fd ? fstat (fd, &parent_sb) : stat (".", &parent_sb)) < 0)
    die (EXIT_FAILURE, errno, _("failed to stat %s"),
         quote (nth_parent (parent_height)));

  /* If parent and child directory are on different devices, then we
     can't rely on d_ino for useful i-node numbers; use lstat instead.  */
  /* 如果父目录和子目录在不同的设备上,那么我们不能依赖d_ino来获取有用的i-node编号;请使用lstat */
  // 判断父目录和子目录是否在同一个设备上
  use_lstat = (parent_sb.st_dev != dot_sb->st_dev);

  found = false;  /* 先将用于判断是否找到根目录的变量设为否 */
  while (1)
    {
      struct dirent const *dp;  /* 创建目录结构体 */
      struct stat ent_sb;  /* 创建文件信息结构体 */
      ino_t ino;  /* 创建i节点类型变量 */

      errno = 0;  /* 先将全错错误变量设为0 */

      if ((dp = readdir_ignoring_dot_and_dotdot (dirp)) == NULL)  /* 读目录忽略点和点点 */
        {
          if (errno)
            {
              /* Save/restore errno across closedir call.  */
              int e = errno;
              closedir (dirp);
              errno = e;

              /* Arrange to give a diagnostic after exiting this loop.  */
              dirp = NULL;
            }
          break;
        }

      ino = D_INO (dp);  /* 保存目录的i节点编号 */

      if (ino == NOT_AN_INODE_NUMBER || use_lstat)  /* i节点编号是否=或flase */
        {
          if (lstat (dp->d_name, &ent_sb) < 0)
            {
              /* Skip any entry we can't stat.  */
              continue;
            }
          ino = ent_sb.st_ino;
        }

      if (ino != dot_sb->st_ino)  /* 当前读的目录的i节点编号和当前工作目录编号不同的话就跳过此次循环 */
        continue;

      /* If we're not crossing a device boundary, then a simple i-node
         match is enough.  */
      /* 如果我们没有跨越设备边界,那么简单的i节点匹配就足够了 */
      if ( ! use_lstat || ent_sb.st_dev == dot_sb->st_dev)
        {
          file_name_prepend (file_name, dp->d_name, _D_EXACT_NAMLEN (dp));  /* 保存文件路径 */
          found = true;
          break;
        }
    }

  if (dirp == NULL || closedir (dirp) != 0)
    {
      /* Note that this diagnostic serves for both readdir
         and closedir failures.  */
      /* 请注意,此诊断用于readdir和closedir故障。 */
      die (EXIT_FAILURE, errno, _("reading directory %s"),
           quote (nth_parent (parent_height)));
    }

  if ( ! found)
    die (EXIT_FAILURE, 0,
         _("couldn't find directory entry in %s with matching i-node"),
         quote (nth_parent (parent_height)));

  *dot_sb = parent_sb;  /* 将父级文件信息结构体赋予当前 */
}

/* Construct the full, absolute name of the current working
   directory and store it in *FILE_NAME.
   The getcwd function performs nearly the same task, but is typically
   unable to handle names longer than PATH_MAX.  This function has
   no such limitation.  However, this function *can* fail due to
   permission problems or a lack of memory, while GNU/Linux's getcwd
   function works regardless of restricted permissions on parent
   directories.  Upon failure, give a diagnostic and exit nonzero.

   Note: although this function is similar to getcwd, it has a fundamental
   difference in that it gives a diagnostic and exits upon failure.
   I would have liked a function that did not exit, and that could be
   used as a getcwd replacement.  Unfortunately, considering all of
   the information the caller would require in order to produce good
   diagnostics, it doesn't seem worth the added complexity.
   In any case, any getcwd replacement must *not* exceed the PATH_MAX
   limitation.  Otherwise, functions like 'chdir' would fail with
   ENAMETOOLONG.

   FIXME-maybe: if find_dir_entry fails due to permissions, try getcwd,
   in case the unreadable directory is close enough to the root that
   getcwd works from there.  */

/* GNU实现的getcwd函数 */
static void
robust_getcwd (struct file_name *file_name)
{
  /* 1.先获取根目录“/”的i节点编号和设备编号。 
   * 2.获取程序执行的目录信息*/


  size_t height = 1; /* 用于后续判断递归深度 */
  struct dev_ino dev_ino_buf;
  struct dev_ino *root_dev_ino = get_root_dev_ino (&dev_ino_buf);  /* get_root_dev_ino函数主要是获取根目录“/”i节点编号和设备编号(函数实现在:lib/root_dev_ino.c) */
  struct stat dot_sb;

  /* 初始化(chopt_init)包含该成员的结构体(Chown_option)时,root_dev_ino默认=NULL,但是这里的root_dev_ino使用了get_root_dev_ino来初始化,这个if主要是判断get_root_dev_ino是否成功获取根目录“/”的i节点编号和设备编号 */
  if (root_dev_ino == NULL)
    die (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
         quoteaf ("/"));

  /* 获取程序执行的目录信息 */
  if (stat (".", &dot_sb) < 0)
    die (EXIT_FAILURE, errno, _("failed to stat %s"), quoteaf ("."));

  while (1)
    {
      /* If we've reached the root, we're done.  */
      /* 如果到达根目录,则递归结束(用i节点编号和设备编号来确认是否到达根) */
      if (SAME_INODE (dot_sb, *root_dev_ino))  /* 函数作用:判断传入的两个结构体的st_ino和st_dev是否相同 */
        break;

      /* 参数一:当前工作目录的文件结构信息
       * 参数二:只是初始化了的struct file_name结构体
       * 参数三:递归层级的深度*/
      find_dir_entry (&dot_sb, file_name, height++);
    }

  /* See if a leading slash is needed; file_name_prepend adds one.  */
  if (file_name->start[0] == '\0')
    file_name_prepend (file_name, "", 0);
}


/* Return PWD from the environment if it is acceptable for 'pwd -L'
   output, otherwise NULL.  */
/* pwd -L 选项-L的处理函数,从环境变量中获取pwd的输出,如果环境变量中没有pwd则为NULL */
static char *
logical_getcwd (void)
{
  struct stat st1;
  struct stat st2;
  char *wd = getenv ("PWD"); /* 获取环境变量中的PWD */
  char *p;

  /* Textual validation first.  */
  /* 先验证文本 */
  if (!wd || wd[0] != '/') /* 如果wd为空,或第一个字符不为/。就认为getenv没有从环境变量内获取到PWD就直接退出程序返回NULL */
    return NULL;
  p = wd; /* p保存从环境变量中获取的路径 */
  while ((p = strstr (p, "/."))) /* 判断路径 */ /* 可能是检测是否有.开头的路径(隐藏文件夹) */
    {
        /*条件1: /.后面没有字符(NULL)就认为p[2]空字符(到结尾了)直接返回NULL
         *  形式:/.NULL
         *条件2: 假如路径第三位还是/ 则直接返回NULL
         *  形式:/./
         *条件3: 假如p[2]=. 和p[3]内的元素是NULL或p[3]=/ 就返回NULL
         *上面条件3的!p[3]有双重否定的意思,因为要p[3]=NULL才能进入该if,但是NULL在if是判为false所以只能!p[3]来=true
         *  形式:/..NULL 或 /../ 就直接返回NULL
        */
      if (!p[2] || p[2] == '/'
          || (p[2] == '.' && (!p[3] || p[3] == '/')))
        return NULL;
      p++;
    }

  /* System call validation.  */
  /* 系统调用验证 */
  /* 只有进入该if才能正确返回wd(想要的结果) */
  /* 判断环境变量PWD的i节点和执行程序目录的i节点编号是否相同,只有环境变量的pwd路径和程序执行时的PWD路径的i节点相同才能进入该if正常输出
   * 条件1:成功将wd的文件结构信息(环境变量的PWD路径)保存到st1
   * 条件2:成功将当前工作目录(程序执行的目录)结构信息保存到st2
   * 条件3:st1和st2的stat结构内的st_ino相同(SAME_INODE函数定义在lib/same-inode)
   *
   * */
  if (stat (wd, &st1) == 0 && stat (".", &st2) == 0 && SAME_INODE (st1, st2))
    return wd;
  
  /* 没有进入任何if则直接返回NULL退出 */
  return NULL;
}


int
main (int argc, char **argv)
{
  char *wd;
  /* POSIX requires a default of -L, but most scripts expect -P.
     Currently shells default to -L, while stand-alone
     pwd implementations default to -P.  */
  /* POSIX要求默认值为-L,但大多数脚本都期望-P。
   * 当前shell默认为-L,而独立
   * pwd实现默认为-P。*/
  bool logical = (getenv ("POSIXLY_CORRECT") != NULL); /* 环境变量POSIXLY_CORRECT是否为空 */

  /* 初始化 */
  initialize_main (&argc, &argv);
  set_program_name (argv[0]);
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  atexit (close_stdout);

  /* 处理选项 */
  while (1)
    {
      int c = getopt_long (argc, argv, "LP", longopts, NULL);
      if (c == -1)
        break;
      switch (c)
        {
        case 'L':
          logical = true;
          break;
        case 'P':
          logical = false;
          break;
         
        case_GETOPT_HELP_CHAR;
          
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
          
        default:
          usage (EXIT_FAILURE);
        }
    }

  if (optind < argc)  /* 判断getopt_long是否正确执行 */
    error (0, 0, _("ignoring non-option arguments"));

  /* PWD -L 进入该if */
  if (logical)
    {
      /* 用getenv从环境变量中获取当前路径,logical_getcwd函数主要是错误处理 */
      wd = logical_getcwd ();
      if (wd) /* logical_getcwd正确返回的话进入该if */
        {
          puts (wd);
          return EXIT_SUCCESS;
        }
    }

  /* 无选项或-P执行到此处。(比-L简单多了,-L从环境变量中获取了还要对比当前工作目录的i节点和PWD获取的目录的i节点来确保正确获取) */
  wd = xgetcwd ();  /* xgetcwd内部使用getcwd获取当前工作目录 */
  if (wd != NULL)
    {
      puts (wd);  /* 输出getcwd的返回值(当前工作目录,程序执行时的目录) */
      free (wd);  /* 释放内存 */
    }
  else
    {
      /* 如果系统没有getcwd函数,将会执行到此处,自定义实现了一个getcwd类似的函数 */
      struct file_name *file_name = file_name_init ();  /* 初始化指针大小,开辟了一个用于存储路径名的指针 */
      robust_getcwd (file_name);  /* 获取文件路径 */
      puts (file_name->start);  /* 输出结构体的start(获取的路径名) */
      file_name_free (file_name);  /* 释放指针内存 */
    }

  return EXIT_SUCCESS;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值