2 * Copyright (C) 2014 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|\
49 /* Under WIN32, g_spawn requires a helper program which we'd rather avoid */
55 LUALIB_API int luaopen_posix(lua_State *L);
63 int verify_and_fix(const char *root)
69 INT_PTR CALLBACK ProgressDialogProc(HWND dialog,UINT msg,WPARAM w_param,
77 progress=GetDlgItem(dialog,IDC_PROGRESS);
78 style=GetWindowLong(progress,GWL_STYLE);
79 SetWindowLong(progress,GWL_STYLE,style|PBS_MARQUEE);
80 SendMessage(progress,PBM_SETMARQUEE,(WPARAM)TRUE,(LPARAM)30);
83 return (INT_PTR)FALSE;
90 unsigned pre_install_thread(void *data)
93 const char *repository=data;
95 char *install[]={"plover-gtkui",NULL};
97 plover__uri_handler_init();
98 prefix=plover_pre_install_prefix();
99 s=g_strconcat(prefix,"/var/log/pre-install",NULL);
102 s=g_strconcat(prefix,"/var/lib/razor",NULL);
103 uri=razor_path_to_uri(s);
105 razor_set_database_uri(uri);
107 if (verify_and_fix(prefix))
112 retval=!plover_install_uri(repository,prefix,install,&error);
114 retval=!plover_update_uri(repository,prefix,NULL,&error);
117 fprintf(stderr,"%s\n",error->message);
121 PostQuitMessage(retval);
122 PostThreadMessage(main_thread_id,WM_QUIT,retval,0);
123 _endthreadex(retval);
129 * The idea is that if pre_install() fails, update/setup should fall back
130 * to console interfaces.
137 pre_install(const char *repository)
144 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
145 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
147 retval=(HANDLE)_beginthreadex(NULL,0,pre_install_thread,(void *)repository,
150 if (pre_install_thread((void *)repository))
153 retval=(void *)1; /* Non-NULL to indicate success */
159 int remove_ignore(const char *fpath,const struct stat *sb,int typeflag,
167 gboolean deltree(const char *path)
170 /* Based on g_local_file_trash() */
171 SHFILEOPSTRUCTW op={0};
175 wfilename=g_utf8_to_utf16(path,-1,NULL,&len,NULL);
176 /* SHFILEOPSTRUCT.pFrom is double-zero-terminated */
177 wfilename=g_renew(wchar_t,wfilename,len+2);
182 success=!SHFileOperationW(&op);
183 if (success && op.fAnyOperationsAborted)
188 return nftw(path,remove_ignore,64,FTW_DEPTH|FTW_PHYS);
192 gboolean pre_uninstall(void)
197 razor_set_lua_loader("posix",(void (*)())luaopen_posix);
198 razor_set_lua_loader("whelk",(void (*)())luaopen_whelk);
199 prefix=plover_pre_install_prefix();
200 s=g_strconcat(prefix,"/var/lib/razor",NULL);
201 uri=razor_path_to_uri(s);
203 razor_set_database_uri(uri);
205 success=plover_remove(NULL,&error);
208 fprintf(stderr,"%s\n",error->message);
215 #if defined(WIN32) && !defined(USE_G_SPAWN)
216 /* Based on glib's g_spawn_win32.c */
219 win32_cmdline_quote(const char *string)
221 const gchar *p=string;
224 gboolean need_dblquotes=FALSE;
227 if (*p==' ' || *p=='\t')
234 while (*pp && *pp=='\\')
242 q=retval=g_malloc(len+need_dblquotes*2+1);
253 while (*pp && *pp=='\\')
267 /* Create a win32-style wide-character argv with suitable quoting */
268 wchar_t **win32_argv_import(char **argv,GError **error)
273 GError *tmp_error=NULL;
274 n=g_strv_length(argv);
275 wargv=g_new(wchar_t *,n+1);
278 s=win32_cmdline_quote(argv[i]);
279 wargv[i]=g_utf8_to_utf16(s,-1,NULL,NULL,&tmp_error);
283 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
284 "Invalid argument #%d: %s",i,tmp_error->message);
285 g_error_free(tmp_error);
296 gboolean spawn_sync(char **argv,GError **error)
298 wchar_t *wargv0,**wargv;
300 GError *tmp_error=NULL;
301 wargv0=g_utf8_to_utf16(argv[0],-1,NULL,NULL,&tmp_error);
304 fprintf(stderr,"Conversion error in post\n");
305 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
306 "Invalid program name: %s",tmp_error->message);
307 g_error_free(tmp_error);
310 wargv=win32_argv_import(argv,error);
313 fprintf(stderr,"Conversion error in post\n");
318 rc=_wspawnvp(P_WAIT,wargv0,(const wchar_t **)wargv);
320 g_strfreev((gchar **)wargv);
321 if (rc==-1 && errno!=0)
323 fprintf(stderr,"Failed to start post command (%s)\n",g_strerror(errno));
324 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
325 "Failed to execute post command: %s",g_strerror(errno));
328 if (rc!=EXIT_SUCCESS)
330 fprintf(stderr,"post command failed (%ld)\n",(long)rc);
331 g_set_error(error,G_SPAWN_ERROR,G_SPAWN_ERROR_FAILED,
332 "Post command exited with code %ld",(long)rc);
338 #endif /* defined(WIN32) && !defined(USE_G_SPAWN) */
341 * Run a command after completing request.
343 * Command may refer to %INSTALL_PREFIX% which will be replaced by the
344 * (first) install prefix used, by %TEST_RESULT% which will be replaced
345 * by either "pass" or "fail" depending as to whether the request succeeded
346 * or not and/or by %REPOSITORY% which will be replaced by the URI of the
347 * repository used. Command may also include double quotes which will be used
348 * to affect how the command is split into arguments much like a shell does.
350 gboolean run_post(int argc,char **argv,gboolean test_result,
351 const char *repository,GError **error)
358 gchar *standard_output,*standard_error;
361 GError *tmp_error=NULL;
364 g_set_error_literal(error,G_FILE_ERROR,G_FILE_ERROR_NOENT,
365 "--post: No command given");
368 printf("Running post command: %s\n",argv[1]);
369 if (!g_shell_parse_argv(argv[1],&post_argc,&post_argv,&tmp_error))
371 g_propagate_prefixed_error(error,tmp_error,"%s: ",argv[1]);
374 for(i=0;i<post_argc;i++)
376 s=strstr(post_argv[i],"%INSTALL_PREFIX%");
380 s+=strlen("%INSTALL_PREFIX%");
381 expanded=g_strconcat(post_argv[i],prefix,s,NULL);
382 g_free(post_argv[i]);
383 post_argv[i]=expanded;
385 s=strstr(post_argv[i],"%TEST_RESULT%");
389 s+=strlen("%TEST_RESULT%");
390 expanded=g_strconcat(post_argv[i],test_result?"pass":"fail",s,NULL);
391 g_free(post_argv[i]);
392 post_argv[i]=expanded;
394 s=strstr(post_argv[i],"%REPOSITORY%");
398 s+=strlen("%REPOSITORY%");
399 expanded=g_strconcat(post_argv[i],repository,s,NULL);
400 g_free(post_argv[i]);
401 post_argv[i]=expanded;
405 if (!g_spawn_sync(NULL,post_argv,NULL,G_SPAWN_SEARCH_PATH,NULL,NULL,
406 &standard_output,&standard_error,&exit_status,&tmp_error))
408 fprintf(stderr,"Failed to start post command\n");
409 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
412 if (standard_output && *standard_output)
414 printf("Output from post command %s:\n",post_argv[0]);
415 fputs(standard_output,stdout);
417 g_free(standard_output);
418 if (standard_error && *standard_error)
420 printf("Error output from post command %s:\n",post_argv[0]);
421 fputs(standard_error,stdout);
423 g_free(standard_error);
424 if (!g_spawn_check_exit_status(exit_status,&tmp_error))
426 fprintf(stderr,"post command failed\n");
427 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
431 if (!spawn_sync(post_argv,&tmp_error))
433 g_propagate_prefixed_error(error,tmp_error,"%s: ",post_argv[0]);
441 DWORD win32_pre_install_gui(char *repository)
444 INITCOMMONCONTROLSEX icc={0,};
447 main_thread_id=GetCurrentThreadId();
448 thread=(HANDLE)pre_install(repository);
451 icc.dwSize=sizeof(icc);
452 icc.dwICC=ICC_WIN95_CLASSES;
453 InitCommonControlsEx(&icc);
454 DialogBox(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_PROGRESSDIALOG),NULL,
455 &ProgressDialogProc);
456 while(GetMessage(&msg,NULL,0,0)>0)
458 TranslateMessage(&msg);
459 DispatchMessage(&msg);
461 WaitForSingleObject(thread,INFINITE);
462 GetExitCodeThread(thread,&retval);
468 gchar *pre_install_default_repository(const char *argv0)
474 struct razor_error *tmp_error=NULL;
476 * The default repository is the executable itself if it's an archive
477 * or otherwise the directory in which the executable is stored.
479 path=plover_get_program(argv0);
480 uri=razor_path_to_uri(path);
482 s=g_strconcat(uri,"/repodata/comps.xml",NULL);
483 contents=razor_uri_get_contents(s,&length,FALSE,&tmp_error);
486 razor_uri_free_contents(contents,length);
489 if (razor_error_matches(tmp_error,RAZOR_GENERAL_ERROR,
490 RAZOR_GENERAL_ERROR_UNSUPPORTED_ARCHIVE))
492 path=plover_get_program_directory(argv0);
493 uri=razor_path_to_uri(path);
496 razor_error_free(tmp_error);
501 int main(int argc,char **argv)
503 gboolean success,uninstall=FALSE,enable_post=FALSE;
505 gchar *path=NULL,*repository=NULL;
506 GOptionContext *context;
507 GOptionEntry options[]={
508 {"repository",0,0,G_OPTION_ARG_STRING,&repository,
509 "Repository location","uri"},
510 {"path",0,0,G_OPTION_ARG_FILENAME,&path,
511 "Repository path","path"},
512 {"uninstall",'u',0,G_OPTION_ARG_NONE,&uninstall,
513 "Uninstall all packages",NULL},
514 {"post",0,0,G_OPTION_ARG_NONE,&enable_post,
515 "Run command after request is processed",NULL},
520 * pre-inst is normally a GUI application, but rpm scripts may well
521 * call console applications and it looks ugly if console windows keep
522 * popping up. Avoid this by allocating our own console and hiding it.
524 * - If pre-inst is a console application (typically for debugging),
525 * then skip this step.
526 * - Call ShowWindow twice to negate special handling on first call.
528 if (!GetConsoleWindow())
531 ShowWindow(GetConsoleWindow(),SW_HIDE);
532 ShowWindow(GetConsoleWindow(),SW_HIDE);
535 plover_exception_handler_init();
536 context=g_option_context_new("[command] - install the main installer");
537 g_option_context_add_main_entries(context,options,NULL);
538 g_option_context_set_description(context,
539 "If --post is specified, then the command to run and its arguments\n"
540 "should be listed at the end of the command line.\n"
541 "Command may refer to %INSTALL_PREFIX% which will be replaced by the\n"
542 "(first) install prefix used, by %TEST_RESULT% which will be\n"
543 "replaced by either \"pass\" or \"fail\" depending as to whether the\n"
544 "request succeeded or not and/or by %REPOSITORY% which will be\n"
545 "replaced by the URI of the repository used. Command may also include\n"
546 "double quotes which will be used to affect how the command is split\n"
547 "into arguments much like a shell does.");
548 g_option_context_set_strict_posix(context,TRUE);
549 g_option_context_set_ignore_unknown_options(context,TRUE);
550 if (!g_option_context_parse(context,&argc,&argv,&error))
552 g_printerr("pre-install: %s\n",error->message);
553 g_printerr("Use \"%s --help\" for help\n",(*argv)[0]);
556 if (repository && path)
558 g_printerr("pre-install: "
559 "Only one of --repository and --path can be specified\n");
563 repository=razor_path_to_uri(path);
564 else if (!repository)
565 repository=pre_install_default_repository(argv[0]);
567 success=pre_uninstall();
571 success=win32_pre_install_gui(repository)==EXIT_SUCCESS;
573 success=!!pre_install(repository);
576 if (enable_post && !run_post(argc,argv,success,repository,&error))
579 fprintf(stderr,"Error in post: %s\n",error->message);
581 MessageBox(NULL,error->message,"Error in post",MB_ICONERROR|MB_OK);
587 return success?EXIT_SUCCESS:EXIT_FAILURE;