Skip to content

Commit f186933

Browse files
committed
lib: add file locking to downloads
Usually the files in the cache directory are protected by the pkgdb write lock. But if xbps-install uses separate root directories but shared cache directories there needs to be extra locking to avoid file corruption.
1 parent 38597cd commit f186933

File tree

1 file changed

+85
-26
lines changed

1 file changed

+85
-26
lines changed

lib/download.c

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* From FreeBSD fetch(8):
3030
* $FreeBSD: src/usr.bin/fetch/fetch.c,v 1.84.2.1 2009/08/03 08:13:06 kensmith Exp $
3131
*/
32+
#include <sys/file.h>
3233
#include <sys/param.h>
3334
#include <sys/stat.h>
3435
#include <sys/time.h>
@@ -94,19 +95,64 @@ xbps_fetch_error_string(void)
9495
return fetchLastErrString;
9596
}
9697

98+
static int
99+
acquire_lock(char *lockfile, size_t lockfilesz, const char *filename)
100+
{
101+
int fd;
102+
int r;
103+
104+
r = snprintf(lockfile, lockfilesz, "%s.lock", filename);
105+
if (r < 0 || (size_t)r >= lockfilesz) {
106+
errno = ENAMETOOLONG;
107+
return -1;
108+
}
109+
110+
fd = open(lockfile, O_RDWR|O_CREAT|O_CLOEXEC, 0600);
111+
if (fd == -1)
112+
return -errno;
113+
114+
if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
115+
if (errno == EWOULDBLOCK)
116+
xbps_warn_printf(
117+
"%s: file locked, waiting...\n", filename);
118+
if (errno != EWOULDBLOCK || flock(fd, LOCK_EX) == -1) {
119+
r = -errno;
120+
(void) close(fd);
121+
return r;
122+
}
123+
}
124+
125+
return fd;
126+
}
127+
128+
static void
129+
release_lock(const char *lockfile, int lockfd)
130+
{
131+
(void) close(lockfd);
132+
if (remove(lockfile) == -1 && errno != ENOENT) {
133+
xbps_warn_printf("failed to remove lock file: %s: %s\n",
134+
lockfile, strerror(errno));
135+
}
136+
}
137+
97138
int
98-
xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri, const char *filename, const char *flags, unsigned char *digest, size_t digestlen)
139+
xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri,
140+
const char *filename, const char *flags, unsigned char *digest,
141+
size_t digestlen)
99142
{
100-
struct stat st, st_tmpfile, *stp;
143+
char buf[BUFSIZ];
144+
char tempfile[PATH_MAX];
145+
char lockfile[PATH_MAX];
146+
struct stat st = {0}, st_tmpfile = {0}, *stp;
101147
struct url *url = NULL;
102148
struct url_stat url_st;
103149
struct fetchIO *fio = NULL;
104150
struct timespec ts[2];
105151
off_t bytes_dload = 0;
106152
ssize_t bytes_read = 0, bytes_written = 0;
107-
char buf[4096], *tempfile = NULL;
108153
char fetch_flags[8];
109154
int fd = -1, rv = 0;
155+
int lockfd = -1;
110156
bool refetch = false, restart = false;
111157
SHA256_CTX sha256;
112158

@@ -132,34 +178,46 @@ xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri, const char
132178
if (flags != NULL)
133179
xbps_strlcpy(fetch_flags, flags, 7);
134180

135-
tempfile = xbps_xasprintf("%s.part", filename);
136-
/*
137-
* Check if we have to resume a transfer.
138-
*/
139-
memset(&st_tmpfile, 0, sizeof(st_tmpfile));
140-
if (stat(tempfile, &st_tmpfile) == 0) {
141-
if (st_tmpfile.st_size > 0)
142-
restart = true;
143-
} else {
144-
if (errno != ENOENT) {
145-
rv = -1;
146-
goto fetch_file_out;
147-
}
181+
rv = snprintf(tempfile, sizeof(tempfile), "%s.part", filename);
182+
if (rv < 0 || (size_t)rv >= sizeof(tempfile)) {
183+
errno = ENAMETOOLONG;
184+
return -1;
185+
}
186+
187+
lockfd = acquire_lock(lockfile, sizeof(lockfile), filename);
188+
if (lockfd < 0) {
189+
xbps_error_printf("failed to lock file: %s: %s\n", filename,
190+
strerror(-lockfd));
191+
return -1;
148192
}
193+
149194
/*
150195
* Check if we have to refetch a transfer.
151196
*/
152-
memset(&st, 0, sizeof(st));
153197
if (stat(filename, &st) == 0) {
154198
refetch = true;
155199
url->last_modified = st.st_mtime;
156200
xbps_strlcat(fetch_flags, "i", sizeof(fetch_flags));
157-
} else {
158-
if (errno != ENOENT) {
159-
rv = -1;
160-
goto fetch_file_out;
161-
}
201+
} else if (errno != ENOENT) {
202+
xbps_error_printf("failed to stat target file: %s: %s\n",
203+
filename, strerror(errno));
204+
rv = -1;
205+
goto fetch_file_out;
206+
}
207+
208+
/*
209+
* Check if we have to resume a transfer.
210+
*/
211+
if (stat(tempfile, &st_tmpfile) == 0) {
212+
if (st_tmpfile.st_size > 0)
213+
restart = true;
214+
} else if (errno != ENOENT) {
215+
xbps_error_printf("failed to stat temporary file: %s: %s\n",
216+
tempfile, strerror(errno));
217+
rv = -1;
218+
goto fetch_file_out;
162219
}
220+
163221
if (refetch && !restart) {
164222
/* fetch the whole file, filename available */
165223
stp = &st;
@@ -184,6 +242,7 @@ xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri, const char
184242
if (fio == NULL) {
185243
if (fetchLastErrCode == FETCH_UNCHANGED) {
186244
/* Last-Modified matched */
245+
rv = 0;
187246
goto fetch_file_out;
188247
} else if (fetchLastErrCode == FETCH_PROTO && url_st.size == stp->st_size) {
189248
/* 413, requested offset == length */
@@ -312,7 +371,8 @@ xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri, const char
312371
rename_file:
313372
/* File downloaded successfully, rename to destfile */
314373
if (rename(tempfile, filename) == -1) {
315-
xbps_dbg_printf("failed to rename %s to %s: %s",
374+
xbps_error_printf(
375+
"failed to rename temporary file: %s: to: %s: %s\n",
316376
tempfile, filename, strerror(errno));
317377
rv = -1;
318378
goto fetch_file_out;
@@ -329,9 +389,8 @@ xbps_fetch_file_dest_sha256(struct xbps_handle *xhp, const char *uri, const char
329389
(void)close(fd);
330390
if (url != NULL)
331391
fetchFreeURL(url);
332-
333-
free(tempfile);
334-
392+
if (lockfd != -1)
393+
release_lock(lockfile, lockfd);
335394
return rv;
336395
}
337396

0 commit comments

Comments
 (0)