Bo2SS

Bo2SS

Manual Implementation of ls -al in Linux

Functional Requirements#

  • Achieve effects similar to the native Linux command [ls -al]
  • Image
  • Required information includes: file information, link count, username, group name, file size, modification time, file name
  • Additional implementations: file sorting, color beautification, display of soft links

Final Effect#

  • Image
  • Basic template of ls -al has been implemented

Implementation Process#

Thought Process Flowchart#

  • image-20210117100933677

Getting Command Line Parameters#

Capture the options of ls -al and optional option parameter path

  • Identify ls -al, ls -l, ls -al path, ls -l path, and report errors for others
#include "head.h"
void myls(char **argv, char *path, int a_flag) {
    if (a_flag) printf("run %s -al %s\n", argv[0], path);
    else printf("run %s -l %s\n", argv[0], path);
    return ;
}
void show_tips(char **argv) {
    fprintf(stderr, "Usage: %s -[a]l [path]\n", argv[0]);
    return ;
}
int main(int argc, char **argv) {
    // Without path parameter [default is current directory]
    if (argc == 2){
        if (!strcmp(argv[1], "-al")) myls(argv, ".", 1);
        else if (!strcmp(argv[1], "-l")) myls(argv, ".", 0);
        else show_tips(argv), exit(1);
        return 0;
    }
    int opt;
    int a_flag = 0;  // Determine -a option
    // With path parameter
    while ((opt = getopt(argc, argv, "al:")) != -1) {
        switch (opt) {
            case 'a': {
                a_flag = 1;
            } break;
            case 'l': {
                myls(argv, optarg, a_flag);
            } break;
            default: {
                show_tips(argv);
                exit(1);
            }
        }
    }
    return 0;
}
  • Because in getopt, for optional option parameters, the option [option followed by "::"] must have its parameter immediately following it [e.g., ls -al123], and cannot be separated by a space like the native ls -al path [e.g., ls -al 123], so the option parameter is set as required [option followed by ":"], and a check is made for cases without option parameters
  • The header file head.h is at the end
  • Use the test script test.sh [see end] for testing, the effect is as follows:
    • Image
    • Can already capture and identify ls -al, ls -l, ls -al path, ls -l path, as well as other erroneous inputs

Reading Directory#

Read the list of file names in the directory

  • Use opendir, readdir [later changed to scandir for sorting convenience, see sorting display]
  • Just modify the myls() function, the code is as follows
void myls(char *path, int a_flag) {
    DIR *dirp;
    // 0. Open and read the directory
    if ((dirp = opendir(path)) != NULL) {
        struct dirent *dent;
        while (dent = readdir(dirp)) {
            if (dent->d_name[0] == '.' && !a_flag) continue;
            printf("%s\n", dent->d_name);
        }
    } else {
        perror("opendir");
        exit(1);
    }
    return ;
}
  • At this point, myls no longer needs the argv parameter
  • Determine whether to display hidden files based on the presence of the a option
    • Any file name starting with "." is a hidden file
  • Some test effects are as follows
    • Image
    • Can already read all file names in the directory and distinguish hidden files

Reading and Processing File Information⭐#

Get file information based on each file's name

lstat to Get Basic File Information#

  1. Use [lstat] to get basic file information [file permissions, hard link count, uid, gid, file size, modification time]
// Path concatenation to obtain absolute path
void absolute_path(char *ab_path, char* path, char *filename) {
    strcpy(ab_path, path);                       // Keep the original path value
    strcat(ab_path, "/");                        // Note to add path separator
    strcat(ab_path, filename);                   // Concatenate
    return ;
}

Friendly Display of File Information#

  1. File information <file type, file [special] permissions> 👉 character form
// Get friendly file information
void mode2str(mode_t smode, char *mode) {
    int i = 0;
    // rwx array, corresponding to 000, 001, ..., 110, 111
    char *rwx[] = {
        "---", "--x", "-w-",
        "-wx", "r--", "r-x",
        "rw-", "rwx"
    };
    // Get file type
    if (S_ISREG(smode)) mode[0] = '-';
    else if (S_ISDIR(smode)) mode[0] = 'd';
    else if (S_ISBLK(smode)) mode[0] = 'b';
    else if (S_ISCHR(smode)) mode[0] = 'c';
#ifdef S_ISFIFO
    else if (S_ISFIFO(smode)) mode[0] = 'p';
#endif
#ifdef S_ISLNK
    else if (S_ISLNK(smode)) mode[0] = 'l';
#endif
#ifdef S_ISSOCK
    else if (S_ISSOCK(smode)) mode[0] = 's';
#endif
    else mode[i++] = '?';
    // Get file permissions
    strcpy(&mode[1], rwx[(smode >> 6) & 7]);
    strcpy(&mode[4], rwx[(smode >> 3) & 7]);
    strcpy(&mode[7], rwx[smode & 7]);
    // Get file special permissions
    if (smode & S_ISUID) mode[3] = (smode & S_IXUSR) ? 's' : 'S';
    if (smode & S_ISGID) mode[6] = (smode & S_IXGRP) ? 's' : 'S';
    if (smode & S_ISVTX) mode[9] = (smode & S_IXOTH) ? 't' : 'T';
    return ;
}

Friendly Display of Modification Time#

  1. Timestamp 👉 friendly readable time
// Get friendly mtime
void mtim2str(struct timespec *mtim, char *str, size_t max) {
    struct tm *tmp_time = localtime(&mtim->tv_sec);           // Convert to local calendar time
    strftime(str, max, "%b %e %H:%M", tmp_time);              // Convert to specified format
    return ;
}
  • Timestamp → time structure → specified format
    • The first step conversion is completed through localtime—cppreference to obtain the tm structure
    • The second step conversion is completed through strftime—cppreference to obtain the string
  • Refer to Timestamps and Time Formats in C——Jianshu
  • [Alternatively] refer to the example in the man page, using ctime—cppreference, and then extract the substring

Get Owner Username and Group Name#

4, 5. Obtain the file's [username, group name] through [getpwuid, getgrgid] with uid, gid

strcpy(uname, getpwuid(st.st_uid)->pw_name);  // 4. Get the file owner's name
strcpy(gname, getgrgid(st.st_gid)->gr_name);  // 5. Get the file's group name
  • The effect is as follows:
    • Image
    • Only tools that are unimaginable can be tried to implement the process of obtaining names through uid, gid

Sorting Display#

  • See the complete code at the end
  • readdir reads directory files in a disordered manner, while scandir can read the file list in dictionary order
  • Refer to the EXAMPLE of man scandir
    • Image
    • Create a file list, read in dictionary order, and output in reverse order
  • scandir completes the work of opendir and readdir, so replace opendir and readdir with scandir, and then use d_name for lstat
  • The effect is as follows
    • Image
    • The sorting rules are slightly different, and next, control the color and display the soft link target

Color Control#

  • See the complete code at the end [Color macros see head.h]
  • Mainly beautified the colors for directories, soft links, executable files, and some special permission files
    • Other colors are similar
  • The effect is as follows
    • Image
    • Then implement the display of soft link targets
  • See the complete code at the end
  • Use readlink to read the source file name pointed to by the soft link, refer to man readlink
  • The effect is as follows
    • Image
    • 🔚

Complete Code#

myls.c#

#include "head.h"
// Path concatenation to obtain absolute path
void absolute_path(char *ab_path, char* path, char *filename) {
    strcpy(ab_path, path);                       // Keep the original path value
    strcat(ab_path, "/");                        // Note to add path separator
    strcat(ab_path, filename);                   // Concatenate
    return ;
}
// Get friendly file information
void mode2str(mode_t smode, char *mode, char *color) {
    int i = 0;
    // rwx array, corresponding to 000, 001, ..., 110, 111
    char *rwx[] = {
        "---", "--x", "-w-",
        "-wx", "r--", "r-x",
        "rw-", "rwx"
    };
    // Get file type
    if (S_ISREG(smode)) mode[0] = '-';
    else if (S_ISDIR(smode)) mode[0] = 'd', strcpy(color, "BLUE_HL");
    else if (S_ISBLK(smode)) mode[0] = 'b';
    else if (S_ISCHR(smode)) mode[0] = 'c';
#ifdef S_ISFIFO
    else if (S_ISFIFO(smode)) mode[0] = 'p';
#endif
#ifdef S_ISLNK
    else if (S_ISLNK(smode)) mode[0] = 'l', strcpy(color, "BLUE");
#endif
#ifdef S_ISSOCK
    else if (S_ISSOCK(smode)) mode[0] = 's';
#endif
    else mode[i++] = '?';
    // Get file permissions
    strcpy(&mode[1], rwx[(smode >> 6) & 7]);
    strcpy(&mode[4], rwx[(smode >> 3) & 7]);
    strcpy(&mode[7], rwx[smode & 7]);
    // Get file special permissions
    if (smode & S_ISUID) mode[3] = (smode & S_IXUSR) ? 's' : 'S';
    if (smode & S_ISGID) mode[6] = (smode & S_IXGRP) ? 's' : 'S';
    if (smode & S_ISVTX) mode[9] = (smode & S_IXOTH) ? 't' : 'T';
    // + Configure color [also above]
    if (mode[0] == '-') {
        if (strstr(mode, "x")) strcpy(color, "GREEN_HL");
        if (strstr(mode, "s") || strstr(mode, "S")) strcpy(color, "YELLOW_BG");
    }
    return ;
}
// Get friendly mtime
void mtim2str(struct timespec *mtim, char *str, size_t max) {
    struct tm *tmp_time = localtime(&mtim->tv_sec);           // Convert to local calendar time
    strftime(str, max, "%b %e %H:%M", tmp_time);              // Convert to specified format
    return ;
}
void myls(char *path, int a_flag) {
    // 0. Read the directory's file list in [dictionary order]
    struct dirent **namelist;
    int n, i = -1;
    n = scandir(path, &namelist, NULL, alphasort);           // Read in dictionary order
    if (n == -1) {
        perror("scandir");
        exit(1);
    }
    // Traverse the file list in order
    while (i < n - 1) {
        i++;
        struct dirent *dent = namelist[i];
        if (dent->d_name[0] == '.' && !a_flag) continue;  // Whether to display hidden files
        struct stat st;                                   // Memory must be allocated for this structure
        char ab_path[128];                                // Record absolute path
        absolute_path(ab_path, path, dent->d_name);      // +. Path concatenation
        // 1. Read file information
        if (!lstat(ab_path, &st)) {
            char mode[16], mtime[32], uname[16], gname[16], filename[512], color[16] = {0};
            mode2str(st.st_mode, mode, color);           // 2. Process file information [and set color]
            mtim2str(&st.st_mtim, mtime, sizeof(mtime)); // 3. Process modification time
            strcpy(uname, getpwuid(st.st_uid)->pw_name); // 4. Get the file owner's name
            strcpy(gname, getgrgid(st.st_gid)->gr_name); // 5. Get the file's group name
            // Print basic information except file name
            printf("%s %lu %s %s %*lu %s ",
                   mode, st.st_nlink, uname, gname,
                   5, st.st_size, mtime
                   );
            // 6. Wrap file name according to color
            if (!strcmp(color, "BLUE")) sprintf(filename, BLUE("%s"), dent->d_name);
            else if (!strcmp(color, "BLUE_HL")) sprintf(filename, BLUE_HL("%s"), dent->d_name);
            else if (!strcmp(color, "GREEN_HL")) sprintf(filename, GREEN_HL("%s"), dent->d_name);
            else if (!strcmp(color, "YELLOW_BG")) sprintf(filename, YELLOW_BG("%s"), dent->d_name);
            else strcpy(filename, dent->d_name);
            // 7. Special handling for soft links
            if (mode[0] == 'l') {
                char linkfile[32];
                readlink(ab_path, linkfile, 32);
                strcat(filename, " -> ");
                strcat(filename, linkfile);
            }
            // Print file name separately [including color]
            printf("%s\n", filename);
        } else {
            perror("lstat");
            exit(1);
        }
        free(namelist[i]);
    }
    free(namelist);
    return ;
}
void show_tips(char **argv) {
    fprintf(stderr, "Usage: %s -[a]l [path]\n", argv[0]);
    return ;
}
int main(int argc, char **argv) {
    // Without path parameter [default is current directory]
    if (argc == 2){
        if (!strcmp(argv[1], "-al")) myls(".", 1);
        else if (!strcmp(argv[1], "-l")) myls(".", 0);
        else show_tips(argv), exit(1);
        return 0;
    }
    int opt, a_flag = 0;  // a_flag: determine -a option
    // With path parameter
    while ((opt = getopt(argc, argv, "al:")) != -1) {
        switch (opt) {
            case 'a': {
                a_flag = 1;
            } break;
            case 'l': {
                myls(optarg, a_flag);
            } break;
            default: {
                show_tips(argv);
                exit(1);
            }
        }
    }
    return 0;
}
  • See comments for details
  • Column spacing can be controlled using printf("%*s", len, "xyz") format, with len controlling the value of *, len is adjusted based on string length [not considered]
  • The sorting display changes from opendir + readdir to scandir, which can read files in order into a string array
  • Color control
    • ❗ When setting colors in mode2str, the assignment of the color character array should use strcpy; if directly assigned with "=", the value assigned to color in the function will not exit the function, even if color is a character pointer. Therefore, use the color character array with the strcpy function [here color adopts the output parameter method]
    • Use strcmp for string comparison, not "=="
    • To facilitate the binding of file name colors and file names, create a 512 byte size character array filename to encapsulate the file name and corresponding color
      • Using 512 is because in sprintf, the compiler considers a maximum of 256 bytes
      • 🆒 There is a more elegant solution—using snprintf and dynamically allocating space based on data, refer to Detecting String Truncation with GCC 8
        • Image
        • malloc is the essence
  • Display of soft link targets
    • The generation of filename originates from this, with the arrow pointing to the original file displayed in the default color

head.h#

#ifndef _HEAD_H
#define _HEAD_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#define COLOR(a, b) "\033[" #b "m" a "\033[0m"
#define COLOR_BG(a, b) "\033[2;" #b "m" a "\033[0m"
#define COLOR_HL(a, b) "\033[1;" #b "m" a "\033[0m"
#define RED(a) COLOR(a, 31)
#define GREEN(a) COLOR(a, 32)
#define YELLOW(a) COLOR(a, 33)
#define BLUE(a) COLOR(a, 34)
#define PURPLE(a) COLOR(a, 35)
#define RED_HL(a) COLOR_HL(a, 31)
#define GREEN_HL(a) COLOR_HL(a, 32)
#define YELLOW_HL(a) COLOR_HL(a, 33)
#define BLUE_HL(a) COLOR_HL(a, 34)
#define PURPLE_HL(a) COLOR_HL(a, 35)
#define RED_BG(a) COLOR_BG(a, 41)
#define GREEN_BG(a) COLOR_BG(a, 42)
#define YELLOW_BG(a) COLOR_BG(a, 43)
#define BLUE_BG(a) COLOR_BG(a, 44)
#define PURPLE_BG(a) COLOR_BG(a, 45)
#endif

test.sh#

#!/bin/bash
path="x.TestDir"
echo "./ls -al:" | lolcat
./ls -al
echo "./ls -l:" | lolcat
./ls -l
echo "./ls -al $path:" | lolcat
./ls -al $path
echo "./ls -l $path:" | lolcat
./ls -l $path
echo "./ls -b:" | lolcat
./ls -b
echo "./ls -b $path:" | lolcat
./ls -b $path
  • ./ls is the executable file name generated using gcc ... -o ls

[PS]

  • How to view what format control strings correspond to complex structure member variables?
    • Such as %s, %lu...
    • ① Write one casually and see the error message prompt
    • ② Or use ctags to view the source code: ctrl + ]

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.