2 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "got_compat.h"
20 #include <sys/queue.h>
31 #include "got_cancel.h"
32 #include "got_error.h"
33 #include "got_reference.h"
35 #include "got_worktree.h"
36 #include "got_repository.h"
37 #include "got_gotconfig.h"
38 #include "got_object.h"
40 #include "got_lib_worktree.h"
41 #include "got_lib_gotconfig.h"
44 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
47 static const struct got_error
*
48 read_meta_file(char **content
, const char *path_got
, const char *name
)
50 const struct got_error
*err
= NULL
;
58 if (asprintf(&path
, "%s/%s", path_got
, name
) == -1) {
59 err
= got_error_from_errno("asprintf");
64 fd
= open(path
, O_RDONLY
| O_NOFOLLOW
| O_CLOEXEC
);
67 err
= got_error_path(path
, GOT_ERR_WORKTREE_META
);
69 err
= got_error_from_errno2("open", path
);
72 if (flock(fd
, LOCK_SH
| LOCK_NB
) == -1) {
73 err
= (errno
== EWOULDBLOCK
? got_error(GOT_ERR_WORKTREE_BUSY
)
74 : got_error_from_errno2("flock", path
));
78 if (fstat(fd
, &sb
) != 0) {
79 err
= got_error_from_errno2("fstat", path
);
82 *content
= calloc(1, sb
.st_size
);
83 if (*content
== NULL
) {
84 err
= got_error_from_errno("calloc");
88 n
= read(fd
, *content
, sb
.st_size
);
89 if (n
!= sb
.st_size
) {
90 err
= (n
== -1 ? got_error_from_errno2("read", path
) :
91 got_error_path(path
, GOT_ERR_WORKTREE_META
));
94 if ((*content
)[sb
.st_size
- 1] != '\n') {
95 err
= got_error_path(path
, GOT_ERR_WORKTREE_META
);
98 (*content
)[sb
.st_size
- 1] = '\0';
101 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
102 err
= got_error_from_errno2("close", path_got
);
111 static const struct got_error
*
112 open_worktree(struct got_worktree
**worktree
, const char *path
,
113 const char *meta_dir
)
115 const struct got_error
*err
= NULL
;
117 char *formatstr
= NULL
;
118 char *uuidstr
= NULL
;
119 char *path_lock
= NULL
;
120 char *base_commit_id_str
= NULL
;
121 int version
, fd
= -1;
123 struct got_repository
*repo
= NULL
;
124 int *pack_fds
= NULL
;
125 uint32_t uuid_status
;
129 if (asprintf(&path_meta
, "%s/%s", path
, meta_dir
) == -1) {
130 err
= got_error_from_errno("asprintf");
135 if (asprintf(&path_lock
, "%s/%s", path_meta
, GOT_WORKTREE_LOCK
) == -1) {
136 err
= got_error_from_errno("asprintf");
141 fd
= open(path_lock
, O_RDWR
| O_EXLOCK
| O_NONBLOCK
| O_CLOEXEC
);
143 err
= (errno
== EWOULDBLOCK
? got_error(GOT_ERR_WORKTREE_BUSY
)
144 : got_error_from_errno2("open", path_lock
));
148 err
= read_meta_file(&formatstr
, path_meta
, GOT_WORKTREE_FORMAT
);
152 version
= strtonum(formatstr
, 1, INT_MAX
, &errstr
);
154 err
= got_error_msg(GOT_ERR_WORKTREE_META
,
155 "could not parse work tree format version number");
158 if (version
!= GOT_WORKTREE_FORMAT_VERSION
) {
159 err
= got_error(GOT_ERR_WORKTREE_VERS
);
163 *worktree
= calloc(1, sizeof(**worktree
));
164 if (*worktree
== NULL
) {
165 err
= got_error_from_errno("calloc");
168 (*worktree
)->lockfd
= -1;
170 (*worktree
)->root_path
= realpath(path
, NULL
);
171 if ((*worktree
)->root_path
== NULL
) {
172 err
= got_error_from_errno2("realpath", path
);
175 (*worktree
)->meta_dir
= meta_dir
;
176 err
= read_meta_file(&(*worktree
)->repo_path
, path_meta
,
177 GOT_WORKTREE_REPOSITORY
);
181 err
= read_meta_file(&(*worktree
)->path_prefix
, path_meta
,
182 GOT_WORKTREE_PATH_PREFIX
);
186 err
= read_meta_file(&base_commit_id_str
, path_meta
,
187 GOT_WORKTREE_BASE_COMMIT
);
191 err
= read_meta_file(&uuidstr
, path_meta
, GOT_WORKTREE_UUID
);
194 uuid_from_string(uuidstr
, &(*worktree
)->uuid
, &uuid_status
);
195 if (uuid_status
!= uuid_s_ok
) {
196 err
= got_error_uuid(uuid_status
, "uuid_from_string");
200 err
= got_repo_pack_fds_open(&pack_fds
);
204 err
= got_repo_open(&repo
, (*worktree
)->repo_path
, NULL
, pack_fds
);
208 err
= got_object_resolve_id_str(&(*worktree
)->base_commit_id
, repo
,
213 err
= read_meta_file(&(*worktree
)->head_ref_name
, path_meta
,
214 GOT_WORKTREE_HEAD_REF
);
218 if (asprintf(&(*worktree
)->gotconfig_path
, "%s/%s/%s",
219 (*worktree
)->root_path
, (*worktree
)->meta_dir
,
220 GOT_GOTCONFIG_FILENAME
) == -1) {
221 err
= got_error_from_errno("asprintf");
225 err
= got_gotconfig_read(&(*worktree
)->gotconfig
,
226 (*worktree
)->gotconfig_path
);
230 (*worktree
)->root_fd
= open((*worktree
)->root_path
,
231 O_DIRECTORY
| O_CLOEXEC
);
232 if ((*worktree
)->root_fd
== -1) {
233 err
= got_error_from_errno2("open", (*worktree
)->root_path
);
238 const struct got_error
*close_err
= got_repo_close(repo
);
243 const struct got_error
*pack_err
=
244 got_repo_pack_fds_close(pack_fds
);
250 free(base_commit_id_str
);
256 if (*worktree
!= NULL
)
257 got_worktree_close(*worktree
);
260 (*worktree
)->lockfd
= fd
;
265 const struct got_error
*
266 got_worktree_open(struct got_worktree
**worktree
, const char *path
,
267 const char *meta_dir
)
269 const struct got_error
*err
= NULL
;
271 const char *meta_dirs
[] = {
272 GOT_WORKTREE_GOT_DIR
,
277 worktree_path
= strdup(path
);
278 if (worktree_path
== NULL
)
279 return got_error_from_errno("strdup");
284 if (meta_dir
== NULL
) {
285 for (i
= 0; i
< nitems(meta_dirs
); i
++) {
286 err
= open_worktree(worktree
, worktree_path
,
289 err
->code
== GOT_ERR_WORKTREE_BUSY
)
293 err
= open_worktree(worktree
, worktree_path
, meta_dir
);
294 if (err
&& !(err
->code
== GOT_ERR_ERRNO
&& errno
== ENOENT
)) {
302 if (worktree_path
[0] == '/' && worktree_path
[1] == '\0')
304 err
= got_path_dirname(&parent_path
, worktree_path
);
306 if (err
->code
!= GOT_ERR_BAD_PATH
) {
313 worktree_path
= parent_path
;
317 return got_error(GOT_ERR_NOT_WORKTREE
);
320 const struct got_error
*
321 got_worktree_close(struct got_worktree
*worktree
)
323 const struct got_error
*err
= NULL
;
325 if (worktree
->lockfd
!= -1) {
326 if (close(worktree
->lockfd
) == -1)
327 err
= got_error_from_errno2("close",
328 got_worktree_get_root_path(worktree
));
330 if (close(worktree
->root_fd
) == -1 && err
== NULL
)
331 err
= got_error_from_errno2("close",
332 got_worktree_get_root_path(worktree
));
333 free(worktree
->repo_path
);
334 free(worktree
->path_prefix
);
335 free(worktree
->base_commit_id
);
336 free(worktree
->head_ref_name
);
337 free(worktree
->root_path
);
338 free(worktree
->gotconfig_path
);
339 got_gotconfig_free(worktree
->gotconfig
);
345 got_worktree_get_root_path(struct got_worktree
*worktree
)
347 return worktree
->root_path
;
351 got_worktree_get_repo_path(struct got_worktree
*worktree
)
353 return worktree
->repo_path
;
357 got_worktree_get_path_prefix(struct got_worktree
*worktree
)
359 return worktree
->path_prefix
;