Skip to content

Commit 634a22c

Browse files
committed
feat: detect file ownership conflicts in trans_prepare before
installation
1 parent 62925aa commit 634a22c

6 files changed

Lines changed: 161 additions & 1 deletion

File tree

include/apg/package.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ bool install_package(struct package *pkg);
121121
*/
122122
bool install_package_in_root(struct package *pkg, const char *root_path);
123123

124+
/**
125+
* @brief Extract a package archive and collect its file list without
126+
* installing any files.
127+
*
128+
* Populates @p pkg->package_files so callers can inspect which paths the
129+
* package would install before committing to a transaction. Safe to call
130+
* multiple times — existing file list is replaced.
131+
*
132+
* @param pkg Package whose archive to inspect.
133+
* @param root_path Filesystem root used to locate the temporary work area.
134+
* @return true on success, false if the archive could not be extracted.
135+
*/
136+
bool package_collect_files(struct package *pkg, const char *root_path);
137+
124138
/**
125139
* @brief Parse a package from disk.
126140
*

include/apg/transaction.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ typedef enum
5252
TRANS_ERR_INSTALL_FAILED, /**< One or more packages failed to install. */
5353
TRANS_ERR_UNSIGNED, /**< Package rejected by signature policy. */
5454
TRANS_ERR_HAS_DEPENDENTS, /**< Removal blocked by installed dependents. */
55+
TRANS_ERR_FILE_CONFLICT, /**< File already owned by another package. */
5556
} trans_error_t;
5657

5758
/**
@@ -78,6 +79,18 @@ struct trans_conflict
7879
char *conflicts_with; /**< Existing package that conflicts with it. */
7980
};
8081

82+
/**
83+
* @brief A file conflict between a package being installed and an existing one.
84+
*
85+
* The array is owned by the transaction and valid until trans_free().
86+
*/
87+
struct trans_file_conflict
88+
{
89+
char *path; /**< Conflicting file path. */
90+
char *requested_by; /**< Package being installed that claims the file. */
91+
char *owned_by; /**< Currently installed package that owns the file. */
92+
};
93+
8194
/**
8295
* @brief A removal blocked by installed dependents.
8396
*
@@ -212,6 +225,20 @@ const struct trans_conflict *trans_get_conflicts(const struct apg_trans *trans,
212225
const struct trans_blocked_remove *
213226
trans_get_blocked_removes(const struct apg_trans *trans, size_t *count);
214227

228+
/**
229+
* @brief Retrieve file conflicts detected by trans_prepare().
230+
*
231+
* Valid after a trans_prepare() call that returned
232+
* @ref TRANS_ERR_FILE_CONFLICT. The returned array is owned by the transaction
233+
* and valid until trans_free().
234+
*
235+
* @param trans Transaction after trans_prepare().
236+
* @param count Output parameter set to the number of conflicts.
237+
* @return Pointer to the first entry, or NULL if none.
238+
*/
239+
const struct trans_file_conflict *
240+
trans_get_file_conflicts(const struct apg_trans *trans, size_t *count);
241+
215242
/**
216243
* @brief Execute the prepared plan.
217244
*

src/package.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,38 @@ install_package_in_root(struct package *pkg, const char *root_path)
156156
return true;
157157
}
158158

159+
bool
160+
package_collect_files(struct package *pkg, const char *root_path)
161+
{
162+
if (!pkg || !pkg->pkg_path)
163+
return false;
164+
165+
char *real_tmp = concat_dirs(root_path, tmp_path);
166+
if (!real_tmp)
167+
return false;
168+
create_dir(real_tmp);
169+
170+
if (!unarchive_package_in_root(pkg, real_tmp))
171+
{
172+
free(real_tmp);
173+
return false;
174+
}
175+
176+
char *data_src = concat_dirs(real_tmp, "data");
177+
free(real_tmp);
178+
if (!data_src)
179+
return false;
180+
181+
int file_count = 0;
182+
char **files = collect_files(data_src, &file_count);
183+
free(data_src);
184+
185+
str_list_free(&pkg->package_files);
186+
pkg->package_files.items = files;
187+
pkg->package_files.count = file_count;
188+
return true;
189+
}
190+
159191
struct package *
160192
parse_package(const char *path, const char *root_path)
161193
{

src/transaction/prepare.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "trans_priv.h"
88
#include "../../include/apg/graph.h"
99
#include "../../include/apg/db.h"
10+
#include "../../include/apg/package.h"
1011

1112
static bool
1213
in_strarray(const char **arr, size_t count, const char *name)
@@ -311,7 +312,72 @@ trans_prepare(struct apg_trans *trans)
311312
}
312313
}
313314

314-
if (trans->blocked_remove_count > 0)
315+
for (size_t i = 0; i < trans->install_count; i++)
316+
{
317+
struct package *pkg = trans->install_pkgs[i];
318+
if (!pkg->meta || !pkg->meta->name)
319+
continue;
320+
321+
if (pkg->package_files.count == 0)
322+
package_collect_files(pkg, "/");
323+
324+
for (int j = 0; j < pkg->package_files.count; j++)
325+
{
326+
const char *path = pkg->package_files.items[j];
327+
if (!path)
328+
continue;
329+
char *owner = db_owner(trans->db, path);
330+
if (!owner)
331+
continue;
332+
if (strcmp(owner, pkg->meta->name) == 0)
333+
{
334+
free(owner);
335+
continue;
336+
}
337+
338+
if (!trans->file_conflicts)
339+
{
340+
trans->file_conflicts =
341+
malloc(4 * sizeof(*trans->file_conflicts));
342+
if (!trans->file_conflicts)
343+
{
344+
free(owner);
345+
ret = TRANS_ERR_NOMEM;
346+
goto cleanup;
347+
}
348+
trans->file_conflict_cap = 4;
349+
}
350+
else if (trans->file_conflict_count == trans->file_conflict_cap)
351+
{
352+
size_t new_cap = trans->file_conflict_cap * 2;
353+
struct trans_file_conflict *tmp =
354+
realloc(trans->file_conflicts, new_cap * sizeof(*tmp));
355+
if (!tmp)
356+
{
357+
free(owner);
358+
ret = TRANS_ERR_NOMEM;
359+
goto cleanup;
360+
}
361+
trans->file_conflicts = tmp;
362+
trans->file_conflict_cap = new_cap;
363+
}
364+
365+
struct trans_file_conflict *fc =
366+
&trans->file_conflicts[trans->file_conflict_count++];
367+
fc->path = strdup(path);
368+
fc->requested_by = strdup(pkg->meta->name);
369+
fc->owned_by = owner;
370+
if (!fc->path || !fc->requested_by)
371+
{
372+
ret = TRANS_ERR_NOMEM;
373+
goto cleanup;
374+
}
375+
}
376+
}
377+
378+
if (trans->file_conflict_count > 0)
379+
ret = TRANS_ERR_FILE_CONFLICT;
380+
else if (trans->blocked_remove_count > 0)
315381
ret = TRANS_ERR_HAS_DEPENDENTS;
316382
else if (trans->conflict_count > 0)
317383
ret = TRANS_ERR_CONFLICT;

src/transaction/trans_priv.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ struct apg_trans
3737
size_t blocked_remove_count;
3838
size_t blocked_remove_cap;
3939

40+
struct trans_file_conflict *file_conflicts;
41+
size_t file_conflict_count;
42+
size_t file_conflict_cap;
43+
4044
bool prepared;
4145
bool committed;
4246

src/transaction/transaction.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ trans_free(struct apg_trans *trans)
102102
}
103103
free(trans->blocked_removes);
104104

105+
for (size_t i = 0; i < trans->file_conflict_count; i++)
106+
{
107+
free(trans->file_conflicts[i].path);
108+
free(trans->file_conflicts[i].requested_by);
109+
free(trans->file_conflicts[i].owned_by);
110+
}
111+
free(trans->file_conflicts);
112+
105113
free(trans);
106114
}
107115

@@ -179,6 +187,15 @@ trans_get_plan(const struct apg_trans *trans, size_t *count)
179187
return trans->plan;
180188
}
181189

190+
const struct trans_file_conflict *
191+
trans_get_file_conflicts(const struct apg_trans *trans, size_t *count)
192+
{
193+
if (!trans || !count)
194+
return NULL;
195+
*count = trans->file_conflict_count;
196+
return trans->file_conflicts;
197+
}
198+
182199
const struct trans_blocked_remove *
183200
trans_get_blocked_removes(const struct apg_trans *trans, size_t *count)
184201
{

0 commit comments

Comments
 (0)