#include <system.h>
#include <rmt.h>
#include "common.h"
#include <quotearg.h>
#include <save-cwd.h>
#include <xgetcwd.h>
#include <unlinkdir.h>
#include <utimens.h>
#if HAVE_STROPTS_H
# include <stropts.h>
#endif
#if HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
void
assign_string (char **string, const char *value)
{
if (*string)
free (*string);
*string = value ? xstrdup (value) : 0;
}
char *
quote_copy_string (const char *string)
{
const char *source = string;
char *destination = 0;
char *buffer = 0;
int copying = 0;
while (*source)
{
int character = *source++;
switch (character)
{
case '\n': case '\\':
if (!copying)
{
size_t length = (source - string) - 1;
copying = 1;
buffer = xmalloc (length + 2 + 2 * strlen (source) + 1);
memcpy (buffer, string, length);
destination = buffer + length;
}
*destination++ = '\\';
*destination++ = character == '\\' ? '\\' : 'n';
break;
default:
if (copying)
*destination++ = character;
break;
}
}
if (copying)
{
*destination = '\0';
return buffer;
}
return 0;
}
int
unquote_string (char *string)
{
int result = 1;
char *source = string;
char *destination = string;
while (*source)
if (*source == '\\')
switch (*++source)
{
case '\\':
*destination++ = '\\';
source++;
break;
case 'a':
*destination++ = '\a';
source++;
break;
case 'b':
*destination++ = '\b';
source++;
break;
case 'f':
*destination++ = '\f';
source++;
break;
case 'n':
*destination++ = '\n';
source++;
break;
case 'r':
*destination++ = '\r';
source++;
break;
case 't':
*destination++ = '\t';
source++;
break;
case 'v':
*destination++ = '\v';
source++;
break;
case '?':
*destination++ = 0177;
source++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
{
int value = *source++ - '0';
if (*source < '0' || *source > '7')
{
*destination++ = value;
break;
}
value = value * 8 + *source++ - '0';
if (*source < '0' || *source > '7')
{
*destination++ = value;
break;
}
value = value * 8 + *source++ - '0';
*destination++ = value;
break;
}
default:
result = 0;
*destination++ = '\\';
if (*source)
*destination++ = *source++;
break;
}
else if (source != destination)
*destination++ = *source++;
else
source++, destination++;
if (source != destination)
*destination = '\0';
return result;
}
void
code_ns_fraction (int ns, char *p)
{
if (ns == 0)
*p = '\0';
else
{
int i = 9;
*p++ = '.';
while (ns % 10 == 0)
{
ns /= 10;
i--;
}
p[i] = '\0';
for (;;)
{
p[--i] = '0' + ns % 10;
if (i == 0)
break;
ns /= 10;
}
}
}
char const *
code_timespec (struct timespec t, char sbuf[TIMESPEC_STRSIZE_BOUND])
{
time_t s = t.tv_sec;
int ns = t.tv_nsec;
char *np;
bool negative = s < 0;
if (negative && ns != 0)
{
s++;
ns = BILLION - ns;
}
np = umaxtostr (negative ? - (uintmax_t) s : (uintmax_t) s, sbuf + 1);
if (negative)
*--np = '-';
code_ns_fraction (ns, sbuf + UINTMAX_STRSIZE_BOUND);
return np;
}
static char *before_backup_name;
static char *after_backup_name;
static bool
must_be_dot_or_slash (char const *file_name)
{
file_name += FILE_SYSTEM_PREFIX_LEN (file_name);
if (ISSLASH (file_name[0]))
{
for (;;)
if (ISSLASH (file_name[1]))
file_name++;
else if (file_name[1] == '.'
&& ISSLASH (file_name[2 + (file_name[2] == '.')]))
file_name += 2 + (file_name[2] == '.');
else
return ! file_name[1];
}
else
{
while (file_name[0] == '.' && ISSLASH (file_name[1]))
{
file_name += 2;
while (ISSLASH (*file_name))
file_name++;
}
return ! file_name[0] || (file_name[0] == '.' && ! file_name[1]);
}
}
static int
safer_rmdir (const char *file_name)
{
if (must_be_dot_or_slash (file_name))
{
errno = 0;
return -1;
}
return rmdir (file_name);
}
int
remove_any_file (const char *file_name, enum remove_option option)
{
bool try_unlink_first = cannot_unlink_dir ();
if (try_unlink_first)
{
if (unlink (file_name) == 0)
return 1;
if (errno != EPERM && errno != EISDIR)
return 0;
}
if (safer_rmdir (file_name) == 0)
return 1;
switch (errno)
{
case ENOTDIR:
return !try_unlink_first && unlink (file_name) == 0;
case 0:
case EEXIST:
#if defined ENOTEMPTY && ENOTEMPTY != EEXIST
case ENOTEMPTY:
#endif
switch (option)
{
case ORDINARY_REMOVE_OPTION:
break;
case WANT_DIRECTORY_REMOVE_OPTION:
return -1;
case RECURSIVE_REMOVE_OPTION:
{
char *directory = savedir (file_name);
char const *entry;
size_t entrylen;
if (! directory)
return 0;
for (entry = directory;
(entrylen = strlen (entry)) != 0;
entry += entrylen + 1)
{
char *file_name_buffer = new_name (file_name, entry);
int r = remove_any_file (file_name_buffer,
RECURSIVE_REMOVE_OPTION);
int e = errno;
free (file_name_buffer);
if (! r)
{
free (directory);
errno = e;
return 0;
}
}
free (directory);
return safer_rmdir (file_name) == 0;
}
}
break;
}
return 0;
}
bool
maybe_backup_file (const char *file_name, bool this_is_the_archive)
{
struct stat file_stat;
if (this_is_the_archive && _remdev (file_name))
return true;
if (stat (file_name, &file_stat))
{
if (errno == ENOENT)
return true;
stat_error (file_name);
return false;
}
if (S_ISDIR (file_stat.st_mode))
return true;
if (this_is_the_archive
&& (S_ISBLK (file_stat.st_mode) || S_ISCHR (file_stat.st_mode)))
return true;
assign_string (&before_backup_name, file_name);
assign_string (&after_backup_name, 0);
after_backup_name = find_backup_file_name (file_name, backup_type);
if (! after_backup_name)
xalloc_die ();
if (rename (before_backup_name, after_backup_name) == 0)
{
if (verbose_option)
fprintf (stdlis, _("Renaming %s to %s\n"),
quote_n (0, before_backup_name),
quote_n (1, after_backup_name));
return true;
}
else
{
int e = errno;
ERROR ((0, e, _("%s: Cannot rename to %s"),
quotearg_colon (before_backup_name),
quote_n (1, after_backup_name)));
assign_string (&after_backup_name, 0);
return false;
}
}
void
undo_last_backup (void)
{
if (after_backup_name)
{
if (rename (after_backup_name, before_backup_name) != 0)
{
int e = errno;
ERROR ((0, e, _("%s: Cannot rename to %s"),
quotearg_colon (after_backup_name),
quote_n (1, before_backup_name)));
}
if (verbose_option)
fprintf (stdlis, _("Renaming %s back to %s\n"),
quote_n (0, after_backup_name),
quote_n (1, before_backup_name));
assign_string (&after_backup_name, 0);
}
}
int
deref_stat (bool deref, char const *name, struct stat *buf)
{
return deref ? stat (name, buf) : lstat (name, buf);
}
int
set_file_atime (int fd, char const *file, struct timespec const timespec[2])
{
#ifdef _FIOSATIME
if (0 <= fd)
{
struct timeval timeval;
timeval.tv_sec = timespec[0].tv_sec;
timeval.tv_usec = timespec[0].tv_nsec / 1000;
if (ioctl (fd, _FIOSATIME, &timeval) == 0)
return 0;
}
#endif
return gl_futimens (fd, file, timespec);
}
struct wd
{
char const *name;
int saved;
struct saved_cwd saved_cwd;
};
static struct wd *wd;
static size_t wds;
static size_t wd_alloc;
int
chdir_arg (char const *dir)
{
if (wds == wd_alloc)
{
if (wd_alloc == 0)
{
wd_alloc = 2;
wd = xmalloc (sizeof *wd * wd_alloc);
}
else
wd = x2nrealloc (wd, &wd_alloc, sizeof *wd);
if (! wds)
{
wd[wds].name = ".";
wd[wds].saved = 0;
wds++;
}
}
if (dir[0])
{
while (dir[0] == '.' && ISSLASH (dir[1]))
for (dir += 2; ISSLASH (*dir); dir++)
continue;
if (! dir[dir[0] == '.'])
return wds - 1;
}
wd[wds].name = dir;
wd[wds].saved = 0;
return wds++;
}
void
chdir_do (int i)
{
static int previous;
if (previous != i)
{
struct wd *prev = &wd[previous];
struct wd *curr = &wd[i];
if (! prev->saved)
{
int err = 0;
prev->saved = 1;
if (save_cwd (&prev->saved_cwd) != 0)
err = errno;
else if (0 <= prev->saved_cwd.desc)
{
int fd1 = prev->saved_cwd.desc;
int fd2 = dup (fd1);
if (0 <= fd2)
close (fd2);
else if (errno == EMFILE)
{
close (fd1);
prev->saved_cwd.desc = -1;
prev->saved_cwd.name = xgetcwd ();
}
else
err = errno;
}
if (err)
FATAL_ERROR ((0, err, _("Cannot save working directory")));
}
if (curr->saved)
{
if (restore_cwd (&curr->saved_cwd))
FATAL_ERROR ((0, 0, _("Cannot change working directory")));
}
else
{
if (i && ! ISSLASH (curr->name[0]))
chdir_do (i - 1);
if (chdir (curr->name) != 0)
chdir_fatal (curr->name);
}
previous = i;
}
}
void
close_diag (char const *name)
{
if (ignore_failed_read_option)
close_warn (name);
else
close_error (name);
}
void
open_diag (char const *name)
{
if (ignore_failed_read_option)
open_warn (name);
else
open_error (name);
}
void
read_diag_details (char const *name, off_t offset, size_t size)
{
if (ignore_failed_read_option)
read_warn_details (name, offset, size);
else
read_error_details (name, offset, size);
}
void
readlink_diag (char const *name)
{
if (ignore_failed_read_option)
readlink_warn (name);
else
readlink_error (name);
}
void
savedir_diag (char const *name)
{
if (ignore_failed_read_option)
savedir_warn (name);
else
savedir_error (name);
}
void
seek_diag_details (char const *name, off_t offset)
{
if (ignore_failed_read_option)
seek_warn_details (name, offset);
else
seek_error_details (name, offset);
}
void
stat_diag (char const *name)
{
if (ignore_failed_read_option)
stat_warn (name);
else
stat_error (name);
}
void
write_fatal_details (char const *name, ssize_t status, size_t size)
{
write_error_details (name, status, size);
fatal_exit ();
}
pid_t
xfork (void)
{
pid_t p = fork ();
if (p == (pid_t) -1)
call_arg_fatal ("fork", _("child process"));
return p;
}
void
xpipe (int fd[2])
{
if (pipe (fd) < 0)
call_arg_fatal ("pipe", _("interprocess channel"));
}
static inline void *
ptr_align (void *ptr, size_t alignment)
{
char *p0 = ptr;
char *p1 = p0 + alignment - 1;
return p1 - (size_t) p1 % alignment;
}
void *
page_aligned_alloc (void **ptr, size_t size)
{
size_t alignment = getpagesize ();
size_t size1 = size + alignment;
if (size1 < size)
xalloc_die ();
*ptr = xmalloc (size1);
return ptr_align (*ptr, alignment);
}