pre-inst should install 'installer' group rather than the hardcoded plover-gtkui
2 * Copyright (C) 2014, 2020 J. Ali Harlow <ali@juiblex.co.uk>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 * http://www.transmissionzero.co.uk/computing/win32-apps-with-mingw/
24 #define _XOPEN_SOURCE 500
32 #include <plover/plover.h>
33 #include <whelk/whelk.h>
41 #define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
51 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
57 LUALIB_API int luaopen_posix(lua_State *L);
65 int verify_and_fix(const char *root)
71 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
79 progress=GetDlgItem(dialog,IDC_PROGRESS);
80 style=GetWindowLong(progress,GWL_STYLE);
81 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
82 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
85 return (INT_PTR)FALSE;
92 unsigned pre_install_thread(void *data)
96 const char *repository=data;
98 char *install[]={"plover-gtkui",NULL};
101 struct comps_group *group;
102 struct comps_requirement *pkg;
103 struct plover_vector *packages=NULL;
105 plover__uri_handler_init();
106 prefix=plover_pre_install_prefix();
107 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
110 s=g_strconcat(prefix,"/var/lib/razor",NULL);
111 uri=razor_path_to_uri(s);
113 razor_set_database_uri(uri);
115 if (verify_and_fix(prefix))
120 s=g_strconcat(repository,"/repodata/comps.xml",NULL);
121 comps=plover_comps_new_from_uri(s,&error);
126 group=plover_comps_lookup_group(comps,"installer");
129 packages=plover_vector_new();
133 for(pkg=group->packages;pkg;pkg=pkg->next)
135 if (plover_vector_contains(packages,pkg->name))
137 if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
138 pkg->type==COMPS_REQUIREMENT_MANDATORY ||
139 pkg->type==COMPS_REQUIREMENT_CONDITIONAL &&
140 plover_vector_contains(packages,pkg->requires))
143 plover_vector_append(packages,pkg->name);
147 pkgs=packages->strings;
151 retval=!plover_install_uri(repository,prefix,pkgs,&error);
153 retval=!plover_update_uri(repository,prefix,NULL,&error);
155 plover_vector_free(packages);
157 plover_comps_free(comps);
160 fprintf(stderr,"%s\n",error->message);
164 PostQuitMessage(retval);
165 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
166 _endthreadex(retval);
172 * The idea is that if pre_install() fails, update/setup should fall back
173 * to console interfaces.
180 pre_install(const char *repository)
187 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
188 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
190 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
193 if (pre_install_thread((void *)repository))
196 retval=(void *)1; /* Non-NULL to indicate success */
202 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
210 gboolean deltree(const char *path)
213 /* Based on g_local_file_trash() */
214 SHFILEOPSTRUCTW op={0};
218 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
219 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
220 wfilename=g_renew(wchar_t,wfilename,len+2);
225 success=!SHFileOperationW(&op);
226 if (success && op.fAnyOperationsAborted)
231 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
235 gboolean pre_uninstall(void)
240 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
241 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
242 prefix=plover_pre_install_prefix();
243 s=g_strconcat(prefix,"/var/lib/razor",NULL);
244 uri=razor_path_to_uri(s);
246 razor_set_database_uri(uri);
248 success=plover_remove(NULL,&error);
251 fprintf(stderr,"%s\n",error->message);
258 #if defined(WIN32) && !defined(USE_G_SPAWN)
259 /* Based on glib's g_spawn_win32.c */
262 win32_cmdline_quote(const char *string)
264 const gchar *p=string;
267 gboolean need_dblquotes=FALSE;
270 if (*p==' ' || *p=='\t')
277 while (*pp && *pp=='\\')
285 q=retval=g_malloc(len+need_dblquotes*2+1);
296 while (*pp && *pp=='\\')
310 /* Create a win32-style wide-character argv with suitable quoting */
311 wchar_t **win32_argv_import(char **argv,GError **error)
316 GError *tmp_error=NULL;
317 n=g_strv_length(argv);
318 wargv=g_new(wchar_t *,n+1);
321 s=win32_cmdline_quote(argv[i]);
322 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
326 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
327 "Invalid argument #%d: %s",i,tmp_error->message);
328 g_error_free(tmp_error);
339 gboolean spawn_sync(char **argv,GError **error)
341 wchar_t *wargv0,**wargv;
343 GError *tmp_error=NULL;
344 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
347 fprintf(stderr,"Conversion error in post\n");
348 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
349 "Invalid program name: %s",tmp_error->message);
350 g_error_free(tmp_error);
353 wargv=win32_argv_import(argv,error);
356 fprintf(stderr,"Conversion error in post\n");
361 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
363 g_strfreev((gchar **)wargv);
364 if (rc==-1 && errno!=0)
366 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
367 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
368 "Failed to execute post command: %s",g_strerror(errno));
371 if (rc!=EXIT_SUCCESS)
373 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
374 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
375 "Post command exited with code %ld",(long)rc);
381 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
383 gboolean pre_install_spawn_sync(char **argv,GError **error)
385 GError *tmp_error=NULL;
387 gchar *standard_output,*standard_error;
389 if (!g_spawn_sync(NULL,argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
390 &standard_output,&standard_error,&exit_status,&tmp_error))
392 fprintf(stderr,"Failed to start post command\n");
393 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
396 if (standard_output && *standard_output)
398 printf("Output from post command %s:\n",argv[0]);
399 fputs(standard_output,stdout);
401 g_free(standard_output);
402 if (standard_error && *standard_error)
404 printf("Error output from post command %s:\n",argv[0]);
405 fputs(standard_error,stdout);
407 g_free(standard_error);
408 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
410 fprintf(stderr,"post command failed\n");
411 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
415 if (!spawn_sync(argv,&tmp_error))
417 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
425 * Run a command after completing request.
427 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
428 * (first) install prefix used, by %TEST_RESULT% which will be replaced
429 * by either "pass" or "fail" depending as to whether the request succeeded
430 * or not and/or by %REPOSITORY% which will be replaced by the URI of the
431 * repository used. Command may also include double quotes which will be used
432 * to affect how the command is split into arguments much like a shell does.
434 gboolean run_post(int argc,char **argv,gboolean test_result,
435 const char *repository,GError **error)
441 GError *tmp_error=NULL;
444 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
445 "--post: No command given");
448 printf("Running post command: %s\n",argv[1]);
449 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
451 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
454 for(i=0;i<post_argc;i++)
456 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
460 s+=strlen("%INSTALL_PREFIX%");
461 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
462 g_free(post_argv[i]);
463 post_argv[i]=expanded;
465 s=strstr(post_argv[i],"%TEST_RESULT%");
469 s+=strlen("%TEST_RESULT%");
470 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
471 g_free(post_argv[i]);
472 post_argv[i]=expanded;
474 s=strstr(post_argv[i],"%REPOSITORY%");
478 s+=strlen("%REPOSITORY%");
479 expanded=g_strconcat(post_argv[i],repository,s,NULL);
480 g_free(post_argv[i]);
481 post_argv[i]=expanded;
484 return pre_install_spawn_sync(post_argv,error);
488 DWORD win32_pre_install_gui(char *repository)
491 INITCOMMONCONTROLSEX icc={0,};
494 main_thread_id=GetCurrentThreadId();
495 thread=(HANDLE)pre_install(repository);
498 icc.dwSize=sizeof(icc);
499 icc.dwICC=ICC_WIN95_CLASSES;
500 InitCommonControlsEx(&icc);
501 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
502 &ProgressDialogProc);
503 while(GetMessage(&msg,NULL,0,0)>0)
505 TranslateMessage(&msg);
506 DispatchMessage(&msg);
508 WaitForSingleObject(thread,INFINITE);
509 GetExitCodeThread(thread,&retval);
515 gchar *pre_install_default_repository(const char *argv0)
521 struct razor_error *tmp_error=NULL;
523 * The default repository is the executable itself if it's an archive
524 * or otherwise the directory in which the executable is stored.
526 path=plover_get_program(argv0);
527 uri=razor_path_to_uri(path);
529 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
530 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
533 razor_uri_free_contents(contents,length);
536 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
537 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
539 path=plover_get_program_directory(argv0);
540 uri=razor_path_to_uri(path);
543 razor_error_free(tmp_error);
548 gboolean pre_install_post(int argc,char **argv,gboolean success,
549 const char *repository)
552 struct post *post=NULL;
554 if (!argv && !success)
557 * If no --post command has been given, then we want to run the
558 * program specified in %INSTALL_PREFIX%/etc/pre-inst.post
559 * However, if the pre-install failed, then that file isn't likely
560 * to exist (and it's requirements even less so) so we issue an error
561 * instead. Unfortunately, it's hard to include the error message
562 * here. It will have been logged in the plover log, but that's not
563 * much help to users.
567 "Installation failed: Failed to unpack the main installer\n");
569 MessageBox(NULL,"Failed to unpack the main installer",
570 "Installation failed",MB_ICONERROR|MB_OK);
576 post=pre_install_post_new(repository,prefix);
577 uri=razor_path_to_uri(prefix);
578 s=g_strconcat(uri,"/etc/pre-inst.post",NULL);
580 pre_install_post_load_uri(post,s,&error);
582 g_debug("Failed to load post configuration from pre-inst.post: %s",
585 if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
588 * If no post configuration file exists, that's not an error;
589 * there simply is no post action configured.
591 g_clear_error(&error);
594 if (post && post->argc>0)
595 pre_install_spawn_sync(post->argv,&error);
597 run_post(argc,argv,success,repository,&error);
599 pre_install_post_free(post);
603 fprintf(stderr,"Error in post: %s\n",error->message);
605 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
613 int main(int argc,char **argv)
615 gboolean success,uninstall=FALSE,enable_post=FALSE;
617 gchar *path=NULL,*repository=NULL;
618 GOptionContext *context;
619 GOptionEntry options[]={
620 {"repository",0,0,G_OPTION_ARG_STRING,&repository,
621 "Repository location","uri"},
622 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
623 "Repository path","path"},
624 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
625 "Uninstall all packages",NULL},
626 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
627 "Run command after request is processed",NULL},
632 * pre-inst is normally a GUI application, but rpm scripts may well
633 * call console applications and it looks ugly if console windows keep
634 * popping up. Avoid this by allocating our own console and hiding it.
636 * - If pre-inst is a console application (typically for debugging),
637 * then skip this step.
638 * - Call ShowWindow twice to negate special handling on first call.
640 if (!GetConsoleWindow())
643 ShowWindow(GetConsoleWindow(),SW_HIDE);
644 ShowWindow(GetConsoleWindow(),SW_HIDE);
647 plover_exception_handler_init();
648 context=g_option_context_new("[command] - install the main installer");
649 g_option_context_add_main_entries(context,options,NULL);
650 g_option_context_set_description(context,
651 "If --post is specified, then the command to run and its arguments\n"
652 "should be listed at the end of the command line.\n"
653 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
654 "(first) install prefix used, by %TEST_RESULT% which will be\n"
655 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
656 "request succeeded or not and/or by %REPOSITORY% which will be\n"
657 "replaced by the URI of the repository used. Command may also include\n"
658 "double quotes which will be used to affect how the command is split\n"
659 "into arguments much like a shell does.");
660 g_option_context_set_strict_posix(context,TRUE);
661 g_option_context_set_ignore_unknown_options(context,TRUE);
662 if (!g_option_context_parse(context,&argc,&argv,&error))
664 g_printerr("pre-install: %s\n",error->message);
665 g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
668 if (repository && path)
670 g_printerr("pre-install: "
671 "Only one of --repository and --path can be specified\n");
675 repository=razor_path_to_uri(path);
676 else if (!repository)
677 repository=pre_install_default_repository(argv[0]);
679 success=pre_uninstall();
683 success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
685 success=!!pre_install(repository);
689 success=pre_install_post(argc,argv,success,repository);
691 success=pre_install_post(0,NULL,success,repository);
693 return success?EXIT_SUCCESS:EXIT_FAILURE;