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 <plover/uri-handler.h>
34 #include <whelk/whelk.h>
42 #define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
52 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
58 LUALIB_API int luaopen_posix(lua_State *L);
66 int verify_and_fix(const char *root)
72 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
80 progress=GetDlgItem(dialog,IDC_PROGRESS);
81 style=GetWindowLong(progress,GWL_STYLE);
82 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
83 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
86 return (INT_PTR)FALSE;
93 unsigned pre_install_thread(void *data)
97 const char *repository=data;
99 char *install[]={"plover-gtkui",NULL};
102 struct comps_group *group;
103 struct comps_requirement *pkg;
104 struct plover_vector *packages=NULL;
106 plover__uri_handler_init();
107 prefix=plover_pre_install_prefix();
108 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
111 s=g_strconcat(prefix,"/var/lib/razor",NULL);
112 uri=razor_path_to_uri(s);
114 razor_set_database_uri(uri);
116 if (verify_and_fix(prefix))
121 s=g_strconcat(repository,"/repodata/comps.xml",NULL);
122 comps=plover_comps_new_from_uri(s,&error);
127 group=plover_comps_lookup_group(comps,"installer");
130 packages=plover_vector_new();
134 for(pkg=group->packages;pkg;pkg=pkg->next)
136 if (plover_vector_contains(packages,pkg->name))
138 if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
139 pkg->type==COMPS_REQUIREMENT_MANDATORY ||
140 pkg->type==COMPS_REQUIREMENT_CONDITIONAL &&
141 plover_vector_contains(packages,pkg->requires))
144 plover_vector_append(packages,pkg->name);
148 pkgs=packages->strings;
152 retval=!plover_install_uri(repository,prefix,pkgs,&error);
154 retval=!plover_update_uri(repository,prefix,NULL,&error);
156 plover_vector_free(packages);
158 plover_comps_free(comps);
161 fprintf(stderr,"%s\n",error->message);
165 PostQuitMessage(retval);
166 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
167 _endthreadex(retval);
173 * The idea is that if pre_install() fails, update/setup should fall back
174 * to console interfaces.
181 pre_install(const char *repository)
188 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
189 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
191 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
194 if (pre_install_thread((void *)repository))
197 retval=(void *)1; /* Non-NULL to indicate success */
203 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
211 gboolean deltree(const char *path)
214 /* Based on g_local_file_trash() */
215 SHFILEOPSTRUCTW op={0};
219 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
220 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
221 wfilename=g_renew(wchar_t,wfilename,len+2);
226 success=!SHFileOperationW(&op);
227 if (success && op.fAnyOperationsAborted)
232 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
236 gboolean pre_uninstall(void)
241 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
242 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
243 prefix=plover_pre_install_prefix();
244 s=g_strconcat(prefix,"/var/lib/razor",NULL);
245 uri=razor_path_to_uri(s);
247 razor_set_database_uri(uri);
249 success=plover_remove(NULL,&error);
252 fprintf(stderr,"%s\n",error->message);
259 #if defined(WIN32) && !defined(USE_G_SPAWN)
260 /* Based on glib's g_spawn_win32.c */
263 win32_cmdline_quote(const char *string)
265 const gchar *p=string;
268 gboolean need_dblquotes=FALSE;
271 if (*p==' ' || *p=='\t')
278 while (*pp && *pp=='\\')
286 q=retval=g_malloc(len+need_dblquotes*2+1);
297 while (*pp && *pp=='\\')
311 /* Create a win32-style wide-character argv with suitable quoting */
312 wchar_t **win32_argv_import(char **argv,GError **error)
317 GError *tmp_error=NULL;
318 n=g_strv_length(argv);
319 wargv=g_new(wchar_t *,n+1);
322 s=win32_cmdline_quote(argv[i]);
323 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
327 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
328 "Invalid argument #%d: %s",i,tmp_error->message);
329 g_error_free(tmp_error);
340 gboolean spawn_sync(char **argv,GError **error)
342 wchar_t *wargv0,**wargv;
344 GError *tmp_error=NULL;
345 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
348 fprintf(stderr,"Conversion error in post\n");
349 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
350 "Invalid program name: %s",tmp_error->message);
351 g_error_free(tmp_error);
354 wargv=win32_argv_import(argv,error);
357 fprintf(stderr,"Conversion error in post\n");
362 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
364 g_strfreev((gchar **)wargv);
365 if (rc==-1 && errno!=0)
367 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
368 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
369 "Failed to execute post command: %s",g_strerror(errno));
372 if (rc!=EXIT_SUCCESS)
374 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
375 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
376 "Post command exited with code %ld",(long)rc);
382 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
384 gboolean pre_install_spawn_sync(char **argv,GError **error)
386 GError *tmp_error=NULL;
388 gchar *standard_output,*standard_error;
390 if (!g_spawn_sync(NULL,argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
391 &standard_output,&standard_error,&exit_status,&tmp_error))
393 fprintf(stderr,"Failed to start post command\n");
394 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
397 if (standard_output && *standard_output)
399 printf("Output from post command %s:\n",argv[0]);
400 fputs(standard_output,stdout);
402 g_free(standard_output);
403 if (standard_error && *standard_error)
405 printf("Error output from post command %s:\n",argv[0]);
406 fputs(standard_error,stdout);
408 g_free(standard_error);
409 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
411 fprintf(stderr,"post command failed\n");
412 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
416 if (!spawn_sync(argv,&tmp_error))
418 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
426 * Run a command after completing request.
428 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
429 * (first) install prefix used, by %TEST_RESULT% which will be replaced
430 * by either "pass" or "fail" depending as to whether the request succeeded
431 * or not and/or by %REPOSITORY% which will be replaced by the URI of the
432 * repository used. Command may also include double quotes which will be used
433 * to affect how the command is split into arguments much like a shell does.
435 gboolean run_post(int argc,char **argv,gboolean test_result,
436 const char *repository,GError **error)
442 GError *tmp_error=NULL;
445 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
446 "--post: No command given");
449 printf("Running post command: %s\n",argv[1]);
450 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
452 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
455 for(i=0;i<post_argc;i++)
457 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
461 s+=strlen("%INSTALL_PREFIX%");
462 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
463 g_free(post_argv[i]);
464 post_argv[i]=expanded;
466 s=strstr(post_argv[i],"%TEST_RESULT%");
470 s+=strlen("%TEST_RESULT%");
471 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
472 g_free(post_argv[i]);
473 post_argv[i]=expanded;
475 s=strstr(post_argv[i],"%REPOSITORY%");
479 s+=strlen("%REPOSITORY%");
480 expanded=g_strconcat(post_argv[i],repository,s,NULL);
481 g_free(post_argv[i]);
482 post_argv[i]=expanded;
485 return pre_install_spawn_sync(post_argv,error);
489 DWORD win32_pre_install_gui(char *repository)
492 INITCOMMONCONTROLSEX icc={0,};
495 main_thread_id=GetCurrentThreadId();
496 thread=(HANDLE)pre_install(repository);
499 icc.dwSize=sizeof(icc);
500 icc.dwICC=ICC_WIN95_CLASSES;
501 InitCommonControlsEx(&icc);
502 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
503 &ProgressDialogProc);
504 while(GetMessage(&msg,NULL,0,0)>0)
506 TranslateMessage(&msg);
507 DispatchMessage(&msg);
509 WaitForSingleObject(thread,INFINITE);
510 GetExitCodeThread(thread,&retval);
516 gchar *pre_install_default_repository(const char *argv0)
522 struct razor_error *tmp_error=NULL;
524 * The default repository is the executable itself if it's an archive
525 * or otherwise the directory in which the executable is stored.
527 path=plover_get_program(argv0);
528 uri=razor_path_to_uri(path);
530 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
531 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
534 razor_uri_free_contents(contents,length);
537 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
538 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
540 path=plover_get_program_directory(argv0);
541 uri=razor_path_to_uri(path);
544 razor_error_free(tmp_error);
549 gboolean pre_install_post(int argc,char **argv,gboolean success,
550 const char *repository)
553 struct post *post=NULL;
555 if (!argv && !success)
558 * If no --post command has been given, then we want to run the
559 * program specified in %INSTALL_PREFIX%/etc/pre-inst.post
560 * However, if the pre-install failed, then that file isn't likely
561 * to exist (and it's requirements even less so) so we issue an error
562 * instead. Unfortunately, it's hard to include the error message
563 * here. It will have been logged in the plover log, but that's not
564 * much help to users.
568 "Installation failed: Failed to unpack the main installer\n");
570 MessageBox(NULL,"Failed to unpack the main installer",
571 "Installation failed",MB_ICONERROR|MB_OK);
577 post=pre_install_post_new(repository,prefix);
578 uri=razor_path_to_uri(prefix);
579 s=g_strconcat(uri,"/etc/pre-inst.post",NULL);
581 pre_install_post_load_uri(post,s,&error);
583 g_debug("Failed to load post configuration from pre-inst.post: %s",
586 if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
589 * If no post configuration file exists, that's not an error;
590 * there simply is no post action configured.
592 g_clear_error(&error);
595 if (post && post->argc>0)
596 pre_install_spawn_sync(post->argv,&error);
598 run_post(argc,argv,success,repository,&error);
600 pre_install_post_free(post);
604 fprintf(stderr,"Error in post: %s\n",error->message);
606 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
614 int main(int argc,char **argv)
616 gboolean success,uninstall=FALSE,enable_post=FALSE;
618 gchar *path=NULL,*repository=NULL;
619 GOptionContext *context;
620 GOptionEntry options[]={
621 {"repository",0,0,G_OPTION_ARG_STRING,&repository,
622 "Repository location","uri"},
623 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
624 "Repository path","path"},
625 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
626 "Uninstall all packages",NULL},
627 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
628 "Run command after request is processed",NULL},
633 * pre-inst is normally a GUI application, but rpm scripts may well
634 * call console applications and it looks ugly if console windows keep
635 * popping up. Avoid this by allocating our own console and hiding it.
637 * - If pre-inst is a console application (typically for debugging),
638 * then skip this step.
639 * - Call ShowWindow twice to negate special handling on first call.
641 if (!GetConsoleWindow())
644 ShowWindow(GetConsoleWindow(),SW_HIDE);
645 ShowWindow(GetConsoleWindow(),SW_HIDE);
648 plover_exception_handler_init();
649 context=g_option_context_new("[command] - install the main installer");
650 g_option_context_add_main_entries(context,options,NULL);
651 g_option_context_set_description(context,
652 "If --post is specified, then the command to run and its arguments\n"
653 "should be listed at the end of the command line.\n"
654 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
655 "(first) install prefix used, by %TEST_RESULT% which will be\n"
656 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
657 "request succeeded or not and/or by %REPOSITORY% which will be\n"
658 "replaced by the URI of the repository used. Command may also include\n"
659 "double quotes which will be used to affect how the command is split\n"
660 "into arguments much like a shell does.");
661 g_option_context_set_strict_posix(context,TRUE);
662 g_option_context_set_ignore_unknown_options(context,TRUE);
663 if (!g_option_context_parse(context,&argc,&argv,&error))
665 g_printerr("pre-install: %s\n",error->message);
666 g_printerr("Use \"%s --help\" for help\n",argv[0]);
669 if (repository && path)
671 g_printerr("pre-install: "
672 "Only one of --repository and --path can be specified\n");
676 repository=razor_path_to_uri(path);
677 else if (!repository)
678 repository=pre_install_default_repository(argv[0]);
680 success=pre_uninstall();
684 success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
686 success=!!pre_install(repository);
690 success=pre_install_post(argc,argv,success,repository);
692 success=pre_install_post(0,NULL,success,repository);
694 return success?EXIT_SUCCESS:EXIT_FAILURE;