1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/common/cross_process_lock.h>
#include <aws/common/byte_buf.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
#include <aws/common/error.h>
#include <aws/common/file.h>
#include <aws/common/logging.h>
struct aws_cross_process_lock {
struct aws_allocator *allocator;
int locked_fd;
};
struct aws_cross_process_lock *aws_cross_process_lock_try_acquire(
struct aws_allocator *allocator,
struct aws_byte_cursor instance_nonce) {
/* validate we don't have a directory slash. */
struct aws_byte_cursor to_find = aws_byte_cursor_from_c_str("/");
struct aws_byte_cursor found;
AWS_ZERO_STRUCT(found);
if (aws_byte_cursor_find_exact(&instance_nonce, &to_find, &found) != AWS_OP_ERR &&
aws_last_error() != AWS_ERROR_STRING_MATCH_NOT_FOUND) {
AWS_LOGF_ERROR(
AWS_LS_COMMON_GENERAL,
"static: Lock " PRInSTR "creation has illegal character /",
AWS_BYTE_CURSOR_PRI(instance_nonce));
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
return NULL;
}
/*
* The unix standard says /tmp has to be there and be writable. However, while it may be tempting to just use the
* /tmp/ directory, it often has the sticky bit set which would prevent a subprocess from being able to call open
* with create on the file. The solution is simple, just write it to a subdirectory inside
* /tmp and override umask via. chmod of 0777.
*/
struct aws_byte_cursor path_prefix = aws_byte_cursor_from_c_str("/tmp/aws_crt_cross_process_lock/");
struct aws_string *path_to_create = aws_string_new_from_cursor(allocator, &path_prefix);
/* It's probably there already and we don't care if it is. */
if (!aws_directory_exists(path_to_create)) {
/* if this call fails just let it fail on open below. */
aws_directory_create(path_to_create);
/* bypass umask by setting the perms we actually requested */
chmod(aws_string_c_str(path_to_create), S_IRWXU | S_IRWXG | S_IRWXO);
}
aws_string_destroy(path_to_create);
struct aws_byte_cursor path_suffix = aws_byte_cursor_from_c_str(".lock");
struct aws_byte_buf nonce_buf;
aws_byte_buf_init_copy_from_cursor(&nonce_buf, allocator, path_prefix);
aws_byte_buf_append_dynamic(&nonce_buf, &instance_nonce);
aws_byte_buf_append_dynamic(&nonce_buf, &path_suffix);
aws_byte_buf_append_null_terminator(&nonce_buf);
struct aws_cross_process_lock *instance_lock = NULL;
errno = 0;
int fd = open((const char *)nonce_buf.buffer, O_CREAT | O_RDWR, 0666);
if (fd < 0) {
AWS_LOGF_DEBUG(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s failed to open with errno %d",
(const char *)nonce_buf.buffer,
errno);
aws_translate_and_raise_io_error_or(errno, AWS_ERROR_MUTEX_FAILED);
if (aws_last_error() == AWS_ERROR_NO_PERMISSION) {
AWS_LOGF_DEBUG(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s couldn't be opened due to file ownership permissions. Attempting to open as read "
"only",
(const char *)nonce_buf.buffer);
errno = 0;
fd = open((const char *)nonce_buf.buffer, O_RDONLY);
if (fd < 0) {
AWS_LOGF_ERROR(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s failed to open with read-only permissions with errno %d",
(const char *)nonce_buf.buffer,
errno);
aws_translate_and_raise_io_error_or(errno, AWS_ERROR_MUTEX_FAILED);
goto cleanup;
}
} else {
AWS_LOGF_ERROR(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s failed to open. The lock cannot be acquired.",
(const char *)nonce_buf.buffer);
goto cleanup;
}
}
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
AWS_LOGF_TRACE(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s already acquired by another instance",
(const char *)nonce_buf.buffer);
close(fd);
aws_raise_error(AWS_ERROR_MUTEX_CALLER_NOT_OWNER);
goto cleanup;
}
instance_lock = aws_mem_calloc(allocator, 1, sizeof(struct aws_cross_process_lock));
instance_lock->locked_fd = fd;
instance_lock->allocator = allocator;
AWS_LOGF_TRACE(
AWS_LS_COMMON_GENERAL,
"static: Lock file %s acquired by this instance with fd %d",
(const char *)nonce_buf.buffer,
fd);
cleanup:
aws_byte_buf_clean_up(&nonce_buf);
return instance_lock;
}
void aws_cross_process_lock_release(struct aws_cross_process_lock *instance_lock) {
if (instance_lock) {
flock(instance_lock->locked_fd, LOCK_UN);
close(instance_lock->locked_fd);
AWS_LOGF_TRACE(AWS_LS_COMMON_GENERAL, "static: Lock file released for fd %d", instance_lock->locked_fd);
aws_mem_release(instance_lock->allocator, instance_lock);
}
}
|