Serenity Operating System
at portability 417 lines 14 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/HashMap.h> 28#include <AK/QuickSort.h> 29#include <AK/String.h> 30#include <AK/StringBuilder.h> 31#include <AK/Vector.h> 32#include <LibCore/ArgsParser.h> 33#include <LibCore/DateTime.h> 34#include <LibCore/DirIterator.h> 35#include <ctype.h> 36#include <dirent.h> 37#include <errno.h> 38#include <fcntl.h> 39#include <grp.h> 40#include <pwd.h> 41#include <stdio.h> 42#include <string.h> 43#include <sys/ioctl.h> 44#include <sys/stat.h> 45#include <time.h> 46#include <unistd.h> 47 48static int do_file_system_object_long(const char* path); 49static int do_file_system_object_short(const char* path); 50 51static bool flag_colorize = true; 52static bool flag_long = false; 53static bool flag_show_dotfiles = false; 54static bool flag_show_inode = false; 55static bool flag_print_numeric = false; 56static bool flag_human_readable = false; 57static bool flag_sort_by_timestamp = false; 58static bool flag_reverse_sort = false; 59 60static size_t terminal_rows = 0; 61static size_t terminal_columns = 0; 62static bool output_is_terminal = false; 63 64static HashMap<uid_t, String> users; 65static HashMap<gid_t, String> groups; 66 67int main(int argc, char** argv) 68{ 69 if (pledge("stdio rpath tty", nullptr) < 0) { 70 perror("pledge"); 71 return 1; 72 } 73 74 struct winsize ws; 75 int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); 76 if (rc == 0) { 77 terminal_rows = ws.ws_row; 78 terminal_columns = ws.ws_col; 79 output_is_terminal = true; 80 } 81 82 if (pledge("stdio rpath", nullptr) < 0) { 83 perror("pledge"); 84 return 1; 85 } 86 87 Vector<const char*> paths; 88 89 Core::ArgsParser args_parser; 90 args_parser.add_option(flag_show_dotfiles, "Show dotfiles", "all", 'a'); 91 args_parser.add_option(flag_long, "Display long info", "long", 'l'); 92 args_parser.add_option(flag_sort_by_timestamp, "Sort files by timestamp", nullptr, 't'); 93 args_parser.add_option(flag_reverse_sort, "Reverse sort order", "reverse", 'r'); 94 args_parser.add_option(flag_colorize, "Use pretty colors", nullptr, 'G'); 95 args_parser.add_option(flag_show_inode, "Show inode ids", "inode", 'i'); 96 args_parser.add_option(flag_print_numeric, "In long format, display numeric UID/GID", "numeric-uid-gid", 'n'); 97 args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h'); 98 args_parser.add_positional_argument(paths, "Directory to list", "path", Core::ArgsParser::Required::No); 99 args_parser.parse(argc, argv); 100 101 if (flag_long) { 102 setpwent(); 103 for (auto* pwd = getpwent(); pwd; pwd = getpwent()) 104 users.set(pwd->pw_uid, pwd->pw_name); 105 endpwent(); 106 setgrent(); 107 for (auto* grp = getgrent(); grp; grp = getgrent()) 108 groups.set(grp->gr_gid, grp->gr_name); 109 endgrent(); 110 } 111 112 auto do_file_system_object = [&](const char* path) { 113 if (flag_long) 114 return do_file_system_object_long(path); 115 return do_file_system_object_short(path); 116 }; 117 118 int status = 0; 119 if (paths.is_empty()) { 120 status = do_file_system_object("."); 121 } else if (paths.size() == 1) { 122 status = do_file_system_object(paths[0]); 123 } else { 124 for (auto& path : paths) { 125 printf("%s:\n", path); 126 status = do_file_system_object(path); 127 } 128 } 129 return status; 130} 131 132int print_escaped(const char* name) 133{ 134 int printed = 0; 135 136 for (int i = 0; name[i] != '\0'; i++) { 137 if (isprint(name[i])) { 138 putchar(name[i]); 139 printed++; 140 } else { 141 printed += printf("\\%03d", name[i]); 142 } 143 } 144 145 return printed; 146} 147 148size_t print_name(const struct stat& st, const String& name, const char* path_for_link_resolution = nullptr) 149{ 150 size_t nprinted = 0; 151 152 if (!flag_colorize || !output_is_terminal) { 153 nprinted = printf("%s", name.characters()); 154 } else { 155 const char* begin_color = ""; 156 const char* end_color = "\033[0m"; 157 158 if (st.st_mode & S_ISVTX) 159 begin_color = "\033[42;30;1m"; 160 else if (st.st_mode & S_ISUID) 161 begin_color = "\033[41;1m"; 162 else if (S_ISLNK(st.st_mode)) 163 begin_color = "\033[36;1m"; 164 else if (S_ISDIR(st.st_mode)) 165 begin_color = "\033[34;1m"; 166 else if (st.st_mode & 0111) 167 begin_color = "\033[32;1m"; 168 else if (S_ISSOCK(st.st_mode)) 169 begin_color = "\033[35;1m"; 170 else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) 171 begin_color = "\033[33;1m"; 172 printf("%s", begin_color); 173 nprinted = print_escaped(name.characters()); 174 printf("%s", end_color); 175 } 176 if (S_ISLNK(st.st_mode)) { 177 if (path_for_link_resolution) { 178 char linkbuf[PATH_MAX]; 179 ssize_t nread = readlink(path_for_link_resolution, linkbuf, sizeof(linkbuf)); 180 if (nread < 0) 181 perror("readlink failed"); 182 else 183 nprinted += printf(" -> ") + print_escaped(linkbuf); 184 } else { 185 nprinted += printf("@"); 186 } 187 } else if (S_ISDIR(st.st_mode)) { 188 nprinted += printf("/"); 189 } else if (st.st_mode & 0111) { 190 nprinted += printf("*"); 191 } 192 return nprinted; 193} 194 195// FIXME: Remove this hackery once printf() supports floats. 196// FIXME: Also, we should probably round the sizes in ls -lh output. 197static String number_string_with_one_decimal(float number, const char* suffix) 198{ 199 float decimals = number - (int)number; 200 return String::format("%d.%d%s", (int)number, (int)(decimals * 10), suffix); 201} 202 203static String human_readable_size(size_t size) 204{ 205 if (size < 1 * KB) 206 return String::number(size); 207 if (size < 1 * MB) 208 return number_string_with_one_decimal((float)size / (float)KB, "K"); 209 if (size < 1 * GB) 210 return number_string_with_one_decimal((float)size / (float)MB, "M"); 211 return number_string_with_one_decimal((float)size / (float)GB, "G"); 212} 213 214bool print_filesystem_object(const String& path, const String& name, const struct stat& st) 215{ 216 if (flag_show_inode) 217 printf("%08u ", st.st_ino); 218 219 if (S_ISDIR(st.st_mode)) 220 printf("d"); 221 else if (S_ISLNK(st.st_mode)) 222 printf("l"); 223 else if (S_ISBLK(st.st_mode)) 224 printf("b"); 225 else if (S_ISCHR(st.st_mode)) 226 printf("c"); 227 else if (S_ISFIFO(st.st_mode)) 228 printf("f"); 229 else if (S_ISSOCK(st.st_mode)) 230 printf("s"); 231 else if (S_ISREG(st.st_mode)) 232 printf("-"); 233 else 234 printf("?"); 235 236 printf("%c%c%c%c%c%c%c%c", 237 st.st_mode & S_IRUSR ? 'r' : '-', 238 st.st_mode & S_IWUSR ? 'w' : '-', 239 st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'), 240 st.st_mode & S_IRGRP ? 'r' : '-', 241 st.st_mode & S_IWGRP ? 'w' : '-', 242 st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'), 243 st.st_mode & S_IROTH ? 'r' : '-', 244 st.st_mode & S_IWOTH ? 'w' : '-'); 245 246 if (st.st_mode & S_ISVTX) 247 printf("t"); 248 else 249 printf("%c", st.st_mode & S_IXOTH ? 'x' : '-'); 250 251 auto username = users.get(st.st_uid); 252 auto groupname = groups.get(st.st_gid); 253 if (!flag_print_numeric && username.has_value()) { 254 printf(" %7s", username.value().characters()); 255 } else { 256 printf(" %7u", st.st_uid); 257 } 258 if (!flag_print_numeric && groupname.has_value()) { 259 printf(" %7s", groupname.value().characters()); 260 } else { 261 printf(" %7u", st.st_gid); 262 } 263 264 if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { 265 printf(" %4u,%4u ", major(st.st_rdev), minor(st.st_rdev)); 266 } else { 267 if (flag_human_readable) { 268 ASSERT(st.st_size > 0); 269 printf(" %10s ", human_readable_size((size_t)st.st_size).characters()); 270 } else { 271 printf(" %10u ", st.st_size); 272 } 273 } 274 275 printf(" %s ", Core::DateTime::from_timestamp(st.st_mtime).to_string().characters()); 276 277 print_name(st, name, path.characters()); 278 279 printf("\n"); 280 return true; 281} 282 283int do_file_system_object_long(const char* path) 284{ 285 Core::DirIterator di(path, !flag_show_dotfiles ? Core::DirIterator::SkipDots : Core::DirIterator::Flags::NoFlags); 286 if (di.has_error()) { 287 if (di.error() == ENOTDIR) { 288 struct stat stat; 289 int rc = lstat(path, &stat); 290 if (rc < 0) { 291 perror("lstat"); 292 memset(&stat, 0, sizeof(stat)); 293 } 294 if (print_filesystem_object(path, path, stat)) 295 return 0; 296 return 2; 297 } 298 fprintf(stderr, "%s: %s\n", path, di.error_string()); 299 return 1; 300 } 301 302 struct FileMetadata { 303 String name; 304 String path; 305 struct stat stat; 306 }; 307 308 Vector<FileMetadata> files; 309 while (di.has_next()) { 310 FileMetadata metadata; 311 metadata.name = di.next_path(); 312 ASSERT(!metadata.name.is_empty()); 313 if (metadata.name[0] == '.' && !flag_show_dotfiles) 314 continue; 315 StringBuilder builder; 316 builder.append(path); 317 builder.append('/'); 318 builder.append(metadata.name); 319 metadata.path = builder.to_string(); 320 ASSERT(!metadata.path.is_null()); 321 int rc = lstat(metadata.path.characters(), &metadata.stat); 322 if (rc < 0) { 323 perror("lstat"); 324 memset(&metadata.stat, 0, sizeof(metadata.stat)); 325 } 326 files.append(move(metadata)); 327 } 328 329 quick_sort(files.begin(), files.end(), [](auto& a, auto& b) { 330 if (flag_sort_by_timestamp) { 331 if (flag_reverse_sort) 332 return a.stat.st_mtime > b.stat.st_mtime; 333 return a.stat.st_mtime < b.stat.st_mtime; 334 } 335 // Fine, sort by name then! 336 if (flag_reverse_sort) 337 return a.name > b.name; 338 return a.name < b.name; 339 }); 340 341 for (auto& file : files) { 342 if (!print_filesystem_object(file.path, file.name, file.stat)) 343 return 2; 344 } 345 return 0; 346} 347 348bool print_filesystem_object_short(const char* path, const char* name, size_t* nprinted) 349{ 350 struct stat st; 351 int rc = lstat(path, &st); 352 if (rc == -1) { 353 printf("lstat(%s) failed: %s\n", path, strerror(errno)); 354 return false; 355 } 356 357 *nprinted = print_name(st, name); 358 return true; 359} 360 361int do_file_system_object_short(const char* path) 362{ 363 Core::DirIterator di(path, !flag_show_dotfiles ? Core::DirIterator::SkipDots : Core::DirIterator::Flags::NoFlags); 364 if (di.has_error()) { 365 if (di.error() == ENOTDIR) { 366 size_t nprinted = 0; 367 bool status = print_filesystem_object_short(path, path, &nprinted); 368 printf("\n"); 369 if (status) 370 return 0; 371 return 2; 372 } 373 fprintf(stderr, "%s: %s\n", path, di.error_string()); 374 return 1; 375 } 376 377 Vector<String> names; 378 size_t longest_name = 0; 379 while (di.has_next()) { 380 String name = di.next_path(); 381 names.append(name); 382 if (names.last().length() > longest_name) 383 longest_name = name.length(); 384 } 385 quick_sort(names.begin(), names.end(), [](auto& a, auto& b) { return a < b; }); 386 387 size_t printed_on_row = 0; 388 size_t nprinted = 0; 389 for (size_t i = 0; i < names.size(); ++i) { 390 auto& name = names[i]; 391 StringBuilder builder; 392 builder.append(path); 393 builder.append('/'); 394 builder.append(name); 395 if (!print_filesystem_object_short(builder.to_string().characters(), name.characters(), &nprinted)) 396 return 2; 397 int offset = 0; 398 if (terminal_columns > longest_name) 399 offset = terminal_columns % longest_name / (terminal_columns / longest_name); 400 401 // The offset must be at least 2 because: 402 // - With each file an additional char is printed e.g. '@','*'. 403 // - Each filename must be separated by a space. 404 size_t column_width = longest_name + (offset > 0 ? offset : 2); 405 printed_on_row += column_width; 406 407 for (size_t j = nprinted; i != (names.size() - 1) && j < column_width; ++j) 408 printf(" "); 409 if ((printed_on_row + column_width) >= terminal_columns) { 410 printf("\n"); 411 printed_on_row = 0; 412 } 413 } 414 if (printed_on_row) 415 printf("\n"); 416 return 0; 417}