diff --git a/.clang-format b/.clang-format new file mode 100755 index 0000000..19b69a2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,710 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +BasedOnStyle: GNU +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: + Enabled: true + Consecutive: true + AcrossEmptyLines: false + AcrossComments: false + AcrossEmptyLinesAndComments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: + Enabled: true + Consecutive: true + AcrossEmptyLines: false + AcrossComments: false + AcrossEmptyLinesAndComments: false +AlignConsecutiveStyle: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | LC_ALL=C sort -u +ForEachMacros: + - '__ata_qc_for_each' + - '__bio_for_each_bvec' + - '__bio_for_each_segment' + - '__evlist__for_each_entry' + - '__evlist__for_each_entry_continue' + - '__evlist__for_each_entry_from' + - '__evlist__for_each_entry_reverse' + - '__evlist__for_each_entry_safe' + - '__for_each_mem_range' + - '__for_each_mem_range_rev' + - '__for_each_thread' + - '__hlist_for_each_rcu' + - '__map__for_each_symbol_by_name' + - '__perf_evlist__for_each_entry' + - '__perf_evlist__for_each_entry_reverse' + - '__perf_evlist__for_each_entry_safe' + - '__rq_for_each_bio' + - '__shost_for_each_device' + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - 'ata_qc_for_each' + - 'ata_qc_for_each_raw' + - 'ata_qc_for_each_with_internal' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - 'bio_for_each_bvec' + - 'bio_for_each_bvec_all' + - 'bio_for_each_folio_all' + - 'bio_for_each_integrity_vec' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'bpf__perf_for_each_map' + - 'bpf__perf_for_each_map_named' + - 'bpf_for_each_spilled_reg' + - 'bpf_object__for_each_map' + - 'bpf_object__for_each_program' + - 'bpf_object__for_each_safe' + - 'bpf_perf_object__for_each' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_efficient_entry_idx' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'damon_for_each_region' + - 'damon_for_each_region_safe' + - 'damon_for_each_scheme' + - 'damon_for_each_scheme_safe' + - 'damon_for_each_target' + - 'damon_for_each_target_safe' + - 'data__for_each_file' + - 'data__for_each_file_new' + - 'data__for_each_file_start' + - 'device_for_each_child_node' + - 'displayid_iter_for_each' + - 'dma_fence_array_for_each' + - 'dma_fence_chain_for_each' + - 'dma_fence_unwrap_for_each' + - 'dma_resv_for_each_fence' + - 'dma_resv_for_each_fence_unlocked' + - 'do_for_each_ftrace_op' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_atomic_for_each_plane_damage' + - 'drm_client_for_each_connector_iter' + - 'drm_client_for_each_modeset' + - 'drm_connector_for_each_possible_encoder' + - 'drm_for_each_bridge_in_chain' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_crtc_reverse' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_for_each_privobj' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'dsa_switch_for_each_available_port' + - 'dsa_switch_for_each_cpu_port' + - 'dsa_switch_for_each_port' + - 'dsa_switch_for_each_port_continue_reverse' + - 'dsa_switch_for_each_port_safe' + - 'dsa_switch_for_each_user_port' + - 'dsa_tree_for_each_user_port' + - 'dso__for_each_symbol' + - 'dsos__for_each_with_build_id' + - 'elf_hash_for_each_possible' + - 'elf_section__for_each_rel' + - 'elf_section__for_each_rela' + - 'elf_symtab__for_each_symbol' + - 'evlist__for_each_cpu' + - 'evlist__for_each_entry' + - 'evlist__for_each_entry_continue' + - 'evlist__for_each_entry_from' + - 'evlist__for_each_entry_reverse' + - 'evlist__for_each_entry_safe' + - 'flow_action_for_each' + - 'for_each_acpi_dev_match' + - 'for_each_active_dev_scope' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_aggr_pgid' + - 'for_each_available_child_of_node' + - 'for_each_bench' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_btf_ext_rec' + - 'for_each_btf_ext_sec' + - 'for_each_bvec' + - 'for_each_card_auxs' + - 'for_each_card_auxs_safe' + - 'for_each_card_components' + - 'for_each_card_dapms' + - 'for_each_card_pre_auxs' + - 'for_each_card_prelinks' + - 'for_each_card_rtds' + - 'for_each_card_rtds_safe' + - 'for_each_card_widgets' + - 'for_each_card_widgets_safe' + - 'for_each_cgroup_storage_type' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_clear_bitrange' + - 'for_each_clear_bitrange_from' + - 'for_each_cmd' + - 'for_each_cmsghdr' + - 'for_each_collection' + - 'for_each_comp_order' + - 'for_each_compatible_node' + - 'for_each_component_dais' + - 'for_each_component_dais_safe' + - 'for_each_console' + - 'for_each_console_srcu' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dapm_widgets' + - 'for_each_dedup_cand' + - 'for_each_dev_addr' + - 'for_each_dev_scope' + - 'for_each_dma_cap_mask' + - 'for_each_dpcm_be' + - 'for_each_dpcm_be_rollback' + - 'for_each_dpcm_be_safe' + - 'for_each_dpcm_fe' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_element' + - 'for_each_element_extid' + - 'for_each_element_id' + - 'for_each_endpoint_of_node' + - 'for_each_event' + - 'for_each_event_tps' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_pfn_range_in_zone' + - 'for_each_free_mem_pfn_range_in_zone_from' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_group_evsel' + - 'for_each_group_member' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_inject_fn' + - 'for_each_insn' + - 'for_each_insn_prefix' + - 'for_each_intid' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_lang' + - 'for_each_link_codecs' + - 'for_each_link_cpus' + - 'for_each_link_platforms' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_mem_pfn_range' + - 'for_each_mem_range' + - 'for_each_mem_range_rev' + - 'for_each_mem_region' + - 'for_each_member' + - 'for_each_memory' + - 'for_each_migratetype_order' + - 'for_each_missing_reg' + - 'for_each_net' + - 'for_each_net_continue_reverse' + - 'for_each_net_rcu' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_continue_reverse' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_mst_mgr_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_plane_in_state_reverse' + - 'for_each_new_private_obj_in_state' + - 'for_each_new_reg' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_nonreserved_multicast_dest_pgid' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_cpu_node' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_old_mst_mgr_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_mst_mgr_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_plane_in_state_reverse' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_path' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pcm_streams' + - 'for_each_physmem_range' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_probe_cache_entry' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_prop_codec_conf' + - 'for_each_prop_dai_codec' + - 'for_each_prop_dai_cpu' + - 'for_each_prop_dlc_codecs' + - 'for_each_prop_dlc_cpus' + - 'for_each_prop_dlc_platforms' + - 'for_each_property_of_node' + - 'for_each_reg' + - 'for_each_reg_filtered' + - 'for_each_registered_fb' + - 'for_each_requested_gpio' + - 'for_each_requested_gpio_in_range' + - 'for_each_reserved_mem_range' + - 'for_each_reserved_mem_region' + - 'for_each_rtd_codec_dais' + - 'for_each_rtd_components' + - 'for_each_rtd_cpu_dais' + - 'for_each_rtd_dais' + - 'for_each_script' + - 'for_each_sec' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_set_bitrange' + - 'for_each_set_bitrange_from' + - 'for_each_set_clump8' + - 'for_each_sg' + - 'for_each_sg_dma_page' + - 'for_each_sg_page' + - 'for_each_sgtable_dma_page' + - 'for_each_sgtable_dma_sg' + - 'for_each_sgtable_page' + - 'for_each_sgtable_sg' + - 'for_each_shell_test' + - 'for_each_sibling_event' + - 'for_each_subelement' + - 'for_each_subelement_extid' + - 'for_each_subelement_id' + - 'for_each_sublist' + - 'for_each_subsystem' + - 'for_each_supported_activate_fn' + - 'for_each_supported_inject_fn' + - 'for_each_test' + - 'for_each_thread' + - 'for_each_token' + - 'for_each_unicast_dest_pgid' + - 'for_each_vsi' + - 'for_each_wakeup_source' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'func_for_each_insn' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'genradix_for_each' + - 'genradix_for_each_from' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hashmap__for_each_entry' + - 'hashmap__for_each_entry_safe' + - 'hashmap__for_each_key_entry' + - 'hashmap__for_each_key_entry_safe' + - 'hctx_for_each_ctx' + - 'hists__for_each_format' + - 'hists__for_each_sort_list' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - 'hlist_for_each_entry_srcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'i3c_bus_for_each_i2cdev' + - 'i3c_bus_for_each_i3cdev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_continue_ul' + - 'idr_for_each_entry_ul' + - 'in_dev_for_each_ifa_rcu' + - 'in_dev_for_each_ifa_rtnl' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk' + - 'inet_lhash2_for_each_icsk_continue' + - 'inet_lhash2_for_each_icsk_rcu' + - 'interval_tree_for_each_double_span' + - 'interval_tree_for_each_span' + - 'intlist__for_each_entry' + - 'intlist__for_each_entry_safe' + - 'iopt_for_each_contig_area' + - 'kcore_copy__for_each_phdr' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_func_safe' + - 'klp_for_each_func_static' + - 'klp_for_each_object' + - 'klp_for_each_object_safe' + - 'klp_for_each_object_static' + - 'kunit_suite_for_each_test_case' + - 'kvm_for_each_memslot' + - 'kvm_for_each_memslot_in_gfn_range' + - 'kvm_for_each_vcpu' + - 'libbpf_nla_for_each_attr' + - 'list_for_each' + - 'list_for_each_codec' + - 'list_for_each_codec_safe' + - 'list_for_each_continue' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_rcu' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_entry_srcu' + - 'list_for_each_from' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'map__for_each_symbol' + - 'map__for_each_symbol_by_name' + - 'map_for_each_event' + - 'map_for_each_metric' + - 'maps__for_each_entry' + - 'maps__for_each_entry_safe' + - 'mci_for_each_dimm' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'msi_for_each_desc' + - 'nanddev_io_for_each_page' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'pci_doe_for_each_off' + - 'pcl_for_each_chunk' + - 'pcl_for_each_segment' + - 'pcm_for_each_format' + - 'perf_config_items__for_each_entry' + - 'perf_config_sections__for_each_entry' + - 'perf_config_set__for_each_entry' + - 'perf_cpu_map__for_each_cpu' + - 'perf_evlist__for_each_entry' + - 'perf_evlist__for_each_entry_reverse' + - 'perf_evlist__for_each_entry_safe' + - 'perf_evlist__for_each_evsel' + - 'perf_evlist__for_each_mmap' + - 'perf_hpp_list__for_each_format' + - 'perf_hpp_list__for_each_format_safe' + - 'perf_hpp_list__for_each_sort_list' + - 'perf_hpp_list__for_each_sort_list_safe' + - 'perf_pmu__for_each_hybrid_pmu' + - 'ping_portaddr_for_each_entry' + - 'ping_portaddr_for_each_entry_rcu' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rb_for_each' + - 'rbtree_postorder_for_each_entry_safe' + - 'rdma_for_each_block' + - 'rdma_for_each_port' + - 'rdma_umem_for_each_dma_block' + - 'resort_rb__for_each_entry' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_entry' + - 'rht_for_each_entry_from' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_from' + - 'rht_for_each_entry_safe' + - 'rht_for_each_from' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_from' + - 'rq_for_each_bvec' + - 'rq_for_each_segment' + - 'rq_list_for_each' + - 'rq_list_for_each_safe' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'sec_for_each_insn' + - 'sec_for_each_insn_continue' + - 'sec_for_each_insn_from' + - 'shdma_for_each_chan' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_array_for_each' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'strlist__for_each_entry' + - 'strlist__for_each_entry_safe' + - 'sym_for_each_insn' + - 'sym_for_each_insn_continue_reverse' + - 'symbols__for_each_entry' + - 'tb_property_for_each' + - 'tcf_act_for_each_action' + - 'tcf_exts_for_each_action' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'virtio_device_for_each_vq' + - 'while_for_each_ftrace_op' + - 'xa_for_each' + - 'xa_for_each_marked' + - 'xa_for_each_range' + - 'xa_for_each_start' + - 'xas_for_each' + - 'xas_for_each_conflict' + - 'xas_for_each_marked' + - 'xbc_array_for_each_value' + - 'xbc_for_each_key_value' + - 'xbc_node_for_each_array_value' + - 'xbc_node_for_each_child' + - 'xbc_node_for_each_key_value' + - 'xbc_node_for_each_subkey' + - 'zorro_for_each_dev' + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: Custom +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: false + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: false + AfterOverloadedOperator: true + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++20 +TabWidth: 4 +UseTab: AlignWithSpaces +... \ No newline at end of file diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index d85ed88..46ee909 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ rover + +rover.log diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 0000000..9ebe2bc --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/rover", + "args": ["${workspaceFolder}"], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Abilita la riformattazione per gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100755 index 0000000..e538adc --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-std=gnu17", + "-Wall", + "-O0", + "-ggdb", + "-D_DEFAULT_SOURCE", // pkg-config --cflags ncursesw + /* "-D_XOPEN_SOURCE=600", already defined in rover.h */ // pkg-config --cflags ncursesw + "${workspaceFolder}/src/*c", + "-o", + "${workspaceFolder}/rover", + "-lncursesw", // pkg-config --libs ncursesw + "-ltinfo" // pkg-config --libs ncursesw + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "detail": "compiler: /usr/bin/gcc" + } + ] +} \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md old mode 100644 new mode 100755 index 341e2d7..d1b9be7 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,26 @@ # Change Log +## [2.0.1] - 2022-12-31 + +### New revision + +- completely revised and improved functions +- now it can browse the directories with arrows (also scroll mouse) +- now most commands are associated with function keys +- better copy performance usign kernel function sendfile() +- confirmation is asked before overwriting existing files +- a formatted "rover.log" file is created with info and errors +- programming style is closer to GNU standards +- functions are divided into specific files +- Fixed several bugs +- also improved the handling of command line arguments +- VSCode folder with JSON files +- .clang-format file in order to keep a good style + +to do: +- implement a modern progress bar + + ## [1.0.1] - 2020-06-04 ### Bug Fixes diff --git a/FAQ.md b/FAQ.md old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index c2890fe..7d23101 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ LIBS_NCURSESW := `$(PKG_CONFIG) --libs ncursesw` all: rover -rover: rover.c config.h +rover: main.c rover.c rover.h os_funcs.c os_funcs.h ui_funcs.c ui_funcs.h $(CC) $(CFLAGS) $(CFLAGS_NCURSESW) -o $@ $< $(LDFLAGS) $(LIBS_NCURSESW) install: rover diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 40c09ed..50a998a --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Introduction ============ - Rover is a file browser for the terminal. + This is a complete review of Rover is a file browser for the terminal. ![Rover screenshot](/../screenshots/screenshot.png?raw=true "Screenshot") @@ -28,22 +28,23 @@ Quick Start Basic Usage: ``` - q - quit Rover - ? - show Rover manual - j/k - move cursor down/up - J/K - move cursor down/up 10 lines - g/G - move cursor to top/bottom of listing - l - enter selected directory - h - go to parent directory - H - go to $HOME directory - 0-9 - change tab - RETURN - open $SHELL on the current directory - SPACE - open $PAGER with the selected file - e - open $VISUAL or $EDITOR with the selected file - / - start incremental search (RETURN to finish) - n/N - create new file/directory - R - rename selected file or directory - D - delete selected file or (empty) directory + or ? Show this manual. + Quit rover. + Arrow / Move cursor up/down. + Page / Move cursor up/down 10 lines. + / Move cursor to top/bottom of the list. + Arrow / Enter selected directory/Go to parent directory. + / Go to $HOME directory. + l Go to the target of the selected symbolic link. + t Open terminal $SHELL on the current directory. + Open $PAGER with the selected file. + Open $VISUAL or $EDITOR with the selected file. + Open $OPEN with the selected file. + F/D/H Toggle file/directory/hidden listing. + / Create new file/directory. + Rename selected file or directory. + Delete selected file or (empty) directory. + Toggle mark on the selected entry. ``` Please read rover(1) for more information. @@ -60,7 +61,7 @@ Configuration ============= Rover configuration (mostly key bindings and colors) can only be changed by -editing the file `config.h` and rebuilding the binary. +editing the file `rover.h` and rebuilding the binary. Note that the external programs executed by some Rover commands may be changed via the appropriate environment variables. For example, to specify an editor: diff --git a/config.h b/config.h deleted file mode 100644 index 6f314dd..0000000 --- a/config.h +++ /dev/null @@ -1,88 +0,0 @@ -#define RV_VERSION "1.0.1" - -/* CTRL+X: "^X" - ALT+X: "M-X" */ -#define RVK_QUIT "q" -#define RVK_HELP "?" -#define RVK_DOWN "j" -#define RVK_UP "k" -#define RVK_JUMP_DOWN "J" -#define RVK_JUMP_UP "K" -#define RVK_JUMP_TOP "g" -#define RVK_JUMP_BOTTOM "G" -#define RVK_CD_DOWN "l" -#define RVK_CD_UP "h" -#define RVK_HOME "H" -#define RVK_TARGET "t" -#define RVK_COPY_PATH "y" -#define RVK_PASTE_PATH "p" -#define RVK_REFRESH "r" -#define RVK_SHELL "^M" -#define RVK_VIEW " " -#define RVK_EDIT "e" -#define RVK_OPEN "o" -#define RVK_SEARCH "/" -#define RVK_TG_FILES "f" -#define RVK_TG_DIRS "d" -#define RVK_TG_HIDDEN "s" -#define RVK_NEW_FILE "n" -#define RVK_NEW_DIR "N" -#define RVK_RENAME "R" -#define RVK_TG_EXEC "E" -#define RVK_DELETE "D" -#define RVK_TG_MARK "m" -#define RVK_INVMARK "M" -#define RVK_MARKALL "a" -#define RVK_MARK_DELETE "X" -#define RVK_MARK_COPY "C" -#define RVK_MARK_MOVE "V" - -/* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ -#define RVC_CWD GREEN -#define RVC_STATUS CYAN -#define RVC_BORDER BLUE -#define RVC_SCROLLBAR CYAN -#define RVC_LINK CYAN -#define RVC_HIDDEN YELLOW -#define RVC_EXEC GREEN -#define RVC_REG DEFAULT -#define RVC_DIR DEFAULT -#define RVC_CHR MAGENTA -#define RVC_BLK MAGENTA -#define RVC_FIFO BLUE -#define RVC_SOCK MAGENTA -#define RVC_PROMPT DEFAULT -#define RVC_TABNUM DEFAULT -#define RVC_MARKS YELLOW - -/* Special symbols used by the TUI. See for available constants. */ -#define RVS_SCROLLBAR ACS_CKBOARD -#define RVS_MARK ACS_DIAMOND - -/* Prompt strings for line input. */ -#define RV_PROMPT(S) S ": " -#define RVP_SEARCH RV_PROMPT("search") -#define RVP_NEW_FILE RV_PROMPT("new file") -#define RVP_NEW_DIR RV_PROMPT("new dir") -#define RVP_RENAME RV_PROMPT("rename") - -/* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ -#define RV_JUMP 10 - -/* Default listing view flags. - May include SHOW_FILES, SHOW_DIRS and SHOW_HIDDEN. */ -#define RV_FLAGS SHOW_FILES | SHOW_DIRS - -/* Optional macro to be executed when a batch operation finishes. */ -#define RV_ALERT() beep() - -/* Shell used to launch external programs. - Defining this macro will force Rover to launch external - programs with `sh -c "$EXTERNAL_PROGRAM [arg]"`. This gives more - flexibility, allowing command-line arguments to be embedded in - environment variables (e.g. PAGER="less -N"). On the other hand, - this requires the presence of a shell and will spawn an additional - process each time an external program is invoked. Leave this macro - undefined if you prefer external programs to be launched with just - `$EXTERNAL_PROGRAM [arg]`. */ -#define RV_SHELL "/bin/sh" diff --git a/rover.1 b/rover.1 old mode 100644 new mode 100755 index 15aed34..aedaa5e --- a/rover.1 +++ b/rover.1 @@ -1,9 +1,9 @@ -.TH ROVER 1 2020-06-04 rover\-1.0.1 +.TH ROVER 2 2022-12-31 rover\-2.0.1 .SH NAME rover \- file browser for the terminal .SH SYNOPSIS .B rover -[\fB\-d\fR|\fB\-\-save\-cwd\fR \fIFILE\fR] +[\fB\-c\fR|\fB\-\-save\-cwd\fR \fIFILE\fR] [\fB\-m\fR|\fB\-\-save\-marks\fR \fIFILE\fR] [\fIDIR\fR [\fIDIR\fR [\fIDIR\fR [...]]]] .br @@ -16,7 +16,7 @@ rover \- file browser for the terminal Browse current working directory or the ones specified. .SH OPTIONS .TP -\fB\-d\fR, \fB\-\-save\-cwd\fR +\fB\-c\fR, \fB\-\-save\-cwd\fR write last visited path to \fIFILE\fR before exiting .TP \fB\-m\fR, \fB\-\-save\-marks\fR @@ -75,78 +75,81 @@ pointed to the "source" directory of the operation and then issue the command on another tab that is pointed to the "destination" directory. .SH COMMANDS .TP -.B q -Quit rover. +.B or ? +Show this manual. .TP -.B j/k -Move cursor down/up. +.B +Quit rover. .TP -.B J/K -Move cursor down/up 10 lines. +.B Arrow / +Move cursor up/down. .TP -.B g/G -Move cursor to top/bottom of listing. +.B Page / +Move cursor up/down 10 lines. .TP -.B l -Enter selected directory. +.B / +Move cursor to top/bottom of the list. .TP -.B h -Go to parent directory. +.B Arrow / +Enter selected directory/Go to parent directory. .TP -.B H +.B / Go to \fB$HOME\fR directory. .TP -.B t -Go to the target of the selected link. +.B l +Go to the target of the selected symbolic link. .TP -.B y +.B +C Copy location to clipboard. .TP -.B p +.B +V Go to location in clipboard. .TP -.B r +.B Refresh directory listing. .TP -.B -Open \fB$SHELL\fR on the current directory. +.B t +Open terminal \fB$SHELL\fR on the current directory. .TP -.B +.B Open \fB$PAGER\fR with the selected file. .TP -.B e +.B Open \fB$VISUAL\fR or \fB$EDITOR\fR with the selected file. .TP -.B o +.B Open \fB$OPEN\fR with the selected file. .TP -.B / +.B Start incremental search. .TP -.B f/d/s +.B F/D/H Toggle file/directory/hidden listing. .TP -.B n/N +.B / Create new file/directory. .TP -.B R +.B Rename selected file or directory. .TP -.B E +.B Toggle execute permission of the selected file. .TP -.B D +.B Delete selected file or (empty) directory. .TP -.B m +.B Toggle mark on the selected entry. .TP -.B M +.B +s Toggle mark on all visible entries. .TP -.B a +.B +a Mark all visible entries. .TP +.B +d +Unmark all visible entries. +.TP .B X/C/V Delete/copy/move all marked entries. .TP @@ -161,25 +164,25 @@ keys will insert characters at the cursor position. The following shortcuts are available for line editing: .TP .B -Finish editing and \fBcancel\fR command. +Clear line (remove all characters). .TP .B Finish editing and \fBconfirm\fR command. .TP -.B / +.B Arrow / Move insertion cursor left/right. .TP -.B / +.B Arrow / Move insertion cursor to beginning/end of string. .TP .B Remove one character before cursor. .TP -.B +.B Remove one character after cursor. .TP -.B +u -Clear line (remove all characters). +.B +If line is empty than finish editing and \fBcancel\fR command otherwise clear line (remove all characters). .SH ENVIRONMENT VARIABLES .TP .B HOME @@ -226,9 +229,9 @@ will not use or create any external file during its execution, except when asked to do so by user commands or command-line options. .SH NOTES .PP -\fBImportant\fR: Currently, Rover never asks for confirmation before overwriting +\fBImportant\fR: In this version, Rover asks for confirmation before overwriting existing files while copying/moving marked entries. Please be careful to not -accidentally lose your data. +accidentally lose your data anyway. .SH LINKS Rover homepage: . .SH SEE ALSO diff --git a/rover.c b/rover.c deleted file mode 100644 index 342aa48..0000000 --- a/rover.c +++ /dev/null @@ -1,1502 +0,0 @@ -#ifndef _XOPEN_SOURCE -#define _XOPEN_SOURCE 700 -#endif -#define _XOPEN_SOURCE_EXTENDED -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include /* pid_t, ... */ -#include -#include /* PATH_MAX */ -#include /* setlocale(), LC_ALL */ -#include /* chdir(), getcwd(), read(), close(), ... */ -#include /* DIR, struct dirent, opendir(), ... */ -#include -#include -#include /* open() */ -#include /* waitpid() */ -#include /* struct sigaction, sigaction() */ -#include -#include -#include - -#include "config.h" - -/* This signal is not defined by POSIX, but should be - present on all systems that have resizable terminals. */ -#ifndef SIGWINCH -#define SIGWINCH 28 -#endif - -/* String buffers. */ -#define BUFLEN PATH_MAX -static char BUF1[BUFLEN]; -static char BUF2[BUFLEN]; -static char INPUT[BUFLEN]; -static char CLIPBOARD[BUFLEN]; -static wchar_t WBUF[BUFLEN]; - -/* Paths to external programs. */ -static char *user_shell; -static char *user_pager; -static char *user_editor; -static char *user_open; - -/* Listing view parameters. */ -#define HEIGHT (LINES-4) -#define STATUSPOS (COLS-16) - -/* Listing view flags. */ -#define SHOW_FILES 0x01u -#define SHOW_DIRS 0x02u -#define SHOW_HIDDEN 0x04u - -/* Marks parameters. */ -#define BULK_INIT 5 -#define BULK_THRESH 256 - -/* Information associated to each entry in listing. */ -typedef struct Row { - char *name; - off_t size; - mode_t mode; - int islink; - int marked; -} Row; - -/* Dynamic array of marked entries. */ -typedef struct Marks { - char dirpath[PATH_MAX]; - int bulk; - int nentries; - char **entries; -} Marks; - -/* Line editing state. */ -typedef struct Edit { - wchar_t buffer[BUFLEN+1]; - int left, right; -} Edit; - -/* Each tab only stores the following information. */ -typedef struct Tab { - int scroll; - int esel; - uint8_t flags; - char cwd[PATH_MAX]; -} Tab; - -typedef struct Prog { - off_t partial; - off_t total; - const char *msg; -} Prog; - -/* Global state. */ -static struct Rover { - int tab; - int nfiles; - Row *rows; - WINDOW *window; - Marks marks; - Edit edit; - int edit_scroll; - volatile sig_atomic_t pending_usr1; - volatile sig_atomic_t pending_winch; - Prog prog; - Tab tabs[10]; -} rover; - -/* Macros for accessing global state. */ -#define ENAME(I) rover.rows[I].name -#define ESIZE(I) rover.rows[I].size -#define EMODE(I) rover.rows[I].mode -#define ISLINK(I) rover.rows[I].islink -#define MARKED(I) rover.rows[I].marked -#define SCROLL rover.tabs[rover.tab].scroll -#define ESEL rover.tabs[rover.tab].esel -#define FLAGS rover.tabs[rover.tab].flags -#define CWD rover.tabs[rover.tab].cwd - -/* Helpers. */ -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((A) > (B) ? (A) : (B)) -#define ISDIR(E) (strchr((E), '/') != NULL) - -/* Line Editing Macros. */ -#define EDIT_FULL(E) ((E).left == (E).right) -#define EDIT_CAN_LEFT(E) ((E).left) -#define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN-1) -#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] -#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] -#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) -#define EDIT_BACKSPACE(E) (E).left-- -#define EDIT_DELETE(E) (E).right++ -#define EDIT_CLEAR(E) do { (E).left = 0; (E).right = BUFLEN-1; } while(0) - -typedef enum EditStat {CONTINUE, CONFIRM, CANCEL} EditStat; -typedef enum Color {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK} Color; -typedef int (*PROCESS)(const char *path); - -static void -init_marks(Marks *marks) -{ - strcpy(marks->dirpath, ""); - marks->bulk = BULK_INIT; - marks->nentries = 0; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); -} - -/* Unmark all entries. */ -static void -mark_none(Marks *marks) -{ - int i; - - strcpy(marks->dirpath, ""); - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - free(marks->entries[i]); - marks->entries[i] = NULL; - marks->nentries--; - } - if (marks->bulk > BULK_THRESH) { - /* Reset bulk to free some memory. */ - free(marks->entries); - marks->bulk = BULK_INIT; - marks->entries = calloc(marks->bulk, sizeof *marks->entries); - } -} - -static void -add_mark(Marks *marks, char *dirpath, char *entry) -{ - int i; - - if (!strcmp(marks->dirpath, dirpath)) { - /* Append mark to directory. */ - if (marks->nentries == marks->bulk) { - /* Expand bulk to accomodate new entry. */ - int extra = marks->bulk / 2; - marks->bulk += extra; /* bulk *= 1.5; */ - marks->entries = realloc(marks->entries, - marks->bulk * sizeof *marks->entries); - memset(&marks->entries[marks->nentries], 0, - extra * sizeof *marks->entries); - i = marks->nentries; - } else { - /* Search for empty slot (there must be one). */ - for (i = 0; i < marks->bulk; i++) - if (!marks->entries[i]) - break; - } - } else { - /* Directory changed. Discard old marks. */ - mark_none(marks); - strcpy(marks->dirpath, dirpath); - i = 0; - } - marks->entries[i] = malloc(strlen(entry) + 1); - strcpy(marks->entries[i], entry); - marks->nentries++; -} - -static void -del_mark(Marks *marks, char *entry) -{ - int i; - - if (marks->nentries > 1) { - for (i = 0; i < marks->bulk; i++) - if (marks->entries[i] && !strcmp(marks->entries[i], entry)) - break; - free(marks->entries[i]); - marks->entries[i] = NULL; - marks->nentries--; - } else - mark_none(marks); -} - -static void -free_marks(Marks *marks) -{ - int i; - - for (i = 0; i < marks->bulk && marks->nentries; i++) - if (marks->entries[i]) { - free(marks->entries[i]); - marks->nentries--; - } - free(marks->entries); -} - -static void -handle_usr1(int sig) -{ - rover.pending_usr1 = 1; -} - -static void -handle_winch(int sig) -{ - rover.pending_winch = 1; -} - -static void -enable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof (struct sigaction)); - sa.sa_handler = handle_usr1; - sigaction(SIGUSR1, &sa, NULL); - sa.sa_handler = handle_winch; - sigaction(SIGWINCH, &sa, NULL); -} - -static void -disable_handlers() -{ - struct sigaction sa; - - memset(&sa, 0, sizeof (struct sigaction)); - sa.sa_handler = SIG_DFL; - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGWINCH, &sa, NULL); -} - -static void reload(); -static void update_view(); - -/* Handle any signals received since last call. */ -static void -sync_signals() -{ - if (rover.pending_usr1) { - /* SIGUSR1 received: refresh directory listing. */ - reload(); - rover.pending_usr1 = 0; - } - if (rover.pending_winch) { - /* SIGWINCH received: resize application accordingly. */ - delwin(rover.window); - endwin(); - refresh(); - clear(); - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles) - SCROLL = ESEL - HEIGHT; - update_view(); - rover.pending_winch = 0; - } -} - -/* This function must be used in place of getch(). - It handles signals while waiting for user input. */ -static int -rover_getch() -{ - int ch; - - while ((ch = getch()) == ERR) - sync_signals(); - return ch; -} - -/* This function must be used in place of get_wch(). - It handles signals while waiting for user input. */ -static int -rover_get_wch(wint_t *wch) -{ - wint_t ret; - - while ((ret = get_wch(wch)) == (wint_t) ERR) - sync_signals(); - return ret; -} - -/* Get user programs from the environment. */ - -#define ROVER_ENV(dst, src) if ((dst = getenv("ROVER_" #src)) == NULL) \ - dst = getenv(#src); - -static void -get_user_programs() -{ - ROVER_ENV(user_shell, SHELL) - ROVER_ENV(user_pager, PAGER) - ROVER_ENV(user_editor, VISUAL) - if (!user_editor) - ROVER_ENV(user_editor, EDITOR) - ROVER_ENV(user_open, OPEN) -} - -/* Do a fork-exec to external program (e.g. $EDITOR). */ -static void -spawn(char **args) -{ - pid_t pid; - int status; - - setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); - pid = fork(); - if (pid > 0) { - /* fork() succeeded. */ - disable_handlers(); - endwin(); - waitpid(pid, &status, 0); - enable_handlers(); - kill(getpid(), SIGWINCH); - } else if (pid == 0) { - /* Child process. */ - execvp(args[0], args); - } -} - -static void -shell_escaped_cat(char *buf, char *str, size_t n) -{ - char *p = buf + strlen(buf); - *p++ = '\''; - for (n--; n; n--, str++) { - switch (*str) { - case '\'': - if (n < 4) - goto done; - strcpy(p, "'\\''"); - n -= 4; - p += 4; - break; - case '\0': - goto done; - default: - *p = *str; - p++; - } - } -done: - strncat(p, "'", n); -} - -static int -open_with_env(char *program, char *path) -{ - if (program) { -#ifdef RV_SHELL - strncpy(BUF1, program, BUFLEN - 1); - strncat(BUF1, " ", BUFLEN - strlen(program) - 1); - shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2); - spawn((char *[]) {RV_SHELL, "-c", BUF1, NULL}); -#else - spawn((char *[]) {program, path, NULL}); -#endif - return 1; - } - return 0; -} - -/* Curses setup. */ -static void -init_term() -{ - setlocale(LC_ALL, ""); - initscr(); - cbreak(); /* Get one character at a time. */ - timeout(100); /* For getch(). */ - noecho(); - nonl(); /* No NL->CR/NL on output. */ - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - curs_set(FALSE); /* Hide blinking cursor. */ - if (has_colors()) { - short bg; - start_color(); -#ifdef NCURSES_EXT_FUNCS - use_default_colors(); - bg = -1; -#else - bg = COLOR_BLACK; -#endif - init_pair(RED, COLOR_RED, bg); - init_pair(GREEN, COLOR_GREEN, bg); - init_pair(YELLOW, COLOR_YELLOW, bg); - init_pair(BLUE, COLOR_BLUE, bg); - init_pair(CYAN, COLOR_CYAN, bg); - init_pair(MAGENTA, COLOR_MAGENTA, bg); - init_pair(WHITE, COLOR_WHITE, bg); - init_pair(BLACK, COLOR_BLACK, bg); - } - atexit((void (*)(void)) endwin); - enable_handlers(); -} - -/* Update the listing view. */ -static void -update_view() -{ - int i, j; - int numsize; - int ishidden; - int marking; - - mvhline(0, 0, ' ', COLS); - attr_on(A_BOLD, NULL); - color_set(RVC_TABNUM, NULL); - mvaddch(0, COLS - 2, rover.tab + '0'); - attr_off(A_BOLD, NULL); - if (rover.marks.nentries) { - numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries); - color_set(RVC_MARKS, NULL); - mvaddstr(0, COLS - 3 - numsize, BUF1); - } else - numsize = -1; - color_set(RVC_CWD, NULL); - mbstowcs(WBUF, CWD, PATH_MAX); - mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize); - wcolor_set(rover.window, RVC_BORDER, NULL); - wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); - ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); - /* Selection might not be visible, due to cursor wrapping or window - shrinking. In that case, the scroll must be moved to make it visible. */ - if (rover.nfiles > HEIGHT) { - SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); - SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); - } else - SCROLL = 0; - marking = !strcmp(CWD, rover.marks.dirpath); - for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { - ishidden = ENAME(j)[0] == '.'; - if (j == ESEL) - wattr_on(rover.window, A_REVERSE, NULL); - if (ISLINK(j)) - wcolor_set(rover.window, RVC_LINK, NULL); - else if (ishidden) - wcolor_set(rover.window, RVC_HIDDEN, NULL); - else if (S_ISREG(EMODE(j))) { - if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) - wcolor_set(rover.window, RVC_EXEC, NULL); - else - wcolor_set(rover.window, RVC_REG, NULL); - } else if (S_ISDIR(EMODE(j))) - wcolor_set(rover.window, RVC_DIR, NULL); - else if (S_ISCHR(EMODE(j))) - wcolor_set(rover.window, RVC_CHR, NULL); - else if (S_ISBLK(EMODE(j))) - wcolor_set(rover.window, RVC_BLK, NULL); - else if (S_ISFIFO(EMODE(j))) - wcolor_set(rover.window, RVC_FIFO, NULL); - else if (S_ISSOCK(EMODE(j))) - wcolor_set(rover.window, RVC_SOCK, NULL); - if (S_ISDIR(EMODE(j))) { - mbstowcs(WBUF, ENAME(j), PATH_MAX); - if (ISLINK(j)) - wcscat(WBUF, L"/"); - } else { - char *suffix, *suffixes = "BKMGTPEZY"; - off_t human_size = ESIZE(j) * 10; - int length = mbstowcs(WBUF, ENAME(j), PATH_MAX); - int namecols = wcswidth(WBUF, length); - for (suffix = suffixes; human_size >= 10240; suffix++) - human_size = (human_size + 512) / 1024; - if (*suffix == 'B') - swprintf(WBUF + length, PATH_MAX - length, L"%*d %c", - (int) (COLS - namecols - 6), - (int) human_size / 10, *suffix); - else - swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c", - (int) (COLS - namecols - 8), - (int) human_size / 10, (int) human_size % 10, *suffix); - } - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4); - if (marking && MARKED(j)) { - wcolor_set(rover.window, RVC_MARKS, NULL); - mvwaddch(rover.window, i + 1, 1, RVS_MARK); - } else - mvwaddch(rover.window, i + 1, 1, ' '); - if (j == ESEL) - wattr_off(rover.window, A_REVERSE, NULL); - } - for (; i < HEIGHT; i++) - mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); - if (rover.nfiles > HEIGHT) { - int center, height; - center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; - height = (HEIGHT-1) * HEIGHT / rover.nfiles; - if (!height) height = 1; - wcolor_set(rover.window, RVC_SCROLLBAR, NULL); - mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height); - } - BUF1[0] = FLAGS & SHOW_FILES ? 'F' : ' '; - BUF1[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; - BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; - if (!rover.nfiles) - strcpy(BUF2, "0/0"); - else - snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles); - snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2); - color_set(RVC_STATUS, NULL); - mvaddstr(LINES - 1, STATUSPOS, BUF1); - wrefresh(rover.window); -} - -/* Show a message on the status bar. */ -static void -message(Color color, char *fmt, ...) -{ - int len, pos; - va_list args; - - va_start(args, fmt); - vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args); - va_end(args); - len = strlen(BUF1); - pos = (STATUSPOS - len) / 2; - attr_on(A_BOLD, NULL); - color_set(color, NULL); - mvaddstr(LINES - 1, pos, BUF1); - color_set(DEFAULT, NULL); - attr_off(A_BOLD, NULL); -} - -/* Clear message area, leaving only status info. */ -static void -clear_message() -{ - mvhline(LINES - 1, 0, ' ', STATUSPOS); -} - -/* Comparison used to sort listing entries. */ -static int -rowcmp(const void *a, const void *b) -{ - int isdir1, isdir2, cmpdir; - const Row *r1 = a; - const Row *r2 = b; - isdir1 = S_ISDIR(r1->mode); - isdir2 = S_ISDIR(r2->mode); - cmpdir = isdir2 - isdir1; - return cmpdir ? cmpdir : strcoll(r1->name, r2->name); -} - -/* Get all entries in current working directory. */ -static int -ls(Row **rowsp, uint8_t flags) -{ - DIR *dp; - struct dirent *ep; - struct stat statbuf; - Row *rows; - int i, n; - - if(!(dp = opendir("."))) return -1; - n = -2; /* We don't want the entries "." and "..". */ - while (readdir(dp)) n++; - if (n == 0) { - closedir(dp); - return 0; - } - rewinddir(dp); - rows = malloc(n * sizeof *rows); - i = 0; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') - continue; - lstat(ep->d_name, &statbuf); - rows[i].islink = S_ISLNK(statbuf.st_mode); - stat(ep->d_name, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - if (flags & SHOW_DIRS) { - rows[i].name = malloc(strlen(ep->d_name) + 2); - strcpy(rows[i].name, ep->d_name); - if (!rows[i].islink) - strcat(rows[i].name, "/"); - rows[i].mode = statbuf.st_mode; - i++; - } - } else if (flags & SHOW_FILES) { - rows[i].name = malloc(strlen(ep->d_name) + 1); - strcpy(rows[i].name, ep->d_name); - rows[i].size = statbuf.st_size; - rows[i].mode = statbuf.st_mode; - i++; - } - } - n = i; /* Ignore unused space in array caused by filters. */ - qsort(rows, n, sizeof (*rows), rowcmp); - closedir(dp); - *rowsp = rows; - return n; -} - -static void -free_rows(Row **rowsp, int nfiles) -{ - int i; - - for (i = 0; i < nfiles; i++) - free((*rowsp)[i].name); - free(*rowsp); - *rowsp = NULL; -} - -/* Change working directory to the path in CWD. */ -static void -cd(int reset) -{ - int i, j; - - message(CYAN, "Loading \"%s\"...", CWD); - refresh(); - if (chdir(CWD) == -1) { - getcwd(CWD, PATH_MAX-1); - if (CWD[strlen(CWD)-1] != '/') - strcat(CWD, "/"); - goto done; - } - if (reset) ESEL = SCROLL = 0; - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - rover.nfiles = ls(&rover.rows, FLAGS); - if (!strcmp(CWD, rover.marks.dirpath)) { - for (i = 0; i < rover.nfiles; i++) { - for (j = 0; j < rover.marks.bulk; j++) - if ( - rover.marks.entries[j] && - !strcmp(rover.marks.entries[j], ENAME(i)) - ) - break; - MARKED(i) = j < rover.marks.bulk; - } - } else - for (i = 0; i < rover.nfiles; i++) - MARKED(i) = 0; -done: - clear_message(); - update_view(); -} - -/* Select a target entry, if it is present. */ -static void -try_to_sel(const char *target) -{ - ESEL = 0; - if (!ISDIR(target)) - while ((ESEL+1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) - ESEL++; - while ((ESEL+1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) - ESEL++; -} - -/* Reload CWD, but try to keep selection. */ -static void -reload() -{ - if (rover.nfiles) { - strcpy(INPUT, ENAME(ESEL)); - cd(0); - try_to_sel(INPUT); - update_view(); - } else - cd(1); -} - -static off_t -count_dir(const char *path) -{ - DIR *dp; - struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - off_t total; - - if(!(dp = opendir(path))) return 0; - total = 0; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); - total += count_dir(subpath); - } else - total += statbuf.st_size; - } - closedir(dp); - return total; -} - -static off_t -count_marked() -{ - int i; - char *entry; - off_t total; - struct stat statbuf; - - total = 0; - chdir(rover.marks.dirpath); - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - if (ISDIR(entry)) { - total += count_dir(entry); - } else { - lstat(entry, &statbuf); - total += statbuf.st_size; - } - } - } - chdir(CWD); - return total; -} - -/* Recursively process a source directory using CWD as destination root. - For each node (i.e. directory), do the following: - 1. call pre(destination); - 2. call proc() on every child leaf (i.e. files); - 3. recurse into every child node; - 4. call pos(source). - E.g. to move directory /src/ (and all its contents) inside /dst/: - strcpy(CWD, "/dst/"); - process_dir(adddir, movfile, deldir, "/src/"); */ -static int -process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) -{ - int ret; - DIR *dp; - struct dirent *ep; - struct stat statbuf; - char subpath[PATH_MAX]; - - ret = 0; - if (pre) { - char dstpath[PATH_MAX]; - strcpy(dstpath, CWD); - strcat(dstpath, path + strlen(rover.marks.dirpath)); - ret |= pre(dstpath); - } - if(!(dp = opendir(path))) return -1; - while ((ep = readdir(dp))) { - if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) - continue; - snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); - lstat(subpath, &statbuf); - if (S_ISDIR(statbuf.st_mode)) { - strcat(subpath, "/"); - ret |= process_dir(pre, proc, pos, subpath); - } else - ret |= proc(subpath); - } - closedir(dp); - if (pos) ret |= pos(path); - return ret; -} - -/* Process all marked entries using CWD as destination root. - All marked entries that are directories will be recursively processed. - See process_dir() for details on the parameters. */ -static void -process_marked(PROCESS pre, PROCESS proc, PROCESS pos, - const char *msg_doing, const char *msg_done) -{ - int i, ret; - char *entry; - char path[PATH_MAX]; - - clear_message(); - message(CYAN, "%s...", msg_doing); - refresh(); - rover.prog = (Prog) {0, count_marked(), msg_doing}; - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) { - ret = 0; - snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); - if (ISDIR(entry)) { - if (!strncmp(path, CWD, strlen(path))) - ret = -1; - else - ret = process_dir(pre, proc, pos, path); - } else - ret = proc(path); - if (!ret) { - del_mark(&rover.marks, entry); - reload(); - } - } - } - rover.prog.total = 0; - reload(); - if (!rover.marks.nentries) - message(GREEN, "%s all marked entries.", msg_done); - else - message(RED, "Some errors occured while %s.", msg_doing); - RV_ALERT(); -} - -static void -update_progress(off_t delta) -{ - int percent; - - if (!rover.prog.total) return; - rover.prog.partial += delta; - percent = (int) (rover.prog.partial * 100 / rover.prog.total); - message(CYAN, "%s...%d%%", rover.prog.msg, percent); - refresh(); -} - -/* Wrappers for file operations. */ -static int delfile(const char *path) { - int ret; - struct stat st; - - ret = lstat(path, &st); - if (ret < 0) return ret; - update_progress(st.st_size); - return unlink(path); -} -static PROCESS deldir = rmdir; -static int addfile(const char *path) { - /* Using creat(2) because mknod(2) doesn't seem to be portable. */ - int ret; - - ret = creat(path, 0644); - if (ret < 0) return ret; - return close(ret); -} -static int cpyfile(const char *srcpath) { - int src, dst, ret; - size_t size; - struct stat st; - char buf[BUFSIZ]; - char dstpath[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = lstat(srcpath, &st); - if (ret < 0) return ret; - if (S_ISLNK(st.st_mode)) { - ret = readlink(srcpath, BUF1, BUFLEN-1); - if (ret < 0) return ret; - BUF1[ret] = '\0'; - ret = symlink(BUF1, dstpath); - } else { - ret = src = open(srcpath, O_RDONLY); - if (ret < 0) return ret; - ret = dst = creat(dstpath, st.st_mode); - if (ret < 0) return ret; - while ((size = read(src, buf, BUFSIZ)) > 0) { - write(dst, buf, size); - update_progress(size); - sync_signals(); - } - close(src); - close(dst); - ret = 0; - } - return ret; -} -static int adddir(const char *path) { - int ret; - struct stat st; - - ret = stat(CWD, &st); - if (ret < 0) return ret; - return mkdir(path, st.st_mode); -} -static int movfile(const char *srcpath) { - int ret; - struct stat st; - char dstpath[PATH_MAX]; - - strcpy(dstpath, CWD); - strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); - ret = rename(srcpath, dstpath); - if (ret == 0) { - ret = lstat(dstpath, &st); - if (ret < 0) return ret; - update_progress(st.st_size); - } else if (errno == EXDEV) { - ret = cpyfile(srcpath); - if (ret < 0) return ret; - ret = unlink(srcpath); - } - return ret; -} - -static void -start_line_edit(const char *init_input) -{ - curs_set(TRUE); - strncpy(INPUT, init_input, BUFLEN); - rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN); - rover.edit.right = BUFLEN - 1; - rover.edit.buffer[BUFLEN] = L'\0'; - rover.edit_scroll = 0; -} - -/* Read input and change editing state accordingly. */ -static EditStat -get_line_edit() -{ - wchar_t eraser, killer, wch; - int ret, length; - - ret = rover_get_wch((wint_t *) &wch); - erasewchar(&eraser); - killwchar(&killer); - if (ret == KEY_CODE_YES) { - if (wch == KEY_ENTER) { - curs_set(FALSE); - return CONFIRM; - } else if (wch == KEY_LEFT) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit); - } else if (wch == KEY_RIGHT) { - if (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit); - } else if (wch == KEY_UP) { - while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit); - } else if (wch == KEY_DOWN) { - while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit); - } else if (wch == KEY_BACKSPACE) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit); - } else if (wch == KEY_DC) { - if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit); - } - } else { - if (wch == L'\r' || wch == L'\n') { - curs_set(FALSE); - return CONFIRM; - } else if (wch == L'\t') { - curs_set(FALSE); - return CANCEL; - } else if (wch == eraser) { - if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit); - } else if (wch == killer) { - EDIT_CLEAR(rover.edit); - clear_message(); - } else if (iswprint(wch)) { - if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch); - } - } - /* Encode edit contents in INPUT. */ - rover.edit.buffer[rover.edit.left] = L'\0'; - length = wcstombs(INPUT, rover.edit.buffer, BUFLEN); - wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right+1], - BUFLEN-length); - return CONTINUE; -} - -/* Update line input on the screen. */ -static void -update_input(const char *prompt, Color color) -{ - int plen, ilen, maxlen; - - plen = strlen(prompt); - ilen = mbstowcs(NULL, INPUT, 0); - maxlen = STATUSPOS - plen - 2; - if (ilen - rover.edit_scroll < maxlen) - rover.edit_scroll = MAX(ilen - maxlen, 0); - else if (rover.edit.left > rover.edit_scroll + maxlen - 1) - rover.edit_scroll = rover.edit.left - maxlen; - else if (rover.edit.left < rover.edit_scroll) - rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); - color_set(RVC_PROMPT, NULL); - mvaddstr(LINES - 1, 0, prompt); - color_set(color, NULL); - mbstowcs(WBUF, INPUT, COLS); - mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen); - mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); - color_set(DEFAULT, NULL); - if (rover.edit_scroll) - mvaddch(LINES - 1, plen - 1, '<'); - if (ilen > rover.edit_scroll + maxlen) - mvaddch(LINES - 1, plen + maxlen, '>'); - move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); -} - -int -main(int argc, char *argv[]) -{ - int i, ch; - char *program; - char *entry; - const char *key; - const char *clip_path; - DIR *d; - EditStat edit_stat; - FILE *save_cwd_file = NULL; - FILE *save_marks_file = NULL; - FILE *clip_file; - - if (argc >= 2) { - if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) { - printf("rover %s\n", RV_VERSION); - return 0; - } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { - printf( - "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n" - " Browse current directory or the ones specified.\n\n" - " or: rover -h|--help\n" - " Print this help message and exit.\n\n" - " or: rover -v|--version\n" - " Print program version and exit.\n\n" - "See rover(1) for more information.\n" - "Rover homepage: .\n" - ); - return 0; - } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) { - if (argc > 2) { - save_cwd_file = fopen(argv[2], "w"); - argc -= 2; argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) { - if (argc > 2) { - save_marks_file = fopen(argv[2], "a"); - argc -= 2; argv += 2; - } else { - fprintf(stderr, "error: missing argument to %s\n", argv[1]); - return 1; - } - } - } - get_user_programs(); - init_term(); - rover.nfiles = 0; - for (i = 0; i < 10; i++) { - rover.tabs[i].esel = rover.tabs[i].scroll = 0; - rover.tabs[i].flags = RV_FLAGS; - } - strcpy(rover.tabs[0].cwd, getenv("HOME")); - for (i = 1; i < argc && i < 10; i++) { - if ((d = opendir(argv[i]))) { - realpath(argv[i], rover.tabs[i].cwd); - closedir(d); - } else - strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); - } - getcwd(rover.tabs[i].cwd, PATH_MAX); - for (i++; i < 10; i++) - strcpy(rover.tabs[i].cwd, rover.tabs[i-1].cwd); - for (i = 0; i < 10; i++) - if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/') - strcat(rover.tabs[i].cwd, "/"); - rover.tab = 1; - rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); - init_marks(&rover.marks); - cd(1); - strcpy(CLIPBOARD, CWD); - if (rover.nfiles > 0) - strcat(CLIPBOARD, ENAME(ESEL)); - while (1) { - ch = rover_getch(); - key = keyname(ch); - clear_message(); - if (!strcmp(key, RVK_QUIT)) break; - else if (ch >= '0' && ch <= '9') { - rover.tab = ch - '0'; - cd(0); - } else if (!strcmp(key, RVK_HELP)) { - spawn((char *[]) {"man", "rover", NULL}); - } else if (!strcmp(key, RVK_DOWN)) { - if (!rover.nfiles) continue; - ESEL = MIN(ESEL + 1, rover.nfiles - 1); - update_view(); - } else if (!strcmp(key, RVK_UP)) { - if (!rover.nfiles) continue; - ESEL = MAX(ESEL - 1, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_DOWN)) { - if (!rover.nfiles) continue; - ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); - if (rover.nfiles > HEIGHT) - SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); - update_view(); - } else if (!strcmp(key, RVK_JUMP_UP)) { - if (!rover.nfiles) continue; - ESEL = MAX(ESEL - RV_JUMP, 0); - SCROLL = MAX(SCROLL - RV_JUMP, 0); - update_view(); - } else if (!strcmp(key, RVK_JUMP_TOP)) { - if (!rover.nfiles) continue; - ESEL = 0; - update_view(); - } else if (!strcmp(key, RVK_JUMP_BOTTOM)) { - if (!rover.nfiles) continue; - ESEL = rover.nfiles - 1; - update_view(); - } else if (!strcmp(key, RVK_CD_DOWN)) { - if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue; - if (chdir(ENAME(ESEL)) == -1) { - message(RED, "Cannot access \"%s\".", ENAME(ESEL)); - continue; - } - strcat(CWD, ENAME(ESEL)); - cd(1); - } else if (!strcmp(key, RVK_CD_UP)) { - char *dirname, first; - if (!strcmp(CWD, "/")) continue; - CWD[strlen(CWD) - 1] = '\0'; - dirname = strrchr(CWD, '/') + 1; - first = dirname[0]; - dirname[0] = '\0'; - cd(1); - dirname[0] = first; - dirname[strlen(dirname)] = '/'; - try_to_sel(dirname); - dirname[0] = '\0'; - if (rover.nfiles > HEIGHT) - SCROLL = ESEL - HEIGHT / 2; - update_view(); - } else if (!strcmp(key, RVK_HOME)) { - strcpy(CWD, getenv("HOME")); - if (CWD[strlen(CWD) - 1] != '/') - strcat(CWD, "/"); - cd(1); - } else if (!strcmp(key, RVK_TARGET)) { - char *bname, first; - int is_dir = S_ISDIR(EMODE(ESEL)); - ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN-1); - if (len == -1) continue; - BUF1[len] = '\0'; - if (access(BUF1, F_OK) == -1) { - char *msg; - switch (errno) { - case EACCES: - msg = "Cannot access \"%s\"."; - break; - case ENOENT: - msg = "\"%s\" does not exist."; - break; - default: - msg = "Cannot navigate to \"%s\"."; - } - strcpy(BUF2, BUF1); /* message() uses BUF1. */ - message(RED, msg, BUF2); - continue; - } - realpath(BUF1, CWD); - len = strlen(CWD); - if (CWD[len - 1] == '/') - CWD[len - 1] = '\0'; - bname = strrchr(CWD, '/') + 1; - first = *bname; - *bname = '\0'; - cd(1); - *bname = first; - if (is_dir) - strcat(CWD, "/"); - try_to_sel(bname); - *bname = '\0'; - update_view(); - } else if (!strcmp(key, RVK_COPY_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) goto copy_path_fail; - clip_file = fopen(clip_path, "w"); - if (!clip_file) goto copy_path_fail; - fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); - fclose(clip_file); - goto copy_path_done; -copy_path_fail: - strcpy(CLIPBOARD, CWD); - strcat(CLIPBOARD, ENAME(ESEL)); -copy_path_done: - ; - } else if (!strcmp(key, RVK_PASTE_PATH)) { - clip_path = getenv("CLIP"); - if (!clip_path) goto paste_path_fail; - clip_file = fopen(clip_path, "r"); - if (!clip_file) goto paste_path_fail; - fscanf(clip_file, "%s\n", CLIPBOARD); - fclose(clip_file); -paste_path_fail: - strcpy(BUF1, CLIPBOARD); - strcpy(CWD, dirname(BUF1)); - if (strcmp(CWD, "/")) - strcat(CWD, "/"); - cd(1); - strcpy(BUF1, CLIPBOARD); - try_to_sel(strstr(CLIPBOARD, basename(BUF1))); - update_view(); - } else if (!strcmp(key, RVK_REFRESH)) { - reload(); - } else if (!strcmp(key, RVK_SHELL)) { - program = user_shell; - if (program) { -#ifdef RV_SHELL - spawn((char *[]) {RV_SHELL, "-c", program, NULL}); -#else - spawn((char *[]) {program, NULL}); -#endif - reload(); - } - } else if (!strcmp(key, RVK_VIEW)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_pager, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_EDIT)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_editor, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_OPEN)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (open_with_env(user_open, ENAME(ESEL))) - cd(0); - } else if (!strcmp(key, RVK_SEARCH)) { - int oldsel, oldscroll, length; - if (!rover.nfiles) continue; - oldsel = ESEL; - oldscroll = SCROLL; - start_line_edit(""); - update_input(RVP_SEARCH, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int sel; - Color color = RED; - length = strlen(INPUT); - if (length) { - for (sel = 0; sel < rover.nfiles; sel++) - if (!strncmp(ENAME(sel), INPUT, length)) - break; - if (sel < rover.nfiles) { - color = GREEN; - ESEL = sel; - if (rover.nfiles > HEIGHT) { - if (sel < 3) - SCROLL = 0; - else if (sel - 3 > rover.nfiles - HEIGHT) - SCROLL = rover.nfiles - HEIGHT; - else - SCROLL = sel - 3; - } - } - } else { - ESEL = oldsel; - SCROLL = oldscroll; - } - update_view(); - update_input(RVP_SEARCH, color); - } - if (edit_stat == CANCEL) { - ESEL = oldsel; - SCROLL = oldscroll; - } - clear_message(); - update_view(); - } else if (!strcmp(key, RVK_TG_FILES)) { - FLAGS ^= SHOW_FILES; - reload(); - } else if (!strcmp(key, RVK_TG_DIRS)) { - FLAGS ^= SHOW_DIRS; - reload(); - } else if (!strcmp(key, RVK_TG_HIDDEN)) { - FLAGS ^= SHOW_HIDDEN; - reload(); - } else if (!strcmp(key, RVK_NEW_FILE)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_FILE, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - } - update_input(RVP_NEW_FILE, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (addfile(INPUT) == 0) { - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_NEW_DIR)) { - int ok = 0; - start_line_edit(""); - update_input(RVP_NEW_DIR, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) { - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - } - update_input(RVP_NEW_DIR, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (ok) { - if (adddir(INPUT) == 0) { - cd(1); - strcat(INPUT, "/"); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "Could not create \"%s/\".", INPUT); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_RENAME)) { - int ok = 0; - char *last; - int isdir; - strcpy(INPUT, ENAME(ESEL)); - last = INPUT + strlen(INPUT) - 1; - if ((isdir = *last == '/')) - *last = '\0'; - start_line_edit(INPUT); - update_input(RVP_RENAME, RED); - while ((edit_stat = get_line_edit()) == CONTINUE) { - int length = strlen(INPUT); - ok = length; - for (i = 0; i < rover.nfiles; i++) - if ( - !strncmp(ENAME(i), INPUT, length) && - (!strcmp(ENAME(i) + length, "") || - !strcmp(ENAME(i) + length, "/")) - ) { - ok = 0; - break; - } - update_input(RVP_RENAME, ok ? GREEN : RED); - } - clear_message(); - if (edit_stat == CONFIRM) { - if (isdir) - strcat(INPUT, "/"); - if (ok) { - if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) { - del_mark(&rover.marks, ENAME(ESEL)); - add_mark(&rover.marks, CWD, INPUT); - } - cd(1); - try_to_sel(INPUT); - update_view(); - } else - message(RED, "\"%s\" already exists.", INPUT); - } - } else if (!strcmp(key, RVK_TG_EXEC)) { - if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue; - if (S_IXUSR & EMODE(ESEL)) - EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); - else - EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH ; - if (chmod(ENAME(ESEL), EMODE(ESEL))) { - message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); - } else { - message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); - update_view(); - } - } else if (!strcmp(key, RVK_DELETE)) { - if (rover.nfiles) { - message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); - if (rover_getch() == 'Y') { - const char *name = ENAME(ESEL); - int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name); - reload(); - if (ret) - message(RED, "Could not delete \"%s\".", ENAME(ESEL)); - } else - clear_message(); - } else - message(RED, "No entry selected for deletion."); - } else if (!strcmp(key, RVK_TG_MARK)) { - if (MARKED(ESEL)) - del_mark(&rover.marks, ENAME(ESEL)); - else - add_mark(&rover.marks, CWD, ENAME(ESEL)); - MARKED(ESEL) = !MARKED(ESEL); - ESEL = (ESEL + 1) % rover.nfiles; - update_view(); - } else if (!strcmp(key, RVK_INVMARK)) { - for (i = 0; i < rover.nfiles; i++) { - if (MARKED(i)) - del_mark(&rover.marks, ENAME(i)); - else - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = !MARKED(i); - } - update_view(); - } else if (!strcmp(key, RVK_MARKALL)) { - for (i = 0; i < rover.nfiles; i++) - if (!MARKED(i)) { - add_mark(&rover.marks, CWD, ENAME(i)); - MARKED(i) = 1; - } - update_view(); - } else if (!strcmp(key, RVK_MARK_DELETE)) { - if (rover.marks.nentries) { - message(YELLOW, "Delete all marked entries? (Y/n)"); - if (rover_getch() == 'Y') - process_marked(NULL, delfile, deldir, "Deleting", "Deleted"); - else - clear_message(); - } else - message(RED, "No entries marked for deletion."); - } else if (!strcmp(key, RVK_MARK_COPY)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); - else - message(RED, "Cannot copy to the same path."); - } else - message(RED, "No entries marked for copying."); - } else if (!strcmp(key, RVK_MARK_MOVE)) { - if (rover.marks.nentries) { - if (strcmp(CWD, rover.marks.dirpath)) - process_marked(adddir, movfile, deldir, "Moving", "Moved"); - else - message(RED, "Cannot move to the same path."); - } else - message(RED, "No entries marked for moving."); - } - } - if (rover.nfiles) - free_rows(&rover.rows, rover.nfiles); - delwin(rover.window); - if (save_cwd_file != NULL) { - fputs(CWD, save_cwd_file); - fclose(save_cwd_file); - } - if (save_marks_file != NULL) { - for (i = 0; i < rover.marks.bulk; i++) { - entry = rover.marks.entries[i]; - if (entry) - fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); - } - fclose(save_marks_file); - } - free_marks(&rover.marks); - return 0; -} diff --git a/src/main.c b/src/main.c new file mode 100755 index 0000000..257adf3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,134 @@ +#include "rover.h" +#include "ui_funcs.h" +#include "os_funcs.h" + +struct Rover rover; +char rover_home_path[RV_PATH_MAX]; + +int main(int argc, char *argv[]) +{ + int i, opt, long_index = 0; + char clipboard[PATH_MAX]; + char *entry; + DIR *d; + FILE *save_cwd_file = NULL, *save_marks_file = NULL; + static struct option long_options[] = { + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { "save-cwd", required_argument, NULL, 'c' }, + { "save-marks", required_argument, NULL, 'm' }, + { NULL, 0, NULL, 0 } + }; + + while ((opt = getopt_long(argc, argv, "vhc:m:", long_options, &long_index)) != -1) { + switch (opt) { + case 'v': + printf("rover %s\n", RV_VERSION); + + return EXIT_SUCCESS; + break; + case '?': // in case of missing arguments + if (save_cwd_file) + fclose(save_cwd_file); + if (save_marks_file) + fclose(save_marks_file); + fprintf(stderr, "read following instructions:\n"); + getchar(); + case 'h': + puts("rover - file browser for the terminal\n\n" + "SYNOPSIS\n" + "\trover [-c|--save-cwd FILE] [-m|--save-marks FILE] [DIR [DIR [DIR [...]]]]\n" + "\trover -h|--help\n" + "\trover -v|--version\n\n" + "DESCRIPTION\n" + "\tRover browse current working directory or the ones specified.\n\n" + "OPTIONS\n" + "\t-c, --save-cwd\n" + "\t\twrite last visited path to FILE before exiting.\n\n" + "\t-m, --save-marks\n" + "\t\tappend path of marked entries to FILE before exiting; if FILE doesn't exist, it'll be created.\n\n" + "\t-h, --help\n" + "\t\tprint help message and exit.\n\n" + "\t-v, --version\n" + "\t\tprint program version and exit.\n"); + + return EXIT_SUCCESS; + break; + case 'c': + if (isvalidfilename(optarg, false)) + save_cwd_file = fopen(optarg, "w"); + else { + LOG(LOG_ERR, "\"%s\" invalid filename to store last working directory.", optarg); + fprintf(stderr, "\"%s\" invalid filename to store last working directory.\n", optarg); + + return EXIT_FAILURE; + } + break; + case 'm': + if (isvalidfilename(optarg, false)) + save_marks_file = fopen(optarg, "a"); + else { + LOG(LOG_ERR, "\"%s\" invalid filename to store last marked entries.", optarg); + fprintf(stderr, "\"%s\" invalid filename to store last marked entries.\n", optarg); + + return EXIT_FAILURE; + } + break; + default: + LOG(LOG_ERR, "getopt_long() Invalid arguments/options\n"); + fprintf(stderr, "Invalid arguments/options\n"); + return EXIT_FAILURE; + break; + } + } + + init_term(); + INIT_ROVER(rover); //initialize rover struct + + strcpy(rover.tabs[0].cwd, getenv("HOME")); + opt = (argc - optind); + for (i = 0; i < opt && i < 10; i++) { + if ((d = opendir(argv[optind + i]))) { + realpath(argv[optind + i], rover.tabs[i].cwd); + closedir(d); + } else + strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd); + } + + getcwd(rover_home_path, RV_PATH_MAX); //get the current work directory for rover.log and rover manual file + strcpy(rover.tabs[i].cwd, rover_home_path); + + for (i++; i < 10; i++) + strcpy(rover.tabs[i].cwd, rover.tabs[i - 1].cwd); + + for (i = 0; i < 10; i++) + ADDSLASH(rover.tabs[i].cwd); + + rover.tab = 1; + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + init_marks(&rover.marks); + if (!cd(true)) + exit(EXIT_FAILURE); + + strcpy(clipboard, CWD); + if (rover.nfiles > 0) + strcat(clipboard, ENAME(ESEL)); + + main_menu(); + + if (save_cwd_file != NULL) { + fputs(CWD, save_cwd_file); + fclose(save_cwd_file); + } + if (save_marks_file != NULL) { + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) + fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry); + } + fclose(save_marks_file); + } + free_marks(&rover.marks); + + return EXIT_SUCCESS; +} diff --git a/src/os_funcs.c b/src/os_funcs.c new file mode 100644 index 0000000..abe7d3e --- /dev/null +++ b/src/os_funcs.c @@ -0,0 +1,694 @@ +#include "rover.h" +#include "os_funcs.h" +#include "ui_funcs.h" + +/* +assign at param size the total size in bytes +returns the type mode of file specified in fname +*/ +mode_t fileinfo(const char *fname, off_t *size) +{ + struct stat sb; + + if (lstat(fname, &sb) == -1) { + return 0; + } + if (size) //check in NULL pointer + *size = sb.st_size; + + return sb.st_mode; +} + +/* +check if file or directory is accessible +in case of error write detailed error message in log file +return 0 if accesible otherwise -1 +*/ +int fileexist(const char *fname) +{ + int result; + char *msgerr; + + errno = 0; + + result = access(fname, F_OK); + switch (errno) { + case EACCES: + msgerr = "The requested access would be denied to the file, or search permission is denied for one of the directories in the path prefix of pathname."; + break; + case EFAULT: + msgerr = "pathname points outside your accessible address space."; + break; + case EINVAL: + msgerr = "mode was incorrectly specified."; + break; + case EIO: + msgerr = "An I/O error occurred."; + break; + case ELOOP: + msgerr = "Too many symbolic links were encountered in resolving pathname."; + break; + case ENAMETOOLONG: + msgerr = "pathname is too long."; + break; + case ENOENT: + msgerr = "A component of pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + msgerr = "Insufficient kernel memory was available."; + break; + case ENOTDIR: + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EROFS: + msgerr = "Write permission was requested for a file on a read-only filesystem."; + break; + case ETXTBSY: + msgerr = "Write access was requested to an executable which is being executed."; + break; + default: + msgerr = "Unspecified error."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Unable to access: \"%s\" errno[%d]: %s", fname, errno, msgerr); //write detailed error message to log file + + return result; +} + +/* +Delete fname even is file, symbolic link or directory +On success, zero is returned. On error, -1 is returned +*/ +int rm(const char *fname) +{ + int result = -1; + mode_t mode; + char *msgerr; + + errno = 0; + + mode = fileinfo(fname, NULL); + if (S_ISREG(mode) || S_ISLNK(mode)) + result = unlink(fname); + else if (S_ISDIR(mode)) + result = rmdir(fname); + else + LOG(LOG_ERR, "\"%s\" is not a regular file, symbolic link or directory.", fname); + + switch (errno) { + case EACCES: + msgerr = "Write access to the file or directory is not allowed for the process's effective UID, or one of the directories in pathname did not allow search permission."; + break; + case EBUSY: + msgerr = "The file or directory cannot be unlinked because it is being used by the system or another process that prevents its removal."; + break; + case EFAULT: + msgerr = "The file or directory points outside your accessible address space."; + break; + case EIO: + msgerr = "An I/O error occurred."; + break; + case ELOOP: + msgerr = "Too many symbolic links were encountered in translating."; + break; + case ENAMETOOLONG: + msgerr = "pathname was too long."; + break; + case ENOENT: + msgerr = "A component of path does not exist or is a dangling symbolic link, or pathname is empty."; + break; + case ENOMEM: + msgerr = "Insufficient kernel memory was available."; + break; + case ENOTDIR: + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EPERM: + msgerr = "The system does not allow unlinking of directories, or unlinking of directories requires privileges that the calling process doesn't have."; + break; + case EROFS: + msgerr = "The file or directory refers to a file on a read-only filesystem."; + break; + case EBADF: + msgerr = "pathname is relative but dirfd is neither AT_FDCWD nor a valid file descriptor."; + break; + case EINVAL: + msgerr = "An invalid flag value was specified in flags."; + break; + case EISDIR: + msgerr = "pathname refers to a directory, and AT_REMOVEDIR was not specified in flags."; + break; + case ENOTEMPTY: + msgerr = "Dirctory not empty."; + break; + default: + msgerr = "Generic error not specified by OS."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Error removing \"%s\" errno[%d]: %s", fname, errno, msgerr); //write detailed error message to log file + + return result; +} + +/* +create an empty file with 0644 mode +return 0 on success or -1 in case of error +*/ +int addfile(const char *path) +{ + int fd; + + fd = creat(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (fd < 0) + return fd; + + return close(fd); +} + +/* +create an empty dir with 0644 mode +return 0 on success or -1 in case of error +*/ +int adddir(const char *path) +{ + int result; + char *msgerr; + + errno = 0; + + result = mkdir(path, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + switch (errno) { + case EACCES: + msgerr = "The parent directory does not allow write permission to the process, or one of the directories in pathname did not allow search permission."; + break; + case EDQUOT: + msgerr = "The user's quota of disk blocks or inodes on the filesystem has been exhausted."; + break; + case EEXIST: + msgerr = "pathname already exists (not necessarily as a directory). This includes the case where pathname is a symbolic link, dangling or not."; + break; + case EFAULT: + msgerr = "pathname points outside your accessible address space."; + break; + case EINVAL: + msgerr = "The final component (basename) of the new directory's pathname is invalid (e.g., it contains characters not permitted by the underlying filesystem)."; + break; + case ELOOP: + msgerr = "Too many symbolic links were encountered in resolving pathname."; + break; + case EMLINK: + msgerr = "The number of links to the parent directory would exceed LINK_MAX."; + break; + case ENAMETOOLONG: + msgerr = "pathname was too long."; + case ENOENT: + msgerr = "A directory component in pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + msgerr = "Insufficient kernel memory was available."; + break; + case ENOSPC: + msgerr = "The new directory cannot be created because the user's disk quota is exhausted."; + break; + case ENOTDIR: + msgerr = "A component used as a directory in pathname is not, in fact, a directory."; + break; + case EPERM: + msgerr = "The filesystem containing pathname does not support the creation of directories."; + break; + case EROFS: + msgerr = "pathname refers to a file on a read-only filesystem."; + break; + default: + msgerr = "Generic error not specified by OS."; + break; + } + if (errno) //error is occurred + LOG(LOG_ERR, "Error creating dir \"%s\" errno[%d]: %s", path, errno, msgerr); //write detailed error message to log file + + return result; +} + +/* +check if the specified name is a valid file name +return true if is valid or false if is not valid +*/ +bool isvalidfilename(const char *filename, bool wildcard) +{ + char dir_template[] = "/tmp/rover-tmpdir.XXXXXX", forbidden[] = "/<>\"|:&", extmatch[] = "?*+@!", temp_file[FILENAME_MAX], *temp_dir = NULL; + FILE *fd; + bool result = false; + + if (strpbrk(filename, forbidden)) //check for invalid characters for filename + return result; + if (strlen(filename) > 255) //check if the lenght of file exceed the max acceppted by filesystem + return result; + if (strpbrk(filename, extmatch)) { //check if filname contains wildchars FNM_EXTMATCH + if (wildcard) + return result; + + return result; + } + temp_dir = mkdtemp(dir_template); + if (temp_dir == NULL) + return result; + if (!mkdir(temp_dir, S_IRWXU | S_IRWXG | S_IRWXO)) //creating temp dir 0777 + return result; + + sprintf(temp_file, "%s/%s", temp_dir, filename); + fd = fopen(temp_file, "w"); //try to create an empty file using "filename" + if (fd == NULL) { + rm(temp_dir); + + return result; + } + fclose(fd); + + result = S_ISREG(fileinfo(temp_file, NULL)); //check if the temp file is regular + if (rm(temp_file)) + return false; + if (rm(temp_dir)) + return false; + + return result; //return true if filename its valid +} + +/* +returns the inode of filename +*/ +static ino_t inodeof(const char *filename) +{ + struct stat sb; + + if (lstat(filename, &sb) == -1) + LOG(LOG_INFO, "Error reading i-node number of \"%s\"", filename); + + return (ino_t)sb.st_ino; +} + +/* +try to open a new file with specfied mode, +check via inode if file is the same of source, +also check iffname already exist and ask for overwriting + +return file descriptor no if success, -1 in case of error or -2 if same inode +*/ +static int opennew(const char *fname, mode_t mode, ino_t source_inode) +{ + int fd = -3; + bool ok = true; + + if (access(fname, F_OK) == 0) { //check if the target file already exists + if (inodeof(fname) == source_inode) + return -2; + + message(RED, "Warning: \"%s\" already exist. Overwrite (Y/n)?", FILENAME(fname)); + if (rover_getch() == 'Y') + rm(fname); + else + ok = false; + + CLEAR_MESSAGE(); + } + if (ok) + fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, mode); + + return fd; +} + +/* +copyng srcfile file to destination dir using sendfile() with better performance because of kernel +in case of EINVAL or ENOSYS it try to use classic read()/write() funcs with best chunk of memory + +return total bytes copied if success, -1L in case of error, -2L if srcfile == dstfile, -3L user abort overwriting +*/ +static ssize_t filecopy(const char *srcfile, const char *dstfile) +{ + int input, output; + struct stat sb = { 0 }; + ssize_t chunk, bytesCopied, bytesTocopy = 0; + char *msgerr, *data, *ptr, *end; + bool tryAgain = false; /* Applications may wish to fall back to read(2)/write(2) in + the case where sendfile() fails with EINVAL or ENOSYS. */ + + input = open(srcfile, O_RDONLY); //try opening the srcfile file + if (input == -1) { + LOG(LOG_ERR, "File open error: %s", srcfile); + + return -1L; + } + + errno = 0; + if (fstat(input, &sb)) { //read the attributes of the srcfile file + switch (errno) { + case EACCES: + msgerr = "Search permission is denied for one of the directories in the path prefix of srcfile file."; + break; + case EBADF: + msgerr = "fd is not a valid open file descriptor."; + break; + case EFAULT: + msgerr = "Bad address."; + break; + case ELOOP: + msgerr = "Too many symbolic links encountered while traversing the path."; + break; + case ENAMETOOLONG: + msgerr = "Source pathname is too long."; + break; + case ENOENT: + msgerr = "A component of source pathname does not exist or is a dangling symbolic link."; + break; + case ENOMEM: + msgerr = "Out of memory (i.e., kernel memory)."; + break; + case ENOTDIR: + msgerr = "A component of the path prefix of pathname is not a directory."; + break; + case EOVERFLOW: + msgerr = "Source pathname refers to a file whose size, inode number, or number of blocks cannot be represented in, respectively, the types off_t, ino_t, or blkcnt_t.\nThis error can occur when, for example, an application compiled on a 32-bit platform without -D_FILE_OFFSET_BITS=64 calls stat() on a file whose size exceeds (1<<31)-1 bytes."; + break; + case 0: + default: + msgerr = NULL; + break; + } + LOG(LOG_ERR, "Error reading info from \"%s\" errno[%d]: %s", srcfile, errno, msgerr); + message(RED, "Error reading info from source file. more info in \"%s.log\"", ROVER); + rover_getch(); + CLEAR_MESSAGE(); + + return -1L; + } + + output = opennew(dstfile, sb.st_mode, inodeof(srcfile)); //try to open the destinatiion file with same mode of srcfile + if (output < 0) { //try to open the destinatiion file + close(input); + if (output == -1) + msgerr = "Error opening destination file"; + else if (output == -2) + msgerr = "Destination file must be in a different directory of the source file"; + else + msgerr = NULL; + + if (msgerr) { + LOG(LOG_ERR, "%s", msgerr); + message(RED, "%s", msgerr); + rover_getch(); + CLEAR_MESSAGE(); + } + + return (ssize_t)output; + } + + errno = 0; + bytesTocopy = sb.st_size; + bytesCopied = sendfile(output, input, NULL, bytesTocopy); //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+ + switch (errno) { + case EAGAIN: + msgerr = "Nonblocking I/O has been selected using O_NONBLOCK and the write would block."; + break; + case EBADF: + msgerr = "The input file was not opened for reading or the output file was not opened for writing."; + break; + case EFAULT: + msgerr = "Bad address."; + break; + case EINVAL: + case ENOSYS: + msgerr = "Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd, or count is negative."; + tryAgain = true; /* read NOTES at https://man7.org/linux/man-pages/man2/sendfile.2.html */ + break; + case EIO: + msgerr = "Unspecified error while reading from in_fd."; + break; + case ENOMEM: + msgerr = "Insufficient memory to read from in_fd."; + break; + case EOVERFLOW: + msgerr = "count is too large, the operation would result in exceeding the maximum size of either the input file or the output file."; + break; + case ESPIPE: + msgerr = "offset is not NULL but the input file is not seekable."; + break; + case 0: + default: + msgerr = NULL; + break; + } + if (errno) //if an error has occurred + LOG(LOG_ERR, "File copy error errno[%d]: %s", errno, msgerr); + + if (bytesCopied != bytesTocopy) { //verify that all bytes have been copied + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", bytesCopied, bytesTocopy); + rover_getch(); + CLEAR_MESSAGE(); + } + + if (tryAgain) { + LOG(LOG_INFO, "%s try to copy again with read()/write() instead of sendfile().", ROVER); + errno = 0; + chunk = MIN(bytesTocopy, DEFAULT_CHUNK); //set better performance for copy + data = malloc((size_t)chunk); // Allocate temporary data buffer. + if (data) { + bytesCopied = 0L; + do { // read/write loop + bytesTocopy = read(input, data, chunk); + if (bytesTocopy <= 0) + break; // exit from do while loop + + ptr = data; + end = (char *)(data + bytesTocopy); + while (ptr < end) { //write data loop + bytesTocopy = write(output, ptr, (size_t)(end - ptr)); + if (bytesTocopy <= 0) { + tryAgain = false; // exit from do while loop + break; // exit from while loop + } + bytesCopied += bytesTocopy; + ptr += bytesTocopy; + } + } while (tryAgain); + FREE(data); + if (!errno) + LOG(LOG_INFO, "%s copy with success using read()/write() instead of sendfile().", ROVER); + } + } + close(input); //close the file descriptor + close(output); //close the file descriptor + if (errno) + unlink(dstfile); // in case of error remove output file. + + if (sb.st_size != bytesCopied) { // verify that all bytes have been copied + LOG(LOG_ERR, "Error not all data was copied! Writed %ld bytes on %ld bytes: errno[%d]", bytesCopied, sb.st_size, errno); + message(RED, "Error not all data was copied! Writed %ld bytes on %ld bytes", bytesCopied, sb.st_size); + rover_getch(); + CLEAR_MESSAGE(); + } + + return bytesCopied; //return the number of bytes copied +} + +/* +copy source file in CWD using filecopy() func +returns 0 if success +*/ +int cpyfile(const char *srcpath) +{ + char dstpath[PATH_MAX], buffer[PATH_MAX]; + ssize_t bytesTocopy, bytesCopied; + int result; + mode_t mode; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + + mode = fileinfo(srcpath, &bytesTocopy); + if (!mode) + result = -2; + else if (S_ISLNK(mode)) { + result = readlink(srcpath, buffer, PATH_MAX); + if (result < 0) { + message(RED, "Error reading symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + } else { + buffer[result] = '\0'; + result = symlink(buffer, dstpath); + } + } else if (S_ISREG(mode)) { + bytesCopied = filecopy(srcpath, dstpath); + result = (int)((bytesCopied == bytesTocopy) ? false : bytesCopied); + } else { + message(RED, "Error: source file must be a regular file or symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + result = -4; + } + return result; +} + +/* +move source file in CWD using rename() func +returns 0 if success +*/ +int movfile(const char *srcpath) +{ + char dstpath[PATH_MAX], buffer[PATH_MAX]; + ssize_t bytesTocopy; + int result; + mode_t mode; + + strcpy(dstpath, CWD); + strcat(dstpath, srcpath + strlen(rover.marks.dirpath)); + + mode = fileinfo(srcpath, &bytesTocopy); + if (!mode) + result = -2; + else if (S_ISLNK(mode)) { + result = readlink(srcpath, buffer, PATH_MAX); + if (result < 0) { + message(RED, "Error reading symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + } else { + buffer[result] = '\0'; + result = symlink(buffer, dstpath); + result = rm(srcpath); + } + } else if (S_ISREG(mode)) { + errno = 0; + result = rename(srcpath, dstpath); + if (errno == EXDEV) { // see man page at https://man7.org/linux/man-pages/man2/rename.2.html + result = (filecopy(srcpath, dstpath) != bytesTocopy); + result = rm(srcpath); + } + } else { + message(RED, "Error: source file must be a regular file or symbolic link"); + rover_getch(); + CLEAR_MESSAGE(); + result = -3; + } + + return result; +} + +/* Comparison used to sort listing entries. */ +static int rowcmp(const void *a, const void *b) +{ + int isdir1, isdir2, cmpdir; + const Row *r1 = a, *r2 = b; + + isdir1 = S_ISDIR(r1->mode); + isdir2 = S_ISDIR(r2->mode); + cmpdir = isdir2 - isdir1; + + return (cmpdir ? cmpdir : strcoll(r1->name, r2->name)); +} + +/* Get all entries in current working directory. */ +static int ls(Row **rowsp, uint8_t flags) +{ + DIR *dp; + struct dirent *ep; + Row *rows; + mode_t mode; + off_t size = -1L; + int n, i = 0; + + if (!(dp = opendir("."))) + return -1; + + for (n = 0; (ep = readdir(dp)); n++) + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + n--; // We don't want the entries "." and "..". + + if (n == 0) { + closedir(dp); + + return 0; + } + + rewinddir(dp); + rows = malloc(n * sizeof *rows); + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.') + continue; + + mode = fileinfo(ep->d_name, &size); + rows[i].islink = (S_ISLNK(mode)); + if (S_ISDIR(mode)) { + if (flags & SHOW_DIRS) { + rows[i].name = (char *)malloc(strlen(ep->d_name) + 2); + strcpy(rows[i].name, ep->d_name); + if (!rows[i].islink) + ADDSLASH(rows[i].name); + rows[i].mode = mode; + i++; + } + } else if (flags & SHOW_FILES) { + rows[i].name = malloc(strlen(ep->d_name) + 1); + strcpy(rows[i].name, ep->d_name); + rows[i].size = size; + rows[i].mode = mode; + i++; + } + } + n = i; // Ignore unused space in array caused by filters. + qsort(rows, n, sizeof(*rows), rowcmp); + closedir(dp); + *rowsp = rows; + + return n; +} + +/* Change working directory to the path in CWD. */ +bool cd(bool reset) +{ + int i, j; + + message(CYAN, "Loading \"%s\"...", CWD); + refresh(); + + if (chdir(CWD) == -1) { + getcwd(CWD, PATH_MAX - 1); + ADDSLASH(CWD); + } else { + if (reset) + ESEL = SCROLL = 0; + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + + rover.nfiles = ls(&rover.rows, FLAGS); + if (rover.nfiles < 0) { + message(RED, "Error reading current directory"); + rover_getch(); + CLEAR_MESSAGE(); + + return false; + } + + if (!strcmp(CWD, rover.marks.dirpath)) { + for (i = 0; i < rover.nfiles; i++) { + for (j = 0; j < rover.marks.bulk; j++) { + if (rover.marks.entries[j] && + !strcmp(rover.marks.entries[j], ENAME(i))) + break; + } + MARKED(i) = j < rover.marks.bulk; + } + } else { + for (i = 0; i < rover.nfiles; i++) + MARKED(i) = false; + } + } + CLEAR_MESSAGE(); + update_view(); + + return true; +} diff --git a/src/os_funcs.h b/src/os_funcs.h new file mode 100644 index 0000000..67ad826 --- /dev/null +++ b/src/os_funcs.h @@ -0,0 +1,27 @@ +#ifndef _OS_FUNCS_H +#define _OS_FUNCS_H + +#include /* lstat() mkdir() */ +#include /* mode_t */ +#include +#include /* mkdtemp() */ +#include /* sprintf() fopen() fclose() */ +#include /* AT_REMOVEDIR */ +#include /* unlinkat() */ +#include +#include +#include + +#define DEFAULT_CHUNK ((ssize_t)262144L) /* https://stackoverflow.com/questions/42156041/copying-a-huge-file-using-read-write-and-open-apis-in-linux */ + +mode_t fileinfo(const char *fname, off_t *size); +int fileexist(const char *fname); +int rm(const char *fname); +int addfile(const char *path); +int adddir(const char *path); +bool isvalidfilename(const char *filename, bool wildcard); +int cpyfile(const char *srcpath); +int movfile(const char *srcpath); +bool cd(bool reset); + +#endif // _OS_FUNCS_H \ No newline at end of file diff --git a/src/rover.c b/src/rover.c new file mode 100755 index 0000000..2ad0b99 --- /dev/null +++ b/src/rover.c @@ -0,0 +1,219 @@ +#include "rover.h" +#include "ui_funcs.h" +#include "os_funcs.h" + +void logfile(const char *format, ...) +{ + char filelog[PATH_MAX]; + FILE *fd; + time_t current_time; + struct tm *local; + static unsigned int logcount = 0; // each session start from 0 + va_list args; + + sprintf(filelog, "%s/%s.log", rover_home_path, ROVER); + fd = fopen(filelog, "a+"); // a+ create or append that is useful in a log file + if (!fd) { + message(RED, "ERROR APPEND/WRITE LOG FILE \"%s.log\"", ROVER); + rover_getch(); + CLEAR_MESSAGE(); + + return; + } + + time(¤t_time); // get time + local = localtime(¤t_time); // convert to local + fprintf(fd, "%d/%02d/%02d ", local->tm_year + 1900, local->tm_mon + 1, local->tm_mday); // write current date yyyy/mm/dd + fprintf(fd, "%02d:%02d:%02d ", local->tm_hour, local->tm_min, local->tm_sec); // write local time hh:mm:ss + fprintf(fd, "[%03u] ", logcount++); // write the index log of current session + + va_start(args, format); + vfprintf(fd, format, args); // write the info recieved from function arguments + va_end(args); + + fclose(fd); + + return; +} + +/* Curses setup. */ +void init_term(void) +{ + setlocale(LC_ALL, ""); + initscr(); + + cbreak(); // Get one character at a time. + nodelay(stdscr, TRUE); // For getch(). + noecho(); + nonl(); // No NL->CR/NL on output. + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); // Hide blinking cursor. + raw(); //to read Ctrl+C and so on + + if (has_colors()) { + short bg; + start_color(); + +#ifdef NCURSES_EXT_FUNCS + use_default_colors(); + bg = -1; +#else + bg = COLOR_BLACK; +#endif + init_pair(RED, COLOR_RED, bg); + init_pair(GREEN, COLOR_GREEN, bg); + init_pair(YELLOW, COLOR_YELLOW, bg); + init_pair(BLUE, COLOR_BLUE, bg); + init_pair(CYAN, COLOR_CYAN, bg); + init_pair(MAGENTA, COLOR_MAGENTA, bg); + init_pair(WHITE, COLOR_WHITE, bg); + init_pair(BLACK, COLOR_BLACK, bg); + } + atexit((void (*)(void))endsession); + handlers(true); //enable handlers +} + +/* atexit func */ +int endsession(void) +{ + handlers(false); + if (rover.nfiles) + free_rows(&rover.rows, rover.nfiles); + delwin(rover.window); + /* + curs_set(TRUE); + keypad(stdscr, FALSE); + intrflush(stdscr, TRUE); + nl(); + echo(); + nodelay(stdscr, FALSE); + nocbreak(); + */ + clear(); + refresh(); + endwin(); + + return EXIT_SUCCESS; +} + +void free_rows(Row **rowsp, int nfiles) +{ + for (int i = 0; i < nfiles; i++) + FREE((*rowsp)[i].name); + FREE(*rowsp); +} + +/* Select a target entry, if it is present. */ +void try_to_sel(const char *target) +{ + ESEL = 0; + if (!ISDIR(target)) + while ((ESEL + 1) < rover.nfiles && S_ISDIR(EMODE(ESEL))) + ESEL++; + + while ((ESEL + 1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0) + ESEL++; +} + +/* Reload CWD, but try to keep selection. */ +void reload(void) +{ + char input[PATH_MAX]; + + if (rover.nfiles) { + strcpy(input, ENAME(ESEL)); + cd(false); + try_to_sel(input); + update_view(); + } else + cd(true); +} + +/* Recursively process a source directory using CWD as destination root. + For each node (i.e. directory), do the following: + 1. call pre(destination); + 2. call proc() on every child leaf (i.e. files); + 3. recurse into every child node; + 4. call pos(source). + E.g. to move directory /src/ (and all its contents) inside /dst/: + strcpy(CWD, "/dst/"); + process_dir(adddir, movfile, rm, "/src/"); */ +static int process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path) +{ + int result = 0; + DIR *dp; + struct dirent *ep; + char subpath[PATH_MAX], dstpath[PATH_MAX]; + + if (pre) { + strcpy(dstpath, CWD); + strcat(dstpath, path + strlen(rover.marks.dirpath)); + result |= pre(dstpath); + } + + dp = opendir(path); + if (dp == NULL) + return -1; + + while ((ep = readdir(dp))) { + if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) + continue; + + snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name); + if (S_ISDIR(fileinfo(subpath, NULL))) { + ADDSLASH(subpath); + result |= process_dir(pre, proc, pos, subpath); + } else + result |= proc(subpath); + } + closedir(dp); + + if (pos) + result |= pos(path); + + return result; +} + +/* Process all marked entries using CWD as destination root. + All marked entries that are directories will be recursively processed. + See process_dir() for details on the parameters. */ +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done) +{ + int i, result; + char *entry; + char path[PATH_MAX]; + + CLEAR_MESSAGE(); + message(CYAN, "%s...", msg_doing); + refresh(); + for (i = 0; i < rover.marks.bulk; i++) { + entry = rover.marks.entries[i]; + if (entry) { + result = 0; + snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry); + if (ISDIR(entry)) { + if (!strncmp(path, CWD, strlen(path))) + result = -1; + else + result = process_dir(pre, proc, pos, path); + } else { + result = proc(path); + } + + if (!result) { + del_mark(&rover.marks, entry); + reload(); + } + } + } + reload(); + if(result == -3) + message(YELLOW, "%s aborted.", msg_doing) ; + else if (result < 0) //rover.marks.nentries + message(RED, "Some errors occured while %s.", msg_doing); + else + message(GREEN, "%s all marked entries.", msg_done); + + RV_ALERT(); +} diff --git a/src/rover.h b/src/rover.h new file mode 100644 index 0000000..de36cfb --- /dev/null +++ b/src/rover.h @@ -0,0 +1,203 @@ +#ifndef _ROVER_H +#define _ROVER_H + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#ifndef _XOPEN_SOURCE_EXTENDED +#define _XOPEN_SOURCE_EXTENDED +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 +#endif + +#include +#include +#include +#include +#include +#include /* pid_t, ... */ +#include +#include /* PATH_MAX */ +#include /* setlocale(), LC_ALL */ +#include /* chdir(), getcwd(), read(), close(), ... */ +#include /* DIR, struct dirent, opendir(), ... */ +#include +#include /* open() */ +#include /* waitpid() */ +#include /* struct sigaction, sigaction() */ +#include +#include +#include +#include +#include + +#define RV_VERSION "2.0.1" //Sandroid75 + +/* Special symbols used by the TUI. See for available constants. */ +#define RVS_SCROLLBAR ACS_CKBOARD +#define RVS_MARK ACS_DIAMOND + +/* Prompt strings for line input. */ +#define RV_PROMPT(S) S ": " +#define RVP_SEARCH RV_PROMPT("search") +#define RVP_NEW_FILE RV_PROMPT("new file") +#define RVP_NEW_DIR RV_PROMPT("new dir") +#define RVP_RENAME RV_PROMPT("rename") + +/* Default listing view flags. + May include SHOW_FILES, SHOW_DIRS and SHOW_HIDDEN. */ +#define RV_FLAGS SHOW_FILES | SHOW_DIRS + +/* This signal is not defined by POSIX, but should be + present on all systems that have resizable terminals. */ +#ifndef SIGWINCH +#define SIGWINCH 28 +#endif + +/* Listing view parameters. */ +#define HEIGHT (LINES - 4) + +/* Listing view flags. */ +#define SHOW_FILES 0x01u +#define SHOW_DIRS 0x02u +#define SHOW_HIDDEN 0x04u + +/* Marks parameters. */ +#define BULK_INIT 5 +#define BULK_THRESH 256 + +#define ROVER "rover" +#define LOG_INFO false +#define LOG_ERR true +#define RV_PATH_MAX (PATH_MAX - 16) + +/* Information associated to each entry in listing. */ +typedef struct Row { + char *name; + off_t size; + mode_t mode; + bool islink; + bool marked; +} Row; + +/* Dynamic array of marked entries. */ +typedef struct Marks { + char dirpath[PATH_MAX]; + int bulk; + int nentries; + char **entries; +} Marks; + +/* Line editing state. */ +typedef struct Edit { + wchar_t buffer[PATH_MAX + 1]; + int left, right; +} Edit; + +/* Each tab only stores the following information. */ +typedef struct Tab { + int scroll; + int esel; + uint8_t flags; + char cwd[PATH_MAX]; +} Tab; + +typedef struct Prog { + off_t partial; + off_t total; + const char *msg; +} Prog; + +/* Global state. */ +struct Rover { + int tab; + int nfiles; + Row *rows; + WINDOW *window; + Marks marks; + Edit edit; + int edit_scroll; + volatile sig_atomic_t pending_usr1; + volatile sig_atomic_t pending_winch; + Prog prog; + Tab tabs[10]; +}; + +/* Macros for accessing global state. */ +#define ENAME(I) rover.rows[I].name +#define ESIZE(I) rover.rows[I].size +#define EMODE(I) rover.rows[I].mode +#define ISLINK(I) rover.rows[I].islink +#define MARKED(I) rover.rows[I].marked +#define SCROLL rover.tabs[rover.tab].scroll +#define ESEL rover.tabs[rover.tab].esel +#define FLAGS rover.tabs[rover.tab].flags +#define CWD rover.tabs[rover.tab].cwd + +/* Helpers. */ +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define ISDIR(E) (strchr((E), '/') != NULL) + +/* Macro to initialize the rover struct */ +#define INIT_ROVER(_r) \ + { \ + _r.nfiles = 0; \ + for (int idx = 0; idx < 10; idx++) { \ + _r.tabs[idx].esel = 0; \ + _r.tabs[idx].scroll = 0; \ + _r.tabs[idx].flags = RV_FLAGS; \ + } \ + } + +/* Optional macro to be executed when a batch operation finishes. */ +#define RV_ALERT() beep() + +/* Macro to manage logfile() func in order to read detailed info from source files */ +#define LOG(flag, msg, ...) \ + { \ + logfile("{%s} %s %s() <%d>: " msg "\n", (flag ? "ERR" : "INFO"), FILENAME(__FILE__), __func__, __LINE__, ##__VA_ARGS__); \ + } + +/* Get file or dir name without path */ +#define FILENAME(_path) (strrchr(_path, '/') + 1) + +/* Check if not root dir than add / at the end of path */ +#define ADDSLASH(_path) \ + { \ + if (strcmp(_path, "/") && \ + _path[strlen(_path) - 1] != '/') \ + strcat(_path, "/"); \ + } + +#define DELSLASH(_path) \ + { \ + if (strcmp(_path, "/") && \ + _path[strlen(_path) - 1] == '/') \ + _path[strlen(_path) - 1] = '\0'; \ + } + +/* Safe version of free() don't need assign NULL after free */ +#define FREE(p) \ + { \ + if ((p)) \ + free((p)); \ + (p) = NULL; \ + } + +typedef int (*PROCESS)(const char *path); + +extern struct Rover rover; +extern char rover_home_path[RV_PATH_MAX]; + +// Functions declaration +void logfile(const char *format, ...); +void init_term(void); +int endsession(void); +void reload(void); +void free_rows(Row **rowsp, int nfiles); +void try_to_sel(const char *target); +void process_marked(PROCESS pre, PROCESS proc, PROCESS pos, const char *msg_doing, const char *msg_done); + +#endif // _ROVER_H \ No newline at end of file diff --git a/src/ui_funcs.c b/src/ui_funcs.c new file mode 100755 index 0000000..a854b6d --- /dev/null +++ b/src/ui_funcs.c @@ -0,0 +1,930 @@ +#include "rover.h" +#include "ui_funcs.h" +#include "os_funcs.h" + +void handle_usr1(int sig) +{ + rover.pending_usr1 = 1; +} + +void handle_winch(int sig) +{ + rover.pending_winch = 1; +} + +void handlers(bool enable) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + + if (enable) { + sa.sa_handler = handle_usr1; + sigaction(SIGUSR1, &sa, NULL); + + sa.sa_handler = handle_winch; + sigaction(SIGWINCH, &sa, NULL); + } else { + sa.sa_handler = SIG_DFL; + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGWINCH, &sa, NULL); + } +} + +/* Update line input on the screen. */ +static void update_input(const char *prompt, Color color, const char *input) +{ + int plen, ilen, maxlen; + wchar_t wbuffer[PATH_MAX]; + + plen = strlen(prompt); + ilen = mbstowcs(NULL, input, 0); + maxlen = STATUSPOS - plen - 2; + if ((ilen - rover.edit_scroll) < maxlen) + rover.edit_scroll = MAX(ilen - maxlen, 0); + else if (rover.edit.left > (rover.edit_scroll + maxlen - 1)) + rover.edit_scroll = rover.edit.left - maxlen; + else if (rover.edit.left < rover.edit_scroll) + rover.edit_scroll = MAX(rover.edit.left - maxlen, 0); + + color_set(RVC_PROMPT, NULL); + mvaddstr(LINES - 1, 0, prompt); + color_set(color, NULL); + mbstowcs(wbuffer, input, COLS); + mvaddnwstr(LINES - 1, plen, &wbuffer[rover.edit_scroll], maxlen); + mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' '); + color_set(DEFAULT, NULL); + if (rover.edit_scroll) + mvaddch(LINES - 1, plen - 1, '<'); + if (ilen > (rover.edit_scroll + maxlen)) + mvaddch(LINES - 1, plen + maxlen, '>'); + move(LINES - 1, plen + rover.edit.left - rover.edit_scroll); +} + +/* Show a message on the status bar. */ +void message(Color color, char *fmt, ...) +{ + int pos; + va_list args; + char buffer[PATH_MAX]; + + va_start(args, fmt); + vsnprintf(buffer, STATUSPOS, fmt, args); + va_end(args); + + pos = (STATUSPOS - (int)strlen(buffer)) / 2; + attr_on(A_BOLD, NULL); + color_set(color, NULL); + mvaddstr(LINES - 1, pos, buffer); + color_set(DEFAULT, NULL); + attr_off(A_BOLD, NULL); +} + +/* Do a fork-exec to external program (e.g. $EDITOR). */ +static void spawn(char **args) +{ + pid_t pid; + int status; + + setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1); + pid = fork(); + if (pid > 0) { + /* fork() succeeded. */ + handlers(false); //disable handlers + endwin(); + waitpid(pid, &status, 0); + handlers(true); //enable handlers + kill(getpid(), SIGWINCH); + } else if (pid == 0) { + /* Child process. */ + execvp(args[0], args); + } +} + +static int open_with_env(char *program, char *path) +{ + char buffer[PATH_MAX]; + + if (program) { +#ifdef RV_SHELL + snprintf(buffer, PATH_MAX, "%s \'%s\'", program, path); + spawn((char *[]){ RV_SHELL, "-c", buffer, NULL }); +#else + spawn((char *[]){ program, path, NULL }); +#endif + return 1; + } + + return 0; +} + +static void start_line_edit(const char *init_input) +{ + char input[PATH_MAX]; + + curs_set(TRUE); + strncpy(input, init_input, PATH_MAX); + rover.edit.left = mbstowcs(rover.edit.buffer, init_input, PATH_MAX); + rover.edit.right = PATH_MAX - 1; + rover.edit.buffer[PATH_MAX] = L'\0'; //buffuer lenght is defined with PATH_MAX +1 + rover.edit_scroll = 0; +} + +void update_view() +{ + int i, j, numsize, ishidden, marking, length, namecols, center, height; + char buffer_one[PATH_MAX], buffer_two[PATH_MAX], *suffix; + wchar_t wbuffer[PATH_MAX]; + off_t human_size; + + mvhline(0, 0, ' ', COLS); + attr_on(A_BOLD, NULL); + color_set(RVC_TABNUM, NULL); + mvaddch(0, COLS - 2, rover.tab + '0'); + attr_off(A_BOLD, NULL); + if (rover.marks.nentries) { + numsize = snprintf(buffer_one, PATH_MAX, "%d", rover.marks.nentries); + color_set(RVC_MARKS, NULL); + mvaddstr(0, COLS - 3 - numsize, buffer_one); + } else + numsize = -1; + color_set(RVC_CWD, NULL); + mbstowcs(wbuffer, CWD, PATH_MAX); + mvaddnwstr(0, 0, wbuffer, COLS - 4 - numsize); + wcolor_set(rover.window, RVC_BORDER, NULL); + wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0); + ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0); + /* Selection might not be visible, due to cursor wrapping or window + shrinking. In that case, the scroll must be moved to make it visible. */ + if (rover.nfiles > HEIGHT) { + SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1); + SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT); + } else + SCROLL = 0; + marking = !strcmp(CWD, rover.marks.dirpath); + for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) { + ishidden = (ENAME(j)[0] == '.'); + if (j == ESEL) + wattr_on(rover.window, A_REVERSE, NULL); + /* following a series of if else in order to set colors of listing */ + if (ISLINK(j)) + wcolor_set(rover.window, RVC_LINK, NULL); + else if (ishidden) + wcolor_set(rover.window, RVC_HIDDEN, NULL); + else if (S_ISREG(EMODE(j))) { + if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH)) + wcolor_set(rover.window, RVC_EXEC, NULL); + else + wcolor_set(rover.window, RVC_REG, NULL); + } else if (S_ISDIR(EMODE(j))) + wcolor_set(rover.window, RVC_DIR, NULL); + else if (S_ISCHR(EMODE(j))) + wcolor_set(rover.window, RVC_CHR, NULL); + else if (S_ISBLK(EMODE(j))) + wcolor_set(rover.window, RVC_BLK, NULL); + else if (S_ISFIFO(EMODE(j))) + wcolor_set(rover.window, RVC_FIFO, NULL); + else if (S_ISSOCK(EMODE(j))) + wcolor_set(rover.window, RVC_SOCK, NULL); + + if (S_ISDIR(EMODE(j))) { + mbstowcs(wbuffer, ENAME(j), PATH_MAX); + if (ISLINK(j)) + wcscat(wbuffer, L"/"); + } else { + human_size = ESIZE(j) * 10; + length = mbstowcs(wbuffer, ENAME(j), PATH_MAX); + namecols = wcswidth(wbuffer, length); + + for (suffix = "BKMGTPEZY"; human_size >= 10240; suffix++) + human_size = (human_size + 512) / 1024; + + if (*suffix == 'B') + swprintf(wbuffer + length, PATH_MAX - length, L"%*d %c", + (int)(COLS - namecols - 6), + (int)human_size / 10, *suffix); + else + swprintf(wbuffer + length, PATH_MAX - length, L"%*d.%d %c", + (int)(COLS - namecols - 8), + (int)human_size / 10, (int)human_size % 10, *suffix); + } + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + mvwaddnwstr(rover.window, i + 1, 2, wbuffer, COLS - 4); + if (marking && MARKED(j)) { + wcolor_set(rover.window, RVC_MARKS, NULL); + mvwaddch(rover.window, i + 1, 1, RVS_MARK); + } else + mvwaddch(rover.window, i + 1, 1, ' '); + + if (j == ESEL) + wattr_off(rover.window, A_REVERSE, NULL); + } + for (; i < HEIGHT; i++) + mvwhline(rover.window, i + 1, 1, ' ', COLS - 2); + + if (rover.nfiles > HEIGHT) { + center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles; + height = (HEIGHT - 1) * HEIGHT / rover.nfiles; + height = height ? height : 1; + wcolor_set(rover.window, RVC_SCROLLBAR, NULL); + mvwvline(rover.window, center - height / 2 + 1, COLS - 1, RVS_SCROLLBAR, height); + } + buffer_one[0] = FLAGS & SHOW_FILES ? 'F' : ' '; + buffer_one[1] = FLAGS & SHOW_DIRS ? 'D' : ' '; + buffer_one[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' '; + + if (!rover.nfiles) + strcpy(buffer_two, "0/0"); + else + snprintf(buffer_two, PATH_MAX, "%d/%d", ESEL + 1, rover.nfiles); + + snprintf(buffer_one + 3, PATH_MAX, "%12s", buffer_two); + color_set(RVC_STATUS, NULL); + mvaddstr(LINES - 1, STATUSPOS, buffer_one); + wrefresh(rover.window); +} + +/* Handle any signals received since last call. */ +static void sync_signals(void) +{ + if (rover.pending_usr1) { + /* SIGUSR1 received: refresh directory listing. */ + reload(); + rover.pending_usr1 = 0; + } + if (rover.pending_winch) { + /* SIGWINCH received: resize application accordingly. */ + delwin(rover.window); + endwin(); + refresh(); + rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + if (HEIGHT < rover.nfiles && + SCROLL + HEIGHT > rover.nfiles) + SCROLL = ESEL - HEIGHT; + + update_view(); + rover.pending_winch = 0; + } +} + +/* This function must be used in place of getch(). + It handles signals while waiting for user input. */ +int rover_getch(void) +{ + int ch; + + while ((ch = getch()) == ERR) + sync_signals(); + + return ch; +} + +/* Read input and change editing state accordingly. */ +static EditStat get_line_edit(char *string) +{ + int ch, length; + ch = rover_getch(); + switch (ch) { + case KEY_ENTER: + case KEY_RETURN: + curs_set(FALSE); + return CONFIRM; + break; + case KEY_ESC: + if (strlen(string)) { + EDIT_CLEAR(rover.edit); + CLEAR_MESSAGE(); + } else { + curs_set(FALSE); + return CANCEL; + } + break; + case KEY_TAB: + EDIT_CLEAR(rover.edit); + CLEAR_MESSAGE(); + break; + case KEY_LEFT: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_RIGHT: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_HOME: + case KEY_PPAGE: + case KEY_UP: + while (EDIT_CAN_LEFT(rover.edit)) + EDIT_LEFT(rover.edit); + break; + case KEY_END: + case KEY_NPAGE: + case KEY_DOWN: + while (EDIT_CAN_RIGHT(rover.edit)) + EDIT_RIGHT(rover.edit); + break; + case KEY_BACKSPACE: + if (EDIT_CAN_LEFT(rover.edit)) + EDIT_BACKSPACE(rover.edit); + break; + case KEY_DC: + if (EDIT_CAN_RIGHT(rover.edit)) + EDIT_DELETE(rover.edit); + break; + case KEY_CANCEL: + break; + case KEY_F(1)... KEY_F(12): + case KEY_IC: + break; + default: + if (iswprint(ch) && !EDIT_FULL(rover.edit)) + EDIT_INSERT(rover.edit, ch); + break; + } + + //Encode edit contents in input. + rover.edit.buffer[rover.edit.left] = L'\0'; + length = wcstombs(string, rover.edit.buffer, PATH_MAX); + wcstombs(&string[length], &rover.edit.buffer[rover.edit.right + 1], PATH_MAX - length); + + return CONTINUE; +} + +void init_marks(Marks *marks) +{ + strcpy(marks->dirpath, ""); + marks->bulk = BULK_INIT; + marks->nentries = 0; + marks->entries = (char **)calloc(marks->bulk, sizeof(*marks->entries)); +} + +void free_marks(Marks *marks) +{ + int i; + + for (i = 0; marks->nentries && i < marks->bulk; i++) + if (marks->entries[i]) { + FREE(marks->entries[i]); + marks->nentries--; + } + + FREE(marks->entries); +} + +/* Unmark all entries. */ +static void mark_none(Marks *marks) +{ + int i; + + strcpy(marks->dirpath, ""); + for (i = 0; marks->nentries && i < marks->bulk; i++) + if (marks->entries[i]) { + FREE(marks->entries[i]); + marks->nentries--; + } + + if (marks->bulk > BULK_THRESH) { + /* Reset bulk to free some memory. */ + FREE(marks->entries); + marks->bulk = BULK_INIT; + marks->entries = (char **)calloc(marks->bulk, sizeof *marks->entries); + } +} + +static void add_mark(Marks *marks, char *dirpath, char *entry) +{ + int i, extra; + + if (!strcmp(marks->dirpath, dirpath)) { + /* Append mark to directory. */ + if (marks->nentries == marks->bulk) { + /* Expand bulk to accomodate new entry. */ + extra = marks->bulk / 2; + marks->bulk += extra; /* bulk *= 1.5; */ + marks->entries = (char **)realloc(marks->entries, marks->bulk * sizeof(*marks->entries)); + memset(&marks->entries[marks->nentries], 0, extra * sizeof *marks->entries); + i = marks->nentries; + } else { + /* Search for empty slot (there must be one). */ + for (i = 0; i < marks->bulk; i++) + if (!marks->entries[i]) + break; + } + } else { + /* Directory changed. Discard old marks. */ + mark_none(marks); + strcpy(marks->dirpath, dirpath); + i = 0; + } + + marks->entries[i] = (char *)malloc(strlen(entry) + 1); + strcpy(marks->entries[i], entry); + marks->nentries++; +} + +void del_mark(Marks *marks, char *entry) +{ + int i; + + if (marks->nentries > 1) { + for (i = 0; i < marks->bulk; i++) + if (marks->entries[i] && !strcmp(marks->entries[i], entry)) + break; + + FREE(marks->entries[i]); + marks->nentries--; + } else + mark_none(marks); +} + +void main_menu(void) +{ + int i, ch, oldsel, oldscroll, length, ok, sel; + bool quit = false, isdir; + char buffer[PATH_MAX], input[PATH_MAX], clipboard[PATH_MAX]; + char *program, *last; + const char *clip_path; + ssize_t link; + Color color = RED; + FILE *clip_file; + EditStat edit_stat; + struct User prg; + mode_t mode; + + ROVER_ENV(prg.Shell, SHELL); + ROVER_ENV(prg.Pager, PAGER); + ROVER_ENV(prg.Editor, VISUAL); + if (!(prg.Editor)) + ROVER_ENV(prg.Editor, EDITOR); + ROVER_ENV(prg.Open, OPEN); + + do { + ch = rover_getch(); + CLEAR_MESSAGE(); + switch (ch) { + case KEY_ESC: //Quit Rover + message(RED, "Press any key to exit or ESC to back"); + if (rover_getch() != KEY_ESC) + quit = true; + CLEAR_MESSAGE(); + break; + case '0' ... '9': //Change Tab + rover.tab = ch - '0'; + cd(false); + break; + case '?': //Help + case KEY_F(1): + sprintf(buffer, "%s/%s.1", rover_home_path, ROVER); // local manfile + spawn((char *[]){ "man", access(buffer, R_OK) ? ROVER : buffer, NULL }); + break; + case KEY_DOWN: //Move cursor down + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + 1, rover.nfiles - 1); + update_view(); + break; + case KEY_UP: //Move cursor up + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - 1, 0); + update_view(); + break; + case KEY_NPAGE: //Move cursordown 10 lines + if (!rover.nfiles) + continue; + ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1); + if (rover.nfiles > HEIGHT) + SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT); + update_view(); + break; + case KEY_PPAGE: //Move cursor up 10 lines + if (!rover.nfiles) + continue; + ESEL = MAX(ESEL - RV_JUMP, 0); + SCROLL = MAX(SCROLL - RV_JUMP, 0); + update_view(); + break; + case KEY_HOME: //Move cursor to top of listing + if (!rover.nfiles) + continue; + ESEL = 0; + update_view(); + break; + case KEY_END: //Move cursor to bottom of listing + if (!rover.nfiles) + continue; + ESEL = rover.nfiles - 1; + update_view(); + break; + case KEY_RIGHT: //Enter selected directory + if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) + continue; + if (chdir(ENAME(ESEL)) == -1) { + message(RED, "Cannot access \"%s\".", ENAME(ESEL)); + continue; + } + strcat(CWD, ENAME(ESEL)); + cd(true); + break; + case KEY_LEFT: //Go to parent directory + if (!strcmp(CWD, "/")) + continue; + DELSLASH(CWD); + strcpy(buffer, FILENAME(CWD)); //copy the cwd for try_to_sel + ADDSLASH(buffer); + dirname(CWD); + ADDSLASH(CWD); + cd(true); + try_to_sel(buffer); + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; + update_view(); + break; + case '/': //Go to home directory + strcpy(CWD, getenv("HOME")); + ADDSLASH(CWD); + cd(true); + break; + case 'l': //Go to the target of the selected link + link = readlink(ENAME(ESEL), buffer, PATH_MAX - 1); + if (link == -1) + continue; + buffer[link] = '\0'; // needed because of readlink return not null-terminated string + if (fileexist(buffer) != 0) { + message(RED, "File doesn't exist, more detail in %s.log", ROVER); + rover_getch(); + CLEAR_MESSAGE(); + continue; + } + mode = fileinfo(buffer, NULL); + strcpy(CWD, buffer); + if (S_ISREG(mode)) { + strcpy(CWD, buffer); + strcpy(buffer, FILENAME(CWD)); + dirname(CWD); + ADDSLASH(CWD); + } + cd(true); + if (S_ISREG(mode)) + try_to_sel(buffer); + if (rover.nfiles > HEIGHT) + SCROLL = ESEL - HEIGHT / 2; + update_view(); + break; + case KEY_CTRL_C: //Copy location to clipboard + clip_path = getenv("CLIP"); + if (clip_path) { + clip_file = fopen(clip_path, "w"); + if (clip_file) { + fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL)); + fclose(clip_file); + } + } + if (!clip_path || !clip_file) + sprintf(clipboard, "%s%s", CWD, ENAME(ESEL)); + break; + case KEY_CTRL_V: //Go to location in clipboard + clip_path = getenv("CLIP"); + if (clip_path) { + clip_file = fopen(clip_path, "r"); + if (clip_file) { + fscanf(clip_file, "%s\n", clipboard); + fclose(clip_file); + } + } + strcpy(buffer, clipboard); + strcpy(CWD, dirname(buffer)); + ADDSLASH(CWD); + cd(true); + strcpy(buffer, clipboard); + try_to_sel(strstr(clipboard, basename(buffer))); + update_view(); + break; + case KEY_F(5): //Refresh directory listing + reload(); + break; + case 't': //Open SHELL on the current directory + program = prg.Shell; + if (program) { +#ifdef RV_SHELL + spawn((char *[]){ RV_SHELL, "-c", program, NULL }); +#else + spawn((char *[]){ program, NULL }); +#endif + reload(); + } + break; + case KEY_F(6): //Open PAGER with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Pager, ENAME(ESEL))) + cd(false); + break; + case KEY_F(7): //Open VISUAL or EDITOR with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Editor, ENAME(ESEL))) + cd(false); + break; + case KEY_F(8): //Open OPEN with the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + if (open_with_env(prg.Open, ENAME(ESEL))) + cd(false); + break; + case KEY_F(3): //Start incremental search + if (!rover.nfiles) + continue; + oldsel = ESEL; + oldscroll = SCROLL; + start_line_edit(""); + update_input(RVP_SEARCH, RED, input); + while ((edit_stat = get_line_edit(input)) == CONTINUE) { + length = strlen(input); + if (length) { + for (sel = 0; sel < rover.nfiles; sel++) + if (!strncmp(ENAME(sel), input, length)) + break; + if (sel < rover.nfiles) { + color = GREEN; + ESEL = sel; + if (rover.nfiles > HEIGHT) { + if (sel < 3) + SCROLL = 0; + else if (sel - 3 > rover.nfiles - HEIGHT) + SCROLL = rover.nfiles - HEIGHT; + else + SCROLL = sel - 3; + } + } + } else { + ESEL = oldsel; + SCROLL = oldscroll; + } + update_view(); + update_input(RVP_SEARCH, color, input); + } + if (edit_stat == CANCEL) { + ESEL = oldsel; + SCROLL = oldscroll; + } + CLEAR_MESSAGE(); + update_view(); + break; + case 'F': //Toggle file listing + FLAGS ^= SHOW_FILES; + reload(); + break; + case 'D': //Toggle dir listing + FLAGS ^= SHOW_DIRS; + reload(); + break; + case 'H': //Toggle hide listing + FLAGS ^= SHOW_HIDDEN; + reload(); + break; + case KEY_F(9): //Create new file + start_line_edit(""); + DELSLASH(input); + update_input(RVP_NEW_FILE, YELLOW, input); + do { + edit_stat = get_line_edit(input); + ok = strlen(input); + for (i = 0; ok && i < rover.nfiles; i++) { + if (!strcmp(ENAME(i), input)) { + ok = 0; + update_input(RVP_NEW_FILE, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" already exists!", input); + rover_getch(); + CLEAR_MESSAGE(); + edit_stat = CONTINUE; + } + } else if (!isvalidfilename(input, false)) { + ok = 0; + update_input(RVP_NEW_FILE, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" invalid filename!", input); + rover_getch(); + CLEAR_MESSAGE(); + edit_stat = CONTINUE; + } + } + } + update_input(RVP_NEW_FILE, (ok ? GREEN : RED), input); + } while (edit_stat == CONTINUE); + CLEAR_MESSAGE(); + + if (ok && edit_stat == CONFIRM) { + if (addfile(input) == 0) { + cd(true); + try_to_sel(input); + update_view(); + } else { + message(RED, "Could not create \"%s\".", input); + rover_getch(); + CLEAR_MESSAGE(); + } + } + break; + case KEY_F(12): //Create new dir + start_line_edit(""); + DELSLASH(input); + update_input(RVP_NEW_DIR, YELLOW, input); + do { + edit_stat = get_line_edit(input); + ok = strlen(input); + for (i = 0; i < rover.nfiles; i++) { + if (!strncmp(ENAME(i), input, length)) { + ok = 0; + update_input(RVP_NEW_DIR, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" already exists!", input); + rover_getch(); + CLEAR_MESSAGE(); + edit_stat = CONTINUE; + } + } else if (!isvalidfilename(input, false)) { + ok = 0; + update_input(RVP_NEW_DIR, RED, input); + if (edit_stat == CONFIRM) { + CLEAR_MESSAGE(); + message(RED, "\"%s\" invalid dirname!", input); + rover_getch(); + CLEAR_MESSAGE(); + edit_stat = CONTINUE; + } + } + } + update_input(RVP_NEW_DIR, (ok ? GREEN : RED), input); + } while (edit_stat == CONTINUE); + + CLEAR_MESSAGE(); + if (ok && edit_stat == CONFIRM) { + if (adddir(input) == 0) { + cd(true); + ADDSLASH(input); + try_to_sel(input); + update_view(); + } else { + message(RED, "Could not create \"%s/\".", input); + rover_getch(); + CLEAR_MESSAGE(); + } + } + break; + case KEY_F(2): //Rename selected file or directory + ok = 0; + strcpy(input, ENAME(ESEL)); + last = input + strlen(input) - 1; + isdir = *last; + if (*last == '/') + *last = '\0'; + start_line_edit(input); + update_input(RVP_RENAME, RED, input); + while ((edit_stat = get_line_edit(input)) == CONTINUE) { + length = strlen(input); + ok = length; + for (i = 0; i < rover.nfiles; i++) + if (!strncmp(ENAME(i), input, length) && + (!strcmp(ENAME(i) + length, "") || !strcmp(ENAME(i) + length, "/"))) { + ok = 0; + i = rover.nfiles; //in order to exit from nested loop without using break; + } + update_input(RVP_RENAME, (ok ? GREEN : RED), input); + } + CLEAR_MESSAGE(); + if (edit_stat == CONFIRM) { + if (isdir) + ADDSLASH(input); + if (ok) { + if (!rename(ENAME(ESEL), input) && MARKED(ESEL)) { + del_mark(&rover.marks, ENAME(ESEL)); + add_mark(&rover.marks, CWD, input); + } + cd(true); + try_to_sel(input); + update_view(); + } else + message(RED, "\"%s\" already exists.", input); + } + break; + case KEY_F(4): //Toggle execute permission of the selected file + if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) + continue; + + if (S_IXUSR & EMODE(ESEL)) + EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH); + else + EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH; + + if (chmod(ENAME(ESEL), EMODE(ESEL))) { + message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL)); + } else { + message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL)); + update_view(); + } + rover_getch(); + CLEAR_MESSAGE(); + break; + case KEY_SPACE: //Toggle mark on the selected entry + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + else + add_mark(&rover.marks, CWD, ENAME(ESEL)); + MARKED(ESEL) = !MARKED(ESEL); //toggle mark + ESEL = (ESEL + 1);// % rover.nfiles; // if remove comment to the modulo the selection restart from first element of listing + update_view(); + break; + case KEY_CTRL_S: //Toggle mark on all visible entries + for (i = 0; i < rover.nfiles; i++) { + if (MARKED(i)) + del_mark(&rover.marks, ENAME(i)); + else + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = !MARKED(i); + } + update_view(); + break; + case KEY_CTRL_A: //Mark all visible entries + for (i = 0; i < rover.nfiles; i++) + if (!MARKED(i)) { + add_mark(&rover.marks, CWD, ENAME(i)); + MARKED(i) = true; + } + update_view(); + break; + case KEY_CTRL_D: //Unmark all visible entries + for (i = 0; i < rover.nfiles; i++) + if (MARKED(i)) { + del_mark(&rover.marks, ENAME(i)); + MARKED(i) = false; + } + update_view(); + break; + case KEY_DC: //Delete selected file or (empty) directory + if (rover.marks.nentries) { + message(YELLOW, "There is marked entries, to delete them use X"); + rover_getch(); + CLEAR_MESSAGE(); + } + if (rover.nfiles) { + message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL)); + if (rover_getch() == 'Y') { + strcpy(buffer, ENAME(ESEL)); + if (MARKED(ESEL)) + del_mark(&rover.marks, ENAME(ESEL)); + ok = rm(buffer); + reload(); + CLEAR_MESSAGE(); + if (ok == 0) + message(GREEN, "\"%s\" deleted!", buffer); + else + message(RED, "Error removing \"%s\", to get more info read \"%s.log\"", buffer, ROVER); //print error message + } + } else + message(RED, "No entry for deletion."); + + rover_getch(); + CLEAR_MESSAGE(); + break; + case 'X': //Delete all marked entries + if (rover.marks.nentries) { + message(YELLOW, "Delete all marked entries? (Y/n)"); + if (rover_getch() == 'Y') + process_marked(NULL, rm, rm, "Deleting", "Deleted"); + else + CLEAR_MESSAGE(); + } else + message(RED, "No entries marked for deletion."); + break; + case 'C': //Copy all marked entries + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, cpyfile, NULL, "Copying", "Copied"); + else + message(RED, "Cannot copy to the same path."); + } else + message(RED, "No entries marked for copying."); + break; + case 'V': //Move all marked entries + if (rover.marks.nentries) { + if (strcmp(CWD, rover.marks.dirpath)) + process_marked(adddir, movfile, rm, "Moving", "Moved"); + else + message(RED, "Cannot move to the same path."); + } else + message(RED, "No entries marked for moving."); + break; + default: + //LOG(LOG_INFO, "keypressed [0x%02x]", ch); // Used for DEBUG + RV_ALERT(); + break; + } + } while (!quit); + + return; +} \ No newline at end of file diff --git a/src/ui_funcs.h b/src/ui_funcs.h new file mode 100755 index 0000000..c2ea114 --- /dev/null +++ b/src/ui_funcs.h @@ -0,0 +1,156 @@ +#ifndef _UI_FUNCS_H +#define _UI_FUNCS_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* for asprintf() environ */ +#endif + +#include +#include /* setlocale(), LC_ALL */ +#include /* PATH_MAX */ +#include +#include /* environ PAGER SHELL EDITOR VISUAL */ +#include /* dirname() */ +#include + +/* Shell used to launch external programs. + Defining this macro will force Rover to launch external + programs with `sh -c "$EXTERNAL_PROGRAM [arg]"`. This gives more + flexibility, allowing command-line arguments to be embedded in + environment variables (e.g. PAGER="less -N"). On the other hand, + this requires the presence of a shell and will spawn an additional + process each time an external program is invoked. Leave this macro + undefined if you prefer external programs to be launched with just + `$EXTERNAL_PROGRAM [arg]`. */ +#define RV_SHELL "/bin/sh" + +#define STATUSPOS (COLS - 16) + +/* Number of entries to jump on RVK_JUMP_DOWN and RVK_JUMP_UP. */ +#define RV_JUMP 10 + +/* Colors available: DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK. */ +#define RVC_CWD GREEN +#define RVC_STATUS CYAN +#define RVC_BORDER BLUE +#define RVC_SCROLLBAR CYAN +#define RVC_LINK YELLOW +#define RVC_HIDDEN BLUE +#define RVC_EXEC CYAN +#define RVC_REG GREEN +#define RVC_DIR WHITE +#define RVC_CHR MAGENTA +#define RVC_BLK MAGENTA +#define RVC_FIFO BLUE +#define RVC_SOCK MAGENTA +#define RVC_PROMPT DEFAULT +#define RVC_TABNUM DEFAULT +#define RVC_MARKS YELLOW + +/* KEY not defined by curses.h */ +#ifndef KEY_SPACE +#define KEY_SPACE 0x20 +#endif +#ifndef KEY_ESC +#define KEY_ESC 0x1B +#endif +#ifndef KEY_TAB +#define KEY_TAB 0x09 +#endif +#ifndef KEY_RETURN +#define KEY_RETURN 0x0D +#endif +#ifndef KEY_CTRL_DEL +#define KEY_CTRL_DEL 0x207 +#endif +#ifndef KEY_CTRL_BS +#define KEY_CTRL_BS 0x08 +#endif +#ifndef KEY_CTRL_LEFT +#define KEY_CTRL_LEFT 0x221 +#endif +#ifndef KEY_CTRL_RIGHT +#define KEY_CTRL_RIGHT 0x230 +#endif +#ifndef KEY_CTRL_X +#define KEY_CTRL_X 0x18 +#endif +#ifndef KEY_CTRL_C +#define KEY_CTRL_C 0x03 +#endif +#ifndef KEY_CTRL_V +#define KEY_CTRL_V 0x16 +#endif +#ifndef KEY_CTRL_A +#define KEY_CTRL_A 0x01 +#endif +#ifndef KEY_CTRL_S +#define KEY_CTRL_S 0x13 +#endif +#ifndef KEY_CTRL_D +#define KEY_CTRL_D 0x04 +#endif + +/* Line Editing Macros. */ +#define EDIT_FULL(E) ((E).left == (E).right) +#define EDIT_CAN_LEFT(E) ((E).left) +#define EDIT_CAN_RIGHT(E) ((E).right < PATH_MAX - 1) +#define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left] +#define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right] +#define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C) +#define EDIT_BACKSPACE(E) (E).left-- +#define EDIT_DELETE(E) (E).right++ +#define EDIT_CLEAR(E) \ + { \ + (E).left = 0; \ + (E).right = PATH_MAX - 1; \ + } + +/* Get user programs from the environment vars */ +#define ROVER_ENV(dst, src) \ + { \ + if ((dst = getenv("ROVER_" #src)) == NULL) \ + dst = getenv(#src); \ + } + +/* Clear message line */ +#define CLEAR_MESSAGE() mvhline(LINES - 1, 0, ' ', STATUSPOS) + +typedef enum Color { + DEFAULT, + RED, + GREEN, + YELLOW, + BLUE, + CYAN, + MAGENTA, + WHITE, + BLACK +} Color; + +struct User { + char *Shell; + char *Pager; + char *Editor; + char *Open; +}; + +typedef enum EditStat { + CONTINUE, + CONFIRM, + CANCEL +} EditStat; + +// Function declarations +void handle_usr1(int sig); +void handle_winch(int sig); +void handlers(bool enable); +int rover_getch(void); +void message(Color color, char *fmt, ...); +void update_view(void); +void init_marks(Marks *marks); +void free_marks(Marks *marks); +void del_mark(Marks *marks, char *entry); +void main_menu(void); + +#endif // _UI_FUNCS_H \ No newline at end of file