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
31 #include <plover/plover.h>
32 #include <whelk/whelk.h>
40 #define FOF_NO_UI (FOF_SILENT|FOF_NOCONFIRMATION|FOF_NOERRORUI|\
50 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
56 LUALIB_API int luaopen_posix(lua_State *L);
64 int verify_and_fix(const char *root)
70 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
78 progress=GetDlgItem(dialog,IDC_PROGRESS);
79 style=GetWindowLong(progress,GWL_STYLE);
80 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
81 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
84 return (INT_PTR)FALSE;
91 unsigned pre_install_thread(void *data)
94 const char *repository=data;
96 char *install[]={"plover-gtkui",NULL};
98 plover__uri_handler_init();
99 prefix=plover_pre_install_prefix();
100 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
103 s=g_strconcat(prefix,"/var/lib/razor",NULL);
104 uri=razor_path_to_uri(s);
106 razor_set_database_uri(uri);
108 if (verify_and_fix(prefix))
113 retval=!plover_install_uri(repository,prefix,install,&error);
115 retval=!plover_update_uri(repository,prefix,NULL,&error);
118 fprintf(stderr,"%s\n",error->message);
122 PostQuitMessage(retval);
123 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
124 _endthreadex(retval);
130 * The idea is that if pre_install() fails, update/setup should fall back
131 * to console interfaces.
138 pre_install(const char *repository)
145 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
146 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
148 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
151 if (pre_install_thread((void *)repository))
154 retval=(void *)1; /* Non-NULL to indicate success */
160 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
168 gboolean deltree(const char *path)
171 /* Based on g_local_file_trash() */
172 SHFILEOPSTRUCTW op={0};
176 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
177 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
178 wfilename=g_renew(wchar_t,wfilename,len+2);
183 success=!SHFileOperationW(&op);
184 if (success && op.fAnyOperationsAborted)
189 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
193 gboolean pre_uninstall(void)
198 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
199 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
200 prefix=plover_pre_install_prefix();
201 s=g_strconcat(prefix,"/var/lib/razor",NULL);
202 uri=razor_path_to_uri(s);
204 razor_set_database_uri(uri);
206 success=plover_remove(NULL,&error);
209 fprintf(stderr,"%s\n",error->message);
216 #if defined(WIN32) && !defined(USE_G_SPAWN)
217 /* Based on glib's g_spawn_win32.c */
220 win32_cmdline_quote(const char *string)
222 const gchar *p=string;
225 gboolean need_dblquotes=FALSE;
228 if (*p==' ' || *p=='\t')
235 while (*pp && *pp=='\\')
243 q=retval=g_malloc(len+need_dblquotes*2+1);
254 while (*pp && *pp=='\\')
268 /* Create a win32-style wide-character argv with suitable quoting */
269 wchar_t **win32_argv_import(char **argv,GError **error)
274 GError *tmp_error=NULL;
275 n=g_strv_length(argv);
276 wargv=g_new(wchar_t *,n+1);
279 s=win32_cmdline_quote(argv[i]);
280 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
284 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
285 "Invalid argument #%d: %s",i,tmp_error->message);
286 g_error_free(tmp_error);
297 gboolean spawn_sync(char **argv,GError **error)
299 wchar_t *wargv0,**wargv;
301 GError *tmp_error=NULL;
302 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
305 fprintf(stderr,"Conversion error in post\n");
306 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
307 "Invalid program name: %s",tmp_error->message);
308 g_error_free(tmp_error);
311 wargv=win32_argv_import(argv,error);
314 fprintf(stderr,"Conversion error in post\n");
319 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
321 g_strfreev((gchar **)wargv);
322 if (rc==-1 && errno!=0)
324 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
325 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
326 "Failed to execute post command: %s",g_strerror(errno));
329 if (rc!=EXIT_SUCCESS)
331 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
332 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
333 "Post command exited with code %ld",(long)rc);
339 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
341 gboolean pre_install_spawn_sync(char **argv,GError **error)
343 GError *tmp_error=NULL;
345 gchar *standard_output,*standard_error;
347 if (!g_spawn_sync(NULL,argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
348 &standard_output,&standard_error,&exit_status,&tmp_error))
350 fprintf(stderr,"Failed to start post command\n");
351 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
354 if (standard_output && *standard_output)
356 printf("Output from post command %s:\n",argv[0]);
357 fputs(standard_output,stdout);
359 g_free(standard_output);
360 if (standard_error && *standard_error)
362 printf("Error output from post command %s:\n",argv[0]);
363 fputs(standard_error,stdout);
365 g_free(standard_error);
366 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
368 fprintf(stderr,"post command failed\n");
369 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
373 if (!spawn_sync(argv,&tmp_error))
375 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[0]);
383 * Run a command after completing request.
385 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
386 * (first) install prefix used, by %TEST_RESULT% which will be replaced
387 * by either "pass" or "fail" depending as to whether the request succeeded
388 * or not and/or by %REPOSITORY% which will be replaced by the URI of the
389 * repository used. Command may also include double quotes which will be used
390 * to affect how the command is split into arguments much like a shell does.
392 gboolean run_post(int argc,char **argv,gboolean test_result,
393 const char *repository,GError **error)
399 GError *tmp_error=NULL;
402 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
403 "--post: No command given");
406 printf("Running post command: %s\n",argv[1]);
407 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
409 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
412 for(i=0;i<post_argc;i++)
414 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
418 s+=strlen("%INSTALL_PREFIX%");
419 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
420 g_free(post_argv[i]);
421 post_argv[i]=expanded;
423 s=strstr(post_argv[i],"%TEST_RESULT%");
427 s+=strlen("%TEST_RESULT%");
428 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
429 g_free(post_argv[i]);
430 post_argv[i]=expanded;
432 s=strstr(post_argv[i],"%REPOSITORY%");
436 s+=strlen("%REPOSITORY%");
437 expanded=g_strconcat(post_argv[i],repository,s,NULL);
438 g_free(post_argv[i]);
439 post_argv[i]=expanded;
442 return pre_install_spawn_sync(post_argv,error);
446 DWORD win32_pre_install_gui(char *repository)
449 INITCOMMONCONTROLSEX icc={0,};
452 main_thread_id=GetCurrentThreadId();
453 thread=(HANDLE)pre_install(repository);
456 icc.dwSize=sizeof(icc);
457 icc.dwICC=ICC_WIN95_CLASSES;
458 InitCommonControlsEx(&icc);
459 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
460 &ProgressDialogProc);
461 while(GetMessage(&msg,NULL,0,0)>0)
463 TranslateMessage(&msg);
464 DispatchMessage(&msg);
466 WaitForSingleObject(thread,INFINITE);
467 GetExitCodeThread(thread,&retval);
473 gchar *pre_install_default_repository(const char *argv0)
479 struct razor_error *tmp_error=NULL;
481 * The default repository is the executable itself if it's an archive
482 * or otherwise the directory in which the executable is stored.
484 path=plover_get_program(argv0);
485 uri=razor_path_to_uri(path);
487 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
488 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
491 razor_uri_free_contents(contents,length);
494 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
495 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
497 path=plover_get_program_directory(argv0);
498 uri=razor_path_to_uri(path);
501 razor_error_free(tmp_error);
506 gboolean pre_install_post(int argc,char **argv,gboolean success,
507 const char *repository)
510 struct post *post=NULL;
512 if (!argv && !success)
515 * If no --post command has been given, then we want to run the
516 * program specified in %INSTALL_PREFIX%/etc/pre-inst.post
517 * However, if the pre-install failed, then that file isn't likely
518 * to exist (and it's requirements even less so) so we issue an error
519 * instead. Unfortunately, it's hard to include the error message
520 * here. It will have been logged in the plover log, but that's not
521 * much help to users.
525 "Installation failed: Failed to unpack the main installer\n");
527 MessageBox(NULL,"Failed to unpack the main installer",
528 "Installation failed",MB_ICONERROR|MB_OK);
534 post=pre_install_post_new(repository,prefix);
535 uri=razor_path_to_uri(prefix);
536 s=g_strconcat(uri,"/etc/pre-inst.post",NULL);
538 pre_install_post_load_uri(post,s,&error);
540 g_debug("Failed to load post configuration from pre-inst.post: %s",
543 if (g_error_matches(error,PLOVER_POSIX_ERROR,ENOENT))
546 * If no post configuration file exists, that's not an error;
547 * there simply is no post action configured.
549 g_clear_error(&error);
552 if (post && post->argc>0)
553 pre_install_spawn_sync(post->argv,&error);
555 run_post(argc,argv,success,repository,&error);
557 pre_install_post_free(post);
561 fprintf(stderr,"Error in post: %s\n",error->message);
563 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
571 int main(int argc,char **argv)
573 gboolean success,uninstall=FALSE,enable_post=FALSE;
575 gchar *path=NULL,*repository=NULL;
576 GOptionContext *context;
577 GOptionEntry options[]={
578 {"repository",0,0,G_OPTION_ARG_STRING,&repository,
579 "Repository location","uri"},
580 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
581 "Repository path","path"},
582 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
583 "Uninstall all packages",NULL},
584 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
585 "Run command after request is processed",NULL},
590 * pre-inst is normally a GUI application, but rpm scripts may well
591 * call console applications and it looks ugly if console windows keep
592 * popping up. Avoid this by allocating our own console and hiding it.
594 * - If pre-inst is a console application (typically for debugging),
595 * then skip this step.
596 * - Call ShowWindow twice to negate special handling on first call.
598 if (!GetConsoleWindow())
601 ShowWindow(GetConsoleWindow(),SW_HIDE);
602 ShowWindow(GetConsoleWindow(),SW_HIDE);
605 plover_exception_handler_init();
606 context=g_option_context_new("[command] - install the main installer");
607 g_option_context_add_main_entries(context,options,NULL);
608 g_option_context_set_description(context,
609 "If --post is specified, then the command to run and its arguments\n"
610 "should be listed at the end of the command line.\n"
611 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
612 "(first) install prefix used, by %TEST_RESULT% which will be\n"
613 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
614 "request succeeded or not and/or by %REPOSITORY% which will be\n"
615 "replaced by the URI of the repository used. Command may also include\n"
616 "double quotes which will be used to affect how the command is split\n"
617 "into arguments much like a shell does.");
618 g_option_context_set_strict_posix(context,TRUE);
619 g_option_context_set_ignore_unknown_options(context,TRUE);
620 if (!g_option_context_parse(context,&argc,&argv,&error))
622 g_printerr("pre-install: %s\n",error->message);
623 g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
626 if (repository && path)
628 g_printerr("pre-install: "
629 "Only one of --repository and --path can be specified\n");
633 repository=razor_path_to_uri(path);
634 else if (!repository)
635 repository=pre_install_default_repository(argv[0]);
637 success=pre_uninstall();
641 success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
643 success=!!pre_install(repository);
647 success=pre_install_post(argc,argv,success,repository);
649 success=pre_install_post(0,NULL,success,repository);
651 return success?EXIT_SUCCESS:EXIT_FAILURE;