diff options
| author | James Youngman <jay@gnu.org> | 2014-07-27 16:50:55 +0100 |
|---|---|---|
| committer | James Youngman <jay@gnu.org> | 2014-07-28 01:18:11 +0100 |
| commit | cb5e4ca42dc01103228d662911be5144da4cb2d5 (patch) | |
| tree | 3173dd4997828fec0628520670e73546f1fb6b65 | |
| parent | a1b826653c30c19285989a45156bddd4630d9b98 (diff) | |
| download | cssc-cb5e4ca42dc01103228d662911be5144da4cb2d5.tar.gz | |
Support executable history files (=> g-files executable on get).
* src/sf-admin.cc (create): Open any file specified with the -i
command-line option before calling start_update, and using the
open file pointer, call set_sfile_executable if the history file
should be created with execute bits set.
* src/sf-write.cc (start_update): Use sfile_should_be_executable
rather than flags.executable in order to decide whether the
updated history file should be executable.
* NEWS: Mention that the 'x' flag no longer determines the mode of
the updated history file.
* src/sccsfile.h (sfile_executable): new boolean member variable.
If true, the history file, when we create it, should be
executable.
(set_sfile_executable): setter for sfile_executable.
(sfile_should_be_executable): getter for sfile_executable.
(gfile_should_be_executable): new method; when true, the gotten
file should be executable.
* src/sccsfile.cc (sccs_file): Point out that setting the 'x' flag
is not the most portable way to make gotten files executable.
(set_sfile_executable): Implement (sets the sfile_executable
member variable).
(gfile_should_be_executable): Implement (when this method returns
true, the gotten file should be executable).
(sfile_should_be_executable): Implement (when this method returns
true, the (updated) history file should be executable).
(sccs_file): If the history file is executable, remember this
so that the gotten file is created with the relevant execute bits
set.
* src/get.cc (main): The gotten file should be executable if
sccs_file::gfile_should_be_executable() returns true (that is, if
either the 'x' flag is set or the history file is executable).
* src/sf-delta.cc (add_delta): When greating gotten files (for
diffing) use gfile_should_be_executable rather than
flags.executable.
* docs/cssc.texi (Executable File Support): Document the fact that
the execute bits of the history file's mode are now copied to the
gotten file's mode (subject to umask) and in the reverse direction
in the case of admin -i.
(admin): Document the same behaviour.
(Flags): Mention the mode of the history file as an alternative to
setting the 'x' flag.
(SCCS Version Differences): Mention this behaviour (which follows
Solaris) for admin and get.
(Data Keywords): Add missing comma after @xref.
* tests/admin/executable.sh: New test file; verifies that the
execute bits of the file offered via admin -i are copied to the
history file. Verify that setting the 'x' flag cuases the g-file
to be created executable, and that not setting it does not (when
the history file is not executable). Verify that the history file
can still be set as executable when the initial body is presented
on stdin.
* tests/common/test-common (docommand): pass through the exit
status of the child command. Log the return value, stdout and
stderr in command.log.
* src/file.cc (get_mode_bits): New function; Factors stat call out
of is_writable.
(get_open_file_xbits): New function; populate an output flag as
true IFF the FILE* argument is open on a file which has an execute
bit set.
* src/file.h: Declare get_open_file_xbits.
* tests/get/executable.sh: New test file; verify that the
executable bit is copied from the history file to the gotten
file.
| -rw-r--r-- | ChangeLog | 66 | ||||
| -rw-r--r-- | NEWS | 5 | ||||
| -rw-r--r-- | docs/cssc.texi | 48 | ||||
| -rw-r--r-- | src/file.cc | 51 | ||||
| -rw-r--r-- | src/file.h | 1 | ||||
| -rw-r--r-- | src/get.cc | 6 | ||||
| -rw-r--r-- | src/sccsfile.cc | 37 | ||||
| -rw-r--r-- | src/sccsfile.h | 6 | ||||
| -rw-r--r-- | src/sf-admin.cc | 107 | ||||
| -rw-r--r-- | src/sf-delta.cc | 8 | ||||
| -rw-r--r-- | src/sf-write.cc | 5 | ||||
| -rw-r--r-- | tests/admin/executable.sh | 237 | ||||
| -rw-r--r-- | tests/common/test-common | 10 | ||||
| -rw-r--r-- | tests/get/executable.sh | 44 |
14 files changed, 542 insertions, 89 deletions
@@ -1,12 +1,74 @@ 2014-07-28 James Youngman <jay@gnu.org> + Support executable history files (=> g-files executable on get). + * src/sf-admin.cc (create): Open any file specified with the -i + command-line option before calling start_update, and using the + open file pointer, call set_sfile_executable if the history file + should be created with execute bits set. + * src/sf-write.cc (start_update): Use sfile_should_be_executable + rather than flags.executable in order to decide whether the + updated history file should be executable. + * NEWS: Mention that the 'x' flag no longer determines the mode of + the updated history file. + * src/sccsfile.h (sfile_executable): new boolean member variable. + If true, the history file, when we create it, should be + executable. + (set_sfile_executable): setter for sfile_executable. + (sfile_should_be_executable): getter for sfile_executable. + (gfile_should_be_executable): new method; when true, the gotten + file should be executable. + * src/sccsfile.cc (sccs_file): Point out that setting the 'x' flag + is not the most portable way to make gotten files executable. + (set_sfile_executable): Implement (sets the sfile_executable + member variable). + (gfile_should_be_executable): Implement (when this method returns + true, the gotten file should be executable). + (sfile_should_be_executable): Implement (when this method returns + true, the (updated) history file should be executable). + (sccs_file): If the history file is executable, remember this so + that the gotten file is created with the relevant execute bits + set. + * src/get.cc (main): The gotten file should be executable if + sccs_file::gfile_should_be_executable() returns true (that is, if + either the 'x' flag is set or the history file is executable). + * src/sf-delta.cc (add_delta): When greating gotten files (for + diffing) use gfile_should_be_executable rather than + flags.executable. + * docs/cssc.texi (Executable File Support): Document the fact that + the execute bits of the history file's mode are now copied to the + gotten file's mode (subject to umask) and in the reverse direction + in the case of admin -i. + (admin): Document the same behaviour. + (Flags): Mention the mode of the history file as an alternative to + setting the 'x' flag. + (SCCS Version Differences): Mention this behaviour (which follows + Solaris) for admin and get. + (Data Keywords): Add missing comma after @xref. + * tests/admin/executable.sh: New test file; verifies that the + execute bits of the file offered via admin -i are copied to the + history file. Verify that setting the 'x' flag cuases the g-file + to be created executable, and that not setting it does not (when + the history file is not executable). Verify that the history file + can still be set as executable when the initial body is presented + on stdin. + * tests/common/test-common (docommand): pass through the exit + status of the child command. Log the return value, stdout and + stderr in command.log. + * src/file.cc (get_mode_bits): New function; Factors stat call out + of is_writable. + (get_open_file_xbits): New function; populate an output flag as + true IFF the FILE* argument is open on a file which has an execute + bit set. + * src/file.h: Declare get_open_file_xbits. + * tests/get/executable.sh: New test file; verify that the + executable bit is copied from the history file to the gotten + file. + Process test files in a consistent order for a case where this matters. * tests/get/middle-fail.sh (e3): List the history files explicitly so that we know that SCCS/s.b is definitely the second file of three processed. -2014-07-27 James Youngman <jay@gnu.org> - Show the label of the test command in the command.log file. * tests/common/test-common (docommand): Show the label for the current command in command.log. @@ -1,5 +1,10 @@ New in CSSC-1.4.1-rc1, 2014-MM-DD + * When the history file is updated (with admin or delta for + example) the history file is not made executable simply + because the 'x' flag is set. Instead, preserve the + executable-ness from the history file we are replacing. + New in CSSC-1.4.0, 2014-07-11 * New stable release, almost identical to the test release diff --git a/docs/cssc.texi b/docs/cssc.texi index 9baf615..089163a 100644 --- a/docs/cssc.texi +++ b/docs/cssc.texi @@ -185,6 +185,13 @@ This creates the archive file @file{s.foo.c} and initialises it with the current contents of your source file, @file{foo.c}. If you use Emacs as your editor, you can just use @kbd{C-x v i} instead. +@cindex umask +@cindex Solaris +When the input file @file{foo.c} is executable, the resulting history +file @file{s.foo.c} is also executable, subject to the current umask +setting. This behaviour follows that of Solaris; other @sc{sccs} +implementations may not do this. + Another frequently-used option is @samp{-b}, which indicates that the file is to be treated as a binary file rather than as text. You might want to do this because the file actually contains binary data, or just @@ -360,7 +367,9 @@ Sets the executable bit on the g-file. This flag is a SCO OpenServer extension and is not supported by other versions of @sc{sccs}. Setting this flag with @code{admin -fx} generates a warning to this effect. If @sc{cssc} is simply processing a file which already has -this flag set, no message will be generated. See +this flag set, no message will be generated. A more portable way to +achieve the same effect is to change the file mode of the history file +to make it executable. See @ref{Interoperability} for more information on compatibility between @sc{cssc} and other implementations of @sc{sccs}. @end table @@ -1280,7 +1289,7 @@ the excluded sequence numbers are printed. If there are ignored deltas, @samp{/} is printed as a separator and then the ignored sequence numbers are printed. This means it's difficult to tell from the result of @samp{:DI:} whether the sequence numbers after the last -@samp{/} were excluded or ignored. @xref{SCCS Version Differences} +@samp{/} were excluded or ignored. @xref{SCCS Version Differences}, for a brief description of how @samp{:DI:} expands to different things on different @sc{sccs} implmentations. If you need reliable information, then you will need to use something like @@ -2501,7 +2510,7 @@ but do not need to be turned off. @menu * Binary File Support:: Enabling or disabling support for binary files. -* Executable File Support:: Support for a SCO extension. +* Executable File Support:: Control over the excutable bits in file modes. * BitKeeper:: Limited support for Bitkeeper files exists. * Maximum Line Length:: Limiting the length of lines in the SCCS file. * Limitations of diff:: Diff has limits too, on many non-GNU systems. @@ -2551,6 +2560,25 @@ OpenServer's @sc{sccs}. Do not use this feature if you wish to interoperate with other implementations of @sc{sccs}. Setting this flag with @code{admin -fx} generates a warning about this. +@cindex umask +@cindex Solaris +A more portable alternative is for the history file itself to be +executable. When this is the case, the mode of the gotten file is +also executable (subject to the current umask value). When a history +file is created with @code{admin -i}, the history file is created with +execute bits set (subject to the current umask value) if the input +file is executable. This usage of @code{admin -i} does not set the +@code{x} flag in the history file. + +When no argument is specified to the @samp{-i} option of @code{admin}, +the initial file body is taken from stdin. If stdin is redirected +from an file with an execute permission bit set in its mode, the +history file will also be executable (subject to the umask setting). + +This behaviour (of setting and using the execute permissions on the +history file) follows that of Solaris; other @sc{sccs} implementations +may not do this. + @node BitKeeper, Maximum Line Length, Executable File Support, Interoperability @section BitKeeper @cindex BitKeeper @@ -2803,6 +2831,16 @@ feature. While @sc{csssc} provides this feature also, its use is not recommended. The @code{prt -f} command does not indicate the value of the @code{x} flag. +@cindex Solaris +The Solaris implementation of @sc{sccs} copies the execute bits from +the mode of the history file to the mode of the gotten file. It also +does a similar thing for @code{admin -i}. This feature is also +implemented in @sc{cssc}, though other implementations of @sc{sccs} +may not do this. The mode of the gotten file (and the history file +for @code{admin -i} is still controlled by the vurrent umask setting. + +@ref{Executable File Support}. + @item Initial delta The @option{-r} option is used to specify the release of the initial delta. Some implementations of @sc{sccs} allow this to be used to @@ -2833,6 +2871,10 @@ in the @sc{sccs} file, only the specified @sc{sccs} keywords will be expanded in the gotten file (assuming that the @option{-k} and @option{-e} options are not used). +Solaris and @sc{cssc} set the execute bits of the gotten file if the +history file is executable. Other versions of @sc{sccs} may or may +not do this. + @cindex SCO The @code{get} command on SCO OpenServer honours the setting of the @code{x} flag. This is described above. diff --git a/src/file.cc b/src/file.cc index 30a41d9..c2c4512 100644 --- a/src/file.cc +++ b/src/file.cc @@ -33,8 +33,9 @@ #include "ioerr.h" #include "defaults.h" -#include <unistd.h> +#include <sys/types.h> #include <sys/stat.h> +#include <unistd.h> #include <stdio.h> #include "dirent-safer.h" @@ -111,6 +112,35 @@ is_readable(const char *name) { return access(name, 04) != -1; } +static bool +get_mode_bits(const char *filename, mode_t mask, mode_t *result) +{ + struct stat st; + if (0 != stat(filename, &st)) + return false; + + *result = (st.st_mode & mask); + return true; +} + +bool +get_open_file_xbits (FILE *f, bool *is_executable) +{ + const int fd = fileno(f); + if (fd < 0) + return false; + + struct stat st; + if (0 != fstat(fd, &st)) + return false; + + *is_executable = st.st_mode & 0111; + return true; +} + + + + /* Determine if a given file is "writable". If we * are root, we can write to a mode 000 file, but * we deem files of that sort not writable for these @@ -126,22 +156,12 @@ is_readable(const char *name) { static int is_writable(const char *filename, int /* as_real_user = 1 */ ) { - struct stat st; - if (0 != stat(filename, &st)) - { - return 0; // can't stat it so can't read it, probably. - } - else - { - if (st.st_mode & 0222) - return 1; // at least one of the write bits is set. - else - return 0; // no write bits set. - } - + mode_t bits; + if (!get_mode_bits(filename, 0222, &bits)) + return 0; // cannot tell + return bits ? 1 : 0; } - /* Returns true if the file exists. */ int @@ -621,7 +641,6 @@ create(mystring name, int mode) { if (mode & CREATE_EXECUTABLE) { - // A SCO extension. perms |= 0111; } @@ -52,6 +52,7 @@ FILE *open_null(); int is_readable(const char *name); int is_directory(const char *name); int file_exists(const char *name); +bool get_open_file_xbits (FILE *f, bool *is_executable); const char *get_user_name(); int user_is_group_member(gid_t gid); FILE *fcreate(mystring name, int mode); @@ -419,7 +419,7 @@ main(int argc, char **argv) mode |= CREATE_READ_ONLY; } - if (file.executable_flag_set()) + if (file.gfile_should_be_executable()) { mode |= CREATE_EXECUTABLE; } @@ -480,7 +480,7 @@ main(int argc, char **argv) if (suppress_keywords) { if (!set_gfile_writable(gname, true, - file.executable_flag_set())) + file.gfile_should_be_executable())) retval = 1; maybe_clear_archive_bit(gname); } @@ -492,7 +492,7 @@ main(int argc, char **argv) */ give_up_privileges(); if (!set_gfile_writable(gname, false, - file.executable_flag_set())) + file.gfile_should_be_executable())) status.success = false; restore_privileges(); } diff --git a/src/sccsfile.cc b/src/sccsfile.cc index a5697a1..d698509 100644 --- a/src/sccsfile.cc +++ b/src/sccsfile.cc @@ -35,6 +35,7 @@ #include "quit.h" #include "mylist.h" #include "ioerr.h" +#include "file.h" #include <ctype.h> #include <unistd.h> // SEEK_SET on SunOS. @@ -767,6 +768,13 @@ sccs_file::sccs_file(sccs_name &n, enum _mode m) ctor_fail(-1, "%s: Cannot open SCCS file.\n", name.c_str()); } + // If the history file is executable, remember this fact. + bool is_executable; + if (get_open_file_xbits(f, &is_executable)) + { + set_sfile_executable (is_executable); + } + char c; read_line(&c); // FIXME: check for EOF @@ -956,6 +964,8 @@ sccs_file::sccs_file(sccs_name &n, enum _mode m) case 'x': // The 'x' flag is supported by SCO's version of SCCS. // When this flag is set, the g-file is marked executable. + // The g-file is also executable when the s-file is executable + // (to follow the example of Solaris). flags.executable = 1; break; @@ -1239,12 +1249,6 @@ bool sccs_file::branches_allowed() const return 0 != flags.branch; } -bool sccs_file::executable_flag_set() const -{ - return 0 != flags.executable; -} - - /* There are some features that we don't properly understand. * If we see them, we should abandon any attempt to modify the * file. We call saw_unknown_feature() when we see one. It @@ -1303,6 +1307,27 @@ is_known_keyword_char(char c) return strchr("MIRLBSDHTEGUYFPQCZWA", c) != NULL; } +void sccs_file:: +set_sfile_executable(bool state) +{ + sfile_executable = state; +} + +bool sccs_file:: +gfile_should_be_executable() const +{ + return sfile_executable || flags.executable; +} + + +bool sccs_file:: +sfile_should_be_executable() const +{ + return sfile_executable; +} + + + /* Local variables: */ /* mode: c++ */ /* End: */ diff --git a/src/sccsfile.h b/src/sccsfile.h index 970c0cd..0331872 100644 --- a/src/sccsfile.h +++ b/src/sccsfile.h @@ -59,6 +59,7 @@ private: int body_lineno; bool xfile_created; bool is_bk_file; + bool sfile_executable; cssc_delta_table *delta_table; cssc_linebuf *plinebuf; @@ -107,6 +108,9 @@ private: void check_bk_comment(char ch, char arg) const; bool edit_mode_ok(bool editing) const; + void set_sfile_executable(bool state); + bool sfile_should_be_executable() const; + public: // sccs_file::sccs_file(sccs_name&, enum _mode) MUST @@ -265,11 +269,11 @@ public: void set_reserved_flag(const char *s); void set_expanded_keyword_flag(const char *s); void set_type_flag(const char *s); + bool gfile_should_be_executable() const; /* Used by get.cc (implemented in sccsfile.cc) */ bool branches_allowed() const; - bool executable_flag_set() const; /* val.cc */ const mystring get_module_type_flag(); diff --git a/src/sf-admin.cc b/src/sf-admin.cc index cb6c78b..de64a68 100644 --- a/src/sf-admin.cc +++ b/src/sf-admin.cc @@ -374,64 +374,73 @@ sccs_file::create(const sid &id, ASSERT (new_delta.deleted() == 0); ASSERT (new_delta.unchanged() == 0); + bool ret = true; + + FILE *in = NULL; + if (iname != NULL) + { + if (strcmp(iname, "-") == 0) + { + in = stdin; + } + else + { + in = fopen(iname, "r"); + if (NULL == in) + { + errormsg_with_errno("%s: Can't open file for reading", iname); + return false; + } + } + + bool file_is_executable = false; + if (get_open_file_xbits(in, &file_is_executable)) + set_sfile_executable(file_is_executable); + } + FILE *out = start_update(new_delta); if (NULL == out) - return false; - + { + fclose(in); + return false; + } + if (fprintf_failed(fprintf(out, "\001I 1\n"))) return false; - bool ret = true; - if (iname != NULL) + + if (NULL != in) { - FILE *in; - - if (strcmp(iname, "-") == 0) - { - in = stdin; - } - else - { - in = fopen(iname, "r"); - if (NULL == in) + bool found_id = false; + unsigned long int lines = 0uL; + + // Insert the body... + if (body_insert(&force_binary, + iname, // input file name + name.xfile().c_str(), // output file name + in, out, + &lines, &found_id)) { - errormsg_with_errno("%s: Can't open file for reading", iname); - // TODO: delete output file? - fclose(out); - return false; + new_delta.set_inserted(lines); + + if (force_binary) + flags.encoded = true; // fixup file in sccs_file::end_update() } - } - - bool found_id = false; - unsigned long int lines = 0uL; - - // Insert the body... - if (body_insert(&force_binary, - iname, // input file name - name.xfile().c_str(), // output file name - in, out, - &lines, &found_id)) - { - new_delta.set_inserted(lines); - - if (force_binary) - flags.encoded = true; // fixup file in sccs_file::end_update() - } - else - { - ret = false; - } - - if (in != stdin) - fclose(in); + else + { + ret = false; + } + + if (in != stdin) + fclose(in); - // TODO: what if no id keywords is fatal? Delete current s-file? - // If so, do we continue with the next? - if (!found_id) - { - no_id_keywords(name.c_str()); // this function normally returns. - } - } + // TODO: what if no id keywords is fatal? Delete current s-file? + // If so, do we continue with the next? + if (!found_id) + { + no_id_keywords(name.c_str()); // this function normally returns. + } + } if (fprintf_failed(fprintf(out, "\001E 1\n"))) return false; diff --git a/src/sf-delta.cc b/src/sf-delta.cc index 18bf147..286b44f 100644 --- a/src/sf-delta.cc +++ b/src/sf-delta.cc @@ -519,14 +519,12 @@ sccs_file::add_delta(mystring gname, * ID. I believe that using the flag O_EXCL as fcreate() does resolves * that problem. */ - FILE *get_out = fcreate(dname, - CREATE_EXCLUSIVE | - (flags.executable ? CREATE_EXECUTABLE : 0)); + const int xmode = gfile_should_be_executable() ? CREATE_EXECUTABLE : 0; + FILE *get_out = fcreate(dname, CREATE_EXCLUSIVE | xmode); if (NULL == get_out) { remove(dname.c_str()); - get_out = fcreate(dname, CREATE_EXCLUSIVE | - (flags.executable ? CREATE_EXECUTABLE : 0) ); + get_out = fcreate(dname, CREATE_EXCLUSIVE | xmode); } if (NULL == get_out) diff --git a/src/sf-write.cc b/src/sf-write.cc index 5b44b83..74c2d31 100644 --- a/src/sf-write.cc +++ b/src/sf-write.cc @@ -82,8 +82,7 @@ sccs_file::start_update() { // The 'x' flag is a SCO extension. - const int x = (flags.executable ? CREATE_EXECUTABLE : 0); - + const int x = sfile_should_be_executable() ? CREATE_EXECUTABLE : 0; FILE *out = fcreate(xname, CREATE_READ_ONLY | CREATE_FOR_UPDATE | x); if (out == NULL) @@ -342,6 +341,8 @@ sccs_file::write(FILE *out) const // x - executable flag (a SCO extension) + // Setting execute perms on the history file is a more portable way to + // achieve what the user probably wants. if (flags.executable) { if (printf_failed(fprintf(out, "\001f x\n"))) diff --git a/tests/admin/executable.sh b/tests/admin/executable.sh new file mode 100644 index 0000000..f2a1b7e --- /dev/null +++ b/tests/admin/executable.sh @@ -0,0 +1,237 @@ +. ../common/test-common +. ../common/real-thing + +# This test creates an empty executable file, checks it in with admin, and then +# verifies that the resulting s-file is also executable. +g=foo +s=s.$g + +cleanup() { + cmd="rm -f ${g} ${s}" + echo "${cmd}" >> command.log + $cmd +} +cleanup + +expect_args() { + function_name="$1" + expected="$2" + got="$3" + + detail="expected ${expected} arguments, but got ${got}" + if test "${got}" -lt "${expected}"; then + miscarry "Too few arguments to ${function_name}: ${detail}" + elif test "${got}" -gt "${expected}"; then + miscarry "Too many arguments to ${function_name}: ${detail}" + fi +} + +setup() { + expect_args setup 3 $# + #label="$1" + #mask="$2" + #mode="$3" + cleanup + umask "${2}" + docommand --silent "${1}-setup0" "touch ${g}" IGNORE IGNORE IGNORE || miscarry "failed to create ${g}" && + docommand --silent "${1}-setup1" "chmod ${3} ${g}" 0 "" "" + docommand "${1}" "${admin} -i${g} -n ${s}" 0 IGNORE IGNORE +} + + +should_support_execute_bits() { + expect_args should_support_execute_bits 0 $# + if "${TESTING_CSSC}"; then + true + else + case "`uname -s`" in + SunOS) true;; + *) false;; + esac + fi +} + +if should_support_execute_bits; then + # Not using ! is a workaround for the fact that Solaris /bin/sh doesn't support it. + true +else + echo "Your version of SCCS is not expected to support execute permissions; skipping." + exit 0 +fi + +file_permissions() { + expect_args file_permissions 1 $# + ls -ld "$1" 2> /dev/null | sed -n -e '1 s/^.\(.........\).*/\1/p' +} + +is_owner_executable() { + expect_args is_owner_executable 1 $# + case `file_permissions "$1"` in + ??x*) true;; + *) false;; + esac +} + +is_group_executable() { + expect_args is_group_executable 1 $# + case `file_permissions "$1"` in + ?????x*) true;; + *) false;; + esac +} + +is_other_executable() { + expect_args is_other_executable 1 $# + case `file_permissions "$1"` in + ????????x*) true;; + *) false;; + esac +} + +execute_perms() { + expect_args execute_perms 1 $# + result="" + if is_owner_executable "$1"; then + result="${result}u" + fi + if is_group_executable "$1"; then + result="${result}g" + fi + if is_other_executable "$1"; then + result="${result}o" + fi + echo "${result}" +} + +selfcheck() { + expect_args selfcheck 2 $# + chmod_mode="$1" + execute_perms_expected="$2" + shift 2 + touch "${g}" || miscarry "self-check: cannot create ${g}" + chmod "${chmod_mode}" "${g}" || miscarry "self-check: cannot chmod ${chmod_mode} ${g}" + perms="`execute_perms ${g}`" + if test "${perms}" != "${execute_perms_expected}"; then + fail "self-check: ${g} should be have execute permissions for ${execute_perms_expected} but actually has them for ${perms:-nobody}" + fi +} + + +### Some self-checks +selfcheck 0160 u +selfcheck 0616 g +selfcheck 0441 o +selfcheck 0170 ug +selfcheck 0171 ugo +selfcheck 0071 go + + +( + setup x01 0077 0700 + test -f "${g}" || miscarry "where is ${g}?" + if is_owner_executable "$g"; then + # Not using ! is a workaround for the fact that Solaris /bin/sh doesn't support it. + true + else + miscarry "Cannot create an executable file" + fi + + echo_nonl "${test_script}:x02..." + if test "`execute_perms ${s}`" = u; then + echo passed + else + fail "x02: ${s} should be owner-executable" + fi +) || rv=1 + +( + setup x03 0007 0770 + echo_nonl "${test_script}:x04..." + if test "`execute_perms ${s}`" = ug; then + echo passed + else + fail "x04: ${s} should be owner- and group-executable" + fi +) || rv=1 + +( + setup x05 0000 0777 + echo_nonl "${test_script}:x06..." + if test "`execute_perms ${s}`" = ugo; then + echo passed + else + fail "x06: ${s} should be owner-, group- and world-executable" + fi +) || rv=1 + + +# The following tests verify that we DO set execute bits on the +# history file which were not set on the original file (from which the +# history file was created). Solaris does this. +# +# The result of this is that when get is used on the resulting history +# file, that file could be world-executable even if the original file +# (offered via "admin -i") was not. It might seem logical to avoid +# following this example (since the execute bits on the original file +# may have been set that way to avoid world-execute permissions). +# However, the Principle of Least Astonishment guides us to treat the +# execute bits in the same way as the read bits are treated (all +# versions of SCCS create world-readable gotten files if the umask +# allows it). +# +# These tests check the behaviours described above. +( + setup x07 0000 0700 + + echo_nonl "${test_script}:x08..." + perms="`execute_perms ${s}`" + case "${perms}" in + ugo) echo passed;; + u|ug|ugo) fail "x08: ${s} should be mode 0777 when umask is 0 and the -i file is executable.";; + "") fail "x08: execute permissions not copied to history file at all";; + *) miscarry "unexpected execute perms ${perms} for file ${s}";; + esac || exit 1 +) || rv=1 + +# On SCO OpenServer and CSSC, the 'x' flag, if set, makes the g-file executable. +setup x09 0177 0600 +# The gfile exists but is not executable. +docommand x10 "test -x ${g}" 1 "" "" + +if docommand x11 "${admin} -fx ${s}" IGNORE IGNORE IGNORE; then + # x-flag is suported. The s-file should still not be executable, since the x flag + # controls the mode of the gotten file, not the history file. + docommand x12 "test -x ${s}" 1 "" "" + docommand x13 "rm -f ${g}" 0 "" "" + docommand x14 "${get} ${s}" 0 IGNORE IGNORE + # The gotten file should be executable, now (i.e. controlled by the x flag) + docommand x15 "test -x ${g}" 0 "" "" + # The history file should still not be executable + docommand x16 "test -x ${s}" 1 "" "" + # Unset the x-flag + docommand x17 "${admin} -dx ${s}" IGNORE IGNORE IGNORE + # The history file should still not be executable + docommand x18 "test -x ${s}" 1 "" "" + # Get the file again, verify that it exists but is not executable. + docommand x19 "${get} ${s}" 0 IGNORE IGNORE + docommand x20 "test -f ${g}" 0 "" "" + docommand x21 "test -x ${g}" 1 "" "" +else + # Cannot do the rest of the x-flag tests. + echo "Your version of SCCS does not support the 'x' flag; skipping some tests." +fi + +# Verify that we correctly set the executable bits on the history file +# when there is no argument to -i (and hence we are reading the +# initial body from stdin). +cleanup +umask 077 +docommand x22 "touch ${g}" IGNORE IGNORE IGNORE || miscarry "failed to create ${g}" && +docommand x23 "chmod 0500 ${g}" 0 "" "" +# We present the initial body via stdin. It's executable. +docommand x24 "${admin} -i -n ${s} < ${g}" 0 IGNORE IGNORE +docommand x25 "test -x ${s}" 0 "" "" + + +cleanup +exit $rv diff --git a/tests/common/test-common b/tests/common/test-common index cc97f8c..962a931 100644 --- a/tests/common/test-common +++ b/tests/common/test-common @@ -207,6 +207,9 @@ echo "# ${label}" >> command.log echo $1 >> command.log eval "$1" >got.stdout 2>got.stderr rv=$? +echo '# $? was' "$rv; $(env LC_ALL=C wc -c < got.stdout) bytes on stdout; $(env LC_ALL=C wc -c < got.stderr) bytes on stderr" >> command.log +sed -e 's/^/# stdout: /' >> command.log < got.stdout +sed -e 's/^/# stderr: /' >> command.log < got.stderr if test "$2" != "IGNORE" then @@ -248,7 +251,7 @@ fi remove last.command expected.stdout expected.stderr got.stdout got.stderr eval "$silent" || echo "passed " -true ; +( exit $rv; ) } ####################### @@ -271,6 +274,9 @@ echo "# ${label}" >> command.log echo $1 >> command.log $1 >got.stdout 2>got.stderr rv=$? +echo '# $? was' "$rv; $(env LC_ALL=C wc -c < got.stdout) bytes on stdout; $(env LC_ALL=C wc -c < got.stderr) bytes on stderr" >> command.log +sed -e 's/^/# stdout: /' >> command.log < got.stdout +sed -e 's/^/# stderr: /' >> command.log < got.stderr if test "$2" != "IGNORE" then @@ -293,5 +299,5 @@ fi remove last.command expected.stdout expected.stderr got.stdout got.stderr echo "passed " -true ; +( exit ${rv}; ) } diff --git a/tests/get/executable.sh b/tests/get/executable.sh new file mode 100644 index 0000000..01b7776 --- /dev/null +++ b/tests/get/executable.sh @@ -0,0 +1,44 @@ +. ../common/test-common +. ../common/real-thing + +g=foo +s=s.$g +p=p.$g +z=z.$g + +cleanup() { + cmd="rm -f ${g} ${s} ${p} ${z}" + echo "${cmd}" >> command.log + $cmd +} +cleanup + +# We use -i/dev/null below in order not to spuriously make the test +# suite fail on Dynix. +docommand x00 "${admin} -i/dev/null -n ${s}" 0 IGNORE IGNORE +# Get the file with the history file not executable; check that the +# gotten file is not executable. +docommand x01 "${get} ${s}" 0 IGNORE IGNORE +# Make sure the gfile exists at all first. +docommand x02 "test -f ${g}" 0 "" "" +docommand x03 "test -x ${g}" 1 "" "" + +# Make the history file executable +docommand x04 "chmod +x ${s}" 0 "" "" +docommand --silent x04-sanity "test -x ${s}" 0 "" "" + +# Check that the gotten file is now executable. +rm -f "${g}" +docommand x05 "${get} ${s}" 0 IGNORE IGNORE +# Make sure the gfile exists at all first. +docommand x06 "test -f ${g}" 0 "" "" +docommand x07 "test -x ${g}" 0 "" "" + +# get -e should also result in an executable gotten file. +rm -f "${g}" +docommand x08 "${get} -e ${s}" 0 IGNORE IGNORE +# Make sure the gfile exists at all first. +docommand x09 "test -f ${g}" 0 "" "" +docommand x10 "test -x ${g}" 0 "" "" + +cleanup |
