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 may require a helper program which we'd rather avoid.
53 * At least with glib 2.58, this isn't needed if g_spawn_sync() is used (or
54 * G_SPAWN_DO_NOT_REAP_CHILD is specified), so we can now define USE_G_SPAWN
59 LUALIB_API int luaopen_posix(lua_State *L);
67 int verify_and_fix(const char *root)
73 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
81 progress=GetDlgItem(dialog,IDC_PROGRESS);
82 style=GetWindowLong(progress,GWL_STYLE);
83 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
84 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
87 return (INT_PTR)FALSE;
94 unsigned pre_install_thread(void *data)
98 const char *repository=data;
100 char *install[]={"plover-gtkui",NULL};
103 struct comps_group *group;
104 struct comps_requirement *pkg;
105 struct plover_vector *packages=NULL;
107 plover__uri_handler_init();
108 prefix=plover_pre_install_prefix();
109 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
112 s=g_strconcat(prefix,"/var/lib/razor",NULL);
113 uri=razor_path_to_uri(s);
115 razor_set_database_uri(uri);
117 if (verify_and_fix(prefix))
122 s=g_strconcat(repository,"/repodata/comps.xml",NULL);
123 comps=plover_comps_new_from_uri(s,&error);
128 group=plover_comps_lookup_group(comps,"installer");
131 packages=plover_vector_new();
135 for(pkg=group->packages;pkg;pkg=pkg->next)
137 if (plover_vector_contains(packages,pkg->name))
139 if (pkg->type==COMPS_REQUIREMENT_DEFAULT ||
140 pkg->type==COMPS_REQUIREMENT_MANDATORY ||
141 pkg->type==COMPS_REQUIREMENT_CONDITIONAL &&
142 plover_vector_contains(packages,pkg->requires))
145 plover_vector_append(packages,pkg->name);
149 pkgs=packages->strings;
153 retval=!plover_install_uri(repository,prefix,pkgs,&error);
155 retval=!plover_update_uri(repository,prefix,NULL,&error);
157 plover_vector_free(packages);
159 plover_comps_free(comps);
162 fprintf(stderr,"%s\n",error->message);
166 PostQuitMessage(retval);
167 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
168 _endthreadex(retval);
174 * The idea is that if pre_install() fails, update/setup should fall back
175 * to console interfaces.
182 pre_install(const char *repository)
189 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
190 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
192 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
195 if (pre_install_thread((void *)repository))
198 retval=(void *)1; /* Non-NULL to indicate success */
204 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
212 gboolean deltree(const char *path)
215 /* Based on g_local_file_trash() */
216 SHFILEOPSTRUCTW op={0};
220 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
221 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
222 wfilename=g_renew(wchar_t,wfilename,len+2);
227 success=!SHFileOperationW(&op);
228 if (success && op.fAnyOperationsAborted)
233 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
237 gboolean pre_uninstall(void)
242 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
243 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
244 prefix=plover_pre_install_prefix();
245 s=g_strconcat(prefix,"/var/lib/razor",NULL);
246 uri=razor_path_to_uri(s);
248 razor_set_database_uri(uri);
250 success=plover_remove(NULL,&error);
253 fprintf(stderr,"%s\n",error->message);
260 #if defined(WIN32) && !defined(USE_G_SPAWN)
261 /* Based on glib's g_spawn_win32.c */
264 win32_cmdline_quote(const char *string)
266 const gchar *p=string;
269 gboolean need_dblquotes=FALSE;
272 if (*p==' ' || *p=='\t')
279 while (*pp && *pp=='\\')
287 q=retval=g_malloc(len+need_dblquotes*2+1);
298 while (*pp && *pp=='\\')
312 /* Create a win32-style wide-character argv with suitable quoting */
313 wchar_t **win32_argv_import(char **argv,GError **error)
318 GError *tmp_error=NULL;
319 n=g_strv_length(argv);
320 wargv=g_new(wchar_t *,n+1);
323 s=win32_cmdline_quote(argv[i]);
324 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
328 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
329 "Invalid argument #%d: %s",i,tmp_error->message);
330 g_error_free(tmp_error);
341 gboolean spawn_sync(char **argv,GError **error)
343 wchar_t *wargv0,**wargv;
345 GError *tmp_error=NULL;
346 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
349 fprintf(stderr,"Conversion error in post\n");
350 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
351 "Invalid program name: %s",tmp_error->message);
352 g_error_free(tmp_error);
355 wargv=win32_argv_import(argv,error);
358 fprintf(stderr,"Conversion error in post\n");
363 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
365 g_strfreev((gchar **)wargv);
366 if (rc==-1 && errno!=0)
368 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
369 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
370 "Failed to execute post command: %s",g_strerror(errno));
373 if (rc!=EXIT_SUCCESS)
375 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
376 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
377 "Post command exited with code %ld",(long)rc);
380 fprintf(stderr,"post command exited normally\n");
384 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
386 gboolean pre_install_spawn_sync(char **argv,GError **error)
388 GError *tmp_error=NULL;
390 gchar *standard_output,*standard_error;
392 if (!g_spawn_sync(NULL,argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
393 &standard_output,&standard_error,&exit_status,&tmp_error))
395 fprintf(stderr,"Failed to start post command\n");
396 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
399 if (standard_output && *standard_output)
401 printf("Output from post command %s:\n",argv[0]);
402 fputs(standard_output,stdout);
404 g_free(standard_output);
405 if (standard_error && *standard_error)
407 printf("Error output from post command %s:\n",argv[0]);
408 fputs(standard_error,stdout);
410 g_free(standard_error);
411 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
413 fprintf(stderr,"post command failed\n");
414 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
418 if (!spawn_sync(argv,&tmp_error))
420 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
427 print_arguments(int argc,char **argv)
434 if (strchr(argv[i],' ') || strchr(argv[i],'"') || strchr(argv[i],'\n'))
437 for(j=0;argv[i][j];j++)
439 if (strchr("$`\"\\\n",argv[i][j]))
446 fputs(argv[i],stdout);
451 * Run a command after completing request.
453 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
454 * (first) install prefix used, by %TEST_RESULT% which will be replaced
455 * by either "pass" or "fail" depending as to whether the request succeeded
456 * or not and/or by %REPOSITORY% which will be replaced by the URI of the
457 * repository used. Command may also include double quotes which will be used
458 * to affect how the command is split into arguments much like a shell does.
460 gboolean run_post(int argc,char **argv,gboolean test_result,
461 const char *repository,GError **error)
467 GError *tmp_error=NULL;
470 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
471 "--post: No command given");
474 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
476 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
479 for(i=0;i<post_argc;i++)
481 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
485 s+=strlen("%INSTALL_PREFIX%");
486 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
487 g_free(post_argv[i]);
488 post_argv[i]=expanded;
490 s=strstr(post_argv[i],"%TEST_RESULT%");
494 s+=strlen("%TEST_RESULT%");
495 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
496 g_free(post_argv[i]);
497 post_argv[i]=expanded;
499 s=strstr(post_argv[i],"%REPOSITORY%");
503 s+=strlen("%REPOSITORY%");
504 expanded=g_strconcat(post_argv[i],repository,s,NULL);
505 g_free(post_argv[i]);
506 post_argv[i]=expanded;
509 printf("Running post command: ");
510 print_arguments(post_argc,post_argv);
512 return pre_install_spawn_sync(post_argv,error);
516 DWORD win32_pre_install_gui(char *repository)
519 INITCOMMONCONTROLSEX icc={0,};
522 main_thread_id=GetCurrentThreadId();
523 thread=(HANDLE)pre_install(repository);
526 icc.dwSize=sizeof(icc);
527 icc.dwICC=ICC_WIN95_CLASSES;
528 InitCommonControlsEx(&icc);
529 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
530 &ProgressDialogProc);
531 while(GetMessage(&msg,NULL,0,0)>0)
533 TranslateMessage(&msg);
534 DispatchMessage(&msg);
536 WaitForSingleObject(thread,INFINITE);
537 GetExitCodeThread(thread,&retval);
543 gchar *pre_install_default_repository(const char *argv0)
549 struct razor_error *tmp_error=NULL;
551 * The default repository is the executable itself if it's an archive
552 * or otherwise the directory in which the executable is stored.
554 path=plover_get_program(argv0);
555 uri=razor_path_to_uri(path);
557 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
558 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
561 razor_uri_free_contents(contents,length);
564 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
565 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
567 path=plover_get_program_directory(argv0);
568 uri=razor_path_to_uri(path);
571 razor_error_free(tmp_error);
576 gboolean pre_install_post(int argc,char **argv,gboolean success,
577 const char *repository)
580 struct post *post=NULL;
582 if (!argv && !success)
585 * If no --post command has been given, then we want to run the
586 * program specified in %INSTALL_PREFIX%/etc/pre-inst.post
587 * However, if the pre-install failed, then that file isn't likely
588 * to exist (and it's requirements even less so) so we issue an error
589 * instead. Unfortunately, it's hard to include the error message
590 * here. It will have been logged in the plover log, but that's not
591 * much help to users.
595 "Installation failed: Failed to unpack the main installer\n");
597 MessageBox(NULL,"Failed to unpack the main installer",
598 "Installation failed",MB_ICONERROR|MB_OK);
604 post=pre_install_post_new(repository,prefix);
605 uri=razor_path_to_uri(prefix);
606 s=g_strconcat(uri,"/etc/pre-inst.post",NULL);
608 pre_install_post_load_uri(post,s,&error);
610 g_debug("Failed to load post configuration from pre-inst.post: %s",
613 if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
616 * If no post configuration file exists, that's not an error;
617 * there simply is no post action configured.
619 g_clear_error(&error);
622 if (post && post->argc>0)
624 printf("Running post command: ");
625 print_arguments(post->argc,post->argv);
627 pre_install_spawn_sync(post->argv,&error);
630 run_post(argc,argv,success,repository,&error);
632 pre_install_post_free(post);
636 fprintf(stderr,"Error in post: %s\n",error->message);
638 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
646 int main(int argc,char **argv)
648 gboolean success,uninstall=FALSE,enable_post=FALSE;
650 gchar *path=NULL,*repository=NULL;
651 GOptionContext *context;
652 GOptionEntry options[]={
653 {"repository",0,0,G_OPTION_ARG_STRING,&repository,
654 "Repository location","uri"},
655 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
656 "Repository path","path"},
657 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
658 "Uninstall all packages",NULL},
659 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
660 "Run command after request is processed",NULL},
665 * pre-inst is normally a GUI application, but rpm scripts may well
666 * call console applications and it looks ugly if console windows keep
667 * popping up. Avoid this by allocating our own console and hiding it.
669 * - If pre-inst is a console application (typically for debugging),
670 * then skip this step.
671 * - Call ShowWindow twice to negate special handling on first call.
673 if (!GetConsoleWindow())
676 ShowWindow(GetConsoleWindow(),SW_HIDE);
677 ShowWindow(GetConsoleWindow(),SW_HIDE);
680 plover_exception_handler_init();
681 context=g_option_context_new("[command] - install the main installer");
682 g_option_context_add_main_entries(context,options,NULL);
683 g_option_context_set_description(context,
684 "If --post is specified, then the command to run and its arguments\n"
685 "should be listed at the end of the command line.\n"
686 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
687 "(first) install prefix used, by %TEST_RESULT% which will be\n"
688 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
689 "request succeeded or not and/or by %REPOSITORY% which will be\n"
690 "replaced by the URI of the repository used. Command may also include\n"
691 "double quotes which will be used to affect how the command is split\n"
692 "into arguments much like a shell does.");
693 g_option_context_set_strict_posix(context,TRUE);
694 g_option_context_set_ignore_unknown_options(context,TRUE);
695 if (!g_option_context_parse(context,&argc,&argv,&error))
697 g_printerr("pre-install: %s\n",error->message);
698 g_printerr("Use \"%s --help\" for help\n",argv[0]);
701 if (repository && path)
703 g_printerr("pre-install: "
704 "Only one of --repository and --path can be specified\n");
708 repository=razor_path_to_uri(path);
709 else if (!repository)
710 repository=pre_install_default_repository(argv[0]);
712 success=pre_uninstall();
716 success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
718 success=!!pre_install(repository);
722 success=pre_install_post(argc,argv,success,repository);
724 success=pre_install_post(0,NULL,success,repository);
726 return success?EXIT_SUCCESS:EXIT_FAILURE;