//
// aegis - project change supervisor
// Copyright (C) 2001-2008, 2011, 2012, 2014 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int show_removed_files = -1;
int show_dot_files = -1;
int recursive_flag;
int long_flag = 0;
int mode_flag = -1;
static output::pointer mode_col;
int attr_flag = -1;
static output::pointer attr_col;
int user_flag = -1;
static output::pointer user_col;
int group_flag = -1;
static output::pointer group_col;
int size_flag = -1;
static output::pointer size_col;
int when_flag = -1;
static output::pointer when_col;
static output::pointer name_col;
static col::pointer col_ptr;
static nstring_list dirs;
static change::pointer cp;
static project *pp;
static time_t oldest;
static time_t youngest;
//
// NAME
// stat_stack
//
// SYNOPSIS
// string_ty *stat_stack(string_ty *path, struct stat *st);
//
// DESCRIPTION
// The stat_stack function is used to walk the directory search
// stack, looking for the named file. On success, the stat structure
// is filled in, and the resolved file name is returned.
//
// It is a fatal error if the file cannot be found.
//
static nstring
stat_stack(const nstring &path, struct stat &st)
{
for (size_t j = 0;; ++j)
{
nstring dir = stack_nth(j);
if (dir.empty())
break;
nstring resolved_path = os_path_cat(dir, path);
#ifdef S_IFLNK
if (!glue_lstat(resolved_path.c_str(), &st))
return resolved_path;
int errno_old = errno;
if (errno_old != ENOENT)
{
sub_context_ty sc;
sc.errno_setx(errno_old);
sc.var_set_string("File_Name", resolved_path);
sc.fatal_intl(i18n("lstat $filename: $errno"));
// NOTREACHED
}
#else
if (!glue_stat(resolved_path.c_str(), &st))
return resolved_path;
int errno_old = errno;
if (errno_old != ENOENT)
{
sub_context_ty sc;
sc.errno_setx(errno_old);
sc.var_set_string("File_Name", resolved_path);
sc.fatal_intl(i18n("stat $filename: $errno"));
// NOTREACHED
}
#endif
}
sub_context_ty sc;
sc.errno_setx(ENOENT);
sc.var_set_string("File_Name", path);
sc.fatal_intl(i18n("stat $filename: $errno"));
// NOTREACHED
return nstring();
}
//
// NAME
// readdir_stack
//
// SYNOPSIS
// void readdir_stack(string_ty *path, string_list_ty *result);
//
// DESCRIPTION
// The readdir_stack function is used to walk the directory search
// stack reading the named directory at each level. The results are
// "unioned" together to provide a single view.
//
static void
readdir_stack(const nstring &path, nstring_list &result)
{
result.clear();
for (size_t j = 0;; ++j)
{
nstring dir = stack_nth(j);
if (!dir)
break;
nstring s = os_path_cat(dir, path);
if (read_whole_dir__wla(s, result))
{
int errno_old = errno;
if (errno_old == ENOENT)
{
continue;
}
sub_context_ty *scp = sub_context_new();
sub_errno_setx(scp, errno_old);
sub_var_set_string(scp, "File_Name", path);
fatal_intl(scp, i18n("read $filename: $errno"));
// NOTREACHED
}
}
}
static void
print_mode_column(const struct stat &st)
{
//
// First the type indicator
//
if (S_ISDIR(st.st_mode))
mode_col->fputc('d');
else if (S_ISCHR(st.st_mode))
mode_col->fputc('c');
else if (S_ISBLK(st.st_mode))
mode_col->fputc('b');
#ifdef S_ISFIFO
else if (S_ISFIFO(st.st_mode))
mode_col->fputc('p');
#endif
#ifdef S_ISLNK
else if (S_ISLNK(st.st_mode))
mode_col->fputc('l');
#endif
#ifdef S_ISSOCK
else if (S_ISSOCK(st.st_mode))
mode_col->fputc('s');
#endif
else
mode_col->fputc('-');
//
// Now the user bits
//
if (st.st_mode & S_IRUSR)
mode_col->fputc('r');
else
mode_col->fputc('-');
if (st.st_mode & S_IWUSR)
mode_col->fputc('w');
else
mode_col->fputc('-');
if (st.st_mode & S_IXUSR)
{
if (st.st_mode & S_ISUID)
mode_col->fputc('s');
else
mode_col->fputc('x');
}
else
{
if (st.st_mode & S_ISUID)
mode_col->fputc('S');
else
mode_col->fputc('-');
}
//
// Now the group bits
//
if (st.st_mode & S_IRGRP)
mode_col->fputc('r');
else
mode_col->fputc('-');
if (st.st_mode & S_IWGRP)
mode_col->fputc('w');
else
mode_col->fputc('-');
if (st.st_mode & S_IXGRP)
{
if (st.st_mode & S_ISGID)
mode_col->fputc('s');
else
mode_col->fputc('x');
}
else
{
if (st.st_mode & S_ISGID)
mode_col->fputc('S');
else
mode_col->fputc('-');
}
//
// Now the other bits
//
if (st.st_mode & S_IROTH)
mode_col->fputc('r');
else
mode_col->fputc('-');
if (st.st_mode & S_IWOTH)
mode_col->fputc('w');
else
mode_col->fputc('-');
if (st.st_mode & S_IXOTH)
{
#ifdef S_ISVTX
if (st.st_mode & S_ISVTX)
mode_col->fputc('t');
else
#endif
mode_col->fputc('x');
}
else
{
#ifdef S_ISVTX
if (st.st_mode & S_ISVTX)
mode_col->fputc('T');
else
#endif
mode_col->fputc('-');
}
}
//
// NAME
// list_file
//
// SYNOPSIS
// void list_file(string_ty *long_name, string-ty *short_name,
// struct stat *st);
//
// DESCRIPTION
// The list_file function is used to print the information about
// the file onto the columnised output.
//
static void
list_file(const nstring &long_name, const nstring &short_name, struct stat &st,
const nstring &resolved_name)
{
fstate_src_ty *c_src =
cp ? cp->file_find(long_name, view_path_first) : 0;
if (c_src && c_src->about_to_be_created_by)
c_src = 0;
if (c_src)
{
switch (c_src->action)
{
case file_action_remove:
if (show_removed_files <= 0)
{
return;
}
break;
case file_action_create:
case file_action_modify:
case file_action_insulate:
case file_action_transparent:
#ifndef DEBUG
default:
#endif
// should be file_action_remove
assert(!c_src->deleted_by);
if (c_src->deleted_by)
{
if (show_removed_files <= 0)
{
return;
}
}
break;
}
}
fstate_src_ty *p_src = pp->file_find(long_name.get_ref(), view_path_simple);
if (p_src)
{
assert(!p_src->about_to_be_created_by); // hidden by viewpath
switch (p_src->action)
{
case file_action_remove:
if (show_removed_files <= 0)
{
return;
}
break;
case file_action_create:
case file_action_modify:
case file_action_insulate:
case file_action_transparent:
#ifndef DEBUG
default:
#endif
// should be file_action_remove
assert(!p_src->deleted_by);
if (p_src->deleted_by)
{
if (show_removed_files <= 0)
{
return;
}
}
break;
}
}
nstring link;
if (mode_col)
{
if (S_ISLNK(st.st_mode))
{
//
// If a link points to a relative file of exactly
// the same name, then it's a link to a baseline.
// Pretend it isn't a line.
//
os_become_orig();
link = os_readlink(resolved_name);
os_become_undo();
if (link[0] != '/')
{
os_become_orig();
link = os_path_cat(os_dirname(resolved_name), link);
os_become_undo();
}
nstring s2 = stack_relative(link);
if (!s2.empty())
{
if (s2 == long_name)
{
// nuke the link-ness
st.st_mode = (st.st_mode & ~S_IFMT) | S_IFREG;
link.clear();
}
}
}
print_mode_column(st);
}
if (attr_col)
{
if (c_src)
attr_col->fputc('C');
else if (p_src)
attr_col->fputc('P');
else
attr_col->fputc('-');
if (c_src)
{
char action_indicator = '?';
switch (c_src->action)
{
case file_action_create:
action_indicator = 'c';
break;
case file_action_modify:
action_indicator = 'm';
break;
case file_action_remove:
action_indicator = 'r';
break;
case file_action_insulate:
action_indicator = 'i';
break;
case file_action_transparent:
action_indicator = 't';
break;
}
attr_col->fputc(action_indicator);
}
else
{
attr_col->fputc('-');
}
fstate_src_ty *src = c_src ? c_src : p_src;
if (src)
{
char usage_indicator = '?';
switch (src->usage)
{
case file_usage_source:
usage_indicator = 's';
break;
case file_usage_config:
usage_indicator = 'c';
break;
case file_usage_build:
usage_indicator = 'b';
break;
case file_usage_test:
usage_indicator = 't';
break;
case file_usage_manual_test:
//
// should this be 'm' ?
// all the others are lower case
//
usage_indicator = 'T';
break;
}
attr_col->fputc(usage_indicator);
}
else
{
attr_col->fputc('-');
}
}
if (user_col)
{
struct passwd *pw = getpwuid_cached(st.st_uid);
if (pw)
user_col->fputs(pw->pw_name);
else
user_col->fprintf("%d", (int)st.st_uid);
}
if (group_col)
{
struct group *gr = getgrgid_cached(st.st_uid);
if (gr)
group_col->fputs(gr->gr_name);
else
group_col->fprintf("%d", (int)st.st_gid);
}
if (size_col)
{
size_col->fprintf("%8ld", (long)st.st_size);
}
if (when_col)
{
struct tm *the_time = localtime(&st.st_mtime);
char buffer[100];
if (st.st_mtime < oldest || st.st_mtime > youngest)
strftime(buffer, sizeof(buffer), "%b %d %Y", the_time);
else
strftime(buffer, sizeof(buffer), "%b %d %H:%M", the_time);
when_col->fputs(buffer);
}
//
// output the name
//
name_col->fputs(short_name);
if (!link.empty() && long_flag)
{
name_col->fputs(" -> ");
name_col->fputs(link);
}
col_ptr->eoln();
}
//
// NAME
// list_dir
//
// SYNOPSIS
// void list_dir(string_ty *dirname);
//
// DESCRIPTION
// The list_dir function is used to list the contents of a directory
// onto the columnised output.
//
static void
list_dir(const nstring &dirname)
{
os_become_orig();
nstring_list wl;
readdir_stack(dirname, wl);
os_become_undo();
wl.sort();
nstring_list more_dirs;
for (size_t j = 0; j < wl.size(); ++j)
{
if (show_dot_files <= 0 && wl[j][0] == '.')
continue;
nstring s = os_path_cat(dirname, wl[j]);
os_become_orig();
struct stat st;
nstring resolved_path = stat_stack(s, st);
os_become_undo();
if (recursive_flag && (st.st_mode & S_IFMT) == S_IFDIR)
more_dirs.push_back(s);
list_file(s, wl[j], st, resolved_path);
}
dirs.push_front(more_dirs);
}
//
// NAME
// list
//
// SYNOPSIS
// void list(string_list_ty *paths);
//
// DESCRIPTION
// The list function is used to list the named files and the contents
// of the named directories onto the columnised output.
//
void
list(const nstring_list &paths, project *a_pp, const change::pointer &a_cp)
{
time_t when = now();
oldest = when - 6L * 30 * 24 * 60 * 60;
youngest = when + 6L * 30 * 24 * 60 * 60;
pp = a_pp;
cp = a_cp;
if (long_flag > 0)
{
if (mode_flag < 0)
mode_flag = 1;
if (attr_flag < 0)
attr_flag = 1;
if (user_flag < 0)
user_flag = 1;
if (group_flag < 0)
group_flag = 1;
if (size_flag < 0)
size_flag = 1;
if (when_flag < 0)
when_flag = 1;
}
col_ptr = col::open((string_ty *)0);
int column = 0;
if (mode_flag > 0)
{
int width = 10;
mode_col = col_ptr->create(column, column + width, "Mode");
column += width + 1;
}
if (attr_flag)
{
int width = 4;
attr_col = col_ptr->create(column, column + width, "Attr");
column += width + 1;
}
if (user_flag > 0)
{
int width = 8;
user_col =
col_ptr->create(column, column + width, "User\n--------");
column += width + 1;
}
if (group_flag > 0)
{
int width = 8;
group_col =
col_ptr->create(column, column + width, "Group\n--------");
column += width + 1;
}
if (size_flag > 0)
{
int width = 8;
size_col =
col_ptr->create(column, column + width, "Size\n--------");
column += width + 1;
}
if (when_flag > 0)
{
int width = 12;
when_col =
col_ptr->create(column, column + width, "When\n------------");
column += width + 1;
}
name_col = col_ptr->create(column, 0, "File Name\n-----------");
nstring t1 = "Project " + nstring(pp->name_get()).quote_c();
if (cp)
t1 += nstring::format(", Change %ld", cp->number_get_mzd());
nstring t2 = "Annotated Listing";
col_ptr->title(t1, t2);
bool need_eject = false;
for (size_t j = 0; j < paths.size(); ++j)
{
nstring path = paths[j];
os_become_orig();
struct stat st;
nstring resolved_path = stat_stack(path, st);
os_become_undo();
if ((st.st_mode & S_IFMT) == S_IFDIR)
dirs.push_back(path);
else
{
list_file(path, path, st, resolved_path);
need_eject = true;
}
}
while (!dirs.empty())
{
if (need_eject)
col_ptr->eject();
nstring path = dirs[0];
dirs.remove(path);
col_ptr->title
(
t1,
"Annotated " + path.quote_c() + " Directory Listing"
);
list_dir(path);
need_eject = true;
}
mode_col.reset();
attr_col.reset();
user_col.reset();
group_col.reset();
size_col.reset();
when_col.reset();
name_col.reset();
col_ptr.reset();
}
// vim: set ts=8 sw=4 et :