help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Help-bash] Query on _evalfile function in bash-4.3/builtins/evalfil


From: Eduardo A . Bustamante López
Subject: Re: [Help-bash] Query on _evalfile function in bash-4.3/builtins/evalfile.c
Date: Thu, 19 Feb 2015 22:50:59 -0600
User-agent: Mutt/1.5.23 (2014-03-12)

On Fri, Feb 20, 2015 at 08:15:02AM +0530, Vijay Karode wrote:
> Hi All,
> 
> BASH is a wonderful shell and you guys are doing a wonderful job
> maintaining it.Hats Off to you all.
Hey! You'd be amazed to know that Chet Ramey manages to maintain bash alone,
hats off to him.

> 
> From past few days I was getting curious as of how the shell actually
> works.So,started digging into the source code.Shell obviously does loads of
> things,but one of the things which it does after login program has given
> the control is to load the user specific profile file like
> .bash_profile.This is obviously not done via a fork and exec as we want the
> env to be set up in the parent and not in subshell.
> The builtin source command also does the same thing.It affects the current
> shell.
Nice to know :-)

Ok, I'll try my best at explaining. I have to confess that I'm not that
familiar with the codebase:


$ cat -n builtins/evalfile.c
     1  /* evalfile.c - read and evaluate commands from a file or file 
descriptor */
     2  
     3  /* Copyright (C) 1996-2009 Free Software Foundation, Inc.
     4  
     5     This file is part of GNU Bash, the Bourne Again SHell.
     6  
     7     Bash is free software: you can redistribute it and/or modify
     8     it under the terms of the GNU General Public License as published by
     9     the Free Software Foundation, either version 3 of the License, or
    10     (at your option) any later version.
    11  
    12     Bash is distributed in the hope that it will be useful,
    13     but WITHOUT ANY WARRANTY; without even the implied warranty of
    14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15     GNU General Public License for more details.
    16  
    17     You should have received a copy of the GNU General Public License
    18     along with Bash.  If not, see <http://www.gnu.org/licenses/>.
    19  */
    20  

    [...]

    77  
    78  static int
    79  _evalfile (filename, flags)
    80       const char *filename;
    81       int flags;
    82  {

    [...]

        These variables are automatically setup by bash to be used in your 
eval'ed file.

   103  #if defined (ARRAY_VARS)
   104    GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a);
   105    GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a);
   106    GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a);
   107  #  if defined (DEBUGGER)
   108    GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
   109    GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
   110  #  endif
   111  #endif
   112  


        Open the file and check that it's a regular file

   113    fd = open (filename, O_RDONLY);
   114  
   115    if (fd < 0 || (fstat (fd, &finfo) == -1))
   116      {
   117        i = errno;
   118        if (fd >= 0)
   119          close (fd);
   120        errno = i;
   121  
   122  file_error_and_exit:
   123        if (((flags & FEVAL_ENOENTOK) == 0) || errno != ENOENT)
   124          file_error (filename);
   125  
   126        if (flags & FEVAL_LONGJMP)
   127          {
   128            last_command_exit_value = 1;
   129            jump_to_top_level (EXITPROG);
   130          }
   131  
   132        return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE
   133                                        : ((errno == ENOENT) ? 0 : -1));
   134      }
   135  
   136    errfunc = ((flags & FEVAL_BUILTIN) ? builtin_error : internal_error);
   137  
   138    if (S_ISDIR (finfo.st_mode))
   139      {
   140        (*errfunc) (_("%s: is a directory"), filename);
   141        close (fd);
   142        return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
   143      }
   144    else if ((flags & FEVAL_REGFILE) && S_ISREG (finfo.st_mode) == 0)
   145      {
   146        (*errfunc) (_("%s: not a regular file"), filename);
   147        close (fd);
   148        return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
   149      }
   150  


          Find out the size of the file, and store the whole file in memory.

   151    file_size = (size_t)finfo.st_size;
   152    /* Check for overflow with large files. */
   153    if (file_size != finfo.st_size || file_size + 1 < file_size)
   154      {
   155        (*errfunc) (_("%s: file is too large"), filename);
   156        close (fd);
   157        return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
   158      }      
   159  
   160    if (S_ISREG (finfo.st_mode) && file_size <= SSIZE_MAX)
   161      {
   162        string = (char *)xmalloc (1 + file_size);
   163        nr = read (fd, string, file_size);
   164        if (nr >= 0)
   165          string[nr] = '\0';
   166      }
   167    else
   168      nr = zmapfd (fd, &string, 0);
   169  
   170    return_val = errno;
   171    close (fd);
   172    errno = return_val;
   173  
   174    if (nr < 0)           /* XXX was != file_size, not < 0 */
   175      {
   176        free (string);
   177        goto file_error_and_exit;
   178      }
   179  
   180    if (nr == 0)
   181      {
   182        free (string);
   183        return ((flags & FEVAL_BUILTIN) ? EXECUTION_SUCCESS : 1);
   184      }


          check_binary_file() tries to determine if the file is binary, by 
checking for \0 bytes, IIRC

   186    if ((flags & FEVAL_CHECKBINARY) && 
   187        check_binary_file (string, (nr > 80) ? 80 : nr))
   188      {
   189        free (string);
   190        (*errfunc) (_("%s: cannot execute binary file"), filename);
   191        return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1);
   192      }
   193  
   194    i = strlen (string);
   195    if (i < nr)
   196      {
   197        for (nnull = i = 0; i < nr; i++)
   198          if (string[i] == '\0')
   199            {
   200              memmove (string+i, string+i+1, nr - i);
   201              nr--;
   202              /* Even if the `check binary' flag is not set, we want to 
avoid
   203                 sourcing files with more than 256 null characters -- that
   204                 probably indicates a binary file. */
   205              if ((flags & FEVAL_BUILTIN) && ++nnull > 256)
   206                {
   207                  free (string);
   208                  (*errfunc) (_("%s: cannot execute binary file"), 
filename);
   209                  return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1);
   210                }
   211            }
   212      }

        TBH I still don't understand the longjmp stuff, I think it's a kind of
        goto but between very different parts of the code.

   214    if (flags & FEVAL_UNWINDPROT)
   215      {
   216        begin_unwind_frame ("_evalfile");
   217  
   218        unwind_protect_int (return_catch_flag);
   219        unwind_protect_jmp_buf (return_catch);
   220        if (flags & FEVAL_NONINT)
   221          unwind_protect_int (interactive);
   222        unwind_protect_int (sourcelevel);
   223      }
   224    else
   225      {
   226        COPY_PROCENV (return_catch, old_return_catch);
   227        if (flags & FEVAL_NONINT)
   228          old_interactive = interactive;
   229      }
   230  
   231    if (flags & FEVAL_NONINT)
   232      interactive = 0;
   233  
   234    return_catch_flag++;
   235    sourcelevel++;

        Sets the values of the special variables

   237  #if defined (ARRAY_VARS)
   238    array_push (bash_source_a, (char *)filename);
   239    t = itos (executing_line_number ());
   240    array_push (bash_lineno_a, t);
   241    free (t);
   242    array_push (funcname_a, "source");    /* not exactly right */
   243  #  if defined (DEBUGGER)
   244    /* Have to figure out a better way to do this when `source' is 
supplied
   245       arguments */
   246    if ((flags & FEVAL_NOPUSHARGS) == 0)
   247      {
   248        array_push (bash_argv_a, (char *)filename);
   249        tt[0] = '1'; tt[1] = '\0';
   250        array_push (bash_argc_a, tt);
   251      }
   252  #  endif
   253  #endif
   254  

         Here it unleashes the parser beast.

   271    else
   272      result = parse_and_execute (string, filename, pflags);


The parser stuff is in parse.y, that's what has the bash grammar and all the
code to handle special cases while parsing. It is very complex. It's meant to
be used with the yacc tool.


Oh wait, actually, the code that calls the parser is: builtins/evalstring.c

$ cat -n builtins/evalstring.c

     1  /* evalstring.c - evaluate a string as one or more shell commands. */
     2  
     3  /* Copyright (C) 1996-2012 Free Software Foundation, Inc.
     4  
     5     This file is part of GNU Bash, the Bourne Again SHell.
     6  
     7     Bash is free software: you can redistribute it and/or modify
     8     it under the terms of the GNU General Public License as published by
     9     the Free Software Foundation, either version 3 of the License, or
    10     (at your option) any later version.
    11  
    12     Bash is distributed in the hope that it will be useful,
    13     but WITHOUT ANY WARRANTY; without even the implied warranty of
    14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15     GNU General Public License for more details.
    16  
    17     You should have received a copy of the GNU General Public License
    18     along with Bash.  If not, see <http://www.gnu.org/licenses/>.
    19  */
    20  

   189  int
   190  parse_and_execute (string, from_file, flags)
   191       char *string;
   192       const char *from_file;
   193       int flags;
   194  {

notice the function parse_command(), this one is in eval.c

   298            
   299        if (parse_command () == 0)
   300          {
   301            if ((flags & SEVAL_PARSEONLY) || (interactive_shell == 0 && 
read_but_dont_execute))
   302              {
   303                last_result = EXECUTION_SUCCESS;
   304                dispose_command (global_command);
   305                global_command = (COMMAND *)NULL;
   306              }
   307            else if (command = global_command)
   308              {
   309                struct fd_bitmap *bitmap;


[...]

     1  /* eval.c -- reading and evaluating commands. */
     2  
     3  /* Copyright (C) 1996-2011 Free Software Foundation, Inc.
     4  
     5     This file is part of GNU Bash, the Bourne Again SHell.
     6  
     7     Bash is free software: you can redistribute it and/or modify
     8     it under the terms of the GNU General Public License as published by
     9     the Free Software Foundation, either version 3 of the License, or
    10     (at your option) any later version.
    11  
    12     Bash is distributed in the hope that it will be useful,
    13     but WITHOUT ANY WARRANTY; without even the implied warranty of
    14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15     GNU General Public License for more details.
    16  
    17     You should have received a copy of the GNU General Public License
    18     along with Bash.  If not, see <http://www.gnu.org/licenses/>.
    19  */

    [...]

Ok, this one actually calls yyparse(), which is the YACC generated parser.

   211  /* Call the YACC-generated parser and return the status of the parse.
   212     Input is read from the current input stream (bash_input).  yyparse
   213     leaves the parsed command in the global variable GLOBAL_COMMAND.
   214     This is where PROMPT_COMMAND is executed. */
   215  int
   216  parse_command ()
   217  {
   218    int r;
   219    char *command_to_execute;
   220  
   221    need_here_doc = 0;
   222    run_pending_traps ();
   223  
   224    /* Allow the execution of a random command just before the printing
   225       of each primary prompt.  If the shell variable PROMPT_COMMAND
   226       is set then the value of it is the command to execute. */
   227    if (interactive && bash_input.type != st_string)
   228      {
   229        command_to_execute = get_string_value ("PROMPT_COMMAND");
   230        if (command_to_execute)
   231          execute_variable_command (command_to_execute, "PROMPT_COMMAND");
   232  
   233        if (running_under_emacs == 2)
   234          send_pwd_to_eterm ();   /* Yuck */
   235      }
   236  
   237    current_command_line_count = 0;
   238    r = yyparse ();
   239  
   240    if (need_here_doc)
   241      gather_here_documents ();
   242  
   243    return (r);
   244  }

That's a brief overview of how it works. Hope it helps.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]