lexer.c 7.75 KB
Newer Older
1 2 3
/* lexer.c - The scripting lexer.  */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2005,2006,2007,2008,2009,2010  Free Software Foundation, Inc.
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
12 13 14 15 16
 *  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
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
18 19
 */

20 21
#include <config.h>

22 23 24
#include <grub/parser.h>
#include <grub/misc.h>
#include <grub/mm.h>
25
#include <grub/script_sh.h>
26
#include <grub/i18n.h>
27

28
#define yytext_ptr char *
29
#include "grub_script.tab.h"
30
#include "grub_script.yy.h"
31 32

void
33
grub_script_lexer_ref (struct grub_lexer_param *state)
34
{
35
  state->refs++;
36 37 38
}

void
39
grub_script_lexer_deref (struct grub_lexer_param *state)
40
{
41
  state->refs--;
42 43
}

44
/* Start recording all characters passing through the lexer.  */
BVK Chaitanya's avatar
BVK Chaitanya committed
45
unsigned
46
grub_script_lexer_record_start (struct grub_parser_param *parser)
47
{
48 49
  struct grub_lexer_param *lexer = parser->lexerstate;

BVK Chaitanya's avatar
BVK Chaitanya committed
50 51 52
  lexer->record++;
  if (lexer->recording)
    return lexer->recordpos;
53

BVK Chaitanya's avatar
BVK Chaitanya committed
54
  lexer->recordpos = 0;
55
  lexer->recordlen = GRUB_LEXER_INITIAL_RECORD_SIZE;
56 57 58 59 60 61
  lexer->recording = grub_malloc (lexer->recordlen);
  if (!lexer->recording)
    {
      grub_script_yyerror (parser, 0);
      lexer->recordlen = 0;
    }
BVK Chaitanya's avatar
BVK Chaitanya committed
62
  return lexer->recordpos;
63 64 65
}

char *
BVK Chaitanya's avatar
BVK Chaitanya committed
66
grub_script_lexer_record_stop (struct grub_parser_param *parser, unsigned offset)
67
{
BVK Chaitanya's avatar
BVK Chaitanya committed
68
  int count;
69 70 71
  char *result;
  struct grub_lexer_param *lexer = parser->lexerstate;

BVK Chaitanya's avatar
BVK Chaitanya committed
72
  if (!lexer->record)
73
    return 0;
74

BVK Chaitanya's avatar
BVK Chaitanya committed
75 76 77
  lexer->record--;
  if (!lexer->recording)
    return 0;
78

BVK Chaitanya's avatar
BVK Chaitanya committed
79 80 81 82 83 84
  count = lexer->recordpos - offset;
  result = grub_script_malloc (parser, count + 1);
  if (result) {
    grub_strncpy (result, lexer->recording + offset, count);
    result[count] = '\0';
  }
85

BVK Chaitanya's avatar
BVK Chaitanya committed
86 87 88 89 90 91 92
  if (lexer->record == 0)
    {
      grub_free (lexer->recording);
      lexer->recording = 0;
      lexer->recordlen = 0;
      lexer->recordpos = 0;
    }
93
  return result;
94 95
}

96 97 98
/* Record STR if input recording is enabled.  */
void
grub_script_lexer_record (struct grub_parser_param *parser, char *str)
99
{
100
  int len;
101
  char *old;
102 103
  struct grub_lexer_param *lexer = parser->lexerstate;

BVK Chaitanya's avatar
BVK Chaitanya committed
104
  if (!lexer->record || !lexer->recording)
105 106 107
    return;

  len = grub_strlen (str);
108
  if (lexer->recordpos + len + 1 > lexer->recordlen)
109
    {
110
      old = lexer->recording;
111 112 113
      if (lexer->recordlen < len)
	lexer->recordlen = len;
      lexer->recordlen *= 2;
114 115
      lexer->recording = grub_realloc (lexer->recording, lexer->recordlen);
      if (!lexer->recording)
116 117
	{
	  grub_free (old);
118
	  lexer->recordpos = 0;
BVK Chaitanya's avatar
BVK Chaitanya committed
119
	  lexer->recordlen = 0;
120 121
	  grub_script_yyerror (parser, 0);
	  return;
122 123
	}
    }
124 125
  grub_strcpy (lexer->recording + lexer->recordpos, str);
  lexer->recordpos += len;
126 127
}

128
/* Read next line of input if necessary, and set yyscanner buffers.  */
129
int
BVK Chaitanya's avatar
BVK Chaitanya committed
130 131
grub_script_lexer_yywrap (struct grub_parser_param *parserstate,
			  const char *input)
132
{
133
  grub_size_t len = 0;
BVK Chaitanya's avatar
BVK Chaitanya committed
134 135
  char *p = 0;
  char *line = 0;
136 137
  YY_BUFFER_STATE buffer;
  struct grub_lexer_param *lexerstate = parserstate->lexerstate;
138

BVK Chaitanya's avatar
BVK Chaitanya committed
139 140
  if (! lexerstate->refs && ! lexerstate->prefix && ! input)
    return 1;
141

BVK Chaitanya's avatar
BVK Chaitanya committed
142
  if (! lexerstate->getline && ! input)
143
    {
144
      grub_script_yyerror (parserstate, N_("unexpected end of file"));
BVK Chaitanya's avatar
BVK Chaitanya committed
145
      return 1;
146
    }
147

148
  line = 0;
BVK Chaitanya's avatar
BVK Chaitanya committed
149
  if (! input)
150
    lexerstate->getline (&line, 1, lexerstate->getline_data);
BVK Chaitanya's avatar
BVK Chaitanya committed
151 152
  else
    line = grub_strdup (input);
BVK Chaitanya's avatar
BVK Chaitanya committed
153

154 155 156 157 158 159 160 161
  if (! line)
    {
      grub_script_yyerror (parserstate, N_("out of memory"));
      return 1;
    }

  len = grub_strlen (line);

BVK Chaitanya's avatar
BVK Chaitanya committed
162
  /* Ensure '\n' at the end.  */
163
  if (line[0] == '\0')
164
    {
BVK Chaitanya's avatar
BVK Chaitanya committed
165 166
      grub_free (line);
      line = grub_strdup ("\n");
167
      len = 1;
168
    }
169
  else if (len && line[len - 1] != '\n')
170
    {
BVK Chaitanya's avatar
BVK Chaitanya committed
171 172 173 174 175 176 177
      p = grub_realloc (line, len + 2);
      if (p)
	{
	  p[len++] = '\n';
	  p[len] = '\0';
	}
      line = p;
178
    }
BVK Chaitanya's avatar
BVK Chaitanya committed
179

BVK Chaitanya's avatar
BVK Chaitanya committed
180
  if (! line)
181
    {
182
      grub_script_yyerror (parserstate, N_("out of memory"));
BVK Chaitanya's avatar
BVK Chaitanya committed
183
      return 1;
184 185
    }

BVK Chaitanya's avatar
BVK Chaitanya committed
186
  /* Prepend any left over unput-text.  */
BVK Chaitanya's avatar
BVK Chaitanya committed
187
  if (lexerstate->prefix)
188
    {
BVK Chaitanya's avatar
BVK Chaitanya committed
189 190
      int plen = grub_strlen (lexerstate->prefix);

BVK Chaitanya's avatar
BVK Chaitanya committed
191
      p = grub_malloc (len + plen + 1);
BVK Chaitanya's avatar
BVK Chaitanya committed
192
      if (! p)
193
	{
BVK Chaitanya's avatar
BVK Chaitanya committed
194 195
	  grub_free (line);
	  return 1;
196
	}
BVK Chaitanya's avatar
BVK Chaitanya committed
197 198 199 200
      grub_strcpy (p, lexerstate->prefix);
      lexerstate->prefix = 0;

      grub_strcpy (p + plen, line);
BVK Chaitanya's avatar
BVK Chaitanya committed
201
      grub_free (line);
BVK Chaitanya's avatar
BVK Chaitanya committed
202 203 204

      line = p;
      len = len + plen;
205 206
    }

BVK Chaitanya's avatar
BVK Chaitanya committed
207
  buffer = yy_scan_string (line, lexerstate->yyscanner);
208
  grub_free (line);
BVK Chaitanya's avatar
BVK Chaitanya committed
209 210

  if (! buffer)
211 212
    {
      grub_script_yyerror (parserstate, 0);
BVK Chaitanya's avatar
BVK Chaitanya committed
213
      return 1;
214
    }
BVK Chaitanya's avatar
BVK Chaitanya committed
215
  return 0;
216
}
217

218 219
struct grub_lexer_param *
grub_script_lexer_init (struct grub_parser_param *parser, char *script,
220
			grub_reader_getline_t arg_getline, void *getline_data)
221 222 223 224 225 226 227
{
  struct grub_lexer_param *lexerstate;

  lexerstate = grub_zalloc (sizeof (*lexerstate));
  if (!lexerstate)
    return 0;

228 229
  lexerstate->size = GRUB_LEXER_INITIAL_TEXT_SIZE;
  lexerstate->text = grub_malloc (lexerstate->size);
230 231 232 233 234
  if (!lexerstate->text)
    {
      grub_free (lexerstate);
      return 0;
    }
235

236
  lexerstate->getline = arg_getline;
237 238 239
  lexerstate->getline_data = getline_data;
  /* The other elements of lexerstate are all zeros already.  */

240 241 242 243 244 245 246
  if (yylex_init (&lexerstate->yyscanner))
    {
      grub_free (lexerstate->text);
      grub_free (lexerstate);
      return 0;
    }

BVK Chaitanya's avatar
BVK Chaitanya committed
247 248
  yyset_extra (parser, lexerstate->yyscanner);
  parser->lexerstate = lexerstate;
249

BVK Chaitanya's avatar
BVK Chaitanya committed
250
  if (grub_script_lexer_yywrap (parser, script ?: "\n"))
251
    {
BVK Chaitanya's avatar
BVK Chaitanya committed
252
      parser->lexerstate = 0;
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
      yylex_destroy (lexerstate->yyscanner);
      grub_free (lexerstate->yyscanner);
      grub_free (lexerstate->text);
      grub_free (lexerstate);
      return 0;
    }

  return lexerstate;
}

void
grub_script_lexer_fini (struct grub_lexer_param *lexerstate)
{
  if (!lexerstate)
    return;

  yylex_destroy (lexerstate->yyscanner);

  grub_free (lexerstate->recording);
  grub_free (lexerstate->text);
  grub_free (lexerstate);
}

int
grub_script_yylex (union YYSTYPE *value,
		   struct grub_parser_param *parserstate)
{
  char *str;
  int token;
  grub_script_arg_type_t type;
  struct grub_lexer_param *lexerstate = parserstate->lexerstate;

  value->arg = 0;
  if (parserstate->err)
    return GRUB_PARSER_TOKEN_BAD;

  if (lexerstate->eof)
    return GRUB_PARSER_TOKEN_EOF;

  /* 
   * Words with environment variables, like foo${bar}baz needs
   * multiple tokens to be merged into a single grub_script_arg.  We
   * use two variables to achieve this: lexerstate->merge_start and
   * lexerstate->merge_end
   */

  lexerstate->merge_start = 0;
  lexerstate->merge_end = 0;
  do
    {
      /* Empty lexerstate->text.  */
304
      lexerstate->used = 1;
305 306 307 308 309 310 311 312
      lexerstate->text[0] = '\0';

      token = yylex (value, lexerstate->yyscanner);
      if (token == GRUB_PARSER_TOKEN_BAD)
	break;

      /* Merging feature uses lexerstate->text instead of yytext.  */
      if (lexerstate->merge_start)
313
	{
314 315
	  str = lexerstate->text;
	  type = lexerstate->type;
316
	}
317 318
      else
	{
319 320
	  str = yyget_text (lexerstate->yyscanner);
	  type = GRUB_SCRIPT_ARG_TYPE_TEXT;
321
	}
322
      grub_dprintf("lexer", "token %u text [%s]\n", token, str);
323

324
      value->arg = grub_script_arg_add (parserstate, value->arg, type, str);
325
    }
326
  while (lexerstate->merge_start && !lexerstate->merge_end);
327

328 329
  if (!value->arg || parserstate->err)
    return GRUB_PARSER_TOKEN_BAD;
330

331
  return token;
332 333 334
}

void
335
grub_script_yyerror (struct grub_parser_param *state, char const *err)
336
{
337 338 339 340 341
  if (err)
    grub_error (GRUB_ERR_INVALID_COMMAND, err);

  grub_print_error ();
  state->err++;
342
}