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 if (sb
.st_size
== 0) {
83 err
= got_error_path(path
, GOT_ERR_WORKTREE_META
);
86 *content
= calloc(1, sb
.st_size
);
87 if (*content
== NULL
) {
88 err
= got_error_from_errno("calloc");
92 n
= read(fd
, *content
, sb
.st_size
);
93 if (n
!= sb
.st_size
) {
94 err
= (n
== -1 ? got_error_from_errno2("read", path
) :
95 got_error_path(path
, GOT_ERR_WORKTREE_META
));
98 if ((*content
)[sb
.st_size
- 1] != '\n') {
99 err
= got_error_path(path
, GOT_ERR_WORKTREE_META
);
102 (*content
)[sb
.st_size
- 1] = '\0';
105 if (fd
!= -1 && close(fd
) == -1 && err
== NULL
)
106 err
= got_error_from_errno2("close", path_got
);
115 static const struct got_error
*
116 open_worktree(struct got_worktree
**worktree
, const char *path
,
117 const char *meta_dir
)
119 const struct got_error
*err
= NULL
;
121 char *formatstr
= NULL
;
122 char *uuidstr
= NULL
;
123 char *path_lock
= NULL
;
124 char *base_commit_id_str
= NULL
;
125 int version
, fd
= -1;
127 struct got_repository
*repo
= NULL
;
128 int *pack_fds
= NULL
;
129 uint32_t uuid_status
;
133 if (asprintf(&path_meta
, "%s/%s", path
, meta_dir
) == -1) {
134 err
= got_error_from_errno("asprintf");
139 if (asprintf(&path_lock
, "%s/%s", path_meta
, GOT_WORKTREE_LOCK
) == -1) {
140 err
= got_error_from_errno("asprintf");
145 fd
= open(path_lock
, O_RDWR
| O_EXLOCK
| O_NONBLOCK
| O_CLOEXEC
);
147 err
= (errno
== EWOULDBLOCK
? got_error(GOT_ERR_WORKTREE_BUSY
)
148 : got_error_from_errno2("open", path_lock
));
152 err
= read_meta_file(&formatstr
, path_meta
, GOT_WORKTREE_FORMAT
);
156 version
= strtonum(formatstr
, 1, INT_MAX
, &errstr
);
158 err
= got_error_msg(GOT_ERR_WORKTREE_META
,
159 "could not parse work tree format version number");
162 if (version
!= GOT_WORKTREE_FORMAT_VERSION
) {
163 err
= got_error(GOT_ERR_WORKTREE_VERS
);
167 *worktree
= calloc(1, sizeof(**worktree
));
168 if (*worktree
== NULL
) {
169 err
= got_error_from_errno("calloc");
172 (*worktree
)->lockfd
= -1;
173 (*worktree
)->format_version
= version
;
175 (*worktree
)->root_path
= realpath(path
, NULL
);
176 if ((*worktree
)->root_path
== NULL
) {
177 err
= got_error_from_errno2("realpath", path
);
180 (*worktree
)->meta_dir
= meta_dir
;
181 err
= read_meta_file(&(*worktree
)->repo_path
, path_meta
,
182 GOT_WORKTREE_REPOSITORY
);
186 err
= read_meta_file(&(*worktree
)->path_prefix
, path_meta
,
187 GOT_WORKTREE_PATH_PREFIX
);
191 err
= read_meta_file(&base_commit_id_str
, path_meta
,
192 GOT_WORKTREE_BASE_COMMIT
);
196 err
= read_meta_file(&uuidstr
, path_meta
, GOT_WORKTREE_UUID
);
199 uuid_from_string(uuidstr
, &(*worktree
)->uuid
, &uuid_status
);
200 if (uuid_status
!= uuid_s_ok
) {
201 err
= got_error_uuid(uuid_status
, "uuid_from_string");
205 err
= got_repo_pack_fds_open(&pack_fds
);
209 err
= got_repo_open(&repo
, (*worktree
)->repo_path
, NULL
, pack_fds
);
213 err
= got_object_resolve_id_str(&(*worktree
)->base_commit_id
, repo
,
218 err
= read_meta_file(&(*worktree
)->head_ref_name
, path_meta
,
219 GOT_WORKTREE_HEAD_REF
);
223 if (asprintf(&(*worktree
)->gotconfig_path
, "%s/%s/%s",
224 (*worktree
)->root_path
, (*worktree
)->meta_dir
,
225 GOT_GOTCONFIG_FILENAME
) == -1) {
226 err
= got_error_from_errno("asprintf");
230 err
= got_gotconfig_read(&(*worktree
)->gotconfig
,
231 (*worktree
)->gotconfig_path
);
235 (*worktree
)->root_fd
= open((*worktree
)->root_path
,
236 O_DIRECTORY
| O_CLOEXEC
);
237 if ((*worktree
)->root_fd
== -1) {
238 err
= got_error_from_errno2("open", (*worktree
)->root_path
);
243 const struct got_error
*close_err
= got_repo_close(repo
);
248 const struct got_error
*pack_err
=
249 got_repo_pack_fds_close(pack_fds
);
255 free(base_commit_id_str
);
261 if (*worktree
!= NULL
)
262 got_worktree_close(*worktree
);
265 (*worktree
)->lockfd
= fd
;
270 const struct got_error
*
271 got_worktree_open(struct got_worktree
**worktree
, const char *path
,
272 const char *meta_dir
)
274 const struct got_error
*err
= NULL
;
276 const char *meta_dirs
[] = {
277 GOT_WORKTREE_GOT_DIR
,
282 worktree_path
= strdup(path
);
283 if (worktree_path
== NULL
)
284 return got_error_from_errno("strdup");
289 if (meta_dir
== NULL
) {
290 for (i
= 0; i
< nitems(meta_dirs
); i
++) {
291 err
= open_worktree(worktree
, worktree_path
,
294 err
->code
== GOT_ERR_WORKTREE_BUSY
)
298 err
= open_worktree(worktree
, worktree_path
, meta_dir
);
299 if (err
&& !(err
->code
== GOT_ERR_ERRNO
&& errno
== ENOENT
)) {
307 if (worktree_path
[0] == '/' && worktree_path
[1] == '\0')
309 err
= got_path_dirname(&parent_path
, worktree_path
);
311 if (err
->code
!= GOT_ERR_BAD_PATH
) {
318 worktree_path
= parent_path
;
322 return got_error(GOT_ERR_NOT_WORKTREE
);
325 const struct got_error
*
326 got_worktree_close(struct got_worktree
*worktree
)
328 const struct got_error
*err
= NULL
;
330 if (worktree
->lockfd
!= -1) {
331 if (close(worktree
->lockfd
) == -1)
332 err
= got_error_from_errno2("close",
333 got_worktree_get_root_path(worktree
));
335 if (close(worktree
->root_fd
) == -1 && err
== NULL
)
336 err
= got_error_from_errno2("close",
337 got_worktree_get_root_path(worktree
));
338 free(worktree
->repo_path
);
339 free(worktree
->path_prefix
);
340 free(worktree
->base_commit_id
);
341 free(worktree
->head_ref_name
);
342 free(worktree
->root_path
);
343 free(worktree
->gotconfig_path
);
344 got_gotconfig_free(worktree
->gotconfig
);
350 got_worktree_get_root_path(struct got_worktree
*worktree
)
352 return worktree
->root_path
;
356 got_worktree_get_repo_path(struct got_worktree
*worktree
)
358 return worktree
->repo_path
;
362 got_worktree_get_path_prefix(struct got_worktree
*worktree
)
364 return worktree
->path_prefix
;
368 got_worktree_get_format_version(struct got_worktree
*worktree
)
370 return worktree
->format_version
;