From 3b381ba118a0da3154a4255e8f56693d0bad472e Mon Sep 17 00:00:00 2001 From: ge Date: Sun, 25 Jan 2026 13:31:10 +0300 Subject: [PATCH] init --- .editorconfig | 8 +++ .gitattributes | 8 +++ .github/workflows/docs.yaml | 48 +++++++++++++ .github/workflows/test.yaml | 24 +++++++ .gitignore | 24 +++++++ README.md | 8 +++ UNLICENSE | 22 ++++++ pwd.c.v | 137 ++++++++++++++++++++++++++++++++++++ pwd_test.v | 17 +++++ v.mod | 7 ++ 10 files changed, 303 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/docs.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 UNLICENSE create mode 100644 pwd.c.v create mode 100644 pwd_test.v create mode 100644 v.mod diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01072ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9a98968 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=lf +*.bat eol=crlf + +*.v linguist-language=V +*.vv linguist-language=V +*.vsh linguist-language=V +v.mod linguist-language=V +.vdocignore linguist-language=ignore diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..8a753c0 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,48 @@ +name: Docs +on: + push: + branches: [ "master" ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup V + run: | + wget -qO /tmp/v.zip https://github.com/vlang/v/releases/latest/download/v_linux.zip + unzip -q /tmp/v.zip -d /tmp + echo /tmp/v >> "$GITHUB_PATH" + + - name: Build docs + run: | + v doc -f html -m . + pushd _docs + ln -vs ${{ github.event.repository.name }}.html index.html + ls -alFh + popd + + - name: Upload static files as artifact + id: deployment + uses: actions/upload-pages-artifact@v3 + with: + path: _docs/ + + deploy: + needs: build + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + +permissions: + contents: read + pages: write + id-token: write diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..f9d8c51 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,24 @@ +name: Tests +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup V + run: | + wget -qO /tmp/v.zip https://github.com/vlang/v/releases/latest/download/v_linux.zip + unzip -q /tmp/v.zip -d /tmp + echo /tmp/v >> "$GITHUB_PATH" + + - name: Run tests + run: | + v -stats test . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..266ea5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Binaries for programs and plugins +main +pwd +*.exe +*.exe~ +*.so +*.dylib +*.dll + +# Ignore binary output folders +bin/ + +# Ignore common editor/system specific metadata +.DS_Store +.idea/ +.vscode/ +*.iml + +# ENV +.env + +# vweb and database +*.db +*.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef0579d --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Access to the UNIX Password Database + +`pwd` module provides thread-safe access to the UNIX user account and password +database. + +See [passwd(5)](https://man7.org/linux/man-pages/man5/passwd.5.html), +[getpwent(3)](https://man7.org/linux/man-pages/man3/getpwent.3.html) and +[getpwnam(3)](https://man7.org/linux/man-pages/man3/getpwnam.3.html) for info. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..c91541e --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,22 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and +successors. We intend this dedication to be an overt act of relinquishment in +perpetuity of all present and future rights to this software under copyright +law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/pwd.c.v b/pwd.c.v new file mode 100644 index 0000000..d21b8b7 --- /dev/null +++ b/pwd.c.v @@ -0,0 +1,137 @@ +@[has_globals] +module pwd + +import sync + +$if windows { + $compile_error('pwd: MS Windows is not supported') +} + +#include +#include + +__global pwd_mutex &sync.Mutex + +fn init() { + pwd_mutex = sync.new_mutex() +} + +fn cleanup() { + pwd_mutex.destroy() + unsafe { free(pwd_mutex) } +} + +struct C.passwd { + pw_name &char + pw_passwd &char + pw_uid i32 + pw_gid i32 + pw_gecos &char + pw_dir &char + pw_shell &char +} + +fn C.getpwuid(i32) &C.passwd +fn C.getpwnam(&char) &C.passwd +fn C.setpwent() +fn C.endpwent() +fn C.getpwent() &C.passwd + +pub struct Passwd { +pub: + name string // username + passwd string // user password, usually set to 'x' + uid int // used ID + gid int // group ID + gecos string // user information + dir string // home directory + shell string // shell program +} + +fn make_passwd(pw &C.passwd) Passwd { + return unsafe { + Passwd{ + name: cstring_to_vstring(pw.pw_name) + passwd: cstring_to_vstring(pw.pw_passwd) + uid: int(pw.pw_uid) + gid: int(pw.pw_gid) + gecos: cstring_to_vstring(pw.pw_gecos) + dir: cstring_to_vstring(pw.pw_dir) + shell: cstring_to_vstring(pw.pw_shell) + } + } +} + +fn make_error(name string, uid int) IError { + err := C.errno + if err !in [4, 5, 12, 23, 24, 34] { + // Error is not EINTR, EIO, ENOMEM, ENFILE, EMFILE or ERANGE + return EntryNotFoundError{ + name: name + uid: uid + } + } + return error_with_code(c_error_number_str(err), err) +} + +// get_by_uid returns the passwd database entry by user ID. +// If the entry is not found, the EntryNotFoundError error will be returned. +pub fn get_by_uid(uid int) !Passwd { + pwd_mutex.lock() + defer { + pwd_mutex.unlock() + } + pw := C.getpwuid(uid) + if isnil(pw) { + return make_error('', uid) + } + return make_passwd(pw) +} + +// get_by_uid returns the passwd database entry by user name. +// If the entry is not found, the EntryNotFoundError error will be returned. +pub fn get_by_name(name string) !Passwd { + pwd_mutex.lock() + defer { + pwd_mutex.unlock() + } + pw := C.getpwnam(&char(name.str)) + if isnil(pw) { + return make_error(name, -1) + } + return make_passwd(pw) +} + +// get_all returns all entries from passwd database in arbitrary order. +pub fn get_all() []Passwd { + mut pwds := []Passwd{} + pwd_mutex.lock() + C.setpwent() + defer { + C.endpwent() + pwd_mutex.unlock() + } + for { + pw := C.getpwent() + if isnil(pw) { + break + } + pwds << make_passwd(pw) + } + return pwds +} + +// EntryNotFoundError designates that an entry with the specified UID or name was not found. +pub struct EntryNotFoundError { + Error + name string + uid int +} + +// msg returns the string representation of EntryNotFoundError error. +pub fn (e EntryNotFoundError) msg() string { + if e.name != '' { + return "no passwd entry found by username '${e.name}'" + } + return 'no passwd entry found by user ID ${e.uid}' +} diff --git a/pwd_test.v b/pwd_test.v new file mode 100644 index 0000000..376e0ef --- /dev/null +++ b/pwd_test.v @@ -0,0 +1,17 @@ +// vtest build: !windows +import pwd + +fn test_get_by_uid() { + pw := pwd.get_by_uid(0)! + assert pw.name == 'root' +} + +fn test_get_by_name() { + pw := pwd.get_by_name('root')! + assert pw.uid == 0 +} + +fn test_get_all() { + pws := pwd.get_all() + assert pws.len > 0 +} diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..f90418f --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'pwd' + description: 'Access to the UNIX password database' + version: '0.1.0' + license: 'Unlicense' + dependencies: [] +}