xref: /haiku/src/apps/terminal/TermApp.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
3  * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
4  *
5  * Distributed unter the terms of the MIT license.
6  */
7 #include <stdio.h>
8 #include <signal.h>
9 #include <sys/wait.h>
10 #include <stdlib.h>
11 #include <malloc.h>
12 
13 #include <storage/Path.h>
14 #include <NodeInfo.h>
15 #include <Roster.h>
16 #include <Alert.h>
17 #include <Screen.h>
18 #include <String.h>
19 #include <TextView.h>
20 
21 #include "TermApp.h"
22 #include "TermBuffer.h"
23 #include "TermWindow.h"
24 #include "TermConst.h"
25 #include "spawn.h"
26 #include "PrefHandler.h"
27 #include "CodeConv.h"
28 
29 // Preference temporary
30 extern PrefHandler *gTermPref;
31 extern char gWindowName[];
32 
33 bool usage_requested = false;
34 bool geometry_requested = false;
35 bool color_requested = false;
36 
37 struct standard_args {
38 	char *name;
39 	char *longname;
40 	int priority;
41 	int nargs;
42 	const char *prefname;
43 };
44 
45 struct standard_args standard_args[] = {
46 	{ "-h", "--help", 90, 0, NULL },
47 	{ "-p", "--preference", 80, 1, NULL },
48 	{ "-t", "--title", 70, 1, NULL },
49 	{ "-geom", "--geometry", 50, 1, NULL },
50 #if 0
51 	{ "-fg", "--text-fore-color", 40, 1, PREF_TEXT_FORE_COLOR },
52 	{ "-bg", "--text-back-color", 35, 1, PREF_TEXT_BACK_COLOR },
53 	{ "-curfg", "--cursor-fore-color", 30, 1, PREF_CURSOR_FORE_COLOR },
54 	{ "-curbg", "--cursor-back-color", 25, 1, PREF_CURSOR_BACK_COLOR },
55 	{ "-selfg", "--select-fore-color", 20, 1, PREF_SELECT_FORE_COLOR },
56 	{ "-selbg", "--select-back-color", 15, 1, PREF_SELECT_BACK_COLOR },
57 	{ "-imfg", "--im-fore-color", 10, 1, PREF_IM_FORE_COLOR },
58 	{ "-imbg", "--im-back-color", 5, 1, PREF_IM_BACK_COLOR },
59 	{ "-imsel", "--im-select-color", 0, 1, PREF_IM_SELECT_COLOR },
60 #endif
61 };
62 
63 int argmatch(char **, int, char *, char *, int, char **, int *);
64 void sort_args(int, char **);
65 int text_to_rgb(char *, rgb_color *, char *);
66 
67 
68 const ulong MSG_ACTIVATE_TERM = 'msat';
69 const ulong MSG_TERM_IS_MINIMIZE = 'mtim';
70 
71 
72 TermApp::TermApp()
73 	: BApplication(TERM_SIGNATURE)
74 {
75 	BList teams;
76 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
77 	fWindowNumber = teams.CountItems();
78 
79 	if (fWindowNumber == 0) {
80 		be_roster->GetAppList(R5_TERM_SIGNATURE, &teams);
81 		fWindowNumber = teams.CountItems();
82 	}
83 
84 	char title[256];
85 	snprintf(title, sizeof(title), "Terminal %d", fWindowNumber);
86 	fWindowTitle = title;
87 
88 	int i = fWindowNumber / 16;
89 	int j = fWindowNumber % 16;
90 
91 	int k =(j * 16) + (i * 64) + 50;
92 	int l =(j * 16)  + 50;
93 
94 	fTermWindow = NULL;
95 	fAboutPanel = NULL;
96 	fTermFrame.Set(k, l, k + 50, k + 50);
97 
98 	gTermPref = new PrefHandler();
99 }
100 
101 
102 TermApp::~TermApp()
103 {
104 	// delete gTermPref;
105 }
106 
107 
108 void
109 TermApp::ReadyToRun()
110 {
111 	// Prevent opeing window when option -h is given.
112 	if (usage_requested)
113 		return;
114 
115 	const char *command = NULL;
116 	const char *encoding;
117 	int rows, cols;
118 
119 	encoding = gTermPref->getString(PREF_TEXT_ENCODING);
120 
121 	// Get encoding name (setenv TTYPE in spawn_shell functions)
122 	const etable *p = encoding_table;
123 	while (p->name) {
124 		if (!strcmp(p->name, encoding)) {
125 			encoding = p->shortname;
126 			break;
127 		}
128 		p++;
129 	}
130 
131 	if (CommandLine.Length() > 0)
132 		command = CommandLine.String();
133 	else
134 		command = gTermPref->getString(PREF_SHELL);
135 
136 	rows = gTermPref->getInt32(PREF_ROWS);
137 	if (rows < 1)
138 		gTermPref->setInt32(PREF_ROWS, rows = 1);
139 
140 	cols = gTermPref->getInt32(PREF_COLS);
141 	if (cols < MIN_COLS)
142 		gTermPref->setInt32(PREF_COLS, cols = MIN_COLS);
143 
144 	pfd = spawn_shell(rows, cols, command, encoding);
145 
146 	// failed spawn, print stdout and open alert panel
147 	if (pfd == -1 ) {
148 		(new BAlert("alert", "Terminal couldn't start the shell. Sorry.",
149 			"ok", NULL, NULL, B_WIDTH_FROM_LABEL,
150 			B_INFO_ALERT))->Go(NULL);
151 		PostMessage(B_QUIT_REQUESTED);
152 	}
153 
154 	MakeTermWindow(fTermFrame);
155 }
156 
157 
158 void
159 TermApp::Quit()
160 {
161 	if (!usage_requested){
162 		int status;
163 
164 		kill(-sh_pid, SIGHUP);
165 		wait(&status);
166 	}
167 
168 	delete gTermPref;
169 	BApplication::Quit();
170 }
171 
172 
173 void
174 TermApp::AboutRequested()
175 {
176 	BAlert *alert = new BAlert("about", "Terminal\n"
177 		"\twritten by Kazuho Okui and Takashi Murai\n"
178 		"\tupdated by Kian Duffy and others\n\n"
179 		"\tCopyright " B_UTF8_COPYRIGHT "2003-2005, Haiku.\n", "Ok");
180 	BTextView *view = alert->TextView();
181 	BFont font;
182 
183 	view->SetStylable(true);
184 
185 	view->GetFont(&font);
186 	font.SetSize(18);
187 	font.SetFace(B_BOLD_FACE);
188 	view->SetFontAndColor(0, 8, &font);
189 
190 	alert->Go();
191 }
192 
193 
194 void
195 TermApp::MessageReceived(BMessage* msg)
196 {
197 	switch (msg->what) {
198 		case MENU_NEW_TREM:
199 			RunNewTerm();
200 			break;
201 
202 		case MENU_SWITCH_TERM:
203 			SwitchTerm();
204 			break;
205 
206 		case MSG_ACTIVATE_TERM:
207 			fTermWindow->TermWinActivate();
208 			break;
209 
210 		case MSG_TERM_IS_MINIMIZE:
211 		{
212 			BMessage reply(B_REPLY);
213 			reply.AddBool("result", fTermWindow->IsMinimized());
214 			msg->SendReply(&reply);
215 			break;
216 		}
217 
218 		default:
219 			BApplication::MessageReceived(msg);
220 			break;
221 	}
222 }
223 
224 
225 void
226 TermApp::ArgvReceived(int32 argc, char **argv)
227 {
228 	int skip_args = 0;
229 	char *value = 0;
230 
231 	if (argc < 2)
232 		return;
233 
234 	sort_args(argc, argv);
235 	argc = 0;
236 	while (argv[argc])
237 		argc++;
238 
239 	// Print usage
240 	if (argmatch(argv, argc, "-help", "--help", 3, NULL, &skip_args)) {
241 		Usage(argv[0]);
242 		usage_requested = true;
243 		PostMessage(B_QUIT_REQUESTED);
244 	}
245 
246 	// Load preference file
247 	if (argmatch(argv, argc, "-p", "--preference", 4, &value, &skip_args))
248 		gTermPref->Open(value);
249 
250 	// Set window title
251 	if (argmatch(argv ,argc, "-t", "--title", 3, &value, &skip_args))
252 		fWindowTitle = value;
253 
254     // Set window geometry
255     if (argmatch(argv, argc, "-geom", "--geometry", 4, &value, &skip_args)) {
256 		int width, height, xpos, ypos;
257 
258 		sscanf(value, "%dx%d+%d+%d", &width, &height, &xpos, &ypos);
259 		if (width < 0 || height < 0 || xpos < 0 || ypos < 0
260 			|| width >= 256 || height >= 256 || xpos >= 2048 || ypos >= 2048) {
261 			fprintf(stderr, "%s: invalid geometry format or value.\n", argv[0]);
262 			fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
263 			usage_requested = true;
264 			PostMessage(B_QUIT_REQUESTED);
265 		}
266 		gTermPref->setInt32(PREF_COLS, width);
267 		gTermPref->setInt32(PREF_ROWS, height);
268 
269 		fTermFrame.Set(xpos, ypos, xpos + 50, ypos + 50);
270 		geometry_requested = true;
271     }
272 
273 #if 0
274 	// Open '/etc/rgb.txt' file
275 	BFile inFile;
276 	off_t size = 0;
277 
278 	status_t status = inFile.SetTo("/etc/rgb.txt", B_READ_ONLY);
279 	if (status != B_OK) {
280 		fprintf(stderr, "%s: Can't open /etc/rgb.txt file.\n", argv[0]);
281 		usage_requested = true;
282 		PostMessage(B_QUIT_REQUESTED);
283 	}
284 
285 	inFile.GetSize(&size);
286 	char *buffer = new char [size];
287 	inFile.Read(buffer, size);
288 
289 	// Set window, cursor, area and IM color
290 	for (int i = 4; i < 13; i++) {
291 		if (argmatch(argv, argc, standard_args[i].name, standard_args[i].longname,
292 				9, &value, &skip_args)) {
293 			rgb_color color;
294 			if (text_to_rgb(value, &color, buffer))
295 				gTermPref->setRGB(standard_args[i].prefname, color);
296 			else
297 				fprintf(stderr, "%s: invalid color string -- %s\n", argv[0], value);
298 		}
299 	}
300 
301 	delete buffer;
302 #endif
303 	skip_args++;
304 
305 	if (skip_args < argc) {
306 		// Check invalid options
307 
308 		if (*argv[skip_args] == '-') {
309 			fprintf(stderr, "%s: invalid option `%s'\n", argv[0], argv[skip_args]);
310 			fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]);
311 			usage_requested = true;
312 			PostMessage(B_QUIT_REQUESTED);
313 		}
314 
315 		CommandLine += argv[skip_args++];
316 		while (skip_args < argc) {
317 			CommandLine += ' ';
318 			CommandLine += argv[skip_args++];
319 		}
320 	}
321 }
322 
323 //void
324 //TermApp::AboutRequested(void)
325 //{
326 //  if(fAboutPanel) {
327   //  fAboutPanel->Activate();
328  // }
329  // else {
330  //   BRect rect(0, 0, 350 - 1, 170 - 1);
331  //   rect.OffsetTo(fTermWindow->Frame().LeftTop());
332  //   rect.OffsetBy(100, 100);
333 
334   //  fAboutPanel = new AboutDlg(rect, this);
335  //   fAboutPanel->Show();
336  // }
337 
338 //}
339 
340 void
341 TermApp::RefsReceived(BMessage *message)
342 {
343 	// Works Only Launced by Double-Click file, or Drags file to App.
344 	if (!IsLaunching())
345 		return;
346 
347 	entry_ref ref;
348 	if (message->FindRef("refs", 0, &ref) != B_OK)
349 		return;
350 
351 	BFile file;
352 	if (file.SetTo(&ref, B_READ_WRITE) != B_OK)
353 		return;
354 
355 	BNodeInfo info(&file);
356 	char mimetype[B_MIME_TYPE_LENGTH];
357 	info.GetType(mimetype);
358 
359 	// if App opened by Pref file
360 	if (!strcmp(mimetype, PREFFILE_MIMETYPE)) {
361 
362 		BEntry ent(&ref);
363 		BPath path(&ent);
364 		gTermPref->OpenText(path.Path());
365 		return;
366 	}
367 
368 	// if App opened by Shell Script
369 	if (!strcmp(mimetype, "text/x-haiku-shscript")){
370 		// Not implemented.
371 		//    beep();
372 		return;
373 	}
374 }
375 
376 
377 void
378 TermApp::MakeTermWindow(BRect &frame)
379 {
380 	fTermWindow = new TermWindow(frame, fWindowTitle.String());
381 	fTermWindow->Show();
382 }
383 
384 
385 void
386 TermApp::RunNewTerm()
387 {
388 	app_info info;
389 	be_app->GetAppInfo(&info);
390 
391 	// try launching two different ways to work around possible problems
392 	if (be_roster->Launch(&info.ref)!=B_OK)
393 		be_roster->Launch(TERM_SIGNATURE);
394 }
395 
396 
397 void
398 TermApp::ActivateTermWindow(team_id id)
399 {
400 	BMessenger app(TERM_SIGNATURE, id);
401 
402 	if (app.IsTargetLocal())
403 		fTermWindow->Activate();
404 	else
405 		app.SendMessage(MSG_ACTIVATE_TERM);
406 }
407 
408 
409 void
410 TermApp::SwitchTerm()
411 {
412 	team_id myId = be_app->Team(); // My id
413 	BList teams;
414 	be_roster->GetAppList(TERM_SIGNATURE, &teams);
415 	int32 numTerms = teams.CountItems();
416 
417 	if (numTerms <= 1 )
418 		return; //Can't Switch !!
419 
420 	// Find position of mine in app teams.
421 	int32 i;
422 
423 	for (i = 0; i < numTerms; i++) {
424 		if (myId == reinterpret_cast<team_id>(teams.ItemAt(i)))
425 			break;
426 	}
427 
428 	do {
429 		if (--i < 0)
430 			i = numTerms - 1;
431 	} while (IsMinimize(reinterpret_cast<team_id>(teams.ItemAt(i))));
432 
433 	// Activate switched terminal.
434 	ActivateTermWindow(reinterpret_cast<team_id>(teams.ItemAt(i)));
435 }
436 
437 
438 bool
439 TermApp::IsMinimize(team_id id)
440 {
441 	BMessenger app(TERM_SIGNATURE, id);
442 
443 	if (app.IsTargetLocal())
444 		return fTermWindow->IsMinimized();
445 
446 	BMessage reply;
447 	bool hidden;
448 
449 	if (app.SendMessage(MSG_TERM_IS_MINIMIZE, &reply) != B_OK)
450 		return true;
451 
452 	reply.FindBool("result", &hidden);
453 	return hidden;
454 }
455 
456 //#ifndef B_NETPOSITIVE_APP_SIGNATURE
457 //#define B_NETPOSITIVE_APP_SIGNATURE "application/x-vnd.Be-NPOS"
458 //#endif
459 //
460 //void
461 //TermApp::ShowHTML(BMessage *msg)
462 //{
463 //  const char *url;
464 //  msg->FindString("Url", &url);
465 //  BMessage message;
466 //
467 //  message.what = B_NETPOSITIVE_OPEN_URL;
468 //  message.AddString("be:url", url);
469 
470 //  be_roster->Launch(B_NETPOSITIVE_APP_SIGNATURE, &message);
471 //  while(!(be_roster->IsRunning(B_NETPOSITIVE_APP_SIGNATURE)))
472 //    snooze(10000);
473 //
474 //  // Activate net+
475 //  be_roster->ActivateApp(be_roster->TeamFor(B_NETPOSITIVE_APP_SIGNATURE));
476 //}
477 
478 #define PR(a) fprintf(stderr, a)
479 
480 // Display usage to stderr
481 void
482 TermApp::Usage(char *name)
483 {
484 	fprintf(stderr, "\n");
485 	fprintf(stderr, "Haiku Terminal\n");
486 	fprintf(stderr, "Copyright 2004 Haiku, Inc.\n");
487 	fprintf(stderr, "Parts Copyright(C) 1999 Kazuho Okui and Takashi Murai.\n");
488 	fprintf(stderr, "\n");
489 	fprintf(stderr, "Usage: %s [OPTION] [SHELL]\n", name);
490 
491 	PR("  -p,     --preference         load preference file\n");
492 	PR("  -t,     --title              set window title\n");
493 	PR("  -geom,  --geometry           set window geometry\n");
494 	PR("                               An example of geometry is \"80x25+100+100\"\n");
495 #if 0
496 	PR("  -fg,    --text-fore-color    set window foreground color\n");
497 	PR("  -bg,    --text-back-color    set window background color\n");
498 	PR("  -curfg, --cursor-fore-color  set cursor foreground color\n");
499 	PR("  -curbg, --cursor-back-color  set cursor background color\n");
500 	PR("  -selfg, --select-fore-color  set selection area foreground color\n");
501 	PR("  -selbg, --select-back-color  set selection area background color\n");
502 	PR("                               Examples of color are \"#FF00FF\" and \"purple\"\n");
503 #endif
504 	PR("\n");
505 }
506 
507 int
508 text_to_rgb(char *name, rgb_color *color, char *buffer)
509 {
510 	if (name[0] != '#') {
511 		// Convert from /etc/rgb.txt.
512 		BString inStr(buffer);
513 		int32 point, offset = 0;
514 
515 		// Search color name
516 		do {
517 			point = inStr.FindFirst(name, offset);
518 			if (point < 0)
519 				return false;
520 			offset = point + 1;
521 		} while(*(buffer + point -1) != '\t');
522 
523 		char *p = buffer + point;
524 		while (*p != '\n')
525 			p--;
526 		p++;
527 
528 		if (sscanf(p, "%d %d %d", (int *)&color->red, (int *)&color->green,
529 				(int *)&color->blue) == EOF)
530 			return false;
531 
532 		color->alpha = 0;
533 	} else if (name[0] == '#') {
534 		// Convert from #RRGGBB format
535 		sscanf(name, "#%2x%2x%2x", (int *)&color->red, (int *)&color->green,
536 			(int *)&color->blue);
537 		color->alpha = 0;
538 	} else
539 		return false;
540 
541 	return true;
542 }
543 
544 // This routine copy from GNU Emacs.
545 // TODO: This might be a GPL licensing issue here. Investigate.
546 int
547 argmatch(char **argv, int argc, char *sstr, char *lstr,
548 	int minlen, char **valptr, int *skipptr)
549 {
550 	char *p = 0;
551 	int arglen;
552 	char *arg;
553 
554 	// Don't access argv[argc]; give up in advance
555 	if (argc <= *skipptr + 1)
556 		return 0;
557 
558 	arg = argv[*skipptr+1];
559 	if (arg == NULL)
560 		return 0;
561 
562 	if (strcmp(arg, sstr) == 0) {
563 		if(valptr != NULL) {
564 			*valptr = argv[*skipptr+2];
565 			*skipptr += 2;
566 		} else
567 			*skipptr += 1;
568 		return 1;
569 	}
570 
571 	arglen =(valptr != NULL &&(p = strchr(arg, '=')) != NULL
572 			? p - arg : strlen(arg));
573 
574 	if(lstr == 0 || arglen < minlen || strncmp(arg, lstr, arglen) != 0)
575 		return 0;
576 	else
577 	if(valptr == NULL)
578 	{
579 		*skipptr += 1;
580 		return 1;
581 	}
582 	else
583 	if(p != NULL)
584 	{
585 		*valptr = p+1;
586 		*skipptr += 1;
587 		return 1;
588 	}
589 	else
590 	if(argv[*skipptr+2] != NULL)
591 	{
592 		*valptr = argv[*skipptr+2];
593 		*skipptr += 2;
594 		return 1;
595 	}
596 	else
597 	{
598 		return 0;
599 	}
600 }
601 
602 // This routine copy from GNU Emacs.
603 // TODO: This might be a GPL licensing issue here. Investigate.
604 void
605 sort_args(int argc, char **argv)
606 {
607 	/*
608 		For each element of argv,
609 		the corresponding element of options is:
610 		0 for an option that takes no arguments,
611 		1 for an option that takes one argument, etc.
612 		-1 for an ordinary non-option argument.
613 	*/
614 
615 	char **newargv =(char **) malloc(sizeof(char *) * argc);
616 
617 	int *options =(int *) malloc(sizeof(int) * argc);
618 	int *priority =(int *) malloc(sizeof(int) * argc);
619 	int to = 1;
620 	int incoming_used = 1;
621 	int from;
622 	int i;
623 	//int end_of_options = argc;
624 
625 	// Categorize all the options,
626 	// and figure out which argv elts are option arguments
627 	for(from = 1; from < argc; from++)
628 	{
629 		options[from] = -1;
630 		priority[from] = 0;
631 		if(argv[from][0] == '-')
632 		{
633 			int match, thislen;
634 			char *equals;
635 
636 			// If we have found "--", don't consider any more arguments as options
637 			if(argv[from][1] == '-' && argv[from][2] == 0)
638 			{
639 				// Leave the "--", and everything following it, at the end.
640 				for(; from < argc; from++)
641 				{
642 					priority[from] = -100;
643 					options[from] = -1;
644 				}
645 				break;
646 			}
647 
648 			// Look for a match with a known old-fashioned option.
649 			for(i = 0; i <(int)(sizeof(standard_args) / sizeof(standard_args[0])); i++)
650 				if(!strcmp(argv[from], standard_args[i].name))
651 				{
652 					options[from] = standard_args[i].nargs;
653 					priority[from] = standard_args[i].priority;
654 					if(from + standard_args[i].nargs >= argc)
655 						fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
656 					from += standard_args[i].nargs;
657 					goto done;
658 				}
659 
660 			/*
661 				Look for a match with a known long option.
662 				MATCH is -1 if no match so far, -2 if two or more matches so far,
663 				>= 0(the table index of the match) if just one match so far.
664 			*/
665 			if(argv[from][1] == '-')
666 			{
667 				match = -1;
668 				thislen = strlen(argv[from]);
669 				equals = strchr(argv[from], '=');
670 				if(equals != 0)
671 					thislen = equals - argv[from];
672 
673 				for(i = 0;i <(int )(sizeof(standard_args) / sizeof(standard_args[0])); i++)
674 					if(standard_args[i].longname
675 						&& !strncmp(argv[from], standard_args[i].longname, thislen))
676 					{
677 						if(match == -1)
678 							match = i;
679 						else
680 							match = -2;
681 					}
682 
683 					// If we found exactly one match, use that
684 					if(match >= 0)
685 					{
686 						options[from] = standard_args[match].nargs;
687 						priority[from] = standard_args[match].priority;
688 
689 						// If --OPTION=VALUE syntax is used,
690 						// this option uses just one argv element
691 						if(equals != 0)
692 							options[from] = 0;
693 						if(from + options[from] >= argc)
694 							fprintf(stderr, "Option `%s' requires an argument\n", argv[from]);
695 						from += options[from];
696 					}
697 			}
698 			done: ;
699 		}
700 	}
701 
702 	// Copy the arguments, in order of decreasing priority, to NEW
703 	newargv[0] = argv[0];
704 	while (incoming_used < argc) {
705 		int best = -1;
706 		int best_priority = -9999;
707 
708 		// Find the highest priority remaining option.
709 		// If several have equal priority, take the first of them.
710 		for (from = 1; from < argc; from++) {
711 			if (argv[from] != 0 && priority[from] > best_priority) {
712 				best_priority = priority[from];
713 				best = from;
714 			}
715 
716 			// Skip option arguments--they are tied to the options.
717 			if (options[from] > 0)
718 				from += options[from];
719 		}
720 
721 		if (best < 0)
722 			abort();
723 
724 		// Copy the highest priority remaining option, with its args, to NEW.
725 		// Unless it is a duplicate of the previous one
726 		if (!(options[best] == 0 && ! strcmp(newargv[to - 1], argv[best]))) {
727 			newargv[to++] = argv[best];
728 			for(i = 0; i < options[best]; i++)
729 				newargv[to++] = argv[best + i + 1];
730 		}
731 
732 		incoming_used += 1 +(options[best] > 0 ? options[best] : 0);
733 
734 		// Clear out this option in ARGV
735 		argv[best] = 0;
736 		for (i = 0; i < options[best]; i++)
737 			argv[best + i + 1] = 0;
738 	}
739 
740 	// If duplicate options were deleted, fill up extra space with null ptrs
741 	while (to < argc)
742 		newargv[to++] = 0;
743 
744 	memcpy(argv, newargv, sizeof(char *) * argc);
745 
746 	free(options);
747 	free(newargv);
748 	free(priority);
749 }
750