master
c 147 lines 3.67 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "file_lock.h"
4
5 #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_MACOS)
6 #include <sys/file.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9 #endif
10
11 #if defined(OS_WINDOWS)
12 #include <windows.h>
13 #include <stdlib.h>
14
15 #if defined(__CYGWIN__) || defined(__MSYS__)
16 #include <sys/types.h>
17 #include <sys/cygwin.h>
18 #endif
19
20 static wchar_t *file_lock_utf8_to_utf16(const char *filename) {
21 int wpath_size = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
22 if(wpath_size <= 0)
23 return NULL;
24
25 wchar_t *wpath = malloc((size_t)wpath_size * sizeof(*wpath));
26 if(!wpath)
27 return NULL;
28
29 if(MultiByteToWideChar(CP_UTF8, 0, filename, -1, wpath, wpath_size) <= 0) {
30 free(wpath);
31 return NULL;
32 }
33
34 return wpath;
35 }
36
37 static wchar_t *file_lock_windows_path(const char *filename) {
38 #if defined(__CYGWIN__) || defined(__MSYS__)
39 ssize_t wpath_size = cygwin_conv_path(CCP_POSIX_TO_WIN_W, filename, NULL, 0);
40 if(wpath_size > 0) {
41 wchar_t *wpath = malloc((size_t)wpath_size);
42 if(!wpath)
43 return NULL;
44
45 if(cygwin_conv_path(CCP_POSIX_TO_WIN_W, filename, wpath, wpath_size) == 0)
46 return wpath;
47
48 free(wpath);
49 }
50
51 // Absolute POSIX-style paths require runtime translation before Win32 APIs can use them.
52 if(filename[0] == '/')
53 return NULL;
54 #endif
55
56 return file_lock_utf8_to_utf16(filename);
57 }
58 #endif
59
60 FILE_LOCK file_lock_get(const char *filename) {
61 if(!filename || !*filename)
62 return FILE_LOCK_INVALID;
63
64 #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_MACOS)
65 // Try to create a new file, or open existing one
66 int fd = open(filename, O_RDWR | O_CREAT, 0600);
67 if(fd == -1)
68 return FILE_LOCK_INVALID;
69
70 // LOCK_NB makes flock() non-blocking
71 if(flock(fd, LOCK_EX | LOCK_NB) == -1) {
72 close(fd);
73 return FILE_LOCK_INVALID;
74 }
75
76 return (FILE_LOCK){ .fd = fd };
77
78 #elif defined(OS_WINDOWS)
79 wchar_t *wpath = file_lock_windows_path(filename);
80 if(!wpath)
81 return FILE_LOCK_INVALID;
82
83 // Open existing file or create new one
84 HANDLE hFile = CreateFileW(
85 wpath,
86 GENERIC_READ | GENERIC_WRITE,
87 FILE_SHARE_READ | FILE_SHARE_WRITE,
88 NULL,
89 OPEN_ALWAYS, // Open if exists, create if doesn't
90 FILE_ATTRIBUTE_NORMAL,
91 NULL
92 );
93
94 free(wpath);
95
96 if(hFile == INVALID_HANDLE_VALUE)
97 return FILE_LOCK_INVALID;
98
99 // Check if file is empty
100 LARGE_INTEGER size;
101 if(!GetFileSizeEx(hFile, &size)) {
102 CloseHandle(hFile);
103 return FILE_LOCK_INVALID;
104 }
105
106 // Write a byte only if file is empty
107 if(size.QuadPart == 0) {
108 DWORD written;
109 if(!WriteFile(hFile, "!", 1, &written, NULL) || written != 1) {
110 CloseHandle(hFile);
111 return FILE_LOCK_INVALID;
112 }
113 }
114
115 // Try to lock the entire file
116 OVERLAPPED overlapped = {0};
117 if(!LockFileEx(
118 hFile,
119 LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
120 0,
121 MAXDWORD,
122 MAXDWORD,
123 &overlapped)) {
124 CloseHandle(hFile);
125 return FILE_LOCK_INVALID;
126 }
127
128 return (FILE_LOCK){ .handle = hFile };
129
130 #else
131 #error "Unsupported operating system"
132 #endif
133 }
134
135 void file_lock_release(FILE_LOCK lock) {
136 #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_MACOS)
137 if(FILE_LOCK_OK(lock)) {
138 // flock is automatically released when file is closed
139 close(lock.fd);
140 }
141 #elif defined(OS_WINDOWS)
142 if(FILE_LOCK_OK(lock)) {
143 // File lock is automatically released when handle is closed
144 CloseHandle(lock.handle);
145 }
146 #endif
147 }