Need numeric argument for chmod system call to change only the sticky bit - linux

I know pretty much how to use chmod directly from the command line. But I'm not quite sure if we can mimic its
chmod o+t filename
function. I usually call it directly with:
chmod(filename, 0666)
But instead of setting the permissions, I want to know how to add a permission without affecting the other ones, like chmod o+t filename. Something like chmod(filename, 1777) will also set the sticky bit, but it also sets all the other bits. What number should we pass to chmod to let it only change the sticky bit? thanks in advance.

As mentioned in comments, first read current mode bits with stat(), then do chmod().
The thing to take home with you is that you shouldn't be working with numeric values directly but instead trust those defined in header files. See man 2 chmod for what is available.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct stat st;
if(argc < 1) return 2;
if(stat(argv[1], &st) != 0) {
perror(argv[1]);
return 2;
}
printf("mode before chmod() = %o\n", st.st_mode);
if(chmod(argv[1], st.st_mode | S_ISVTX) != 0) {
perror(argv[1]);
return 1;
}
if(stat(argv[1], &st) != 0) {
perror(argv[1]);
return 2;
}
printf("mode after chmod() = %o\n", st.st_mode);
return 0;
}
Have fun with homework!

Related

How to monitor changes to pseudo-filesystem on Linux?

As neither dnotify nor inotify is able to monitor changes to pseudo-filesystem content is there an automated way to discover file/directory creation/deletion inside (for example) /sys/block directory?
Of course, I can scan the directory periodically on my own but hope that there is a smarter way.
I decided to use a somewhat naive workaround and monitor /dev directory (which is supported by inotify) instead of /sys/block. Fortunately, each /sys/block entry has its counterpart inside /dev (but not vice versa) so I just check whether an entry that appeared in /dev is also present inside /sys/block.
Not very elegant but sufficient for me.
#include <stdio.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <assert.h>
#include <linux/limits.h>
#include <sys/stat.h>
int main(void)
{
int fd = inotify_init();
assert(fd >= 0);
int wd = inotify_add_watch(fd, "/dev", IN_CREATE);
assert(wd >= 0);
for(;;) {
char _event[sizeof(struct inotify_event) + NAME_MAX + 1];
int res = read(fd, _event, sizeof(_event));
assert(res > 0);
struct inotify_event *event = (struct inotify_event *) _event;
if(event -> len > 0 && event -> mask & IN_CREATE && !(event -> mask & IN_ISDIR)) {
char dev_name[NAME_MAX + 1];
sprintf(dev_name, "/sys/block/%s/stat", event -> name);
struct stat statbuf;
if(0 == stat(dev_name, &statbuf))
printf("new entry appeared: %s\n", event -> name);
}
}
}

What does lseek() mean for a directory file descriptor?

According to strace, lseek(fd, 0, SEEK_END) = 9223372036854775807 when fd refers to a directory. Why is this syscall succeeding at all? What does lseek() mean for a dir fd?
On my test system, if you use opendir(), and readdir() through all the entries in the directory, telldir() then returns the same value:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
int main(int argc, char *argv[]) {
int fd = open(".", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
off_t o = lseek(fd, 0, SEEK_END);
if (o == (off_t)-1) {
perror("lseek");
return 1;
}
printf("Via lseek: %ld\n", (long)o);
close(fd);
DIR *d = opendir(".");
if (!d) {
perror("opendir");
return 1;
}
while (readdir(d)) {
}
printf("via telldir: %ld\n", telldir(d));
closedir(d);
return 0;
}
outputs
Via lseek: 9223372036854775807
via telldir: 9223372036854775807
Quoting from the telldir(3) man page:
In early filesystems, the value returned by telldir() was a simple file offset within a directory. Modern filesystems use tree or hash structures, rather than flat tables, to represent directories. On such filesystems, the value returned by telldir() (and used internally by readdir(3)) is a "cookie" that is used by the implementation to derive a position within a directory. Application programs should treat this strictly as an opaque value, making no assumptions about its contents.
It's a magic number that indicates that the index into the directory's contents is at the end. Don't count on the number always being the same, or being portable. It's a black box. And stick with the dirent API for traversing directory contents unless you really know exactly what you're doing (Under the hood on Linux + glibc, opendir(3) calls openat(2) on the directory, readdir(3) fetches information about its contents with getdents(2), and seekdir(3) calls lseek(2), but that's just implementation details)

What is the cause of the hard limit on the directory nesting depth returned by getcwd on macOS and how can it be circumvented?

On linux and macOS, directories can be nested to seemingly arbitrary depth, as demonstrated by the following C program. However, on macOS but not on linux, there seems to be a hard limit on the nesting level returned by getcwd, specifically a nesting level of 256. When that limit is reached, getcwd returns ENOENT, a rather strange error code. Where does this limit come from? Is there a way around it?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
void fail(char *msg) { perror(msg); exit(1); }
void create_nested_dirs(int n) {
int i;
char name[10];
char cwd[10000];
if (chdir("/tmp") < 0) fail("chdir(\"/tmp\")");
for (i=2; i<=n; i++) {
sprintf(name, "%09d", i);
printf("%s\n",name);
if (mkdir(name, 0777) < 0 && errno != EEXIST) fail("mkdir");
if (chdir(name) < 0) fail("chdir(name)");
if (getcwd(cwd, sizeof(cwd)) == NULL) fail("getcwd");
printf("cwd = \"%s\" strlen(cwd)=%d\n", cwd, strlen(cwd));
}
}
int main() {
long ret = pathconf("/", _PC_PATH_MAX);
printf("PATH_MAX is %ld\n", ret);
create_nested_dirs(300);
return 0;
}
Update
The above program was updated to print the value returned by pathconf("/", _PC_PATH_MAX) and to print the length of the path returned by getcwd.
On my machine running macOS Mojave 10.14, the PATH_MAX is 1024 and the longest string correctly returned by getcwd is 2542 characters long. Then a 2552 character long directory of nesting depth 256 is created by mkdir and then after a successful chdir to that directory a getcwd fails with ENOENT.
If the sprintf(name, "%09d", i); is changed to sprintf(name, "%03d", i); the paths are considerably shorter but the getcwd still fails when the directory nesting depth reaches 256.
So the limiting factor here is the nesting depth, not PATH_MAX.
My understanding of the source code here is that the meat of the work is done by the call fcntl(fd, F_GETPATH, b) so the problem may be in fcntl.

Why "ls" is not colored after forkpty()

Why output of ls executed here is not colored?
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pty.h>
#include <sys/wait.h>
int main(int argc, char **argv ) {
termios termp; winsize winp;
int amaster; char name[128];
if (forkpty(&amaster, name, &termp, &winp) == 0) {
system("ls"); // "ls --color" will work here!
return 0;
}
wait(0);
char buf[128]; int size;
while (1) {
size = read(amaster, buf, 127);
if (size <= 0) break;
buf[size] = 0;
printf("%s", buf);
}
return 0;
}
According to man (and ls.c that I am inspecting) it should be colored if isatty() returns true. After forkpty() it must be true. Besides, ls DOES output in columnized mode in this example! Which means it feels it has tty as output.
Of course I do not want only ls to output color, but an arbitrary program to feel that it has real color enabled tty behind.
I just wrote a simple test:
#include <unistd.h>
int main() {
printf("%i%i%i%i%i\n", isatty(0), isatty(1), isatty(2), isatty(3), isatty(4));
}
and call it in a child part of forkpty, and it displays 11100, which means ls should be colored!
OK, as it seems the fact that ls produces no color output has nothing to do with forkpty(). It is just not color enabled by default. But now, maybe that's another question, why it is not color if it just checks isatty()?

How can I get the source code for the linux utility tail?

this command is really very useful but where I can get the source code to see what is going on inside .
thanks .
The tail utility is part of the coreutils on linux.
Source tarball: ftp://ftp.gnu.org/gnu/coreutils/coreutils-7.4.tar.gz
Source file: https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tail.c (original http link)
I've always found FreeBSD to have far clearer source code than the gnu utilities. So here's tail.c in the FreeBSD project:
http://svnweb.freebsd.org/csrg/usr.bin/tail/tail.c?view=markup
Poke around the uclinux site. Since they distributed the software, they are required to make the source available one way or another.
Or, you could read man fseek and guess at how it might be done.
NB-- See William's comments below, there are cases when you can't use seek.
You might find it an interesting exercise to write your own. The vast majority of the Unix command-line tools are a page or so of fairly straightforward C code.
To just look at the code, the GNU CoreUtils sources are easily found on gnu.org or your favorite Linux mirror site.
/`*This example implements the option n of tail command.*/`
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#define BUFF_SIZE 4096
FILE *openFile(const char *filePath)
{
FILE *file;
file= fopen(filePath, "r");
if(file == NULL)
{
fprintf(stderr,"Error opening file: %s\n",filePath);
exit(errno);
}
return(file);
}
void printLine(FILE *file, off_t startline)
{
int fd;
fd= fileno(file);
int nread;
char buffer[BUFF_SIZE];
lseek(fd,(startline + 1),SEEK_SET);
while((nread= read(fd,buffer,BUFF_SIZE)) > 0)
{
write(STDOUT_FILENO, buffer, nread);
}
}
void walkFile(FILE *file, long nlines)
{
off_t fposition;
fseek(file,0,SEEK_END);
fposition= ftell(file);
off_t index= fposition;
off_t end= fposition;
long countlines= 0;
char cbyte;
for(index; index >= 0; index --)
{
cbyte= fgetc(file);
if (cbyte == '\n' && (end - index) > 1)
{
countlines ++;
if(countlines == nlines)
{
break;
}
}
fposition--;
fseek(file,fposition,SEEK_SET);
}
printLine(file, fposition);
fclose(file);
}
int main(int argc, char *argv[])
{
FILE *file;
file= openFile(argv[2]);
walkFile(file, atol(argv[1]));
return 0;
}
/*Note: take in mind that i not wrote code to parse input options and arguments, neither code to check if the lines number argument is really a number.*/

Resources