"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "compat.c" between
rsync-3.2.1.tar.gz and rsync-3.2.2.tar.gz

About: rsync is a fast incremental file-copying tool for bringing remote (and local) files into sync.

compat.c  (rsync-3.2.1):compat.c  (rsync-3.2.2)
skipping to change at line 23 skipping to change at line 23
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along * You should have received a copy of the GNU General Public License along
* with this program; if not, visit the http://fsf.org website. * with this program; if not, visit the http://fsf.org website.
*/ */
#include "rsync.h" #include "rsync.h"
#include "itypes.h"
extern int am_server; extern int am_server;
extern int am_sender; extern int am_sender;
extern int local_server; extern int local_server;
extern int inplace; extern int inplace;
extern int recurse; extern int recurse;
extern int use_qsort; extern int use_qsort;
extern int allow_inc_recurse; extern int allow_inc_recurse;
extern int preallocate_files; extern int preallocate_files;
extern int append_mode; extern int append_mode;
skipping to change at line 176 skipping to change at line 177
{ {
if (valid_compressions.negotiated_name) if (valid_compressions.negotiated_name)
do_compression = valid_compressions.negotiated_num; do_compression = valid_compressions.negotiated_num;
else if (compress_choice) { else if (compress_choice) {
struct name_num_item *nni = get_nni_by_name(&valid_compressions, compress_choice, -1); struct name_num_item *nni = get_nni_by_name(&valid_compressions, compress_choice, -1);
if (!nni) { if (!nni) {
rprintf(FERROR, "unknown compress name: %s\n", compress_c hoice); rprintf(FERROR, "unknown compress name: %s\n", compress_c hoice);
exit_cleanup(RERR_UNSUPPORTED); exit_cleanup(RERR_UNSUPPORTED);
} }
do_compression = nni->num; do_compression = nni->num;
if (am_server)
validate_choice_vs_env(NSTR_COMPRESS, do_compression, -1)
;
} else if (do_compression) } else if (do_compression)
do_compression = CPRES_ZLIB; do_compression = CPRES_ZLIB;
else else
do_compression = CPRES_NONE; do_compression = CPRES_NONE;
if (do_compression != CPRES_NONE && final_call) if (do_compression != CPRES_NONE && final_call)
init_compression_level(); /* There's a chance this might turn com pression off! */ init_compression_level(); /* There's a chance this might turn com pression off! */
if (do_compression == CPRES_NONE) if (do_compression == CPRES_NONE)
compress_choice = NULL; compress_choice = NULL;
skipping to change at line 244 skipping to change at line 247
int cnt; int cnt;
if (!nno->saw_len) { if (!nno->saw_len) {
for (nni = nno->list; nni->name; nni++) { for (nni = nno->list; nni->name; nni++) {
if (nni->num >= nno->saw_len) if (nni->num >= nno->saw_len)
nno->saw_len = nni->num + 1; nno->saw_len = nni->num + 1;
} }
} }
if (!nno->saw) { if (!nno->saw) {
if (!(nno->saw = new_array0(uchar, nno->saw_len))) nno->saw = new_array0(uchar, nno->saw_len);
out_of_memory("init_nno_saw");
/* We'll take this opportunity to make sure that the main_name va lues are set right. */ /* We'll take this opportunity to make sure that the main_name va lues are set right. */
for (cnt = 1, nni = nno->list; nni->name; nni++, cnt++) { for (cnt = 1, nni = nno->list; nni->name; nni++, cnt++) {
if (nno->saw[nni->num]) if (nno->saw[nni->num])
nni->main_name = nno->list[nno->saw[nni->num]-1]. name; nni->main_name = nno->list[nno->saw[nni->num]-1]. name;
else else
nno->saw[nni->num] = cnt; nno->saw[nni->num] = cnt;
} }
} }
memset(nno->saw, val, nno->saw_len); memset(nno->saw, val, nno->saw_len);
} }
/* Simplify the user-provided string so that it contains valid names without any duplicates. /* Simplify the user-provided string so that it contains valid names without any duplicates.
* It also sets the "saw" flags to a 1-relative count of which name was seen fir st. */ * It also sets the "saw" flags to a 1-relative count of which name was seen fir st. */
static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf , int tobuf_len) static int parse_nni_str(struct name_num_obj *nno, const char *from, char *tobuf , int tobuf_len)
{ {
char *to = tobuf, *tok = NULL; char *to = tobuf, *tok = NULL;
int cnt = 0; int saw_tok = 0, cnt = 0;
while (1) { while (1) {
if (*from == ' ' || !*from) { int at_space = isSpace(from);
char ch = *from++;
if (ch == '&')
ch = '\0';
if (!ch || at_space) {
if (tok) { if (tok) {
struct name_num_item *nni = get_nni_by_name(nno, tok, to - tok); struct name_num_item *nni = get_nni_by_name(nno, tok, to - tok);
if (nni && !nno->saw[nni->num]) { if (nni && !nno->saw[nni->num]) {
nno->saw[nni->num] = ++cnt; nno->saw[nni->num] = ++cnt;
if (nni->main_name) { if (nni->main_name) {
to = tok + strlcpy(tok, nni->main _name, tobuf_len - (tok - tobuf)); to = tok + strlcpy(tok, nni->main _name, tobuf_len - (tok - tobuf));
if (to - tobuf >= tobuf_len) { if (to - tobuf >= tobuf_len) {
to = tok - 1; to = tok - 1;
break; break;
} }
} }
} else } else
to = tok - (tok != tobuf); to = tok - (tok != tobuf);
saw_tok = 1;
tok = NULL; tok = NULL;
} }
if (!*from++) if (!ch)
break; break;
continue; continue;
} }
if (!tok) { if (!tok) {
if (to != tobuf) if (to != tobuf)
*to++ = ' '; *to++ = ' ';
tok = to; tok = to;
} }
if (to - tobuf >= tobuf_len - 1) { if (to - tobuf >= tobuf_len - 1) {
to = tok - (tok != tobuf); to = tok - (tok != tobuf);
break; break;
} }
*to++ = *from++; *to++ = ch;
} }
*to = '\0'; *to = '\0';
if (saw_tok && to == tobuf)
return strlcpy(tobuf, "INVALID", MAX_NSTR_STRLEN);
return to - tobuf; return to - tobuf;
} }
/* This routine is always called with a tmpbuf of MAX_NSTR_STRLEN length, but th
e
* buffer may be pre-populated with a "len" length string to use OR a len of -1
* to tell us to read a string from the fd. */
static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, int len) static void recv_negotiate_str(int f_in, struct name_num_obj *nno, char *tmpbuf, int len)
{ {
struct name_num_item *ret = NULL; struct name_num_item *ret = NULL;
if (len < 0) if (len < 0)
len = read_vstring(f_in, tmpbuf, MAX_NSTR_STRLEN); len = read_vstring(f_in, tmpbuf, MAX_NSTR_STRLEN);
if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) { if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) {
if (am_server) if (am_server)
rprintf(FINFO, "Client %s list (on server): %s\n", nno->t ype, tmpbuf); rprintf(FINFO, "Client %s list (on server): %s\n", nno->t ype, tmpbuf);
else else
rprintf(FINFO, "Server %s list (on client): %s\n", nno->t ype, tmpbuf); rprintf(FINFO, "Server %s list (on client): %s\n", nno->t ype, tmpbuf);
} }
if (len > 0) { if (len > 0) {
struct name_num_item *nni;
int best = nno->saw_len; /* We want best == 1 from the client lis t, so start with a big number. */ int best = nno->saw_len; /* We want best == 1 from the client lis t, so start with a big number. */
char *tok; char *space, *tok = tmpbuf;
if (am_server) while (tok) {
init_nno_saw(nno, 1); /* Since we're parsing client names while (*tok == ' ') tok++; /* Should be unneeded... */
, anything we parse first is #1. */ if (!*tok)
for (tok = strtok(tmpbuf, " \t"); tok; tok = strtok(NULL, " \t")) break;
{ if ((space = strchr(tok, ' ')) != NULL)
struct name_num_item *nni = get_nni_by_name(nno, tok, -1) *space = '\0';
; nni = get_nni_by_name(nno, tok, -1);
if (space) {
*space = ' ';
tok = space + 1;
} else
tok = NULL;
if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni-> num]) if (!nni || !nno->saw[nni->num] || best <= nno->saw[nni-> num])
continue; continue;
ret = nni; ret = nni;
best = nno->saw[nni->num]; best = nno->saw[nni->num];
if (best == 1) if (best == 1 || am_server) /* The server side stops at t he first acceptable client choice */
break; break;
} }
if (ret) { if (ret) {
free(nno->saw); free(nno->saw);
nno->saw = NULL; nno->saw = NULL;
nno->negotiated_name = ret->main_name ? ret->main_name : ret->name; nno->negotiated_name = ret->main_name ? ret->main_name : ret->name;
nno->negotiated_num = ret->num; nno->negotiated_num = ret->num;
return; return;
} }
} }
if (!am_server) if (!am_server || !do_negotiated_strings) {
rprintf(FERROR, "Failed to negotiate a common %s\n", nno->type); char *cp = tmpbuf;
int j;
rprintf(FERROR, "Failed to negotiate a %s choice.\n", nno->type);
rprintf(FERROR, "%s list: %s\n", am_server ? "Client" : "Server",
tmpbuf);
/* Recreate our original list from the saw values. This can't ove
rflow our huge
* buffer because we don't have enough valid entries to get anywh
ere close. */
for (j = 1, *cp = '\0'; j <= nno->saw_len; j++) {
struct name_num_item *nni;
for (nni = nno->list; nni->name; nni++) {
if (nno->saw[nni->num] == j) {
*cp++ = ' ';
cp += strlcpy(cp, nni->name, MAX_NSTR_STR
LEN - (cp - tmpbuf));
break;
}
}
}
if (!*tmpbuf)
strlcpy(cp, " INVALID", MAX_NSTR_STRLEN);
rprintf(FERROR, "%s list:%s\n", am_server ? "Server" : "Client",
tmpbuf);
}
exit_cleanup(RERR_UNSUPPORTED); exit_cleanup(RERR_UNSUPPORTED);
} }
static const char *getenv_nstr(int ntype)
{
const char *env_str = getenv(ntype == NSTR_COMPRESS ? "RSYNC_COMPRESS_LIS
T" : "RSYNC_CHECKSUM_LIST");
/* When writing a batch file, we always negotiate an old-style choice. */
if (write_batch)
env_str = ntype == NSTR_COMPRESS ? "zlib" : protocol_version >= 3
0 ? "md5" : "md4";
if (am_server && env_str) {
char *cp = strchr(env_str, '&');
if (cp)
env_str = cp + 1;
}
return env_str;
}
void validate_choice_vs_env(int ntype, int num1, int num2)
{
struct name_num_obj *nno = ntype == NSTR_COMPRESS ? &valid_compressions :
&valid_checksums;
const char *list_str = getenv_nstr(ntype);
char tmpbuf[MAX_NSTR_STRLEN];
if (!list_str)
return;
while (isSpace(list_str)) list_str++;
if (!*list_str)
return;
init_nno_saw(nno, 0);
parse_nni_str(nno, list_str, tmpbuf, MAX_NSTR_STRLEN);
if (ntype == NSTR_CHECKSUM) /* If "md4" is in the env list, all the old M
D4 choices are OK too. */
nno->saw[CSUM_MD4_ARCHAIC] = nno->saw[CSUM_MD4_BUSTED] = nno->saw
[CSUM_MD4_OLD] = nno->saw[CSUM_MD4];
if (!nno->saw[num1] || (num2 >= 0 && !nno->saw[num2])) {
rprintf(FERROR, "Your --%s-choice value (%s) was refused by the s
erver.\n",
ntype == NSTR_COMPRESS ? "compress" : "checksum",
ntype == NSTR_COMPRESS ? compress_choice : checksum_choic
e);
exit_cleanup(RERR_UNSUPPORTED);
}
free(nno->saw);
nno->saw = NULL;
}
/* The saw buffer is initialized and used to store ordinal values from 1 to N /* The saw buffer is initialized and used to store ordinal values from 1 to N
* for the order of the args in the array. If dup_markup == '\0', duplicates * for the order of the args in the array. If dup_markup == '\0', duplicates
* are removed otherwise the char is prefixed to the duplicate term and, if it * are removed otherwise the char is prefixed to the duplicate term and, if it
* is an opening paren/bracket/brace, the matching closing char is suffixed. */ * is an opening paren/bracket/brace, the matching closing char is suffixed.
* "none" is removed on the client side unless dup_markup != '\0'. */
int get_default_nno_list(struct name_num_obj *nno, char *to_buf, int to_buf_len, char dup_markup) int get_default_nno_list(struct name_num_obj *nno, char *to_buf, int to_buf_len, char dup_markup)
{ {
struct name_num_item *nni; struct name_num_item *nni;
int len = 0, cnt = 0; int len = 0, cnt = 0;
char delim = '\0', post_delim; char delim = '\0', post_delim;
switch (dup_markup) { switch (dup_markup) {
case '(': post_delim = ')'; break; case '(': post_delim = ')'; break;
case '[': post_delim = ']'; break; case '[': post_delim = ']'; break;
case '{': post_delim = '}'; break; case '{': post_delim = '}'; break;
skipping to change at line 370 skipping to change at line 461
} }
init_nno_saw(nno, 0); init_nno_saw(nno, 0);
for (nni = nno->list, len = 0; nni->name; nni++) { for (nni = nno->list, len = 0; nni->name; nni++) {
if (nni->main_name) { if (nni->main_name) {
if (!dup_markup) if (!dup_markup)
continue; continue;
delim = dup_markup; delim = dup_markup;
} }
if (nni->num == 0 && !am_server && !dup_markup)
continue;
if (len) if (len)
to_buf[len++]= ' '; to_buf[len++]= ' ';
if (delim) { if (delim) {
to_buf[len++]= delim; to_buf[len++]= delim;
delim = post_delim; delim = post_delim;
} }
len += strlcpy(to_buf+len, nni->name, to_buf_len - len); len += strlcpy(to_buf+len, nni->name, to_buf_len - len);
if (len >= to_buf_len - 3) if (len >= to_buf_len - 3)
exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE... */ exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE... */
if (delim) { if (delim) {
to_buf[len++]= delim; to_buf[len++]= delim;
delim = '\0'; delim = '\0';
} }
nno->saw[nni->num] = ++cnt; nno->saw[nni->num] = ++cnt;
} }
return len; return len;
} }
static void send_negotiate_str(int f_out, struct name_num_obj *nno, const char * env_name) static void send_negotiate_str(int f_out, struct name_num_obj *nno, int ntype)
{ {
char tmpbuf[MAX_NSTR_STRLEN]; char tmpbuf[MAX_NSTR_STRLEN];
const char *list_str = getenv(env_name); const char *list_str = getenv_nstr(ntype);
int len, fail_if_empty = list_str && strstr(list_str, "FAIL"); int len;
if (!do_negotiated_strings) {
if (!am_server && fail_if_empty) {
rprintf(FERROR, "Remote rsync is too old for %s negotiati
on\n", nno->type);
exit_cleanup(RERR_UNSUPPORTED);
}
return;
}
if (list_str && *list_str && (!am_server || local_server)) { if (list_str && *list_str) {
init_nno_saw(nno, 0); init_nno_saw(nno, 0);
len = parse_nni_str(nno, list_str, tmpbuf, MAX_NSTR_STRLEN); len = parse_nni_str(nno, list_str, tmpbuf, MAX_NSTR_STRLEN);
if (fail_if_empty && !len)
len = strlcpy(tmpbuf, "FAIL", MAX_NSTR_STRLEN);
list_str = tmpbuf; list_str = tmpbuf;
} else } else
list_str = NULL; list_str = NULL;
if (!list_str || !*list_str) if (!list_str || !*list_str)
len = get_default_nno_list(nno, tmpbuf, MAX_NSTR_STRLEN, '\0'); len = get_default_nno_list(nno, tmpbuf, MAX_NSTR_STRLEN, '\0');
if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) { if (DEBUG_GTE(NSTR, am_server ? 3 : 2)) {
if (am_server) if (am_server)
rprintf(FINFO, "Server %s list (on server): %s\n", nno->t ype, tmpbuf); rprintf(FINFO, "Server %s list (on server): %s\n", nno->t ype, tmpbuf);
else else
rprintf(FINFO, "Client %s list (on client): %s\n", nno->t ype, tmpbuf); rprintf(FINFO, "Client %s list (on client): %s\n", nno->t ype, tmpbuf);
} }
if (local_server) { /* Each side sends their list of valid names to the other side and then b
/* A local server doesn't bother to send/recv the strings, it jus oth sides
t constructs * pick the first name in the client's list that is also in the server's
* and parses the same string on both sides. */ list. */
recv_negotiate_str(-1, nno, tmpbuf, len); if (do_negotiated_strings)
} else {
/* Each side sends their list of valid names to the other side an
d then both sides
* pick the first name in the client's list that is also in the s
erver's list. */
write_vstring(f_out, tmpbuf, len); write_vstring(f_out, tmpbuf, len);
}
} }
static void negotiate_the_strings(int f_in, int f_out) static void negotiate_the_strings(int f_in, int f_out)
{ {
/* We send all the negotiation strings before we start to read them to he lp avoid a slow startup. */ /* We send all the negotiation strings before we start to read them to he lp avoid a slow startup. */
if (!checksum_choice) if (!checksum_choice)
send_negotiate_str(f_out, &valid_checksums, "RSYNC_CHECKSUM_LIST" ); send_negotiate_str(f_out, &valid_checksums, NSTR_CHECKSUM);
if (do_compression && !compress_choice) if (do_compression && !compress_choice)
send_negotiate_str(f_out, &valid_compressions, "RSYNC_COMPRESS_LI ST"); send_negotiate_str(f_out, &valid_compressions, NSTR_COMPRESS);
if (valid_checksums.saw) { if (valid_checksums.saw) {
char tmpbuf[MAX_NSTR_STRLEN]; char tmpbuf[MAX_NSTR_STRLEN];
recv_negotiate_str(f_in, &valid_checksums, tmpbuf, -1); int len;
if (do_negotiated_strings)
len = -1;
else
len = strlcpy(tmpbuf, protocol_version >= 30 ? "md5" : "m
d4", MAX_NSTR_STRLEN);
recv_negotiate_str(f_in, &valid_checksums, tmpbuf, len);
} }
if (valid_compressions.saw) { if (valid_compressions.saw) {
char tmpbuf[MAX_NSTR_STRLEN]; char tmpbuf[MAX_NSTR_STRLEN];
recv_negotiate_str(f_in, &valid_compressions, tmpbuf, -1); int len;
if (do_negotiated_strings)
len = -1;
else
len = strlcpy(tmpbuf, "zlib", MAX_NSTR_STRLEN);
recv_negotiate_str(f_in, &valid_compressions, tmpbuf, len);
} }
/* If the other side is too old to negotiate, the above steps just made s
ure that
* the env didn't disallow the old algorithm. Mark things as non-negotiat
ed. */
if (!do_negotiated_strings)
valid_checksums.negotiated_name = valid_compressions.negotiated_n
ame = NULL;
} }
void setup_protocol(int f_out,int f_in) void setup_protocol(int f_out,int f_in)
{ {
assert(file_extra_cnt == 0); assert(file_extra_cnt == 0);
assert(EXTRA64_CNT == 2 || EXTRA64_CNT == 1); assert(EXTRA64_CNT == 2 || EXTRA64_CNT == 1);
/* All int64 values must be set first so that they are guaranteed to be /* All int64 values must be set first so that they are guaranteed to be
* aligned for direct int64-pointer memory access. */ * aligned for direct int64-pointer memory access. */
if (preserve_atimes) if (preserve_atimes)
skipping to change at line 605 skipping to change at line 698
#endif #endif
if (local_server || strchr(client_info, 'f') != NULL) if (local_server || strchr(client_info, 'f') != NULL)
compat_flags |= CF_SAFE_FLIST; compat_flags |= CF_SAFE_FLIST;
if (local_server || strchr(client_info, 'x') != NULL) if (local_server || strchr(client_info, 'x') != NULL)
compat_flags |= CF_AVOID_XATTR_OPTIM; compat_flags |= CF_AVOID_XATTR_OPTIM;
if (local_server || strchr(client_info, 'C') != NULL) if (local_server || strchr(client_info, 'C') != NULL)
compat_flags |= CF_CHKSUM_SEED_FIX; compat_flags |= CF_CHKSUM_SEED_FIX;
if (local_server || strchr(client_info, 'I') != NULL) if (local_server || strchr(client_info, 'I') != NULL)
compat_flags |= CF_INPLACE_PARTIAL_DIR; compat_flags |= CF_INPLACE_PARTIAL_DIR;
if (local_server || strchr(client_info, 'v') != NULL) { if (local_server || strchr(client_info, 'v') != NULL) {
if (!write_batch || protocol_version >= 30) { do_negotiated_strings = 1;
do_negotiated_strings = 1; compat_flags |= CF_VARINT_FLIST_FLAGS;
compat_flags |= CF_VARINT_FLIST_FLAGS;
}
} }
if (strchr(client_info, 'V') != NULL) { /* Support a pre- release 'V' that got superseded */ if (strchr(client_info, 'V') != NULL) { /* Support a pre- release 'V' that got superseded */
if (!write_batch) if (!write_batch)
compat_flags |= CF_VARINT_FLIST_FLAGS; compat_flags |= CF_VARINT_FLIST_FLAGS;
write_byte(f_out, compat_flags); write_byte(f_out, compat_flags);
} else } else
write_varint(f_out, compat_flags); write_varint(f_out, compat_flags);
} else { /* read_varint() is compatible with the older write_byte () when the 0x80 bit isn't on. */ } else { /* read_varint() is compatible with the older write_byte () when the 0x80 bit isn't on. */
compat_flags = read_varint(f_in); compat_flags = read_varint(f_in);
if (compat_flags & CF_VARINT_FLIST_FLAGS) if (compat_flags & CF_VARINT_FLIST_FLAGS)
skipping to change at line 657 skipping to change at line 748
use_safe_inc_flist = (compat_flags & CF_SAFE_FLIST) || protocol_v ersion >= 31; use_safe_inc_flist = (compat_flags & CF_SAFE_FLIST) || protocol_v ersion >= 31;
need_messages_from_generator = 1; need_messages_from_generator = 1;
if (compat_flags & CF_INPLACE_PARTIAL_DIR) if (compat_flags & CF_INPLACE_PARTIAL_DIR)
inplace_partial = 1; inplace_partial = 1;
#ifdef CAN_SET_SYMLINK_TIMES #ifdef CAN_SET_SYMLINK_TIMES
} else if (!am_sender) { } else if (!am_sender) {
receiver_symlink_times = 1; receiver_symlink_times = 1;
#endif #endif
} }
if (read_batch)
do_negotiated_strings = 0;
if (need_unsorted_flist && (!am_sender || inc_recurse)) if (need_unsorted_flist && (!am_sender || inc_recurse))
unsort_ndx = ++file_extra_cnt; unsort_ndx = ++file_extra_cnt;
if (partial_dir && *partial_dir != '/' && (!am_server || local_server)) { if (partial_dir && *partial_dir != '/' && (!am_server || local_server)) {
int rflags = FILTRULE_NO_PREFIXES | FILTRULE_DIRECTORY; int rflags = FILTRULE_NO_PREFIXES | FILTRULE_DIRECTORY;
if (!am_sender || protocol_version >= 30) if (!am_sender || protocol_version >= 30)
rflags |= FILTRULE_PERISHABLE; rflags |= FILTRULE_PERISHABLE;
parse_filter_str(&filter_list, partial_dir, rule_template(rflags) , 0); parse_filter_str(&filter_list, partial_dir, rule_template(rflags) , 0);
} }
 End of changes. 30 change blocks. 
52 lines changed or deleted 159 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)