1/*
2 * Copyright 2014, Rene Gollent, rene@gollent.com.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "DwarfLoadingStateHandler.h"
8
9#include <sys/wait.h>
10
11#include <Entry.h>
12#include <InterfaceDefs.h>
13#include <Path.h>
14#include <package/solver/Solver.h>
15#include <package/solver/SolverPackage.h>
16
17#include "AutoDeleter.h"
18#include "DwarfFile.h"
19#include "DwarfImageDebugInfoLoadingState.h"
20#include "package/manager/PackageManager.h"
21#include "Tracing.h"
22#include "UserInterface.h"
23
24
25using namespace BPackageKit;
26using BPackageKit::BManager::BPrivate::BPackageManager;
27
28
29enum {
30	USER_CHOICE_INSTALL_PACKAGE = 0,
31	USER_CHOICE_LOCATE_FILE ,
32	USER_CHOICE_SKIP
33};
34
35
36DwarfLoadingStateHandler::DwarfLoadingStateHandler()
37	:
38	ImageDebugLoadingStateHandler()
39{
40}
41
42
43DwarfLoadingStateHandler::~DwarfLoadingStateHandler()
44{
45}
46
47
48bool
49DwarfLoadingStateHandler::SupportsState(
50	SpecificImageDebugInfoLoadingState* state)
51{
52	return dynamic_cast<DwarfImageDebugInfoLoadingState*>(state) != NULL;
53}
54
55
56void
57DwarfLoadingStateHandler::HandleState(
58	SpecificImageDebugInfoLoadingState* state, UserInterface* interface)
59{
60	DwarfImageDebugInfoLoadingState* dwarfState
61		= dynamic_cast<DwarfImageDebugInfoLoadingState*>(state);
62
63	if (dwarfState == NULL) {
64		ERROR("DwarfLoadingStateHandler::HandleState() passed "
65			"non-dwarf state object %p.", state);
66		return;
67	}
68
69	DwarfFileLoadingState& fileState = dwarfState->GetFileState();
70
71	BString requiredPackage;
72	_GetMatchingDebugInfoPackage(fileState.externalInfoFileName,
73		requiredPackage);
74
75	// loop so that the user has a chance to retry or locate the file manually
76	// in case package installation fails, e.g. due to transient download
77	// issues.
78	for (;;) {
79		int32 choice;
80		BString message;
81		if (interface->IsInteractive()) {
82			if (requiredPackage.IsEmpty()) {
83				message.SetToFormat("The debug information file '%s' for "
84					"image '%s' is missing. Would you like to locate the file "
85					"manually?", fileState.externalInfoFileName.String(),
86					fileState.dwarfFile->Name());
87				choice = interface->SynchronouslyAskUser("Debug info missing",
88					message.String(), "Locate", "Skip", NULL);
89				if (choice == 0)
90					choice = USER_CHOICE_LOCATE_FILE;
91				else if (choice == 1)
92					choice = USER_CHOICE_SKIP;
93			} else {
94				message.SetToFormat("The debug information file '%s' for "
95					"image '%s' is missing, but can be found in the package "
96					"'%s'. Would you like to install it, or locate the file "
97					"manually?", fileState.externalInfoFileName.String(),
98					fileState.dwarfFile->Name(), requiredPackage.String());
99				choice = interface->SynchronouslyAskUser("Debug info missing",
100					message.String(), "Install", "Locate", "Skip");
101			}
102		} else
103			choice = USER_CHOICE_SKIP;
104
105		if (choice == USER_CHOICE_INSTALL_PACKAGE) {
106			// TODO: integrate the package installation functionality directly.
107			BString command;
108			command.SetToFormat("/bin/pkgman install -y %s",
109				requiredPackage.String());
110			BString notification;
111			notification.SetToFormat("Installing package %s" B_UTF8_ELLIPSIS,
112				requiredPackage.String());
113			interface->NotifyBackgroundWorkStatus(notification);
114			int error = system(command.String());
115			if (interface->IsInteractive()) {
116				if (WIFEXITED(error)) {
117					error = WEXITSTATUS(error);
118					if (error == B_OK)
119						break;
120					message.SetToFormat("Package installation failed: %s.",
121						strerror(error));
122					interface->NotifyUser("Error", message.String(),
123						USER_NOTIFICATION_ERROR);
124					continue;
125				}
126			}
127			break;
128		} else if (choice == USER_CHOICE_LOCATE_FILE) {
129			entry_ref ref;
130			interface->SynchronouslyAskUserForFile(&ref);
131			BPath path(&ref);
132			if (path.InitCheck() == B_OK)
133				fileState.locatedExternalInfoPath = path.Path();
134			break;
135		} else
136			break;
137	}
138
139	fileState.state = DWARF_FILE_LOADING_STATE_USER_INPUT_PROVIDED;
140}
141
142
143status_t
144DwarfLoadingStateHandler::_GetMatchingDebugInfoPackage(
145	const BString& debugFileName, BString& _packageName)
146{
147	BString resolvableName;
148	BPackageVersion requiredVersion;
149	BPackageManager::ClientInstallationInterface clientInterface;
150	BPackageManager::UserInteractionHandler handler;
151
152	BPackageManager packageManager(B_PACKAGE_INSTALLATION_LOCATION_SYSTEM,
153		&clientInterface, &handler);
154	packageManager.Init(BPackageManager::B_ADD_INSTALLED_REPOSITORIES
155		| BPackageManager::B_ADD_REMOTE_REPOSITORIES);
156	BObjectList<BSolverPackage> packages;
157	status_t error = _GetResolvableName(debugFileName, resolvableName,
158		requiredVersion);
159	if (error != B_OK)
160		return error;
161
162	error = packageManager.Solver()->FindPackages(resolvableName,
163		BSolver::B_FIND_IN_PROVIDES, packages);
164	if (error != B_OK)
165		return error;
166	else if (packages.CountItems() == 0)
167		return B_ENTRY_NOT_FOUND;
168
169	for (int32 i = 0; i < packages.CountItems(); i++) {
170		BSolverPackage* package = packages.ItemAt(i);
171		if (requiredVersion.Compare(package->Version()) == 0) {
172			_packageName = package->Name();
173			return B_OK;
174		}
175	}
176
177	return B_ENTRY_NOT_FOUND;
178}
179
180
181status_t
182DwarfLoadingStateHandler::_GetResolvableName(const BString& debugFileName,
183	BString& _resolvableName, BPackageVersion& _resolvableVersion)
184{
185	BString fileName;
186	BString packageName;
187	BString packageVersion;
188
189	int32 startIndex = 0;
190	int32 endIndex = debugFileName.FindFirst('(');
191	if (endIndex < 0)
192		return B_BAD_VALUE;
193
194	debugFileName.CopyInto(fileName, 0, endIndex);
195	startIndex = endIndex + 1;
196	endIndex = debugFileName.FindFirst('-', startIndex);
197	if (endIndex < 0)
198		return B_BAD_VALUE;
199
200	debugFileName.CopyInto(packageName, startIndex, endIndex - startIndex);
201	startIndex = endIndex + 1;
202	endIndex = debugFileName.FindFirst(')', startIndex);
203	if (endIndex < 0)
204		return B_BAD_VALUE;
205
206	debugFileName.CopyInto(packageVersion, startIndex,
207		endIndex - startIndex);
208
209	_resolvableName.SetToFormat("debuginfo:%s(%s)", fileName.String(), packageName.String());
210
211	return _resolvableVersion.SetTo(packageVersion);
212}
213