1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
|
/*
** 1998-08-02 - This module deals with file types. It provides services to initialize the
** default types, add/delete/move previously created types, and even using all
** this type information to analyze files. This is useful stuff.
** 1998-08-15 - Mild redesign; all functions now take a "generic" GList rather than a CfgInfo.
** This allows use of these functions on styles _not_ sitting on the global
** CfgInfo.style list. Very handy when editing.
** 1998-08-26 - Massive hacking to implement support for 'file' RE matching. Got a lot nicer
** than I had first expected, actually. If used, 'file' is always envoked
** exactly once for each dirpane. This cuts down on the overhead of 'file'
** reading and parsing its rather hefty (~120 KB on my system) config file.
** 1998-09-15 - Added support for case-insensitive regular expressions.
** 1998-09-16 - Added priorities to file types, controlling the order in which they are
** checked (and listed, of course). Priorities are in 0..254, which I really
** think should be enough. If I'm wrong, I'll just square that number. :) 0 is
** the highest priority (which should explain why 255 is reserved for "Unknown").
** 1998-09-18 - Regular expressions are now handled by the POSIX code in the standard C library.
** No longer any need for Henry Spencer's code. Feels good.
** 1998-12-13 - Priorities removed. Types now explicitly ordered by user in config.
*/
#include "gentoo.h"
#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stddef.h>
#include "strutil.h"
#include "styles.h"
#include "fileutil.h"
#include "types.h"
/* ----------------------------------------------------------------------------------------- */
/* These improve readability of file I/O calls when dealing with 'file'. */
#define FD_INPUT (0)
#define FD_OUTPUT (1)
/* This list is used between typ_identify_begin() and typ_identify_end() to hold directory
** lines that seem to need to be shown to the external 'file' command.
*/
static GList *file_list = NULL;
/* Another global (shudder), this one signals if there *are* any 'file'-using types around. */
static gint file_used = 0;
/* ----------------------------------------------------------------------------------------- */
/* 1998-08-02 - Create a new type, with the given <name> and identification strings. Use
** NULL for an identifier that should not be used. Returns a pointer to a new
** FType structure, or NULL on failure.
** 1998-08-11 - Now takes the name of a <style> to apply to files of this type, too.
** 1998-09-07 - Added another argument, for the new permissions support. That's eight
** arguments; pretty close to my personal limit. :)
** 1999-05-29 - Removed the <style> argument again, since it was too complex. Use typ_type_set_style().
*/
FType * typ_type_new(CfgInfo *cfg, const gchar *name, mode_t mode, int perm, const gchar *suffix, const gchar *name_re, const gchar *file_re)
{
FType *type;
if((type = malloc(sizeof *type)) != NULL)
{
str_strncpy(type->name, name, sizeof type->name);
type->mode = mode;
type->perm = perm;
type->flags = 0UL;
type->suffix[0] = '\0';
type->name_re_src[0] = '\0';
type->name_re = NULL;
type->file_re_src[0] = '\0';
type->file_re = NULL;
if(perm != 0)
type->flags |= FTFL_REQPERM;
if(suffix != NULL)
{
str_strncpy(type->suffix, suffix, sizeof type->suffix);
type->flags |= FTFL_REQSUFFIX;
}
if(name_re != NULL)
{
str_strncpy(type->name_re_src, name_re, sizeof type->name_re_src);
type->flags |= FTFL_NAMEMATCH;
}
if(file_re != NULL)
{
str_strncpy(type->file_re_src, file_re, sizeof type->file_re_src);
type->flags |= FTFL_FILEMATCH;
}
type->style = NULL;
}
return type;
}
/* 1998-08-14 - Create a copy of the <old> type. Has the side-effect of clearing all compiled
** regular expressions in the original (and the copy).
*/
FType * typ_type_copy(FType *old)
{
FType *nt;
if((nt = malloc(sizeof *nt)) != NULL)
{
if(old->name_re != NULL)
{
regfree(old->name_re);
g_free(old->name_re);
old->name_re = NULL;
}
if(old->file_re != NULL)
{
regfree(old->file_re);
g_free(old->file_re);
old->file_re = NULL;
}
memcpy(nt, old, sizeof *nt);
}
return nt;
}
void typ_type_destroy(FType *type)
{
if(type->name_re != NULL)
{
regfree(type->name_re);
g_free(type->name_re);
}
if(type->file_re != NULL)
{
regfree(type->file_re);
g_free(type->file_re);
}
free(type);
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-09-07 - Check if the file described by <line> matches the permissions requirements
** in <type>. Returns 1 if so, otherwise 0.
** 1999-03-14 - Made the access to the DirRow (previously DirLine) a lot more abstract.
*/
static int check_perm(FType *type, DirRow *row)
{
int pr = 1, pw = 1, px = 1;
mode_t mode = 0;
uid_t uid;
gid_t gid;
/* First build mode mask, for SetUID, SetGID and sticky. */
if(type->perm & FTPM_SETUID)
mode |= S_ISUID;
if(type->perm & FTPM_SETGID)
mode |= S_ISGID;
if(type->perm & FTPM_STICKY)
mode |= S_ISVTX;
/* Now we know the mode requirements - check if fulfilled. */
if((mode != 0) && ((DP_ROW_STAT(row).st_mode & mode) != mode))
return 0;
uid = geteuid();
gid = getegid();
/* Still here? Fine, then do (unorthodox, user-centric) read/write/execute permission checks. */
if(type->perm & FTPM_READ)
pr = fut_can_read(&DP_ROW_STAT(row), uid, gid);
if(type->perm & FTPM_WRITE)
pw = fut_can_write(&DP_ROW_STAT(row), uid, gid);
if(type->perm & FTPM_EXECUTE)
px = fut_can_execute(&DP_ROW_STAT(row), uid, gid);
return pr && pw && px;
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-09-15 - Check the RE in <re>, which has source <re_src>, against <string>.
** Returns 1 on match, 0 on failure.
*/
static int check_re(const gchar *re_src, regex_t **re, gboolean glob, gboolean nocase, const gchar *string)
{
const gchar *glob_re = NULL;
if(*re == NULL) /* RE not compiled? */
{
if(glob)
{
glob_re = str_glob_to_re(re_src);
re_src = glob_re;
}
*re = g_malloc(sizeof **re);
regcomp(*re, re_src, REG_EXTENDED | REG_NOSUB | (nocase ? REG_ICASE : 0));
if(glob_re)
g_free((void *) glob_re); /* Free the globbed version. */
}
if(*re != NULL)
return regexec(*re, string, 0, NULL, 0) != REG_NOMATCH;
return REG_NOMATCH;
}
/* 1998-08-02 - Attempt to apply the identification rules given in by the type <type> to the
** dirpane line in <user>. The string <fout> is output from 'file' on this very file,
** or NULL if 'file' hasn't yet been run. Returns TRUE if there is indeed a match. Note how
** regular expressions are compiled if they're not already, and that the compiled
** version is then kept for future use. This places some demands on the memory
** handling later (config etc) but I'll deal with that then...
** 1998-08-26 - Extended. Now actually does check against 'file' output.
** 1998-08-30 - Now supports glob->RE translations for name and file matches. Smooth.
** 1998-09-15 - Moved the actual RE checking out to a function of its own, since it was getting complex.
** 1999-03-14 - Replaced DirLine with new, opaque, DirRow.
*/
static int check_type(FType *type, DirRow *row, const gchar *fout)
{
guint tries = 0, hits = 0;
gchar *name;
/* Everything matches the unknown type, and that's a fact. */
if(TYP_IS_UNKNOWN(type))
return 1;
/* For links, get actual name (without path) and use that. */
if(S_ISLNK(DP_ROW_LSTAT(row).st_mode))
{
if((name = strrchr(DP_ROW_LINKNAME(row), G_DIR_SEPARATOR)) == NULL)
name = DP_ROW_LINKNAME(row);
else
name++;
}
else
name = DP_ROW_NAME(row);
/* Apply the mode test first, since it's no doubt the fastest. */
if(((DP_ROW_STAT(row).st_mode) & S_IFMT) != type->mode)
return 0;
if(type->flags & FTFL_REQPERM)
{
tries++;
hits += check_perm(type, row);
}
if(type->flags & FTFL_REQSUFFIX)
{
tries++;
hits += str_has_suffix(name, type->suffix);
}
if(type->flags & FTFL_NAMEMATCH)
{
tries++;
hits += check_re(type->name_re_src, &type->name_re, type->flags & FTFL_NAMEGLOB, type->flags & FTFL_NAMENOCASE, name);
}
if(type->flags & FTFL_FILEMATCH)
{
tries++;
if(fout != NULL)
hits += check_re(type->file_re_src, &type->file_re, type->flags & FTFL_FILEGLOB, type->flags & FTFL_FILENOCASE, fout);
}
return tries == hits;
}
/* 1998-08-02 - Identify the <line>, i.e. attempt to find an FType that matches it, and assign
** a pointer to that FType to the line's "type" field. If no user-defined type
** is found, we assign the root type ("Unknown").
** 1998-08-26 - Now returns TRUE if a "sophisticated" type was indeed found, FALSE if the "Unknown"
** type had to do. Also has become 'static', since you can't call this outside of
** the framework established by typ_identify_begin()/typ_idenfify_end(). Note use
** of NULL as third arg to check_type(), since we haven't started running 'file'
** yet.
** 1999-03-14 - Replaced DirLine with DirRow.
*/
static gint identify(MainInfo *min, DirRow *row)
{
GList *here;
for(here = min->cfg.type; here != NULL; here = g_list_next(here))
{
if(check_type((FType *) here->data, row, NULL))
break;
}
DP_ROW_TYPE(row) = (FType *) here->data;
return !TYP_IS_UNKNOWN((FType *) here->data);
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-08-26 - A rewrite of the much of the (three weeks old) file typing architecture, in
** order to accomodate use of 'file' in a way even resembling something efficient.
** Rather than just calling typ_identify() on a dirline, you call first this
** function, which initializes some state. You then, repeatedly, call typ_identify_line()
** for all lines you wish to type. Then call typ_identify_end(). Done!
*/
void typ_identify_begin(MainInfo *min)
{
GList *here;
if(file_list != NULL)
fprintf(stderr, "**TYPES: Attempted to nest calls to typ_identify_begin()!\n");
for(here = min->cfg.type; here != NULL; here = g_list_next(here))
{
if(((FType *) here->data)->flags & FTFL_FILEMATCH)
break;
}
file_used = (here != NULL) ? TRUE : FALSE;
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-08-26 - Identify the given line. If identification fails, i.e. if the "Unknown" type is
** assigned to the line, remember the line for later exposure to the external
** 'file' command.
*/
void typ_identify(MainInfo *min, DirRow *row)
{
if((identify(min, row) == FALSE) && (file_used == TRUE))
file_list = g_list_prepend(file_list, (gpointer) row);
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-08-26 - Attempt to match all types in list against <line>, knowing that 'file' said
** <fout>. Only types that include a 'file'-matching RE are checked, of course.
** Returns the matching type if any, or NULL if there's no match.
*/
static FType * match_file(GList *list, DirRow *row, char *fout)
{
FType *type;
for(; (list != NULL) && (type = (FType *) list->data); list = g_list_next(list))
{
if(check_type(type, row, fout))
return type;
}
return NULL;
}
/* 1998-08-26 - End of batched identification. If any unknown's were found *and* we know that
** there are 1 or more types using the 'file' recognition support, we need to
** call 'file'. The idea here is that we only want to execute the 'file' command
** once, no matter how many files need identification. Also, we use redirection
** of stdin for 'file' to supply it with file names to check (via its "-f -" option),
** since writing out the names to disk seems inefficient.
*/
void typ_identify_end(MainInfo *min, const gchar *path)
{
GList *here;
gchar buf[MAXNAMLEN + 2], *temp_name, line[MAXNAMLEN + 256], *fout;
gint fd[2], len, status;
pid_t file_pid;
FType *type;
FILE *in;
if(file_list == NULL)
return;
if((temp_name = tmpnam(NULL)) == NULL)
return;
if(pipe(fd) == 0)
{
switch((file_pid = fork()))
{
case 0:
assert(close(FD_INPUT) == 0); /* Kill stdin. */
assert(dup(fd[FD_INPUT]) == FD_INPUT); /* Replace with pipe from main. */
assert(close(fd[FD_INPUT]) == 0); /* Close the pipe. */
assert(close(FD_OUTPUT) == 0); /* Kill stdout. */
assert(open(temp_name, O_CREAT|O_RDWR, S_IRWXU) == FD_OUTPUT);
assert(close(fd[FD_OUTPUT]) == 0); /* Close unused pipe. */
assert(chdir(path) == 0); /* Change to the path in question. */
execlp("file", "file", "-f", "-", NULL);
exit(EXIT_FAILURE);
case -1:
fprintf(stderr, "fork() failed! Damn!\n");
exit(EXIT_FAILURE);
}
assert(close(fd[FD_INPUT]) == 0); /* Close input part of pipe (used by child). */
for(here = file_list; here != NULL; here = g_list_next(here))
{
len = g_snprintf(buf, sizeof buf, "%s\n", DP_SEL_NAME(here));
write(fd[FD_OUTPUT], buf, len);
}
close(fd[FD_OUTPUT]);
waitpid(file_pid, &status, 0);
if(WIFEXITED(status))
{
if((in = fopen(temp_name, "rt")) != NULL)
{
for(here = file_list; here != NULL && fgets(line, sizeof line, in); here = g_list_next(here))
{
if((fout = strchr(line, ':')) == NULL)
continue;
for(fout++; isspace((unsigned char) *fout); fout++)
;
if((type = match_file(min->cfg.type, DP_SEL_ROW(here), fout)) != NULL)
DP_SEL_TYPE(here) = type;
}
fclose(in);
}
remove(temp_name);
}
else
fprintf(stderr, "Failure!!\n");
g_list_free(file_list);
file_list = NULL;
}
else
fprintf(stderr, "No pipe!\n");
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-08-02 - Initialize the file typing subsystem with some simple default types.
** 1998-09-02 - Painlessly diked out all but "Unknown" and "Directory", since I think these
** two are the only ones that are going to be built-in.
*/
void typ_init(CfgInfo *cfg)
{
FType *type;
cfg->type = NULL;
if((type = typ_type_new(cfg, "Unknown", 0, 0, NULL, NULL, NULL)) != NULL)
{
cfg->type = typ_type_insert(cfg->type, NULL, type);
cfg->type = typ_type_set_style(cfg->type, type, cfg->style, NULL);
}
if((type = typ_type_new(cfg, "Directory", S_IFDIR, 0, NULL, NULL, NULL)) != NULL)
{
cfg->type = typ_type_insert(cfg->type, NULL, type);
cfg->type = typ_type_set_style(cfg->type, type, cfg->style, "Directory");
}
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-12-14 - Rewritten another time. Now never inserts anything after the "Unknown" type.
** Pretty lazily implemented, but so?
** 1999-01-09 - Rewritten again. Now takes an additional <after> argument, and inserts <typ>
** right after it. If <after> is the "Unknown", we insert before it. If it's
** NULL, the same thing happens.
*/
GList * typ_type_insert(GList *list, FType *after, FType *type)
{
gint li;
if(after == NULL || TYP_IS_UNKNOWN(after)) /* No reference element given, or "Unknown" ref? */
{
GList *last;
if((last = g_list_last(list)) != NULL)
{
if(TYP_IS_UNKNOWN((FType *) last->data))
{
li = g_list_index(list, last->data);
return g_list_insert(list, type, li);
}
}
return g_list_append(list, type);
}
li = g_list_index(list, after);
return g_list_insert(list, type, li + 1);
}
/* 1998-12-14 - Remove a type from the list, and return the new list. Not strictly
** necessary (only called at one place), but makes me feel good. :^)
*/
GList * typ_type_remove(GList *list, FType *type)
{
if(list == NULL || type == NULL)
return NULL;
if(TYP_IS_UNKNOWN(type)) /* Can't remove the "Unknown" type. */
return list;
list = g_list_remove(list, type);
typ_type_destroy(type);
return list;
}
/* ----------------------------------------------------------------------------------------- */
/* 1998-09-17 - Change the name of given type. Since the name is used as a fall-back
** when sorting types of equal priority, this calls for a resort.
** 1998-12-13 - Removing the priorities also removed the sorting dependency on names,
** so this became a lot simpler. I could remove the entire function,
** but I'll keep it. You never know...
*/
GList * typ_type_set_name(GList *list, FType *type, const gchar *name)
{
str_strncpy(type->name, name, sizeof type->name);
return list;
}
/* 1999-05-29 - Set the 'style' field of <type> to the style whose name is <name>. */
GList * typ_type_set_style(GList *list, FType *type, StyleInfo *si, const gchar *name)
{
if((type != NULL) && (si != NULL))
type->style = stl_styleinfo_style_find(si, name);
return list;
}
/* 1998-12-13 - Move given <type> either up (<delta> == -1) or down (1). Returns
** new version of <list>. Other <delta> values are illegal.
*/
GList * typ_type_move(GList *list, FType *type, gint delta)
{
gint pos, np;
if(delta != -1 && delta != 1)
return list;
pos = g_list_index(list, type);
list = g_list_remove(list, type);
np = pos + delta;
if(np < 0)
np = 0;
else if(np > (gint) g_list_length(list) - 1)
np = (gint) g_list_length(list) - 1;
return g_list_insert(list, type, np);
}
|