Mail Archives: cygwin/2008/10/07/14:15:22
I've been testing out the 1.7.0-28 build of cygwin using rsync 3.0.3 with
the preallocate patch. My destination server setup is a Windows Server 2003
R2 machine with a fibre array (10TB). The client is also running the same
version of cygwin and rsync on a Server 2003 box.
I've noticed that when rsync is creating very large files using preallocate
(20GB+) then I will occasionally get a "Resource temporarily unavailable"
error returned from rsync on the destination side (the server with the 10TB
array). I've removed the file on the destination file system and the file
will not get recreated using the preallocate option. When I do not use the
preallocate option there are no issues with rsync.
The preallocate patch is pasted below. The important code segment is here:
+#ifdef SUPPORT_PREALLOCATION
+int do_fallocate(int fd, OFF_T offset, OFF_T length)
+{
+ RETURN_ERROR_IF(dry_run, 0);
+ RETURN_ERROR_IF_RO_OR_LO;
+ /* TODO: Use FALLOC_FL_KEEP_SIZE to avoid the need to truncate. */
+#if defined HAVE_FALLOCATE
+ return fallocate(fd, 0, offset, length);
+#elif defined HAVE_SYS_FALLOCATE
+ return syscall(SYS_fallocate, fd, 0, (loff_t) offset, (loff_t)
length);
+#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE
+ return posix_fallocate(fd, offset, length);
+#else
+#error coding error in SUPPORT_PREALLOCATION
+#endif
+}
+#endif
Any idea on why this error would occur when using the posix_fallocate
option?
Rob
FULL PATCH
----------
This patch adds the --preallocate option that asks rsync to preallocate the
copied files. This slows down the copy, but should reduce fragmentation on
systems that need that.
To use this patch, run these commands for a successful build:
patch -p1 <patches/preallocate.diff
./prepare-source
./configure
make
diff --git a/compat.c b/compat.c
--- a/compat.c
+++ b/compat.c
@@ -33,6 +33,7 @@ extern int inplace;
extern int recurse;
extern int use_qsort;
extern int allow_inc_recurse;
+extern int preallocate_files;
extern int append_mode;
extern int fuzzy_basis;
extern int read_batch;
@@ -184,6 +185,15 @@ void setup_protocol(int f_out,int f_in)
if (read_batch)
check_batch_flags();
+#ifndef SUPPORT_PREALLOCATION
+ if (preallocate_files && !am_sender) {
+ rprintf(FERROR,
+ "preallocation is not supported on this %s\n",
+ am_server ? "server" : "client");
+ exit_cleanup(RERR_SYNTAX);
+ }
+#endif
+
if (protocol_version < 30) {
if (append_mode == 1)
append_mode = 2;
diff --git a/configure.in b/configure.in
--- a/configure.in
+++ b/configure.in
@@ -554,13 +554,40 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod
lchmod mknod mkfifo \
strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
strerror putenv iconv_open locale_charset nl_langinfo getxattr \
- extattr_get_link sigaction sigprocmask setattrlist)
+ extattr_get_link sigaction sigprocmask setattrlist fallocate
posix_fallocate)
dnl cygwin iconv.h defines iconv_open as libiconv_open
if test x"$ac_cv_func_iconv_open" != x"yes"; then
AC_CHECK_FUNC(libiconv_open, [ac_cv_func_iconv_open=yes;
AC_DEFINE(HAVE_ICONV_OPEN, 1)])
fi
+dnl Preallocation stuff (also fallocate, posix_fallocate function tests
above):
+
+AC_CACHE_CHECK([for SYS_fallocate],rsync_cv_have_sys_fallocate,[
+AC_TRY_COMPILE([#include <sys/syscall.h>
+#include <sys/types.h>],
+[syscall(SYS_fallocate, 0, 0, (loff_t) 0, (loff_t) 0);],
+rsync_cv_have_sys_fallocate=yes,rsync_cv_have_sys_fallocate=no)])
+if test x"$rsync_cv_have_sys_fallocate" = x"yes"; then
+ AC_DEFINE(HAVE_SYS_FALLOCATE, 1, [Define to 1 if you have the
SYS_fallocate syscall number])
+fi
+
+if test x"$ac_cv_func_posix_fallocate" = x"yes"; then
+ AC_MSG_CHECKING([whether posix_fallocate is efficient])
+ case $host_os in
+ *cygwin*)
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_EFFICIENT_POSIX_FALLOCATE, 1,
+ [Define if posix_fallocate is efficient (Cygwin)])
+ ;;
+ *)
+ AC_MSG_RESULT(no)
+ ;;
+ esac
+fi
+
+dnl End of preallocation stuff
+
AC_CHECK_FUNCS(getpgrp tcgetpgrp)
if test $ac_cv_func_getpgrp = yes; then
AC_FUNC_GETPGRP
diff --git a/options.c b/options.c
--- a/options.c
+++ b/options.c
@@ -73,6 +73,7 @@ int remove_source_files = 0;
int one_file_system = 0;
int protocol_version = PROTOCOL_VERSION;
int sparse_files = 0;
+int preallocate_files = 0;
int do_compression = 0;
int def_compress_level = Z_DEFAULT_COMPRESSION;
int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
@@ -225,6 +226,7 @@ static void print_rsync_version(enum logcode f)
char const *links = "no ";
char const *iconv = "no ";
char const *ipv6 = "no ";
+ char const *preallocation = "no ";
STRUCT_STAT *dumstat;
#if SUBPROTOCOL_VERSION != 0
@@ -257,6 +259,9 @@ static void print_rsync_version(enum logcode f)
#if defined HAVE_LUTIMES && defined HAVE_UTIMES
symtimes = "";
#endif
+#ifdef SUPPORT_PREALLOCATION
+ preallocation = "";
+#endif
rprintf(f, "%s version %s protocol version %d%s\n",
RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
@@ -270,8 +275,8 @@ static void print_rsync_version(enum logcode f)
(int)(sizeof (int64) * 8));
rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6,
batchfiles, %sinplace,\n",
got_socketpair, hardlinks, links, ipv6, have_inplace);
- rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
- have_inplace, acls, xattrs, iconv, symtimes);
+ rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes,
%spreallocation\n",
+ have_inplace, acls, xattrs, iconv, symtimes, preallocation);
#ifdef MAINTAINER_MODE
rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
@@ -358,6 +363,9 @@ void usage(enum logcode F)
rprintf(F," --fake-super store/recover privileged attrs
using xattrs\n");
#endif
rprintf(F," -S, --sparse handle sparse files
efficiently\n");
+#ifdef SUPPORT_PREALLOCATION
+ rprintf(F," --preallocate allocate dest files before
writing them\n");
+#endif
rprintf(F," -n, --dry-run perform a trial run with no
changes made\n");
rprintf(F," -W, --whole-file copy files whole (without
delta-xfer algorithm)\n");
rprintf(F," -x, --one-file-system don't cross filesystem
boundaries\n");
@@ -542,6 +550,7 @@ static struct poptOption long_options[] = {
{"sparse", 'S', POPT_ARG_VAL, &sparse_files, 1, 0, 0 },
{"no-sparse", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 },
{"no-S", 0, POPT_ARG_VAL, &sparse_files, 0, 0, 0 },
+ {"preallocate", 0, POPT_ARG_NONE, &preallocate_files, 0, 0, 0},
{"inplace", 0, POPT_ARG_VAL, &inplace, 1, 0, 0 },
{"no-inplace", 0, POPT_ARG_VAL, &inplace, 0, 0, 0 },
{"append", 0, POPT_ARG_NONE, 0, OPT_APPEND, 0, 0 },
@@ -2048,6 +2057,9 @@ void server_options(char **args, int *argc_p)
else if (remove_source_files)
args[ac++] = "--remove-sent-files";
+ if (preallocate_files && am_sender)
+ args[ac++] = "--preallocate";
+
*argc_p = ac;
return;
diff --git a/receiver.c b/receiver.c
--- a/receiver.c
+++ b/receiver.c
@@ -45,6 +45,7 @@ extern int cleanup_got_literal;
extern int remove_source_files;
extern int append_mode;
extern int sparse_files;
+extern int preallocate_files;
extern int keep_partial;
extern int checksum_seed;
extern int inplace;
@@ -174,6 +175,18 @@ static int receive_data(int f_in, char *fname_r, int
fd_r, OFF_T size_r,
char *data;
int32 i;
char *map = NULL;
+#ifdef SUPPORT_PREALLOCATION
+ OFF_T preallocated_len = 0;
+
+ if (preallocate_files && fd != -1 && total_size > 0) {
+ /* Preallocate enough space for file's eventual length if
+ * possible; seems to reduce fragmentation on Windows. */
+ if (do_fallocate(fd, 0, total_size) == 0)
+ preallocated_len = total_size;
+ else
+ rsyserr(FWARNING, errno, "do_fallocate %s",
full_fname(fname));
+ }
+#endif
read_sum_head(f_in, &sum);
@@ -284,8 +297,18 @@ static int receive_data(int f_in, char *fname_r, int
fd_r, OFF_T size_r,
goto report_write_error;
#ifdef HAVE_FTRUNCATE
- if (inplace && fd != -1)
- ftruncate(fd, offset);
+ /* inplace: New data could be shorter than old data.
+ * preallocate_files: total_size could have been an overestimate.
+ * Cut off any extra preallocated zeros from dest file. */
+ if ((inplace
+#ifdef SUPPORT_PREALLOCATION
+ || preallocated_len > offset
+#endif
+ ) && fd != -1)
+ if (ftruncate(fd, offset) < 0)
+ /* If we fail to truncate, the dest file may be
wrong, so we
+ * must trigger the "partial transfer" error. */
+ rsyserr(FERROR_XFER, errno, "ftruncate %s",
full_fname(fname));
#endif
if (do_progress)
diff --git a/rsync.h b/rsync.h
--- a/rsync.h
+++ b/rsync.h
@@ -612,6 +612,13 @@ struct ht_int64_node {
#define ACLS_NEED_MASK 1
#endif
+#if defined HAVE_FTRUNCATE \
+ && (defined HAVE_FALLOCATE \
+ || defined HAVE_SYS_FALLOCATE \
+ || defined HAVE_EFFICIENT_POSIX_FALLOCATE)
+#define SUPPORT_PREALLOCATION 1
+#endif
+
union file_extras {
int32 num;
uint32 unum;
diff --git a/rsync.yo b/rsync.yo
--- a/rsync.yo
+++ b/rsync.yo
@@ -352,6 +352,7 @@ to the detailed description below for a complete
description. verb(
--super receiver attempts super-user activities
--fake-super store/recover privileged attrs using xattrs
-S, --sparse handle sparse files efficiently
+ --preallocate allocate dest files before writing
-n, --dry-run perform a trial run with no changes made
-W, --whole-file copy files whole (w/o delta-xfer algorithm)
-x, --one-file-system don't cross filesystem boundaries
@@ -1049,6 +1050,18 @@ NOTE: Don't use this option when the destination is a
Solaris "tmpfs"
filesystem. It doesn't seem to handle seeks over null regions
correctly and ends up corrupting the files.
+dit(bf(--preallocate)) This tells the receiver to allocate each destination
+file to its eventual size before writing data to the file. Rsync will only
use
+the real filesystem-level preallocation support provided by
bf(fallocate)(2) or
+Cygwin's bf(posix_fallocate)(3), not the slow glibc implementation that
writes
+a zero byte into each block. If the receiver is remote, this nonstandard
+option only works if the receiver also has the preallocation patch.
+
+Without this option on MS Windows, very large destination files tend to be
+broken into thousands of fragments; advising Windows ahead of time of the
+eventual file size using this option usually reduces the number of
+fragments to one. The usefulness of this option on Linux is yet to be
tested.
+
dit(bf(-n, --dry-run)) This makes rsync perform a trial run that doesn't
make any changes (and produces mostly the same output as a real run). It
is most commonly used in combination with the bf(-v, --verbose) and/or
diff --git a/syscall.c b/syscall.c
--- a/syscall.c
+++ b/syscall.c
@@ -29,6 +29,10 @@
#include <sys/attr.h>
#endif
+#if defined HAVE_SYS_FALLOCATE && !defined HAVE_FALLOCATE
+#include <sys/syscall.h>
+#endif
+
extern int dry_run;
extern int am_root;
extern int read_only;
@@ -282,3 +286,21 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
return lseek(fd, offset, whence);
#endif
}
+
+#ifdef SUPPORT_PREALLOCATION
+int do_fallocate(int fd, OFF_T offset, OFF_T length)
+{
+ RETURN_ERROR_IF(dry_run, 0);
+ RETURN_ERROR_IF_RO_OR_LO;
+ /* TODO: Use FALLOC_FL_KEEP_SIZE to avoid the need to truncate. */
+#if defined HAVE_FALLOCATE
+ return fallocate(fd, 0, offset, length);
+#elif defined HAVE_SYS_FALLOCATE
+ return syscall(SYS_fallocate, fd, 0, (loff_t) offset, (loff_t)
length);
+#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE
+ return posix_fallocate(fd, offset, length);
+#else
+#error coding error in SUPPORT_PREALLOCATION
+#endif
+}
+#endif
diff --git a/t_stub.c b/t_stub.c
--- a/t_stub.c
+++ b/t_stub.c
@@ -22,6 +22,7 @@
#include "rsync.h"
int modify_window = 0;
+int preallocate_files = 0;
int module_id = -1;
int relative_paths = 0;
int human_readable = 0;
diff --git a/util.c b/util.c
--- a/util.c
+++ b/util.c
@@ -25,6 +25,7 @@
extern int verbose;
extern int dry_run;
+extern int preallocate_files;
extern int module_id;
extern int modify_window;
extern int relative_paths;
@@ -276,6 +277,10 @@ int copy_file(const char *source, const char *dest, int
ofd,
int ifd;
char buf[1024 * 8];
int len; /* Number of bytes read into `buf'. */
+#ifdef SUPPORT_PREALLOCATION
+ OFF_T preallocated_len = 0;
+ OFF_T offset = 0;
+#endif
if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
int save_errno = errno;
@@ -309,7 +314,27 @@ int copy_file(const char *source, const char *dest, int
ofd,
}
}
+#ifdef SUPPORT_PREALLOCATION
+ if (preallocate_files) {
+ /* Preallocate enough space for file's eventual length if
+ * possible; seems to reduce fragmentation on Windows. */
+ STRUCT_STAT srcst;
+ if (do_fstat(ifd, &srcst) == 0) {
+ if (srcst.st_size > 0) {
+ if (do_fallocate(ofd, 0, srcst.st_size) ==
0)
+ preallocated_len = srcst.st_size;
+ else
+ rsyserr(FWARNING, errno,
"do_fallocate %s", full_fname(dest));
+ }
+ } else
+ rsyserr(FWARNING, errno, "fstat %s",
full_fname(source));
+ }
+#endif
+
while ((len = safe_read(ifd, buf, sizeof buf)) > 0) {
+#ifdef SUPPORT_PREALLOCATION
+ offset += len;
+#endif
if (full_write(ofd, buf, len) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "write %s",
full_fname(dest));
@@ -334,6 +359,16 @@ int copy_file(const char *source, const char *dest, int
ofd,
full_fname(source));
}
+#ifdef SUPPORT_PREALLOCATION
+ /* Source file might have shrunk since we fstatted it.
+ * Cut off any extra preallocated zeros from dest file. */
+ if (preallocated_len > offset)
+ if (ftruncate(ofd, offset) < 0)
+ /* If we fail to truncate, the dest file may be
wrong, so we
+ * must trigger the "partial transfer" error. */
+ rsyserr(FERROR_XFER, errno, "ftruncate %s",
full_fname(dest));
+#endif
+
if (close(ofd) < 0) {
int save_errno = errno;
rsyserr(FERROR_XFER, errno, "close failed on %s",
diff -up a/config.h.in b/config.h.in
--- a/config.h.in
+++ b/config.h.in
@@ -86,12 +86,18 @@
*/
#undef HAVE_DIRENT_H
+/* Define if posix_fallocate is efficient (Cygwin) */
+#undef HAVE_EFFICIENT_POSIX_FALLOCATE
+
/* Define to 1 if errno is declared in errno.h */
#undef HAVE_ERRNO_DECL
/* Define to 1 if you have the `extattr_get_link' function. */
#undef HAVE_EXTATTR_GET_LINK
+/* Define to 1 if you have the `fallocate' function. */
+#undef HAVE_FALLOCATE
+
/* Define to 1 if you have the `fchmod' function. */
#undef HAVE_FCHMOD
@@ -289,6 +295,9 @@
/* true if you have posix ACLs */
#undef HAVE_POSIX_ACLS
+/* Define to 1 if you have the `posix_fallocate' function. */
+#undef HAVE_POSIX_FALLOCATE
+
/* Define to 1 if you have the `putenv' function. */
#undef HAVE_PUTENV
@@ -413,6 +422,9 @@
/* Define to 1 if you have the <sys/extattr.h> header file. */
#undef HAVE_SYS_EXTATTR_H
+/* Define to 1 if you have the SYS_fallocate syscall number */
+#undef HAVE_SYS_FALLOCATE
+
/* Define to 1 if you have the <sys/fcntl.h> header file. */
#undef HAVE_SYS_FCNTL_H
diff -up a/configure.sh b/configure.sh
--- a/configure.sh
+++ b/configure.sh
@@ -14796,13 +14796,15 @@ fi
+
+
for ac_func in waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo
\
fchmod fstat ftruncate strchr readlink link utime utimes lutimes
strftime \
memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
strerror putenv iconv_open locale_charset nl_langinfo getxattr \
- extattr_get_link sigaction sigprocmask setattrlist
+ extattr_get_link sigaction sigprocmask setattrlist fallocate
posix_fallocate
do
as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
{ echo "$as_me:$LINENO: checking for $ac_func" >&5
@@ -14988,6 +14990,87 @@ fi
fi
+{ echo "$as_me:$LINENO: checking for SYS_fallocate" >&5
+echo $ECHO_N "checking for SYS_fallocate... $ECHO_C" >&6; }
+if test "${rsync_cv_have_sys_fallocate+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+
+cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h. */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h. */
+#include <sys/syscall.h>
+#include <sys/types.h>
+int
+main ()
+{
+syscall(SYS_fallocate, 0, 0, (loff_t) 0, (loff_t) 0);
+ ;
+ return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext
+if { (ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
+ (eval "$ac_compile") 2>conftest.er1
+ ac_status=$?
+ grep -v '^ *+' conftest.er1 >conftest.err
+ rm -f conftest.er1
+ cat conftest.err >&5
+ echo "$as_me:$LINENO: \$? = $ac_status" >&5
+ (exit $ac_status); } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then
+ rsync_cv_have_sys_fallocate=yes
+else
+ echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ rsync_cv_have_sys_fallocate=no
+fi
+
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ echo "$as_me:$LINENO: result: $rsync_cv_have_sys_fallocate" >&5
+echo "${ECHO_T}$rsync_cv_have_sys_fallocate" >&6; }
+if test x"$rsync_cv_have_sys_fallocate" = x"yes"; then
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_SYS_FALLOCATE 1
+_ACEOF
+
+fi
+
+if test x"$ac_cv_func_posix_fallocate" = x"yes"; then
+ { echo "$as_me:$LINENO: checking whether posix_fallocate is efficient"
>&5
+echo $ECHO_N "checking whether posix_fallocate is efficient... $ECHO_C"
>&6; }
+ case $host_os in
+ *cygwin*)
+ { echo "$as_me:$LINENO: result: yes" >&5
+echo "${ECHO_T}yes" >&6; }
+
+cat >>confdefs.h <<\_ACEOF
+#define HAVE_EFFICIENT_POSIX_FALLOCATE 1
+_ACEOF
+
+ ;;
+ *)
+ { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+ ;;
+ esac
+fi
+
+
+
for ac_func in getpgrp tcgetpgrp
do
diff -up a/proto.h b/proto.h
--- a/proto.h
+++ b/proto.h
@@ -309,6 +309,7 @@ int do_stat(const char *fname, STRUCT_ST
int do_lstat(const char *fname, STRUCT_STAT *st);
int do_fstat(int fd, STRUCT_STAT *st);
OFF_T do_lseek(int fd, OFF_T offset, int whence);
+int do_fallocate(int fd, OFF_T offset, OFF_T length);
void set_compression(const char *fname);
void send_token(int f, int32 token, struct map_struct *buf, OFF_T offset,
int32 n, int32 toklen);
diff -up a/rsync.1 b/rsync.1
--- a/rsync.1
+++ b/rsync.1
@@ -427,6 +427,7 @@ to the detailed description below for a
\-\-super receiver attempts super-user activities
\-\-fake\-super store/recover privileged attrs using xattrs
\-S, \-\-sparse handle sparse files efficiently
+ \-\-preallocate allocate dest files before writing
\-n, \-\-dry\-run perform a trial run with no changes made
\-W, \-\-whole\-file copy files whole (w/o delta-xfer
algorithm)
\-x, \-\-one\-file\-system don't cross filesystem boundaries
@@ -1206,6 +1207,19 @@ NOTE: Don't use this option when the des
filesystem. It doesn't seem to handle seeks over null regions
correctly and ends up corrupting the files.
.IP
+.IP "\fB\-\-preallocate\fP"
+This tells the receiver to allocate each destination
+file to its eventual size before writing data to the file. Rsync will only
use
+the real filesystem-level preallocation support provided by
\fBfallocate\fP(2) or
+Cygwin's \fBposix_fallocate\fP(3), not the slow glibc implementation that
writes
+a zero byte into each block. If the receiver is remote, this nonstandard
+option only works if the receiver also has the preallocation patch.
+.IP
+Without this option on MS Windows, very large destination files tend to be
+broken into thousands of fragments; advising Windows ahead of time of the
+eventual file size using this option usually reduces the number of
+fragments to one. The usefulness of this option on Linux is yet to be
tested.
+.IP
.IP "\fB\-n, \-\-dry\-run\fP"
This makes rsync perform a trial run that doesn't
make any changes (and produces mostly the same output as a real run). It
--
Unsubscribe info: http://cygwin.com/ml/#unsubscribe-simple
Problem reports: http://cygwin.com/problems.html
Documentation: http://cygwin.com/docs.html
FAQ: http://cygwin.com/faq/
- Raw text -