1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
4 * This file is part of gum
6 * Copyright (C) 2013 Intel Corporation.
8 * Contact: Imran Zaman <imran.zaman@intel.com>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
29 #if defined HAVE_SYS_XATTR_H
30 #include <sys/xattr.h>
32 #include <linux/xattr.h>
37 #include <glib/gstdio.h>
39 #include "common/gum-file.h"
40 #include "common/gum-string-utils.h"
41 #include "common/gum-defines.h"
42 #include "common/gum-log.h"
43 #include "common/gum-error.h"
44 #include "common/gum-config.h"
48 * @short_description: Utility functions for file handling
50 * @include: gum/common/gum-file.h
52 * Below is the code snippet, which demonstrate how can file update function be
53 * used to add, remove or modify an entry in the user/group database file
58 * gboolean _custom_update_file_entries (GObject *obj, GumOpType op,
59 * FILE *source_file, FILE *dup_file, gpointer user_data, GError **error)
61 * //loop through the file entries and modify as per operation type
65 * void update_file_entries ()
67 * GObject* obj = NULL;
68 * GError *error = NULL;
69 * const gchar *source_file = "/tmp/passwd";
70 * gum_file_update (obj, GUM_OPTYPE_ADD,
71 * (GumFileUpdateCB)_custom_update_file_entries, source_file, NULL,
81 * Data structure that contains information about file stream as defined in
87 * @GUM_OPTYPE_ADD: add an entry
88 * @GUM_OPTYPE_DELETE: delete an entry
89 * @GUM_OPTYPE_MODIFY: modify an entry
91 * This enumeration lists the operations on file entry.
96 * @object: (transfer none): the instance of #GObject
97 * @op: (transfer none): the #GumOpType operation to be done on the file entry
98 * @source_file: (transfer none): the source file pointer
99 * @dup_file: (transfer none): the duplicate file pointer
100 * @user_data: the user data
101 * @error: (transfer none): the #GError which is set in case of an error
103 * Callback can be used for adding, deleting or modifying file entries. It is
104 * invoked in #gum_file_update function.
106 * Returns: TRUE if successful, FALSE otherwise and @error is set.
109 #define GUM_PERM 0777
116 #if defined(HAVE_LSETXATTR)
117 GumConfig *config = NULL;
120 config = gum_config_new (NULL);
121 const gchar *smack_label = gum_config_get_string (config, key);
123 len = strlen (smack_label);
124 DBG ("Set smack label %s for path %s with len %d",smack_label, path,
128 * Set smack64 extended attribute (when provided in the config file)
132 lsetxattr (path, XATTR_NAME_SMACK, smack_label, len, 0) != 0) {
133 g_object_unref (config);
136 g_object_unref (config);
147 if (!fn || !(fp = fopen (fn, mode))) {
149 WARN ("Could not open file '%s', error: %s", fn, strerror(errno));
156 _copy_file_attributes (
157 const gchar *from_path,
158 const gchar *to_path)
161 ssize_t attrs_size = 0;
162 struct stat from_stat;
164 if (stat (from_path, &from_stat) < 0 ||
165 chmod (to_path, from_stat.st_mode) < 0 ||
166 chown (to_path, from_stat.st_uid, from_stat.st_gid) < 0) {
170 /* copy extended attributes */
171 #if defined(HAVE_LLISTXATTR) && \
172 defined(HAVE_LGETXATTR) && \
173 defined(HAVE_LSETXATTR)
174 attrs_size = llistxattr (from_path, NULL, 0);
175 if (attrs_size > 0) {
177 gchar *names = g_new0 (gchar, attrs_size + 1);
178 if (names && llistxattr (from_path, names, attrs_size) > 0) {
180 gchar *name = names, *value = NULL;
181 gchar *end_names = names + attrs_size;
182 names[attrs_size] = '\0';
185 while (name && name != end_names) {
186 if (name[0] != '\0') {
187 size = lgetxattr (from_path, name, NULL, 0);
189 (value = g_realloc (value, size)) &&
190 lgetxattr (from_path, name, value, size) > 0) {
192 if (lsetxattr (to_path, name, value, size, 0) != 0) {
198 name = strchr (name,'\0') + 1;
210 * gum_file_open_db_files:
211 * @source_file_path: (transfer none): the path to source file
212 * @dup_file_path: (transfer none): the path to duplicate file, created from
214 * @source_file: (transfer none): the file pointer created when source file
215 * is opened in read mode
216 * @dup_file: (transfer none): the file pointer created when duplicate file is
217 * opened in write mode
218 * @error: (transfer none): the #GError which is set in case of an error
220 * Opens the source file @source_file_path in read mode. Then creates the
221 * duplicate file @dup_file_path in write mode and copies source file attributes
222 * to the duplicate file. Open file handles are set in @source_file and
225 * Returns: TRUE if successful, FALSE otherwise and @error is set.
228 gum_file_open_db_files (
229 const gchar *source_file_path,
230 const gchar *dup_file_path,
237 gboolean retval = TRUE;
239 if (!source_file || !dup_file || !source_file_path || !dup_file_path) {
240 GUM_RETURN_WITH_ERROR(GUM_ERROR_FILE_OPEN, "Invalid arguments",
244 if (!(source = _open_file (source_file_path, "r"))) {
245 GUM_RETURN_WITH_ERROR (GUM_ERROR_FILE_OPEN, "Unable to open orig file",
249 if (!(dup = _open_file (dup_file_path, "w+"))) {
250 GUM_SET_ERROR (GUM_ERROR_FILE_OPEN, "Unable to open new file",
251 error, retval, FALSE);
255 if (!_set_smack64_attr (dup_file_path,
256 GUM_CONFIG_GENERAL_SMACK64_NEW_FILES)) {
257 GUM_SET_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
258 "Unable to set smack file attributes", error, retval, FALSE);
262 if (!_copy_file_attributes (source_file_path, dup_file_path)) {
263 GUM_SET_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
264 "Unable to get/set file attributes", error, retval, FALSE);
268 *source_file = source;
274 if (source) fclose(source);
277 g_unlink(dup_file_path);
286 * gum_file_close_db_files:
287 * @source_file_path: (transfer none): the path to source file
288 * @dup_file_path: (transfer none): the path to duplicate file
289 * @source_file: (transfer none): the source file pointer
290 * @dup_file: (transfer none): the duplicate file pointer
291 * @error: (transfer none): the #GError which is set in case of an error
293 * Closes the duplicate file @dup_file_path after flushing all the data. Backup
294 * of the source file @source_file_path is created and duplicate file is renamed
295 * to as the source file.
297 * Returns: TRUE if successful, FALSE otherwise and @error is set.
300 gum_file_close_db_files (
301 const gchar *source_file_path,
302 const gchar *dup_file_path,
307 gboolean retval = TRUE;
308 gchar *old_file_path = NULL;
310 if (source_file) fclose (source_file);
313 fflush (dup_file) != 0 ||
314 fsync (fileno (dup_file)) != 0 ||
315 fclose (dup_file) != 0) {
316 GUM_SET_ERROR (GUM_ERROR_FILE_WRITE, "File write failure", error,
321 if (!source_file_path) {
322 GUM_SET_ERROR(GUM_ERROR_FILE_WRITE, "null source file path", error,
327 if (!dup_file_path) {
328 GUM_RETURN_WITH_ERROR(GUM_ERROR_FILE_WRITE, "null dest file path",
332 if ((old_file_path = g_strdup_printf ("%s.old", source_file_path))) {
333 /* delete obsolote backup file if any */
334 g_unlink (old_file_path);
335 /* Move source file to old file and dup file as updated file */
336 if (link (source_file_path, old_file_path) != 0) {
337 WARN("Could not create a backup file for '%s'", source_file_path);
340 WARN("Could not create a backup file for '%s'", source_file_path);
343 if (g_rename (dup_file_path, source_file_path) != 0) {
344 GUM_SET_ERROR (GUM_ERROR_FILE_MOVE, "Unable to move file", error,
346 g_unlink(old_file_path);
349 g_free (old_file_path);
352 if (dup_file_path) g_unlink (dup_file_path);
359 * @object: (transfer none): the instance of #GObject; can be NULL
360 * @op: (transfer none): the #GumOpType operation to be done on file entry
362 * @callback: (transfer none): the callback #GumFileUpdateCB to be invoked
363 * when the source and duplicate files are opened to be handled
364 * @source_file_path: (transfer none): the source file path
365 * @user_data: user data to be passed on to the @callback
366 * @error: (transfer none): the #GError which is set in case of an error
368 * Opens the files and invokes the callback to do the required operation.
369 * Finally files are flushed and closed.
371 * Returns: TRUE if successful, FALSE otherwise and @error is set.
377 GumFileUpdateCB callback,
378 const gchar *source_file_path,
382 gboolean retval = TRUE;
383 FILE *source_file = NULL, *dup_file = NULL;
384 gchar *dup_file_path = NULL;
386 dup_file_path = g_strdup_printf ("%s-tmp.%lu", source_file_path,
387 (unsigned long)getpid ());
388 retval = gum_file_open_db_files (source_file_path, dup_file_path,
389 &source_file, &dup_file, error);
390 if (!retval) goto _finished;
392 /* Update, sync and close file */
394 GUM_SET_ERROR (GUM_ERROR_FILE_WRITE,
395 "File write function not specified", error, retval, FALSE);
399 retval = (*callback) (object, op, source_file, dup_file, user_data, error);
404 retval = gum_file_close_db_files (source_file_path, dup_file_path,
405 source_file, dup_file, error);
411 if (dup_file) fclose (dup_file);
412 g_unlink (dup_file_path);
413 if (source_file) fclose (source_file);
416 g_free (dup_file_path);
423 * @username: (transfer none): name of the user
424 * @filename: (transfer none): path to the file
426 * Gets the passwd structure from the file based on username.
428 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
432 const gchar *username,
433 const gchar *filename)
435 struct passwd *pent = NULL;
438 if (!username || !filename) {
442 if (!(fp = _open_file (filename, "r"))) {
445 while ((pent = fgetpwent (fp)) != NULL) {
446 if(g_strcmp0 (username, pent->pw_name) == 0)
458 * @filename: (transfer none): path to the file
460 * Gets the passwd structure from the file based on uid.
462 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
467 const gchar *filename)
469 struct passwd *pent = NULL;
476 if (!(fp = _open_file (filename, "r"))) {
480 while ((pent = fgetpwent (fp)) != NULL) {
481 if(uid == pent->pw_uid)
491 * gum_file_find_user_by_gid:
492 * @primary_gid: primary gid of the user
493 * @filename: (transfer none): path to the file
495 * Gets the passwd structure from the file based on the primary group id.
497 * Returns: (transfer full): passwd structure if successful, NULL otherwise.
500 gum_file_find_user_by_gid (
502 const gchar *filename)
504 struct passwd *pent = NULL;
507 if (!filename || primary_gid == GUM_GROUP_INVALID_GID) {
511 if (!(fp = _open_file (filename, "r"))) {
515 while ((pent = fgetpwent (fp)) != NULL) {
516 if(primary_gid == pent->pw_gid)
527 * @username: (transfer none): name of the user
528 * @filename: (transfer none): path to the file
530 * Gets the spwd structure from the file based on the username.
532 * Returns: (transfer full): spwd structure if successful, NULL otherwise.
536 const gchar *username,
537 const gchar *filename)
539 struct spwd *spent = NULL;
542 if (!username || !filename) {
546 if (!(fp = _open_file (filename, "r"))) {
550 while ((spent = fgetspent (fp)) != NULL) {
551 if(g_strcmp0 (username, spent->sp_namp) == 0)
562 * @grname: (transfer none): name of the group
563 * @filename: (transfer none): path to the file
565 * Gets the group structure from the file based on the groupname @grname.
567 * Returns: (transfer full): group structure if successful, NULL otherwise.
572 const gchar *filename)
574 struct group *gent = NULL;
577 if (!grname || !filename) {
581 if (!(fp = _open_file (filename, "r"))) {
584 while ((gent = fgetgrent (fp)) != NULL) {
585 if(g_strcmp0 (grname, gent->gr_name) == 0)
596 * @gid: id of the group
597 * @filename: (transfer none): path to the file
599 * Gets the group structure from the file based on the gid.
601 * Returns: (transfer full): group structure if successful, NULL otherwise.
606 const gchar *filename)
608 struct group *gent = NULL;
615 if (!(fp = _open_file (filename, "r"))) {
618 while ((gent = fgetgrent (fp)) != NULL) {
619 if(gid == gent->gr_gid)
630 * @grname: (transfer none): name of the group
631 * @filename: (transfer none): path to the file
633 * Gets the sgrp structure from the file based on the groupname @grname.
635 * Returns: (transfer full): sgrp structure if successful, NULL otherwise.
640 const gchar *filename)
642 struct sgrp *sgent = NULL;
645 if (!grname || !filename) {
649 if (!g_file_test (filename, G_FILE_TEST_EXISTS) ||
650 !(fp = _open_file (filename, "r"))) {
654 while ((sgent = fgetsgent (fp)) != NULL) {
655 if(g_strcmp0 (grname, sgent->sg_namp) == 0)
666 * @dir: (transfer none): directory path
667 * @filename: (transfer none): name of the file
669 * Builds complete file path based on the @filename and @dir.
671 * Returns: (transfer full): the #GFile if successful, NULL otherwise.
676 const gchar *filename)
681 if (!dir || !filename) {
685 fn = g_build_filename (dir, filename, NULL);
687 file = g_file_new_for_path (fn);
695 _copy_dir_recursively (
703 gboolean retval = TRUE;
704 gboolean stop = FALSE;
705 const gchar *src_fname = NULL;
706 gchar *src_filepath = NULL, *dest_filepath = NULL;
707 GDir *src_dir = NULL;
708 struct stat stat_entry;
711 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
712 "Invalid directory path(s)", error, FALSE);
715 DBG ("copy directory %s -> %s", src, dest);
716 src_dir = g_dir_open (src, 0, NULL);
718 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
719 "Invalid source directory path", error, FALSE);
722 while ((src_fname = g_dir_read_name (src_dir))) {
724 GFile *src_file = NULL, *dest_file = NULL;
726 src_filepath = g_build_filename (src, src_fname, NULL);
727 stop = (lstat(src_filepath, &stat_entry) != 0);
728 if (stop) goto _free_data;
730 dest_filepath = g_build_filename (dest, src_fname, NULL);
731 src_file = g_file_new_for_path (src_filepath);
732 dest_file = g_file_new_for_path (dest_filepath);
734 if (S_ISDIR (stat_entry.st_mode)) {
735 DBG ("copy directory %s", src_filepath);
736 gint mode = GUM_PERM & ~umask;
737 g_mkdir_with_parents (dest_filepath, mode);
738 stop = !_set_smack64_attr (dest_filepath,
739 GUM_CONFIG_GENERAL_SMACK64_USER_FILES);
741 stop = !_copy_dir_recursively (src_filepath, dest_filepath, uid,
744 DBG ("copy file %s", src_filepath);
745 if (!g_file_copy (src_file, dest_file,
746 G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL,
748 WARN("File copy failure error %d:%s", err ? err->code : 0,
749 err ? err->message : "");
750 if (err) g_error_free (err);
754 stop = !_set_smack64_attr (dest_filepath,
755 GUM_CONFIG_GENERAL_SMACK64_USER_FILES);
757 if (!stop) stop = !_copy_file_attributes (src_filepath, dest_filepath);
758 if (!stop) stop = (chown (dest_filepath, uid, gid) < 0);
761 g_free (src_filepath);
762 g_free (dest_filepath);
763 GUM_OBJECT_UNREF (src_file);
764 GUM_OBJECT_UNREF (dest_file);
766 GUM_SET_ERROR (GUM_ERROR_HOME_DIR_COPY_FAILURE,
767 "Home directory copy failure", error, retval, FALSE);
772 g_dir_close (src_dir);
777 * gum_file_create_home_dir:
778 * @home_dir: path to the user home directory
779 * @uid: id of the user
780 * @gid: group id of the user
781 * @umask: the umask to be used for setting the mode of the files/directories
782 * @error: (transfer none): the #GError which is set in case of an error
784 * Creates the home directory of the user. All the files from the
785 * #GUM_CONFIG_GENERAL_SKEL_DIR are copied (recursively) to the user home
788 * Returns: TRUE if successful, FALSE otherwise and @error is set.
791 gum_file_create_home_dir (
792 const gchar *home_dir,
798 gboolean retval = TRUE;
799 gint mode = GUM_PERM & ~umask;
802 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
803 "Invalid home directory path", error, FALSE);
806 if (g_access (home_dir, F_OK) != 0) {
807 GumConfig *config = gum_config_new (NULL);
808 const gchar *skel_dir = NULL;
810 skel_dir = gum_config_get_string (config, GUM_CONFIG_GENERAL_SKEL_DIR);
811 g_object_unref (config);
813 if (!g_file_test (home_dir, G_FILE_TEST_EXISTS)) {
814 g_mkdir_with_parents (home_dir, mode);
817 if (!g_file_test (home_dir, G_FILE_TEST_IS_DIR)) {
818 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
819 "Home directory creation failure", error, FALSE);
822 if (!_set_smack64_attr (home_dir,
823 GUM_CONFIG_GENERAL_SMACK64_USER_FILES)) {
824 GUM_RETURN_WITH_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
825 "Unable to set smack64 home dir attr", error, FALSE);
828 if (!_copy_file_attributes (skel_dir, home_dir)) {
829 GUM_RETURN_WITH_ERROR (GUM_ERROR_FILE_ATTRIBUTE,
830 "Unable to get/set dir attributes", error, FALSE);
833 /* when run in test mode, user may not exist */
838 if (chown (home_dir, uid, gid) < 0) {
839 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_CREATE_FAILURE,
840 "Home directory chown failure", error, FALSE);
843 retval = _copy_dir_recursively (skel_dir, home_dir, uid, gid, umask,
851 * gum_file_delete_home_dir:
852 * @dir: (transfer none): the path to the directory
853 * @error: (transfer none): the #GError which is set in case of an error
855 * Deletes the directory and its sub-directories recursively.
857 * Returns: TRUE if successful, FALSE otherwise and @error is set.
860 gum_file_delete_home_dir (
867 if (!dir || !(gdir = g_dir_open(dir, 0, NULL))) {
868 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
869 "Invalid home directory path", error, FALSE);
872 const gchar *fname = NULL;
874 gchar *filepath = NULL;
875 while ((fname = g_dir_read_name (gdir)) != NULL) {
876 if (g_strcmp0 (fname, ".") == 0 ||
877 g_strcmp0 (fname, "..") == 0) {
881 filepath = g_build_filename (dir, fname, NULL);
883 retval = lstat(filepath, &sent);
885 /* recurse the directory */
886 if (S_ISDIR (sent.st_mode)) {
887 retval = (gint)!gum_file_delete_home_dir (filepath, error);
889 retval = g_remove (filepath);
896 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
897 "Unable to delete files in the directory", error, FALSE);
902 if (g_remove (dir) != 0) {
903 GUM_RETURN_WITH_ERROR (GUM_ERROR_HOME_DIR_DELETE_FAILURE,
904 "Unable to delete home directory", error, FALSE);