Compare commits

..

2 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 2da827c9f8
Rely on make(1) 2023-07-06 02:45:35 +02:00
Xavier Del Campo Romero 9cc9d80008
WIP thumbnail 2023-07-06 02:45:26 +02:00
37 changed files with 3723 additions and 1825 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@ build/
slcl
*.o
*.d
./Makefile

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "libweb"]
path = libweb
url = https://gitea.privatedns.org/xavi/libweb
[submodule "dynstr"]
path = dynstr
url = https://gitea.privatedns.org/xavi92/dynstr

View File

@ -1,36 +1,22 @@
cmake_minimum_required(VERSION 3.13)
project(slcl LANGUAGES C VERSION 0.2.0)
project(slcl)
add_executable(${PROJECT_NAME}
auth.c
base64.c
cftw.c
handler.c
hex.c
html.c
http.c
jwt.c
main.c
page.c
style.c
server.c
wildcard_cmp.c
)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(web 0.3.0)
if(WEB_FOUND)
find_package(dynstr 0.1.0)
else()
message(STATUS "Using in-tree libweb")
add_subdirectory(libweb)
#dynstr is already provided by libweb.
endif()
add_subdirectory(dynstr)
find_package(cJSON 1.0 REQUIRED)
find_package(OpenSSL 2.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr cjson OpenSSL::SSL)
install(TARGETS ${PROJECT_NAME})
install(FILES usergen
TYPE BIN
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
add_subdirectory(doc)
find_package(OpenSSL 3.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE dynstr cjson OpenSSL::SSL)

37
Makefile Normal file
View File

@ -0,0 +1,37 @@
.POSIX:
PROJECT = slcl
O = -O1
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
CFLAGS = $(O) $(CDEFS) -g -Wall -Idynstr/include -MD -MF $(@:.o=.d)
LIBS = -lcjson -lssl -lm -lcrypto
LDFLAGS = $(LIBS)
DEPS = $(OBJECTS:.o=.d)
DYNSTR = dynstr/libdynstr.a
DYNSTR_FLAGS = -Ldynstr -ldynstr
OBJECTS = \
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
server.o \
wildcard_cmp.o \
all: $(PROJECT)
clean:
rm -f $(OBJECTS) $(DEPS)
$(PROJECT): $(OBJECTS) $(DYNSTR)
$(CC) $(OBJECTS) $(LDFLAGS) $(DYNSTR_FLAGS) -o $@
$(DYNSTR):
+cd dynstr && $(MAKE)
-include $(DEPS)

View File

@ -1,26 +1,19 @@
# slcl, a simple and lightweight cloud
# slcl, a suckless cloud
`slcl` is a simple and lightweight implementation of a web file server,
commonly known as "cloud storage" or simply "cloud", written in C99 plus
POSIX.1-2008 extensions.
## Screenshots
![Screenshot of slcl's login page](doc/login.png)
![Screenshot of an example user directory](doc/user.png)
`slcl` is a simple and fast implementation of a web file server, commonly
known as "cloud storage" or simply "cloud", written in C99.
## Disclaimer
Intentionally, `slcl` does not share some of the philosophical views from the
[suckless project](https://suckless.org). However, it still strives towards
portability, minimalism, simplicity and efficiency.
While `slcl` might not share some of the philosophical views from the
[suckless project](https://suckless.org), it still strives towards minimalism,
simplicity and efficiency.
## Features
- Private access directory with file uploading, with configurable quota.
- Read-only public file sharing.
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
- Its own, tiny HTTP/1.1-compatible server.
- A simple JSON file as the credentials database.
- No JavaScript.
@ -45,27 +38,28 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
## Requirements
- A POSIX environment.
- OpenSSL >= 2.0.
- OpenSSL >= 3.0.
- cJSON >= 1.7.15.
- [`dynstr`](https://gitea.privatedns.org/xavi/dynstr)
(provided as a `git` submodule by `libweb`).
- [`libweb`](https://gitea.privatedns.org/xavi/libweb)
- [`dynstr`](https://gitea.privatedns.org/xavi92/dynstr)
(provided as a `git` submodule).
- `xxd` (for [`usergen`](usergen) only).
- `jq` (for [`usergen`](usergen) only).
- CMake (optional).
- `entr` (for [`watchdir`](watchdir) only).
- ImageMagick (for [`tngen`](tngen) only).
### Ubuntu / Debian
#### Mandatory packages
```sh
sudo apt install build-essential libcjson-dev libssl-dev m4 jq
sudo apt install build-essential libcjson-dev libssl-dev
```
#### Optional packages
```sh
sudo apt install cmake
sudo apt install cmake xxd jq inotify-tools imagemagick
```
## How to use
@ -74,8 +68,7 @@ sudo apt install cmake
Two build environments are provided for `slcl` - feel free to choose any of
them:
- A [`configure`](configure) POSIX shell and mostly POSIX-compliant
[`Makefile`](Makefile).
- A mostly POSIX-compliant [`Makefile`](Makefile).
- A [`CMakeLists.txt`](CMakeLists.txt).
`slcl` can be built using the standard build process:
@ -83,15 +76,15 @@ them:
#### Make
```sh
$ ./configure
$ make
```
#### CMake
```sh
$ cmake -B build
$ cmake --build build/
$ mkdir build/
$ cmake ..
$ cmake --build .
```
### Setting up
@ -179,25 +172,6 @@ command line option. For example:
slcl -p 7822 ~/my-db/
```
`slcl` requires a temporary directory where files uploaded by users are
temporarily stored until moved to the user directory. By default, `slcl`
attempts to retrieve the path to the temporary directory by inspecting the
`TMPDIR` environment variable, and falls back to `/tmp` if undefined.
If a custom temporary directory is required, it can be defined via command
line option `-t`. For example:
```sh
slcl -t ~/my-tmp -p 7822 ~/my-db
```
**Note:** system-level temporary directories such as `/tmp` might reside
on a filesytem different than the one where the database resides. This
would force `slcl` to copy the contents from uploaded files from the
temporary directory to the database, which might be an expensive operation.
Therefore, in order to avoid expensive copies, define a custom temporary
directory that resides on the same filesystem.
## Why this project?
Previously, I had been recommended Nextcloud as an alternative to proprietary
@ -228,7 +202,7 @@ be even available e.g.: phones.
## License
```
slcl, a simple and lightweight cloud.
slcl, a suckless cloud.
Copyright (C) 2023 Xavier Del Campo Romero
This program is free software: you can redistribute it and/or modify

7
auth.c
View File

@ -1,7 +1,7 @@
#include "auth.h"
#include "hex.h"
#include "http.h"
#include "jwt.h"
#include <libweb/http.h>
#include <cjson/cJSON.h>
#include <dynstr.h>
#include <openssl/sha.h>
@ -9,7 +9,6 @@
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
@ -422,7 +421,7 @@ int auth_quota(const struct auth *const a, const char *const user,
*available = true;
*quota = strtoull(qs, &end, 10);
const unsigned long long mul = 1024ul * 1024ul;
const unsigned long long mul = 1024 * 1024;
if (errno || *end != '\0')
{
@ -436,7 +435,7 @@ int auth_quota(const struct auth *const a, const char *const user,
goto end;
}
*quota *= mul;
*quota *= 1024 * 1024;
break;
}
}

2
auth.h
View File

@ -1,7 +1,7 @@
#ifndef AUTH_H
#define AUTH_H
#include <libweb/http.h>
#include "http.h"
#include <stdbool.h>
struct auth *auth_alloc(const char *dir);

23
cftw.c
View File

@ -9,8 +9,8 @@
#include <stdint.h>
#include <string.h>
static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), bool *const done, void *const user)
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, void *), void *const user)
{
int ret = -1;
DIR *const d = opendir(dirpath);
@ -57,21 +57,16 @@ static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, path, strerror(errno));
else if (S_ISDIR(sb.st_mode))
{
if ((ret = do_cftw(d.str, fn, done, user)))
;
else if ((ret = fn(d.str, &sb, done, user)))
;
}
ret = cftw(d.str, fn, user);
else if (S_ISREG(sb.st_mode))
ret = fn(d.str, &sb, done, user);
ret = fn(d.str, &sb, user);
else
fprintf(stderr, "%s: unexpected st_mode %ju\n",
__func__, (uintmax_t)sb.st_mode);
dynstr_free(&d);
if (ret || *done)
if (ret)
goto end;
}
@ -87,11 +82,3 @@ end:
return ret;
}
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), void *const user)
{
bool done = false;
return do_cftw(dirpath, fn, &done, user);
}

3
cftw.h
View File

@ -1,12 +1,11 @@
#ifndef CFTW_H
#define CFTW_H
#include <stdbool.h>
#include <sys/stat.h>
/* Thread-safe variant of ftw(3) and nftw(3) that allows passing an
* opaque pointer and removes some unneeded parameters. */
int cftw(const char *dirpath, int (*fn)(const char *fpath,
const struct stat *sb, bool *done, void *user), void *user);
const struct stat *sb, void *user), void *user);
#endif /* CFTW_H */

View File

@ -1,24 +0,0 @@
mark_as_advanced(WEB_LIBRARY WEB_INCLUDE_DIR)
find_library(WEB_LIBRARY NAMES libweb web)
find_path(WEB_INCLUDE_DIR
NAMES
handler.h
html.h
http.h
server.h
wildcard_cmp.h
PATH_SUFFIXES libweb include/libweb)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(web
DEFAULT_MSG WEB_LIBRARY WEB_INCLUDE_DIR)
if(WEB_FOUND)
if(NOT TARGET web)
add_library(web UNKNOWN IMPORTED)
set_target_properties(web PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${WEB_INCLUDE_DIR}"
IMPORTED_LOCATION "${WEB_LIBRARY}")
endif()
endif()

168
configure vendored
View File

@ -1,168 +0,0 @@
#! /bin/sh
set -e
default_prefix=/usr/local
prefix=$default_prefix
default_CC='c99'
# FILE_OFFSET_BITS=64 is required for large file support on 32-bit platforms.
default_CFLAGS='-O1 -g -D_FILE_OFFSET_BITS=64 -Wall -MD'
default_LDFLAGS="-lcjson -lssl -lm -lcrypto"
CC=${CC:-$default_CC}
CFLAGS=${CFLAGS:-"$default_CFLAGS $default_NPCFLAGS"}
LDFLAGS=${LDFLAGS:-$default_LDFLAGS}
help()
{
cat <<-EOF
$0 [OPTION ...]
--prefix Set installation directory [$default_prefix]
Some influential environment variables:
CC C compiler [$default_CC]
CFLAGS C compiler flags [$default_CFLAGS]
LDFLAGS Link-time flags [$default_LDFLAGS]
EOF
}
while true; do
split_arg=0
if printf "%s" "$1" | grep -e '=' > /dev/null
then
key="$(printf "%s" "$1" | cut -d '=' -f1)"
value="$(printf "%s" "$1" | cut -d '=' -f2)"
split_arg=1
else
key="$1"
value="$2"
fi
case "$key" in
--prefix ) prefix="$value"; shift; test $split_arg -eq 0 && shift ;;
-h | --help ) help; exit 0 ;;
* ) test "$1" != "" && help && exit 1 || break ;;
esac
done
if pkg-config dynstr
then
in_tree_dynstr=0
CFLAGS="$CFLAGS $(pkg-config --cflags dynstr)"
LDFLAGS="$LDFLAGS $(pkg-config --libs dynstr)"
else
echo "Info: dynstr not found. Using in-tree copy" >&2
in_tree_dynstr=1
CFLAGS="$CFLAGS -Ilibweb/dynstr/include"
LDFLAGS="$LDFLAGS -Llibweb/dynstr -ldynstr"
fi
if pkg-config libweb
then
in_tree_libweb=0
CFLAGS="$CFLAGS $(pkg-config --cflags libweb)"
LDFLAGS="$LDFLAGS $(pkg-config --libs libweb)"
else
echo "Info: libweb not found. Using in-tree copy" >&2
in_tree_libweb=1
CFLAGS="$CFLAGS -Ilibweb/include"
LDFLAGS="$LDFLAGS -Llibweb -lweb"
fi
cleanup()
{
rm -f $F
}
F=/tmp/Makefile.slcl
trap cleanup EXIT
cat <<EOF > $F
.POSIX:
CC = $CC
PREFIX = $prefix
DST = $prefix/bin
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
EOF
cat <<"EOF" >> $F
PROJECT = slcl
DEPS = $(OBJECTS:.o=.d)
OBJECTS = \
auth.o \
base64.o \
cftw.o \
hex.o \
jwt.o \
main.o \
page.o \
style.o
all: $(PROJECT)
install: all usergen
mkdir -p $(DST)
cp slcl usergen $(DST)
chmod 0755 $(DST)/slcl
chmod 0755 $(DST)/usergen
+cd doc && $(MAKE) PREFIX=$(PREFIX) install
FORCE:
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
DYNSTR = libweb/dynstr/libdynstr.a
$(PROJECT): $(DYNSTR)
$(DYNSTR): FORCE
+cd libweb/dynstr && $(MAKE) CC=$(CC)
EOF
fi
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
LIBWEB = libweb/libweb.a
$(PROJECT): $(LIBWEB)
$(LIBWEB): FORCE
+cd libweb && $(MAKE) CC=$(CC)
EOF
fi
cat <<"EOF" >> $F
clean:
rm -f $(OBJECTS) $(DEPS)
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb/dynstr && $(MAKE) clean
EOF
fi
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb && $(MAKE) clean
EOF
fi
cat <<"EOF" >> $F
distclean: clean
rm Makefile
EOF
cat <<"EOF" >> $F
-include $(DEPS)
EOF
mv $F Makefile

View File

@ -1,4 +0,0 @@
install(DIRECTORY man1
TYPE MAN
FILES_MATCHING PATTERN "*.1"
)

View File

@ -1,8 +0,0 @@
.POSIX:
PREFIX = /usr/local
all:
install: all
+cd man1 && $(MAKE) PREFIX=$(PREFIX) install

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,16 +0,0 @@
.POSIX:
PREFIX = /usr/local
DST = $(PREFIX)/share/man/man1
OBJECTS = \
$(DST)/slcl.1 \
$(DST)/usergen.1
all:
install: $(OBJECTS)
$(DST)/%.1: %.1
mkdir -p $(DST)
cp $< $@
chmod 0644 $@

View File

@ -1,7 +1,7 @@
.TH SLCL 1 slcl
.SH NAME
slcl \- a simple and lightweight cloud
slcl \- a suckless cloud
.SH SYNOPSIS
.B slcl
@ -114,13 +114,14 @@ storing one file each, as well as a publicly-shared file by
└── file2.txt
.EE
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH TODO
Allow deleting files and directories from the web interface.
.SH SEE ALSO
.B usergen(1)

View File

@ -81,13 +81,11 @@ should be updated to something similar to:
}
.EE
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH SEE ALSO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

1
dynstr Submodule

@ -0,0 +1 @@
Subproject commit 5c13c9b8385cb2742f342d4d2f64d4e2d21108ba

315
handler.c Normal file
View File

@ -0,0 +1,315 @@
#define _POSIX_C_SOURCE 200809L
#include "handler.h"
#include "http.h"
#include "server.h"
#include "wildcard_cmp.h"
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct handler
{
struct handler_cfg cfg;
struct elem
{
char *url;
enum http_op op;
handler_fn f;
void *user;
} *elem;
struct server *server;
struct client
{
struct handler *h;
struct server_client *c;
struct http_ctx *http;
struct client *next;
} *clients;
size_t n_cfg;
};
static int on_read(void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_read(buf, n, c->c);
}
static int on_write(const void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_write(buf, n, c->c);
}
static int on_payload(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct client *const c = user;
struct handler *const h = c->h;
for (size_t i = 0; i < h->n_cfg; i++)
{
const struct elem *const e = &h->elem[i];
if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true))
return e->f(p, r, e->user);
}
fprintf(stderr, "Not found: %s\n", p->resource);
*r = (const struct http_response)
{
.status = HTTP_STATUS_NOT_FOUND
};
return 0;
}
static int on_length(const unsigned long long len,
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
struct client *const cl = user;
struct handler *const h = cl->h;
if (h->cfg.length)
return h->cfg.length(len, c, r, h->cfg.user);
return 0;
}
static struct client *find_or_alloc_client(struct handler *const h,
struct server_client *const c)
{
for (struct client *cl = h->clients; cl; cl = cl->next)
{
if (cl->c == c)
return cl;
}
struct client *const ret = malloc(sizeof *ret);
if (!ret)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
const struct http_cfg cfg =
{
.read = on_read,
.write = on_write,
.payload = on_payload,
.length = on_length,
.user = ret,
.tmpdir = h->cfg.tmpdir
};
*ret = (const struct client)
{
.c = c,
.h = h,
.http = http_alloc(&cfg)
};
if (!ret->http)
{
fprintf(stderr, "%s: http_alloc failed\n", __func__);
return NULL;
}
if (!h->clients)
h->clients = ret;
else
{
for (struct client *c = h->clients; c; c = c->next)
if (!c->next)
{
c->next = ret;
break;
}
}
return ret;
}
static void client_free(struct client *const c)
{
if (c)
http_free(c->http);
free(c);
}
static int remove_client_from_list(struct handler *const h,
struct client *const c)
{
int ret = -1;
if (server_client_close(h->server, c->c))
{
fprintf(stderr, "%s: server_client_close failed\n",
__func__);
goto end;
}
for (struct client *cl = h->clients, *prev = NULL; cl;
prev = cl, cl = cl->next)
{
if (cl == c)
{
if (!prev)
h->clients = c->next;
else
prev->next = cl->next;
break;
}
}
ret = 0;
end:
client_free(c);
return ret;
}
int handler_listen(struct handler *const h, const short port)
{
if (!(h->server = server_init(port)))
{
fprintf(stderr, "%s: server_init failed\n", __func__);
return -1;
}
for (;;)
{
bool exit, io;
struct server_client *const c = server_poll(h->server, &io, &exit);
if (exit)
{
printf("Exiting...\n");
break;
}
else if (!c)
{
fprintf(stderr, "%s: server_poll failed\n", __func__);
return -1;
}
struct client *const cl = find_or_alloc_client(h, c);
if (!cl)
{
fprintf(stderr, "%s: find_or_alloc_client failed\n", __func__);
return -1;
}
else if (io)
{
bool write, close;
const int res = http_update(cl->http, &write, &close);
if (res || close)
{
if (res < 0)
{
fprintf(stderr, "%s: http_update failed\n", __func__);
return -1;
}
else if (remove_client_from_list(h, cl))
{
fprintf(stderr, "%s: remove_client_from_list failed\n",
__func__);
return -1;
}
}
else
server_client_write_pending(cl->c, write);
}
}
return 0;
}
static void free_clients(struct handler *const h)
{
for (struct client *c = h->clients; c;)
{
struct client *const next = c->next;
server_client_close(h->server, c->c);
client_free(c);
c = next;
}
}
void handler_free(struct handler *const h)
{
if (h)
{
for (size_t i = 0; i < h->n_cfg; i++)
free(h->elem[i].url);
free(h->elem);
free_clients(h);
server_close(h->server);
}
free(h);
}
struct handler *handler_alloc(const struct handler_cfg *const cfg)
{
struct handler *const h = malloc(sizeof *h);
if (!h)
{
fprintf(stderr, "%s: malloc(3) handler: %s\n",
__func__, strerror(errno));
return NULL;
}
*h = (const struct handler){.cfg = *cfg};
return h;
}
int handler_add(struct handler *const h, const char *url,
const enum http_op op, const handler_fn f, void *const user)
{
const size_t n = h->n_cfg + 1;
struct elem *const elem = realloc(h->elem, n * sizeof *h->elem);
if (!elem)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
struct elem *const e = &elem[h->n_cfg];
*e = (const struct elem)
{
.url = strdup(url),
.op = op,
.f = f,
.user = user
};
if (!e->url)
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
h->elem = elem;
h->n_cfg = n;
return 0;
}

24
handler.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef HANDLER_H
#define HANDLER_H
#include "http.h"
#include <stddef.h>
typedef int (*handler_fn)(const struct http_payload *p,
struct http_response *r, void *user);
struct handler_cfg
{
const char *tmpdir;
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
void *user;
};
struct handler *handler_alloc(const struct handler_cfg *cfg);
void handler_free(struct handler *h);
int handler_add(struct handler *h, const char *url, enum http_op op,
handler_fn f, void *user);
int handler_listen(struct handler *h, short port);
#endif /* HANDLER_H */

301
html.c Normal file
View File

@ -0,0 +1,301 @@
#define _POSIX_C_SOURCE 200809L
#include "html.h"
#include <dynstr.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct html_node
{
struct html_attribute
{
char *attr, *value;
} *attrs;
char *element, *value;
size_t n;
struct html_node *child, *sibling;
};
static char *html_encode(const char *s)
{
struct dynstr d;
dynstr_init(&d);
if (!*s && dynstr_append(&d, ""))
{
fprintf(stderr, "%s: dynstr_append empty failed\n", __func__);
goto failure;
}
while (*s)
{
static const struct esc
{
char c;
const char *str;
} esc[] =
{
{.c = '<', .str = "&gt;"},
{.c = '>', .str = "&lt;"},
{.c = '&', .str = "&amp;"},
{.c = '\"', .str = "&quot;"},
{.c = '\'', .str = "&apos;"}
};
char buf[sizeof "a"] = {0};
const char *str = NULL;
for (size_t i = 0; i < sizeof esc / sizeof *esc; i++)
{
const struct esc *const e = &esc[i];
if (*s == e->c)
{
str = e->str;
break;
}
}
if (!str)
{
*buf = *s;
str = buf;
}
if (dynstr_append(&d, "%s", str))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto failure;
}
s++;
}
return d.str;
failure:
dynstr_free(&d);
return NULL;
}
int html_node_set_value(struct html_node *const n, const char *const val)
{
if (!(n->value = html_encode(val)))
{
fprintf(stderr, "%s: html_encode failed\n", __func__);
return -1;
}
return 0;
}
int html_node_set_value_unescaped(struct html_node *const n,
const char *const val)
{
if (!(n->value = strdup(val)))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
return 0;
}
int html_node_add_attr(struct html_node *const n, const char *const attr,
const char *const val)
{
const size_t el = n->n + 1;
struct html_attribute *const attrs = realloc(n->attrs,
el * sizeof *n->attrs), *a = NULL;
if (!attrs)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
a = &attrs[n->n];
*a = (const struct html_attribute){0};
if (!(a->attr = strdup(attr))
|| (val && !(a->value = strdup(val))))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
free(a->attr);
free(a->value);
return -1;
}
n->attrs = attrs;
n->n = el;
return 0;
}
void html_node_add_sibling(struct html_node *const n,
struct html_node *const sibling)
{
for (struct html_node *c = n; c; c = c->sibling)
if (!c->sibling)
{
c->sibling = sibling;
break;
}
}
struct html_node *html_node_add_child(struct html_node *const n,
const char *const element)
{
struct html_node *const child = html_node_alloc(element);
if (!child)
return NULL;
else if (n->child)
html_node_add_sibling(n->child, child);
else
n->child = child;
return child;
}
int serialize_node(struct dynstr *const d, const struct html_node *const n,
const unsigned level)
{
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
dynstr_append_or_ret_nonzero(d, "<%s", n->element);
if (n->n)
dynstr_append_or_ret_nonzero(d, " ");
for (size_t i = 0; i < n->n; i++)
{
const struct html_attribute *const a = &n->attrs[i];
if (a->value)
dynstr_append_or_ret_nonzero(d, "%s=\"%s\"", a->attr, a->value);
else
dynstr_append_or_ret_nonzero(d, "%s", a->attr);
if (i + 1 < n->n)
dynstr_append_or_ret_nonzero(d, " ");
}
if (!n->value && !n->child)
dynstr_append_or_ret_nonzero(d, "/>");
else
{
dynstr_append_or_ret_nonzero(d, ">");
if (n->value)
dynstr_append_or_ret_nonzero(d, "%s", n->value);
if (n->child)
{
dynstr_append_or_ret_nonzero(d, "\n");
if (serialize_node(d, n->child, level + 1))
{
fprintf(stderr, "%s: serialize_node failed\n", __func__);
return -1;
}
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
}
dynstr_append_or_ret_nonzero(d, "</%s>", n->element);
}
/* TODO: print siblings */
dynstr_append_or_ret_nonzero(d, "\n");
if (n->sibling)
return serialize_node(d, n->sibling, level);
return 0;
}
int html_serialize(const struct html_node *const n, struct dynstr *const d)
{
return serialize_node(d, n, 0);
}
static void html_attribute_free(struct html_attribute *const a)
{
if (a)
{
free(a->attr);
free(a->value);
}
}
void html_node_free(struct html_node *const n)
{
if (n)
{
struct html_node *s = n->sibling;
html_node_free(n->child);
while (s)
{
struct html_node *const next = s->sibling;
html_node_free(s->child);
free(s->element);
free(s->value);
for (size_t i = 0 ; i < s->n; i++)
html_attribute_free(&s->attrs[i]);
free(s->attrs);
free(s);
s = next;
}
free(n->element);
free(n->value);
for (size_t i = 0 ; i < n->n; i++)
html_attribute_free(&n->attrs[i]);
free(n->attrs);
}
free(n);
}
struct html_node *html_node_alloc(const char *const element)
{
struct html_node *const n = malloc(sizeof *n);
if (!n)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*n = (const struct html_node)
{
.element = strdup(element)
};
if (!n->element)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
return n;
failure:
html_node_free(n);
return NULL;
}

15
html.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef HTML_H
#define HTML_H
#include <dynstr.h>
struct html_node *html_node_alloc(const char *element);
void html_node_free(struct html_node *n);
int html_node_set_value(struct html_node *n, const char *val);
int html_node_set_value_unescaped(struct html_node *n, const char *val);
int html_node_add_attr(struct html_node *n, const char *attr, const char *val);
struct html_node *html_node_add_child(struct html_node *n, const char *elem);
void html_node_add_sibling(struct html_node *n, struct html_node *sibling);
int html_serialize(const struct html_node *n, struct dynstr *d);
#endif /* HTML_H */

1956
http.c Normal file

File diff suppressed because it is too large Load Diff

106
http.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef HTTP_H
#define HTTP_H
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
struct http_payload
{
enum http_op
{
HTTP_OP_GET,
HTTP_OP_POST
} op;
const char *resource;
struct http_cookie
{
const char *field, *value;
} cookie;
union
{
struct http_post
{
bool expect_continue;
const void *data;
size_t n;
const char *dir;
const struct http_post_file
{
const char *tmpname, *filename;
} *files;
} post;
} u;
const struct http_arg
{
char *key, *value;
} *args;
size_t n_args;
};
#define HTTP_STATUSES \
X(CONTINUE, "Continue", 100) \
X(OK, "OK", 200) \
X(SEE_OTHER, "See other", 303) \
X(BAD_REQUEST, "Bad Request", 400) \
X(UNAUTHORIZED, "Unauthorized", 401) \
X(FORBIDDEN, "Forbidden", 403) \
X(NOT_FOUND, "Not found", 404) \
X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \
X(INTERNAL_ERROR, "Internal Server Error", 500)
struct http_response
{
enum http_status
{
#define X(x, y, z) HTTP_STATUS_##x,
HTTP_STATUSES
#undef X
} status;
struct http_header
{
char *header, *value;
} *headers;
union
{
const void *ro;
void *rw;
} buf;
FILE *f;
unsigned long long n;
size_t n_headers;
void (*free)(void *);
};
struct http_cfg
{
int (*read)(void *buf , size_t n, void *user);
int (*write)(const void *buf, size_t n, void *user);
int (*payload)(const struct http_payload *p, struct http_response *r,
void *user);
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
const char *tmpdir;
void *user;
};
struct http_ctx *http_alloc(const struct http_cfg *cfg);
void http_free(struct http_ctx *h);
/* Positive return value: user input error, negative: fatal error. */
int http_update(struct http_ctx *h, bool *write, bool *close);
int http_response_add_header(struct http_response *r, const char *header,
const char *value);
char *http_cookie_create(const char *key, const char *value);
char *http_encode_url(const char *url);
char *http_decode_url(const char *url, bool spaces);
#endif /* HTTP_H */

1
libweb

@ -1 +0,0 @@
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f

871
main.c

File diff suppressed because it is too large Load Diff

842
page.c

File diff suppressed because it is too large Load Diff

19
page.h
View File

@ -1,8 +1,7 @@
#ifndef PAGE_H
#define PAGE_H
#include <libweb/http.h>
#include <stdbool.h>
#include "http.h"
#include <stddef.h>
struct page_quota
@ -13,7 +12,7 @@ struct page_quota
struct page_resource
{
struct http_response *r;
const char *dir, *root, *res;
const char *adir, *username, *dir, *root, *res;
const struct page_quota *q;
const struct http_arg *args;
size_t n_args;
@ -26,29 +25,21 @@ struct page_search
char *name;
} *results;
const char *root;
size_t n;
bool limit_exceeded;
};
struct page_rm
{
const char *dir, **items;
const char *root, *adir, *username;
size_t n;
};
int page_login(struct http_response *r);
int page_style(struct http_response *r, const char *path);
int page_style(struct http_response *r);
int page_failed_login(struct http_response *r);
int page_forbidden(struct http_response *r);
int page_bad_request(struct http_response *r);
int page_resource(const struct page_resource *r);
int page_head_resource(struct http_response *r, const char *res);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
int page_search(struct http_response *r, const struct page_search *s);
int page_rm(struct http_response *r, const struct page_rm *rm);
int page_thumbnail(struct http_response *r, const char *res);
#endif /* PAGE_H */

373
server.c Normal file
View File

@ -0,0 +1,373 @@
#define _POSIX_C_SOURCE 200809L
#include "server.h"
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct server
{
int fd;
struct server_client
{
int fd;
bool write;
struct server_client *prev, *next;
} *c;
};
int server_close(struct server *const s)
{
int ret = 0;
if (!s)
return 0;
else if (s->fd >= 0)
ret = close(s->fd);
free(s);
return ret;
}
int server_client_close(struct server *const s, struct server_client *const c)
{
int ret = 0;
for (struct server_client *ref = s->c; ref; ref = ref->next)
{
if (c == ref)
{
struct server_client *const next = ref->next;
if ((ret = close(c->fd)))
{
fprintf(stderr, "%s: close(2): %s\n",
__func__, strerror(errno));
return -1;
}
else if (ref->prev)
ref->prev->next = next;
else
s->c = next;
if (next)
next->prev = ref->prev;
free(ref);
break;
}
}
return ret;
}
int server_read(void *const buf, const size_t n, struct server_client *const c)
{
const ssize_t r = read(c->fd, buf, n);
if (r < 0)
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
return r;
}
int server_write(const void *const buf, const size_t n,
struct server_client *const c)
{
const ssize_t w = write(c->fd, buf, n);
if (w < 0)
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
return w;
}
static struct server_client *alloc_client(struct server *const s)
{
struct sockaddr_in addr;
socklen_t sz = sizeof addr;
const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz);
if (fd < 0)
{
fprintf(stderr, "%s: accept(2): %s\n",
__func__, strerror(errno));
return NULL;
}
const int flags = fcntl(fd, F_GETFL);
if (flags < 0)
{
fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
{
fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
struct server_client *const c = malloc(sizeof *c);
if (!c)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
*c = (const struct server_client)
{
.fd = fd
};
if (!s->c)
s->c = c;
else
for (struct server_client *ref = s->c; ref; ref = ref->next)
if (!ref->next)
{
ref->next = c;
c->prev = ref;
break;
}
return c;
}
void server_client_write_pending(struct server_client *const c,
const bool write)
{
c->write = write;
}
static volatile sig_atomic_t do_exit;
static void handle_signal(const int signum)
{
switch (signum)
{
case SIGINT:
/* Fall through. */
case SIGTERM:
do_exit = 1;
break;
default:
break;
}
}
static size_t get_clients(const struct server *const s)
{
size_t ret = 0;
for (const struct server_client *c = s->c; c; c = c->next)
ret++;
return ret;
}
struct server_client *server_poll(struct server *const s, bool *const io,
bool *const exit)
{
struct server_client *ret = NULL;
const size_t n_clients = get_clients(s);
const nfds_t n = n_clients + 1;
struct pollfd *const fds = malloc(n * sizeof *fds);
if (!fds)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto end;
}
struct pollfd *const sfd = &fds[0];
*io = *exit = false;
*sfd = (const struct pollfd)
{
.fd = s->fd,
.events = POLLIN
};
for (struct {const struct server_client *c; size_t j;}
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
{
struct pollfd *const p = &fds[_.j];
const int fd = _.c->fd;
*p = (const struct pollfd)
{
.fd = fd,
.events = POLLIN
};
if (_.c->write)
p->events |= POLLOUT;
}
int res;
again:
res = poll(fds, n, -1);
if (res < 0)
{
if (do_exit)
{
*exit = true;
goto end;
}
switch (errno)
{
case EAGAIN:
/* Fall through. */
case EINTR:
goto again;
default:
fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno));
break;
}
goto end;
}
else if (!res)
{
fprintf(stderr, "%s: poll(2) returned zero\n", __func__);
goto end;
}
else if (sfd->revents)
{
ret = alloc_client(s);
goto end;
}
for (struct {struct server_client *c; size_t j;}
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
{
const struct pollfd *const p = &fds[_.j];
if (p->revents)
{
*io = true;
ret = _.c;
goto end;
}
}
fprintf(stderr, "%s: unlisted fd\n", __func__);
end:
free(fds);
return ret;
}
static int init_signals(void)
{
struct sigaction sa =
{
.sa_handler = handle_signal,
.sa_flags = SA_RESTART
};
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n",
__func__, strerror(errno));
return -1;
}
else if (sigaction(SIGTERM, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGTERM: %s\n",
__func__, strerror(errno));
return -1;
}
else if (sigaction(SIGPIPE, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGPIPE: %s\n",
__func__, strerror(errno));
return -1;
}
return 0;
}
struct server *server_init(const unsigned short port)
{
struct server *const s = malloc(sizeof *s);
if (!s)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*s = (const struct server)
{
.fd = socket(AF_INET, SOCK_STREAM, 0)
};
if (s->fd < 0)
{
fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (init_signals())
{
fprintf(stderr, "%s: init_signals failed\n", __func__);
goto failure;
}
const struct sockaddr_in addr =
{
.sin_family = AF_INET,
.sin_port = htons(port)
};
enum {QUEUE_LEN = 10};
if (bind(s->fd, (const struct sockaddr *)&addr, sizeof addr))
{
fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (listen(s->fd, QUEUE_LEN))
{
fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno));
goto failure;
}
struct sockaddr_in in;
socklen_t sz = sizeof in;
if (getsockname(s->fd, (struct sockaddr *)&in, &sz))
{
fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno));
goto failure;
}
printf("Listening on port %hu\n", ntohs(in.sin_port));
return s;
failure:
server_close(s);
return NULL;
}

15
server.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef SERVER_H
#define SERVER_H
#include <stdbool.h>
#include <stddef.h>
struct server *server_init(unsigned short port);
struct server_client *server_poll(struct server *s, bool *io, bool *exit);
int server_read(void *buf, size_t n, struct server_client *c);
int server_write(const void *buf, size_t n, struct server_client *c);
int server_close(struct server *s);
int server_client_close(struct server *s, struct server_client *c);
void server_client_write_pending(struct server_client *c, bool write);
#endif /* SERVER_H */

55
style.c
View File

@ -1,55 +0,0 @@
#include "style.h"
#include <stddef.h>
const char style_default[] =
"body\n"
"{\n"
" font-family: 'Courier New', Courier, monospace;\n"
"}\n"
"td\n"
"{\n"
" font-size: 14px;\n"
"}\n"
"a\n"
"{\n"
" text-decoration: none;\n"
"}\n"
".userform\n"
"{\n"
" padding: 4px;\n"
"}\n"
".loginform\n"
"{\n"
" display: grid;\n"
"}\n"
"form, label, table\n"
"{\n"
" margin: auto;\n"
"}\n"
"div\n"
"{\n"
" align-items: center;\n"
" display: grid;\n"
"}\n"
"input, .abutton\n"
"{\n"
" margin: auto;\n"
" border: 1px solid;\n"
" border-radius: 8px;\n"
"}\n"
"header, footer\n"
"{\n"
" display: flex;\n"
" justify-content: center;\n"
" text-decoration: auto;\n"
"}\n"
"table\n"
"{\n"
" max-width: 50%;\n"
"}\n"
"tr:nth-child(even)\n"
"{\n"
" background-color: lightgray;\n"
"}\n";
const size_t style_default_len = sizeof style_default - 1;

View File

@ -1,9 +0,0 @@
#ifndef STYLE_H
#define STYLE_H
#include <stddef.h>
extern const char style_default[];
extern const size_t style_default_len;
#endif

135
tngen Executable file
View File

@ -0,0 +1,135 @@
#! /bin/sh
set -e
usage()
{
echo "$0 [-s <size>] [-j <jobs>] [-h] <path>"
}
SIZE=96
while getopts j:s:h arg
do
case $arg in
j) JOBS="$OPTARG"
;;
s) SIZE="$OPTARG"
;;
h) usage
exit 0
;;
?) usage >&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# != 1 ]; then
usage >&2
exit 1
fi
DIR="$1"
gen()
{
IN="$1"
OUT="$2"
if [ -z "$OUT" ]
then
echo Expected output filename >&2
return 1
fi
mkdir -p "$(dirname "$OUT")"
if convert -thumbnail x$SIZE "$IN" "$OUT"
then
echo Created $OUT
else
echo Failed to create $OUT >&2
fi
}
scan()
{
while read d
do
if [ -n "$d" ]
then
scan "$d"
fi
TDIR="$(echo "$1" | sed "s,$DIR/user,$DIR/thumbnails,")"
eval find \"$1\"/* -prune >/dev/null || return 0
mkdir -p "$TDIR"
MK="$(echo "$TDIR/Makefile")"
cat <<-"EOF" > "$MK"
.POSIX:
EOF
echo "DIRS=\\" >> "$MK"
eval find \"$TDIR\"/* -prune -type d > "$TDIR/.dirs"
while read dir
do
D="$(echo "$dir" | sed "s, ,\\\\ ,g")"
echo "$D \\" >> "$MK"
done < "$TDIR/.dirs"
echo >> "$MK"
echo "DEPS=\\" >> "$MK"
eval find \"$1\"/* -prune -type f \
-a -iname '*.jpeg' \
-o -iname '*.jpg' \
-o -iname '*.png' \
-o -iname '*.jpeg' > "$TDIR/.files"
while read f
do
THUMBNAIL="$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/," \
| sed "s, ,\\\\ ,g")"
echo "$THUMBNAIL \\" >> "$MK"
done < "$TDIR/.files"
echo >> "$MK"
echo 'all: $(DEPS) $(DIRS)' >> "$MK"
echo 'FORCE:' >> "$MK"
while read dir
do
D="$(echo "$dir" | sed "s, ,\\\\ ,g")"
echo "$D: FORCE" >> "$MK"
printf '\t+cd $@ && $(MAKE)\n' >> "$MK"
done < "$TDIR/.dirs"
while read f
do
THUMBNAIL="$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/," \
| sed "s, ,\\\\ ,g")"
EF=$(echo "$f" | sed "s, ,\\\\ ,g")
echo $THUMBNAIL: $EF >> "$MK"
printf '\tconvert -thumbnail x%d "$<" "$@" || :\n' $SIZE >> "$MK"
done < "$TDIR/.files"
echo "Successfully generated $MK"
done <<-EOF
$(eval find \"$1\"/* -prune -type d || return 0)
EOF
}
echo Generating Makefiles...
scan "$DIR/user"
cd "$DIR/thumbnails" && make ${JOBS:+-j$JOBS}

32
usergen
View File

@ -7,23 +7,6 @@ usage()
echo "$0 <dir>"
}
to_hex()
{
od -An -t x1 | tr -d ' ' | tr -d '\n'
}
to_bin()
{
sed -e 's,\([0-9a-f]\{2\}\),\\\\\\x\1,g' | xargs printf
}
mktemp_posix()
{
m4 <<EOF
mkstemp(${TMPDIR:-/tmp}/tmp.XXXXXX)
EOF
}
if [ $# != 1 ]; then
usage >&2
exit 1
@ -59,23 +42,22 @@ echo
echo "Quota, in MiB (leave empty for unlimited quota):" >&2
read -r QUOTA
QUOTA="$(printf '%d' "$QUOTA")"
PWD=$(printf '%s' "$PWD" | to_hex)
SALT=$(openssl rand -hex 32)
KEY=$(openssl rand -hex 32)
PWD=$(printf '%s%s' "$SALT" "$PWD")
PWD=$(printf '%s' "$PWD" | xxd -p | tr -d '\n')
SALT=$(openssl rand 32 | xxd -p | tr -d '\n')
KEY=$(openssl rand 32 | xxd -p | tr -d '\n')
PWD=$(printf '%s%s' $SALT "$PWD")
ROUNDS=1000
for i in $(seq $ROUNDS)
do
printf "\r%d/$ROUNDS" $i >&2
PWD=$(printf '%s' "$PWD" | to_bin | openssl sha256 -r | cut -d' ' -f1)
PWD=$(printf '%s' "$PWD" | xxd -p -r | sha256sum | cut -d' ' -f1)
done
echo >&2
TMP=$(mktemp_posix)
TMP=$(mktemp)
cleanup()
{
@ -93,5 +75,5 @@ jq ".users += [
\"quota\": \"$QUOTA\"
}]" "$DB" > $TMP
mkdir -p "$DIR/user/$USER"
mv $TMP "$DB"
mkdir "$DIR/user/$USER"

51
watchdir Executable file
View File

@ -0,0 +1,51 @@
#! /bin/sh
usage()
{
echo "$0 [-s <size>] [-r] [-h] [-j <jobs>] <dir>"
}
REGEN=0
SIZE=96
while getopts rj:s:h arg
do
case $arg in
j) JOBS="$OPTARG"
;;
r) REGEN=1
;;
s) SIZE="$OPTARG"
;;
h) usage
exit 0
;;
?) usage >&2
exit 1
;;
esac
done
shift $(($OPTIND - 1))
if [ $# != 1 ]; then
usage >&2
exit 1
fi
DIR="$1"
[ "$REGEN" -eq 1 ] && "$(dirname $0)/tngen" \
${SIZE:+-s$SIZE} \
${JOBS:+-j$JOBS} \
"$DIR"
while :
do
F="$(inotifywait -e modify,move,create,delete \
--format "%w%f" -qr "$DIR/user/")"
"$(dirname $0)/tngen" \
${SIZE:+-s$SIZE} \
${JOBS:+-j$JOBS} \
"$DIR"
done

View File

@ -30,8 +30,7 @@ int wildcard_cmp(const char *s, const char *p, const bool casecmp)
return r;
}
const size_t auxn = wc - p, rem = strlen(s),
n = auxn > rem ? rem : auxn;
const size_t n = wc - p;
if (n)
{