-- Copyright (c) 2024 Florian Fischer. All rights reserved. -- -- vis-lockfiles is free software: you can redistribute it and/or modify it under -- the terms of the GNU General Public License as published by the Free Software -- Foundation, either version 3 of the License, or (at your option) any later -- version. -- -- vis-lockfiles is distributed in the hope that it will be useful, but WITHOUT -- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License along with -- vis-lockfiles found in the LICENSE file. -- If not, see . -- Helper to execute a command and capture its output local function capture_cmd(cmd) local p = assert(io.popen(cmd, 'r')) local s = assert(p:read('*a')) local success, _, status = p:close() if not success then local err = cmd .. ' failed with exit code: ' .. status vis:info('vis-lockfiles: ' .. err) end return s end local function lock_file_path(lock_dir, path) -- escape '%' first local filename = path:gsub('%%', '%%25'):gsub('/', '%%2F') return lock_dir .. '/' .. filename .. '.lock' end -- determine vis' pid local vis_pid do local vis_proc_file = io.open('/proc/self/stat', 'r') if vis_proc_file then vis_pid = vis_proc_file:read('*n') vis_proc_file:close() else -- fallback if /proc/self/stat is not available local out = capture_cmd('sh -c "echo $PPID"') vis_pid = tonumber(out) end end assert(vis_pid) local function pid_lives(pid) local tst = io.open('/proc/' .. pid .. '/stat') if tst then tst:close() return true end return false end -- returns PID owning lock (possibly vis_pid) local function create_lock_file(lock_dir, lfile_path) local lock_pid local lock_file = io.open(lfile_path) if lock_file then lock_pid = lock_file:read('*n') lock_file:close() if lock_pid == vis_pid or pid_lives(lock_pid) then return lock_pid end end -- create the lock file with our pid lock_file = io.open(lfile_path, 'w') if not lock_file then -- retry, this time asserting success; first ensure directory exists assert(os.execute('mkdir -p "' .. lock_dir .. '"')) lock_file = assert(io.open(lfile_path, 'w')) end lock_file:write(vis_pid) lock_file:close() -- detect a possible race lock_file = assert(io.open(lfile_path)) lock_pid = tonumber(lock_file:read('*n')) lock_file:close() return lock_pid end local function delete_lock_file(lfile_path) local success, err = os.remove(lfile_path) if not success then vis:info('vis-lockfiles: ' .. err) end end return { vis_pid = vis_pid, capture_cmd = capture_cmd, lock_file_path = lock_file_path, delete_lock_file = delete_lock_file, create_lock_file = create_lock_file, }