从apache派生cgi工作路径看软链接

一、问题和背景
对于apache生成的cgi服务来说,通常需要读取一些特有的配置,而这个配置通常使用的方法还是使用软链接。在使用软链接的场景中,由于二进制是在一个文件夹中,而配置文件和日志文件可能在一个软链接的文件夹,所以配置的时候禁不住要问下apache派生的cgi的当前工作路径在哪里,有没有一个确定的说法?
二、apache的代码
1、派生进程当前工作路径的设置
apache对于cgi进程的派生通常是通过封装过的自己封装的动态运行时库apr实现,这里我们最为关心的就是进程的创建,这个代码其实并不难查找,所以怎么找到进程派生位置的代码在这里就略过不提了。以我们这里关注的linux系统来说,这个进程的派生是通过apr-1.4.6 hreadprocunixproc.c来派生。不要被这里的文件夹名称threadproc迷惑它只能创建线程,其实它后面的proc应该就是进程process的意思,所以这个文件夹创建进程也是妥妥的。由于我找这个问题的时候是自下而上的查找的,所以在这个地方看到的代码对于当前工作路径的设置非常醒目,下面是代码内容:
APR_DECLARE(apr_status_t) apr_proc_create(apr_proc_t *new,
                                          const char *progname,
                                          const char * const *args,
                                          const char * const *env,
                                          apr_procattr_t *attr,
                                          apr_pool_t *pool)
{
……
    if ((new->pid = fork()) < 0) {
        return errno;
    }
    else if (new->pid == 0) {
        /* child process */
……
        if (attr->currdir != NULL) {
            if (chdir(attr->currdir) == -1) {
                if (attr->errfn) {
                    attr->errfn(pool, errno, "change of working directory failed");
                }
                _exit(-1);   /* We have big problems, the child should exit. */
            }
        }
……
也就是新创建的进程的当前工作目录是通过传入参数的attr->currdir控制。
而这个参数的设置也在该文件中
APR_DECLARE(apr_status_t) apr_procattr_dir_set(apr_procattr_t *attr,
                                               const char *dir)
{
    attr->currdir = apr_pstrdup(attr->pool, dir);
    if (attr->currdir) {
        return APR_SUCCESS;
    }
 
    return APR_ENOMEM;
}
2、cgi模块对于该进程的设置
apache_httpd-2.4.2modulesgeneratorsmod_cgi.c
static apr_status_t run_cgi_child(apr_file_t **script_out,
                                  apr_file_t **script_in,
                                  apr_file_t **script_err,
                                  const char *command,
                                  const char * const argv[],
                                  request_rec *r,
                                  apr_pool_t *p,
                                  cgi_exec_info_t *e_info)
{
……
    /* Transmute ourselves into the script.
     * NB only ISINDEX scripts get decoded arguments.
     */
    if (((rc = apr_procattr_create(&procattr, p)) != APR_SUCCESS) ||
        ((rc = apr_procattr_io_set(procattr,
                                   e_info->in_pipe,
                                   e_info->out_pipe,
                                   e_info->err_pipe)) != APR_SUCCESS) ||
        ((rc = apr_procattr_dir_set(procattr,
                        ap_make_dirstr_parent(r->pool,
                                              r->filename))) != APR_SUCCESS) ||
……
        ((rc = apr_procattr_child_errfn_set(procattr, cgi_child_errfn)) != APR_SUCCESS)) {
        /* Something bad happened, tell the world. */
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(01216)
                      "couldn't set child process attributes: %s", r->filename);
……
 
apache_httpd-2.4.2serverutil.c
/*
 * return the parent directory name including trailing / of the file s
 */
AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s)
{
    const char *last_slash = ap_strrchr_c(s, '/');
    char *d;
    int l;
 
    if (last_slash == NULL) {
        return apr_pstrdup(p, "");
    }
    l = (last_slash - s) + 1;
    d = apr_pstrmemdup(p, s, l);
 
    return (d);
}
3、cgi的工作路径
简言之,就是cgi的工作路径就是cgi二进制所在的文件夹。
三、内核对于上一层文件夹的处理
可见这个地方对于上层的处理非常直观,就是直接找到它的"文件系统父节点",这个节点是唯一确定的。
static __always_inline void follow_dotdot(struct nameidata *nd)
{
struct fs_struct *fs = current->fs;
 
while(1) {
……
if (nd->dentry != nd->mnt->mnt_root) {
nd->dentry = dget(nd->dentry->d_parent);
spin_unlock(&dcache_lock);
dput(old);
break;
}
……
nd->mnt = parent;
}
follow_mount(&nd->mnt, &nd->dentry);
}
四、和shell直观经验的冲突
但是这个效果和我们在shell中看到的现象有所违背:在bash中如果通过一个软链接cd到特定目录,然后在通过".."可以经过软链接原路返回。
1、一个“直观”的例子
tsecer@harry: pwd
/home/harry
tsecer@harry: mkdir -p tsecer/r/ -p terry/y  src
tsecer@harry: ln -fs /home/harry/src terry/y/src
tsecer@harry: ln -fs /home/harry/src tsecer/r/src
tsecer@harry: find
.
./src
./src/src
./tsecer
./tsecer/r
./tsecer/r/src
./terry
./terry/y
./terry/y/src
tsecer@harry: cd tsecer/r/src/
tsecer@harry: pwd
/home/harry/tsecer/r/src
tsecer@harry: /bin/pwd 
/home/harry/src
tsecer@harry: cd ../
tsecer@harry: pwd
/home/harry/tsecer/r
可以看到,当shell通过cd切换到包涵软练级的/home/harry/tsecer/r/src文件夹之后,可以通过cd ..再次返回到"原始路径"。并且在通过软链接进入的src文件夹下,通过内置的pwd看到当前工作路径是/home/harry/tsecer/r/src,而独立的二进制命令/bin/pwd 看到的却是/home/harry/src。这个可能就是“旁观者清当局者迷”的一个注脚吧。
2、bash对于工作路径的处理
bash-4.0libshpathcanon.c文件的sh_canonpath (path, flags)函数对cd中的路径进行了所谓的正规化操作,其中的逻辑看起来比较繁琐,但是大致可以明白其中对于".."的预处理是当作一个消除前一个路径的操作,具体这里的操作,在/home/harry/tsecer/r/src文件夹下执行cd ..,此时的"/home/harry/tsecer/r/src/.."中的".."会消除和它紧邻的前一个非".."的目录项(这里的情况是src),所以结果是我们通常所见的/home/harry/tsecer/r/。
但是,这个地方还有一个问题,当我执行cd tsecer/r/src/之后,系统的当前工作目录不应该是在真是文件系统路径/home/harry/scr下吗?这个就是由于bash在cd之后,会把当前的"正规化"之后的字符串保存在the_current_working_directory中,所以bash内置的pwd是从这个变量中取得的字符串。
/* Make NAME our internal idea of the current working directory. */
void
set_working_directory (name)
     char *name;
{
  FREE (the_current_working_directory);
  the_current_working_directory = savestring (name);
}
3、该选项的开关
从代码中看到,可以通过shell的option来启停该功能,下面是bash手册中关于该功能的描述:
-P

If set, do not resolve symbolic links when performing commands such as cd which change the current directory. The physical directory is used instead. By default, Bash follows the logical chain of directories when performing commands which change the current directory.

For example, if /usr/sys is a symbolic link to /usr/local/sys then:

$ cd /usr/sys; echo $PWD
/usr/sys
$ cd ..; pwd
/usr

If set -P is on, then:

$ cd /usr/sys; echo $PWD
/usr/local/sys
$ cd ..; pwd
/usr/local
……
Using ‘+’ rather than ‘-’ causes these options to be turned off. The options can also be used upon invocation of the shell. The current set of options may be found in $-.
4、操作流程
a、打印当前配置
tsecer@harry: set -o
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history         on
ignoreeof       off
interactive-comments    on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           off
privileged      off
verbose         off
vi              off
xtrace          off
b、使能配置
tsecer@harry: set -P
tsecer@harry: set -o
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history         on
ignoreeof       off
interactive-comments    on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        on
pipefail        off
posix           off
privileged      off
verbose         off
vi              off
xtrace          off
c、关闭配置
tsecer@harry: set +P
tsecer@harry: set -o
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history         on
ignoreeof       off
interactive-comments    on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           off
privileged      off
verbose         off
vi              off
xtrace          off
原文地址:https://www.cnblogs.com/tsecer/p/10487959.html