1/*
2 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "FSTransaction.h"
8
9#include <Entry.h>
10#include <package/CommitTransactionResult.h>
11#include <Path.h>
12
13#include <CopyEngine.h>
14#include <RemoveEngine.h>
15
16#include "DebugSupport.h"
17#include "Exception.h"
18
19
20// #pragma mark - OperationInfo
21
22
23struct FSTransaction::OperationInfo {
24public:
25	enum Type {
26		TYPE_CREATE,
27		TYPE_REMOVE,
28		TYPE_MOVE,
29	};
30
31public:
32	OperationInfo(Type type, const std::string& fromPath,
33		const std::string& toPath, int32 modifiedOperation)
34		:
35		fType(type),
36		fFromPath(fromPath),
37		fToPath(toPath),
38		fModifiedOperation(modifiedOperation),
39		fEnabled(true)
40	{
41	}
42
43	const std::string& FromPath() const
44	{
45		return fFromPath;
46	}
47
48	const std::string& ToPath() const
49	{
50		return fToPath;
51	}
52
53	int32 ModifiedOperation() const
54	{
55		return fModifiedOperation;
56	}
57
58	void SetModifiedOperation(int32 modifiedOperation)
59	{
60		fModifiedOperation = modifiedOperation;
61	}
62
63	bool IsEnabled() const
64	{
65		return fEnabled;
66	}
67
68	void SetEnabled(bool enabled)
69	{
70		fEnabled = enabled;
71	}
72
73	status_t RollBack() const
74	{
75		switch (fType) {
76			case TYPE_CREATE:
77			{
78				status_t error = BRemoveEngine().RemoveEntry(
79					Entry(fFromPath.c_str()));
80				if (error != B_OK) {
81					ERROR("Failed to remove \"%s\": %s\n", fFromPath.c_str(),
82						strerror(error));
83				}
84				return error;
85			}
86
87			case TYPE_REMOVE:
88			{
89				if (fToPath.empty())
90					return B_NOT_SUPPORTED;
91
92				status_t error = BCopyEngine(
93						BCopyEngine::COPY_RECURSIVELY
94							| BCopyEngine::UNLINK_DESTINATION)
95					.CopyEntry(fToPath.c_str(), fFromPath.c_str());
96				if (error != B_OK) {
97					ERROR("Failed to copy \"%s\" to \"%s\": %s\n",
98						fToPath.c_str(), fFromPath.c_str(), strerror(error));
99				}
100				return error;
101			}
102
103			case TYPE_MOVE:
104			{
105				BEntry entry;
106				status_t error = entry.SetTo(fToPath.c_str());
107				if (error != B_OK) {
108					ERROR("Failed to init entry for \"%s\": %s\n",
109						fToPath.c_str(), strerror(error));
110					return error;
111				}
112
113				error = entry.Rename(fFromPath.c_str(), true);
114				if (error != B_OK) {
115					ERROR("Failed to move \"%s\" to \"%s\": %s\n",
116						fToPath.c_str(), fFromPath.c_str(), strerror(error));
117					return error;
118				}
119				return error;
120			}
121		}
122
123		return B_ERROR;
124	}
125
126private:
127	Type		fType;
128	std::string	fFromPath;
129	std::string	fToPath;
130	int32		fModifiedOperation;
131	bool		fEnabled;
132};
133
134
135// #pragma mark - FSTransaction
136
137
138FSTransaction::FSTransaction()
139{
140}
141
142
143FSTransaction::~FSTransaction()
144{
145}
146
147
148void
149FSTransaction::RollBack()
150{
151	int32 count = (int32)fOperations.size();
152	for (int32 i = count - 1; i >= 0; i--) {
153		const OperationInfo& operation = fOperations[i];
154		bool rolledBack = false;
155		if (operation.IsEnabled())
156			rolledBack = operation.RollBack() == B_OK;
157
158		if (!rolledBack && operation.ModifiedOperation() >= 0)
159			fOperations[operation.ModifiedOperation()].SetEnabled(false);
160	}
161}
162
163
164int32
165FSTransaction::CreateEntry(const Entry& entry, int32 modifiedOperation)
166{
167	fOperations.push_back(
168		OperationInfo(OperationInfo::TYPE_CREATE, _GetPath(entry),
169			std::string(), modifiedOperation));
170	return (int32)fOperations.size() - 1;
171}
172
173
174int32
175FSTransaction::RemoveEntry(const Entry& entry, const Entry& backupEntry,
176	int32 modifiedOperation)
177{
178	fOperations.push_back(
179		OperationInfo(OperationInfo::TYPE_REMOVE, _GetPath(entry),
180			_GetPath(backupEntry), modifiedOperation));
181	return (int32)fOperations.size() - 1;
182}
183
184
185int32
186FSTransaction::MoveEntry(const Entry& fromEntry, const Entry& toEntry,
187	int32 modifiedOperation)
188{
189	fOperations.push_back(
190		OperationInfo(OperationInfo::TYPE_MOVE, _GetPath(fromEntry),
191			_GetPath(toEntry), modifiedOperation));
192	return (int32)fOperations.size() - 1;
193}
194
195
196void
197FSTransaction::RemoveOperationAt(int32 index)
198{
199	int32 count = fOperations.size();
200	if (index < 0 || index >= count) {
201		ERROR("FSTransaction::RemoveOperationAt(): invalid "
202			"operation index %" B_PRId32 "/%" B_PRId32, index, count);
203		throw Exception(BPackageKit::B_TRANSACTION_INTERNAL_ERROR);
204	}
205
206	fOperations.erase(fOperations.begin() + index);
207
208	for (int32 i = index; i < count; i++) {
209		int32 modifiedOperation = fOperations[i].ModifiedOperation();
210		if (modifiedOperation == index)
211			fOperations[i].SetModifiedOperation(-1);
212		else if (modifiedOperation > index)
213			fOperations[i].SetModifiedOperation(modifiedOperation - 1);
214	}
215}
216
217
218/*static*/ std::string
219FSTransaction::_GetPath(const Entry& entry)
220{
221	BPath pathBuffer;
222	const char* path;
223	status_t error = entry.GetPath(pathBuffer, path);
224	if (error == B_OK && path[0] != '/') {
225		// make absolute
226		error = pathBuffer.SetTo(path);
227	}
228
229	if (error != B_OK) {
230		if (error == B_NO_MEMORY)
231			throw Exception(BPackageKit::B_TRANSACTION_NO_MEMORY);
232
233		throw Exception(BPackageKit::B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH)
234			.SetPath1(entry.PathOrName())
235			.SetSystemError(error);
236	}
237
238	return path;
239}
240