July 6: Fixed a bug which prevented using for and while statements split over multiple lines. The problem was that the cur_command variable stored the last line of an incomplete command. Fixed by concatenating incomplete lines before storing. AndyTim suggested that instead of a memory-intensive list, I just store the offset into the script for interesting lines. A problem with that is that it would act differently for scripts and interactive input. But, it will use a lot less memory. So, this is what I plan to do:
- A global variable start_len will store the offset of the current command for both scripts and user input.
- The looping commands would use this variable as a program counter, to store/retrieve.
- For scripts: use the start_len variable to retrieve the next command. After the command is executed, increment the variable as appropriate (either add the length of the command or, if it has been modified, do not modify it). This would remove the need for a list if a script is executed.
- For shell input: store each line as it is entered, into a dynamic string. The start_len variable is used similarly to the script method.
I started work on these modifications. The script changes were simple, but there's some problem with the shell input. I'll continue work on it tomorrow. Today's commits:
July 7: Yesterday's problem with the shell input turned out to be due to an asprintf() call with a NULL pointer. So I fixed that, with the help of DBG() statements. After that, I decided to implement incomplete statements, using this new method. The only changes made were: instead of a start_len variable, a cur_len variable is used by the script_exec() and shell() functions, to get the current line, which may not be complete. The start_len variable now holds the offset of the current complete line in the script/user input. As usual, a static char *complete_command in the system() function will hold the complete command found so far. The parse functions will set the value of incomplete if an incomplete line is found. If the line is complete, it is passed to execv() and complete_command is freed, else control is passed back to script_exec() or shell() to get the next line and concatenate it to complete_command. Also had a look at the memory allocations and frees. AndyTim had suggested making gPXE with the DEBUG=malloc option. However, this produced too much output. As I just wanted to check whether all the memory allocated was being freed, I printed out the value of the variable freemem (malloc.c) before and after the script_exec() and shell() functions. I found a glaring mistake in arith.c, where I had missed freeing a variable. So I fixed that by trying to do a git rebase. Today's commits are on the offset branch:
July 8: Implemented incomplete arithmetic expressions, on top of yesterday's code. After that, I spent some time just having a look at the memory allocation and freeing, and making sure the free memory before and after the shell matched up. Everything seemed fine. So now I think loops are almost done, and I can start on the next task: return codes. Having a variable to check the success of the previous command would be very useful. However, we also have to make sure that existing scripts work: current gPXE behaviour is to terminate a script at an unsuccessful command. So, we need a way to let a script optionally use the new behaviour or fall back to the old method.
#!gpxe --no-exit #rest of script
or another way could be:
#!gpxe set -e 1 # The value of -e could have a default of 0 to have the old behaviour #rest of script
Besides this, we need to have a variable to denote the return code of the previous statement, say ${rc}. I put in the part of setting rc with the previous return code. Now the only thing required is to decide on the way the script could request the new behaviour. Today's commit:
July 9: Implemented a first attempt at using return codes, using the –no-exit idea. This looks for a #!gpxe<space(s)/tab(s)>–no-exit line at the beginning of the script. Then, after each command is executed, the value of the variable rc is set to the return code of the command. So, the script/user can check the success of a command by looking at the value of the rc variable. An interesting idea that came up was using a stack to store the return code. So, ${rc/0} would be the return code of the previous command, ${rc/1} of the command before that and so on. This would mean implementing rc as a settings block, so I've started reading through core/settings.c to see how I can do that. Today's commit:
July 10: I was out for most of today, and was able to work only in the evening. Tried to make a settings block, but ran into some trouble. ${rc/0} works, but ${rc/1} does not. That's because it sees the number as a tag, and looks for the corresponding setting, which doesn't exist. Then, I tried making a setting and adding it to the generic_settings list, but that doesn't work, either: it still looks for a tag. I'll try it again tomorrow. No commits today.
July 11: An interesting idea about the return code was to use a try-catch block instead of the rc stack:
try kernel tftp://10.0.0.2//kernel initrd tftp://10.0.0.2//initrd boot catch echo "Boot failure" done
Basically, the try-catch block works as an if-else block that can change its truth value as a statement fails. This fits in well with the work on branches and loops. So, I'm going to use this instead of the rc stack. In today's meeting, we discussed a few issues:
- Modifying the project plan to make it more specific
- Writing code with more focus on merging
July 12: Worked on adding try-catch statements to the scripting language. There is one issue though: what happens to loops inside a try block, e.g.
try for i in 0 1 2 3 do ... done catch ... done
Stefan and I decided that the execution should immediately jump to the catch block. Today's commits:
- Added a flags field to struct command.This will help shorten the execv() function, and help in extending, if required later.